This commit is contained in:
Zeeshaun
2026-03-24 15:26:03 -05:00
parent 1e5beb292a
commit 9cc87337b8
333 changed files with 17931 additions and 0 deletions
@@ -0,0 +1,114 @@
using LCU.Api.Internal.DispatcherLib.Repositories;
using Microsoft.Data.SqlClient;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services
{
public class CleanUpJobSettingService : ICleanUpJobSettingService
{
private readonly ICleanUpJobSettingRepository _cleanUpJobSettingRepository;
private readonly IUnitOfWork _unitOfWork;
public CleanUpJobSettingService(IUnitOfWork unitOfWork,
ICleanUpJobSettingRepository cleanUpJobSettingRepository)
{
_unitOfWork = unitOfWork;
_cleanUpJobSettingRepository = cleanUpJobSettingRepository;
}
public async Task<IEnumerable<CleanUpJobSetting>> GetAsync()
{
try
{
return await _cleanUpJobSettingRepository.ListAsync();
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving CleanUpJobSetting.");
throw new ServiceException("Failure retrieving CleanUpJobSetting.");
}
}
public async Task<CleanUpJobSetting> GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId)
{
try
{
return await _cleanUpJobSettingRepository.FindByCleanUpJobSettingIdAsync(cleanUpJobSettingId);
}
catch (ArgumentNullException exception)
{
Log.Error(exception, $"Failure getting dispatcher Clean Up Job Settings by Id. CleanUpJobSettingId was null");
return null;
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId);
throw new ServiceException($"Failure retrieving dispatcher Clean Up Job Settings. CleanUpJobSettingId: {cleanUpJobSettingId}");
}
}
public async Task<CleanUpJobSetting> SaveAsync(CleanUpJobSetting request)
{
try
{
var result = await _cleanUpJobSettingRepository.AddAsync(request);
await _unitOfWork.CompleteAsync();
return result;
}
catch (Exception exception)
{
Log.Error(exception, "Failure saving dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@request.CleanUpJobSettingId}", request.CleanUpJobSettingId);
throw new ServiceException($"Failure saving dispatcher Clean Up Job Settings. CleanUpJobSettingId: {request.CleanUpJobSettingId}");
}
}
public async Task<CleanUpJobSetting> UpdateAsync(CleanUpJobSetting request)
{
try
{
var existing = await GetByCleanUpJobSettingIdAsync(request.CleanUpJobSettingId);
if(existing == null)
{
return null;
}
existing.CompletionDaysPast = request.CompletionDaysPast;
_cleanUpJobSettingRepository.Update(existing);
await _unitOfWork.CompleteAsync();
return existing;
}
catch (SqlException sqlException)
{
Log.Error(sqlException, "SQL Failure updating dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@request.CleanUpJobSettingId}", request.CleanUpJobSettingId);
throw new ServiceException($"SQL Failure updating dispatcher Clean Up Job Settings. CleanUpJobSettingId: {request.CleanUpJobSettingId}");
}
catch (Exception exception)
{
Log.Error(exception, "SQL Failure updating dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@request.CleanUpJobSettingId}", request.CleanUpJobSettingId);
throw new ServiceException($"SQL Failure updating dispatcher Clean Up Job Settings. CleanUpJobSettingId: {request.CleanUpJobSettingId}");
}
}
public async Task DeleteAsync(int cleanUpJobSettingId)
{
try
{
var cleanUpJobSetting = await _cleanUpJobSettingRepository.FindByCleanUpJobSettingIdAsync(cleanUpJobSettingId);
ArgumentNullException.ThrowIfNull(cleanUpJobSetting);
_cleanUpJobSettingRepository.Remove(cleanUpJobSetting);
await _unitOfWork.CompleteAsync();
}
catch (SqlException sqlException)
{
Log.Error(sqlException, "SQL Failure removing dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId);
throw new ServiceException($"SQL Failure removing dispatcher Clean Up Job Settings. CleanUpJobSettingId: {cleanUpJobSettingId}");
}
catch (Exception exception)
{
Log.Error(exception, "Failure removing dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId);
throw new ServiceException($"Failure removing dispatcher Clean Up Job Settings. CleanUpJobSettingId: {cleanUpJobSettingId}");
}
}
}
}
@@ -0,0 +1,135 @@
using LCU.Api.Internal.DispatcherLib.Repositories;
using Microsoft.Data.SqlClient;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services;
public class CleanUpJobTagService : ICleanUpJobTagService
{
private readonly ICleanUpJobTagRepository _cleanUpJobTagRepository;
private readonly IUnitOfWork _unitOfWork;
public CleanUpJobTagService(ICleanUpJobTagRepository cleanUpJobRepository,
IUnitOfWork unitOfWork)
{
_cleanUpJobTagRepository = cleanUpJobRepository;
_unitOfWork = unitOfWork;
}
public async Task<IEnumerable<CleanUpJobTag>> GetAsync()
{
try
{
return await _cleanUpJobTagRepository.ListAsync();
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving CleanUpJobag.");
throw new ServiceException("Failure retrieving CleanUpJobTag.");
}
}
public async Task<CleanUpJobTag> GetByCleanUpJobTagIdAsync(int cleanUpJobTagId)
{
try
{
ArgumentNullException.ThrowIfNull(cleanUpJobTagId);
return await _cleanUpJobTagRepository.FindByCleanUpJobTagIdAsync(cleanUpJobTagId);
}
catch (ArgumentNullException exception)
{
Log.Error(exception, $"Failure getting dispatcher Clean Up Job Tag by Id. CleanUpJobTagId was null");
return null;
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving dispatcher Clean Up Job Tag. CleanUpJobTagId: {@cleanUpJobTagId}", cleanUpJobTagId);
throw new ServiceException($"Failure retrieving dispatcher Clean Up Job Tag. CleanUpJobTagId: {cleanUpJobTagId}");
}
}
public async Task<IEnumerable<CleanUpJobTag>> GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId)
{
try
{
ArgumentNullException.ThrowIfNull(cleanUpJobSettingId);
return await _cleanUpJobTagRepository.FindByCleanUpJobSettingIdAsync(cleanUpJobSettingId);
}
catch (ArgumentNullException exception)
{
Log.Error(exception, $"Failure getting dispatcher Clean Up Job Tags bySettings Id. CleanUpJobSettingId was null");
return null;
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving dispatcher Clean Up Job Tag. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId);
throw new ServiceException($"Failure retrieving dispatcher Clean Up Job Tag. CleanUpJobSettingId: {cleanUpJobSettingId}");
}
}
public async Task<CleanUpJobTag> SaveAsync(CleanUpJobTag request)
{
try
{
var result = await _cleanUpJobTagRepository.AddAsync(request);
await _unitOfWork.CompleteAsync();
return result;
}
catch (Exception exception)
{
Log.Error(exception, "Failure saving dispatcher Clean Up Job Tag. CleanUpJobTagId: {@request.CleanUpJobTagId}", request.CleanUpJobTagId);
throw new ServiceException($"Failure saving dispatcher Clean Up Job Tag. CleanUpJobTagId: {request.CleanUpJobTagId}");
}
}
public async Task DeleteAsync(int cleanUpJobTagId)
{
try
{
var cleanUpJobTag = await _cleanUpJobTagRepository.FindByCleanUpJobTagIdAsync(cleanUpJobTagId);
ArgumentNullException.ThrowIfNull(cleanUpJobTag);
_cleanUpJobTagRepository.Remove(cleanUpJobTag);
await _unitOfWork.CompleteAsync();
}
catch (SqlException sqlException)
{
Log.Error(sqlException, "SQL Failure removing dispatcher Clean Up Job Tag. CleanUpJobTagId: {@cleanUpJobTagId}", cleanUpJobTagId);
throw new ServiceException($"SQL Failure removing dispatcher Clean Up Job Tag. CleanUpJobTagId: {cleanUpJobTagId}");
}
catch (Exception exception)
{
Log.Error(exception, "Failure removing dispatcher Clean Up Job Tag. CleanUpJobTagId: {@cleanUpJobTagId}", cleanUpJobTagId);
throw new ServiceException($"Failure removing dispatcher Clean Up Job Tag. CleanUpJobTagId: {cleanUpJobTagId}");
}
}
public async Task DeleteByCleanUpJobSettingIdAsync(int cleanUpJobSettingId)
{
try
{
var cleanUpJobTags = await _cleanUpJobTagRepository.FindByCleanUpJobSettingIdAsync(cleanUpJobSettingId);
if(!cleanUpJobTags.Any())
{
throw new Exception($"No CleanUpJobTags found.");
}
foreach (var tag in cleanUpJobTags)
{
_cleanUpJobTagRepository.Remove(tag);
}
await _unitOfWork.CompleteAsync();
}
catch (SqlException sqlException)
{
Log.Error(sqlException, "SQL Failure removing dispatcher Clean Up Job Tag. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId);
throw new ServiceException($"SQL Failure removing dispatcher Clean Up Job Tag. CleanUpJobSettingId: {cleanUpJobSettingId}");
}
catch (Exception exception)
{
Log.Error(exception, "Failure removing dispatcher Clean Up Job Tag. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId);
throw new ServiceException($"Failure removing dispatcher Clean Up Job Tag. CleanUpJobSettingId: {cleanUpJobSettingId}");
}
}
}
@@ -0,0 +1,72 @@
using LCU.Api.Internal.Dispatcher.Services.QueueServices;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using System.Collections.Generic;
using System.Threading;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public class BatchErroredJobsService : IBatchErroredJobsService
{
private readonly IQueueSendErroredJobAlertService _queueSendErroredJobAlertService;
private readonly IJobService _jobService;
public BatchErroredJobsService(IJobService jobService,
IQueueSendErroredJobAlertService queueSendErroredJobAlertService)
{
_jobService = jobService;
_queueSendErroredJobAlertService = queueSendErroredJobAlertService;
}
public async Task PerformBatchErroredJobs(CancellationToken cancellationToken)
{
try
{
var jobs = await _jobService.GetErrorMonitoringJobs();
if (jobs == null || !jobs.Any())
{
return;
}
var batchedErroredJobAlertRequest = CreateBatchedErroredJobAlerts(jobs);
await _queueSendErroredJobAlertService.QueueSendErroredJobAlert(batchedErroredJobAlertRequest);
}
catch (Exception ex)
{
//Fatal as we need to know if we can't get out our errors
Log.ForContext(nameof(PropertyEnum.Group),
nameof(GroupEnum.ITDevelopment))
.Fatal(ex, $"Failed to perform Error Monitoring.");
}
}
private static BatchedErroredJobAlertRequest CreateBatchedErroredJobAlerts(IEnumerable<Job> jobs)
{
var batchedErroredJobAlertRequests = new BatchedErroredJobAlertRequest()
{
ErroredJobAlertRequests = new List<ErroredJobAlertRequest>()
};
foreach (var job in jobs)
{
var failedOrProcessingWorkItem = job.WorkItems
.FirstOrDefault(wi => wi.WorkItemExecutions != null &&
wi.WorkItemExecutions.Any(wie => wie.Status == StatusType.Failed || wie.Status == StatusType.Processing));
if (failedOrProcessingWorkItem == null || failedOrProcessingWorkItem.CurrentWorkItemExecution == null)
{
Log.Warning($"Failed or Processing workItem not found during CreateBatchedErroredJobAlerts. JobId and Name: {job.JobId}: {job.Name}");
continue;
}
batchedErroredJobAlertRequests.ErroredJobAlertRequests.Add(new ErroredJobAlertRequest
{
CurrentJob = job,
CurrentWorkItem = failedOrProcessingWorkItem
});
}
return batchedErroredJobAlertRequests;
}
}
@@ -0,0 +1,172 @@
using LCU.Api.Internal.Dispatcher.Configurations;
using LCU.Api.Internal.DispatcherLib.Repositories;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.DispatcherModels.Models.Results;
using Newtonsoft.Json;
using System.Threading;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public class CleanUpJobsService : ICleanUpJobsService
{
private readonly IJobService _jobService;
private readonly ICleanUpJobSettingService _cleanUpJobSettingService;
private readonly IUnitOfWork _unitOfWork;
private readonly ApiConfiguration _apiConfiguration;
public CleanUpJobsService(IJobService jobService,
ICleanUpJobSettingService cleanUpJobSettingService,
IUnitOfWork unitOfWork,
ApiConfiguration apiConfiguration)
{
_jobService = jobService;
_cleanUpJobSettingService = cleanUpJobSettingService;
_unitOfWork = unitOfWork;
_apiConfiguration = apiConfiguration;
}
public async Task PerformCleanUpJobs(CancellationToken cancellationToken)
{
try
{
var result = new CleanUpJobBatchResult();
result = await PerformSoftCleanUpJobTask(result);
result = await PerformHardDeleteCleanUpJobTask(result);
await _unitOfWork.CompleteAsync();
if (result.IsFailure)
{
Log.ForContext(nameof(PropertyEnum.Group),
nameof(GroupEnum.ITDevelopment))
.Error("CleanUpJob Batch Result: {@CleanUpJobBatchResult}", result);
}
}
catch (Exception ex)
{
Log.ForContext(nameof(PropertyEnum.Group),
nameof(GroupEnum.ITDevelopment))
.Error(ex, $"Failed to perform Clean Up Jobs.");
}
}
public async Task<CleanUpJobBatchResult> PerformSoftCleanUpJobTask(CleanUpJobBatchResult result)
{
var jobs = await _jobService.GetInactiveExcludeSoftDeletedTrackedAsync();
var cleanUpJobSettings = await _cleanUpJobSettingService.GetAsync();
foreach (var setting in cleanUpJobSettings)
{
if (setting.CleanUpJobTags == null || !setting.CleanUpJobTags.Any())
{
continue;
}
foreach (var job in jobs)
{
result = PerformCleanUpTaskForJob(result, job, setting);
}
}
return result;
}
public bool ValidateShouldSoftDelete(CleanUpJobSetting setting, Job job, CleanUpJobBatchResult result)
{
var shouldSoftDelete = ShouldSoftDeleteByTag(setting, job);
if (!shouldSoftDelete)
{
return false;
}
return ShouldSoftDeleteByDate(setting, job, result);
}
public bool ShouldSoftDeleteByDate(CleanUpJobSetting setting, Job job, CleanUpJobBatchResult result)
{
try
{
if (!job.WorkItems.Any() || !job.WorkItems.FirstOrDefault().WorkItemExecutions.Any())
{
result.AddToFailureCount($"Job would be cleaned but cannot determine if it is old enough to be cleaned due to not having any workItemExecutions. Job: {job.JobId} - {job.Name}");
return false;
}
var currentDate = DateTime.Now;
var cutoffDate = currentDate.AddDays(setting.CompletionDaysPast);
var jobLastCompletedTime = job.WorkItems.FirstOrDefault()
.WorkItemExecutions.OrderByDescending(wie => wie.CompletedTimestamp).FirstOrDefault().CompletedTimestamp.Value;
return DateTime.Compare(cutoffDate, jobLastCompletedTime) > 0;
}
catch (Exception ex)
{
var message = $"Error while attempting to compare CompletedTimestamp for SoftDeletion. Job: {job.JobId} - {job.Name}. ExeptionMessage: {ex.Message}";
Log.Error(message);
throw new Exception(message);
}
}
public bool ShouldSoftDeleteByTag(CleanUpJobSetting setting, Job job)
{
if (job.JobTags.Count != setting.CleanUpJobTags.Count)
{
return false;
}
var orderedJobTagIds = job.JobTags.Select(t => t.TagId).OrderBy(t => t).ToList();
var orderedCleanUpJobTagIds = setting.CleanUpJobTags.Select(t => t.TagId).OrderBy(t => t).ToList();
return orderedJobTagIds.SequenceEqual(orderedCleanUpJobTagIds);
}
public async Task<CleanUpJobBatchResult> PerformHardDeleteCleanUpJobTask(CleanUpJobBatchResult result)
{
var softDeletedJobs = await _jobService.GetSoftDeletedTrackedAsync(DateTime.Now.AddDays(_apiConfiguration.CleanUpJobsDaysToDelete));
foreach (var job in softDeletedJobs)
{
try
{
await _jobService.DeleteFromCleanupAsync(job);
}
catch (Exception ex)
{
Log.Error(ex.Message, "Failure to hard delete dispatcher job through Clean Up Job Task. Job: {@Job}", job);
result.AddToFailureCount(ex.Message);
continue;
}
}
return result;
}
private CleanUpJobBatchResult PerformCleanUpTaskForJob(CleanUpJobBatchResult result, Job job, CleanUpJobSetting setting)
{
try
{
var shouldSoftDelete = ValidateShouldSoftDelete(setting, job, result);
if (!shouldSoftDelete)
{
return result;
}
job.SoftDeleteDate = DateTime.Now;
var updatedJob = _jobService.UpdateExcludingWorkItemsAsyncWithoutSave(job);
if (updatedJob == null || updatedJob.Result.JobId != job.JobId)
{
throw new Exception($"Failure to setup soft delete update for job. Job:{JsonConvert.SerializeObject(job)}");
}
}
catch (Exception ex)
{
result.AddToFailureCount(ex.Message);
}
return result;
}
}
@@ -0,0 +1,84 @@
using Newtonsoft.Json;
using LCU.Library.Internal.ApiResponse.Models;
using LCU.Api.Internal.Dispatcher.Services.QueueServices;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using LCU.Library.Internal.DispatcherModels.Enums;
using System.Collections.Generic;
using RabbitMQ.Client;
using LCU.Api.Internal.MaidBotLib.Models;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public class CompletionService : ICompletionService
{
private readonly IJobService _jobService;
private readonly IWorkItemService _workItemService;
private readonly IExecutionMessageService _executionMessageService;
private readonly IQueueJobFailureService _queueJobFailureService;
private readonly IQueueDetermineOperationService _queueDetermineOperationService;
public CompletionService(IJobService jobService,
IWorkItemService workItemService,
IExecutionMessageService executionMessageService,
IQueueJobFailureService queueJobFailureService,
IQueueDetermineOperationService queueDetermineOperationService)
{
_jobService = jobService;
_workItemService = workItemService;
_executionMessageService = executionMessageService;
_queueJobFailureService = queueJobFailureService;
_queueDetermineOperationService = queueDetermineOperationService;
}
public async Task HandleCompletionRequest(Response response)
{
var dispatcherReturnResponse = JsonConvert.DeserializeObject<DispatcherReturnResponse>(response.ResponsePayload);
try
{
ArgumentNullException.ThrowIfNull(dispatcherReturnResponse);
var job = await _jobService.GetByWorkItemIdExcludingWorkItemsAsync(dispatcherReturnResponse.WorkItemId);
if (!response.Successful)
{
await _queueJobFailureService.QueueJobFailure(job.JobId,
dispatcherReturnResponse.WorkItemId,
response.ResponseStack == null || response.ResponseStack.Any() ? response.ResponseStack : new List<string>());
return;
}
var workItem = await _workItemService.GetByIdAsync(dispatcherReturnResponse.WorkItemId);
if (workItem.CurrentWorkItemExecution == null)
{
Log.Warning($"CurrentWorkitemExecution was null for WorkItemId:{dispatcherReturnResponse.WorkItemId}");
return;
}
await _executionMessageService.SaveExecutionMessages(workItem.CurrentWorkItemExecution.WorkItemExecutionId, response.ResponseStack);
if (workItem.CanceledJobCounter > 0)
{
workItem = await _workItemService.ResetCanceledJobCounter(workItem);
}
job.JobStatus = JobStatus.DeterminingOperation;
var updatedJob = await _jobService.UpdateAsync(job);
await _queueDetermineOperationService.QueueDetermineOperation(updatedJob, workItem, SetDetermineOperationType(dispatcherReturnResponse.Skipped));
}
catch (Exception exception)
{
var exceptionMessage = $"Failed to perform completion. Job may be stuck in an inoperable status. WorkItemId: {dispatcherReturnResponse.WorkItemId}";
Log.Error(exception, exceptionMessage);
throw new ServiceException(exceptionMessage);
}
}
public DetermineOperationType SetDetermineOperationType(bool isSkipped)
{
if(isSkipped)
{
return DetermineOperationType.Skipped;
}
return DetermineOperationType.Completed;
}
}
@@ -0,0 +1,82 @@
using LCU.Api.Internal.Dispatcher.Services.QueueServices;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.DispatcherModels.Entities;
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices
{
public class DetermineOperationService : IDetermineOperationService
{
private readonly IJobService _jobService;
private readonly IWorkItemService _workItemService;
private readonly IWorkItemExecutionService _workItemExecutionService;
private readonly IQueueExecutionService _queueExecutionService;
public DetermineOperationService(IJobService jobService,
IWorkItemService workItemService,
IWorkItemExecutionService workItemExecutionService,
IQueueExecutionService queueExecutionService)
{
_jobService = jobService;
_workItemService = workItemService;
_workItemExecutionService = workItemExecutionService;
_queueExecutionService = queueExecutionService;
}
public async Task HandleDetermineOperationRequest(DetermineOperationRequest determineOperationRequest)
{
try
{
switch (determineOperationRequest.DetermineOperationType)
{
case DetermineOperationType.Failed:
await HandleFailedWorkItem(determineOperationRequest);
break;
case DetermineOperationType.Skipped:
await HandleWorkItemCompletion(determineOperationRequest, StatusType.Skipped);
break;
case DetermineOperationType.Completed:
await HandleWorkItemCompletion(determineOperationRequest, StatusType.Completed);
break;
default:
throw new NotImplementedException();
}
return;
}
catch (ServiceException serviceException)
{
Log.Error(serviceException, $"Dispatcher Determine Operation Failure. " +
$"The Dispatcher Determine Operation is placed on LCU.Dispatcher.DetermineOperation_error.");
throw new ServiceException($"Failed to handle DetermineOperationRequest.");
}
catch (Exception exception)
{
Log.Error(exception, $"Failed to handle DetermineOperationRequest.");
throw new ServiceException($"Failed to handle DetermineOperationRequest.");
}
}
public async Task HandleFailedWorkItem(DetermineOperationRequest determineOperationRequest)
{
await _jobService.CompleteJobAndWorkItem(determineOperationRequest.CurrentJob, determineOperationRequest.CurrentWorkItem.CurrentWorkItemExecution, StatusType.Failed);
}
public async Task HandleWorkItemCompletion(DetermineOperationRequest determineOperationRequest, StatusType status)
{
var nextWorkItem = await _workItemService.GetNextWorkItemByJobId(determineOperationRequest.CurrentJob.JobId, determineOperationRequest.CurrentWorkItem.Order);
if (nextWorkItem == null)
{
await _jobService.CompleteJobAndWorkItem(determineOperationRequest.CurrentJob, determineOperationRequest.CurrentWorkItem.CurrentWorkItemExecution, status);
return;
}
await _jobService.CompleteWorkItemAndContinueProcessingJob(determineOperationRequest.CurrentJob,
determineOperationRequest.CurrentWorkItem.CurrentWorkItemExecution,
status);
var newWorkItemExecution = await _workItemExecutionService.CreateWorkItemExecution(nextWorkItem.WorkItemId);
await _queueExecutionService.QueueExecution(newWorkItemExecution);
}
}
}
@@ -0,0 +1,96 @@
using LCU.Api.Internal.Dispatcher.Services.QueueServices;
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Library.Internal.LcuHttpClient.HttpRequest;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public class ExecutionService : IExecutionService
{
private readonly IPostRequest _postRequest;
private readonly IWorkItemService _workItemService;
private readonly IJobService _jobService;
private readonly IQueueCompletionService _queueCompletionService;
private const string HttRequestIsNullMessage = "Http Request is null";
public ExecutionService(IPostRequest postRequest,
IWorkItemService workItemService,
IJobService jobService,
IQueueCompletionService queueCompletionService)
{
_postRequest = postRequest;
_workItemService = workItemService;
_jobService = jobService;
_queueCompletionService = queueCompletionService;
}
public async Task HandleExecutionRequest(WorkItemExecution request)
{
try
{
var executionResponse = await ExecuteRequest(request);
if (!executionResponse.IsSuccess)
{
await _queueCompletionService.QueueCompletion(new DispatcherReturnResponse() { WorkItemId = executionResponse.WorkItemExecution.WorkItemId },
executionResponse.Messages, executionResponse.IsSuccess);
}
}
catch (Exception exception)
{
Log.Error(exception, $"Failed to perform execution. WorkItemId: {request.WorkItemId}");
throw new ServiceException($"Failed to perform execution. WorkItemId: {request.WorkItemId}");
}
}
public async Task<ExecutionResponse> ExecuteRequest(WorkItemExecution request)
{
try
{
ArgumentNullException.ThrowIfNull(request);
var requestedWorkItem = await _workItemService.GetByIdAsync(request.WorkItemId);
await UpdateJobStatusToProcessing(requestedWorkItem);
var result = await _postRequest.AsyncWithNewtonSerializedNoThrow(new Uri(requestedWorkItem.Url), requestedWorkItem.SerializedPayload);
if (!result.IsSuccessStatusCode)
{
var content = await result.Content.ReadAsStringAsync();
return new ExecutionResponse(false, request, content);
}
return new ExecutionResponse(true, request);
}
catch (Exception ex)
{
var errorMessage = $"Exception during ExecuteRequest. Exception Message: {ex.Message}. Inner Exception: {ex.InnerException}";
Log.Error(errorMessage);
return new ExecutionResponse(false, request, errorMessage);
}
}
private async Task UpdateJobStatusToProcessing(WorkItem requestedWorkItem)
{
var job = await _jobService.GetByWorkItemIdExcludingWorkItemsAsync(requestedWorkItem.WorkItemId);
if (job.JobStatus == JobStatus.Processing)
{
return;
}
job.JobStatus = JobStatus.Processing;
await _jobService.UpdateAsync(job);
Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus);
}
public struct ExecutionResponse
{
public ExecutionResponse(bool isSuccess, WorkItemExecution workItemExecution, string messages = null)
{
IsSuccess = isSuccess;
WorkItemExecution = workItemExecution;
Messages = messages != null ? new List<string>() { messages } : null;
}
public bool IsSuccess { get; set; }
public List<string> Messages { get; set; }
public WorkItemExecution WorkItemExecution { get; set; }
}
}
@@ -0,0 +1,7 @@
using System.Threading;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public interface IBatchErroredJobsService
{
Task PerformBatchErroredJobs(CancellationToken cancellationToken);
}
@@ -0,0 +1,8 @@
using System.Threading;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public interface ICleanUpJobsService
{
Task PerformCleanUpJobs(CancellationToken cancellationToken);
}
@@ -0,0 +1,7 @@
using LCU.Library.Internal.ApiResponse.Models;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public interface ICompletionService
{
Task HandleCompletionRequest(Response response);
}
@@ -0,0 +1,9 @@
using LCU.Library.Internal.DispatcherModels.Models.Requests;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices
{
public interface IDetermineOperationService
{
Task HandleDetermineOperationRequest(DetermineOperationRequest determineOperationRequest);
}
}
@@ -0,0 +1,6 @@
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public interface IExecutionService
{
Task HandleExecutionRequest(WorkItemExecution request);
}
@@ -0,0 +1,7 @@
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public interface IIngestionService
{
Task HandleIngestionRequest(Job request);
Task<bool> PerformIngestion(Job queueRequest);
}
@@ -0,0 +1,9 @@
using LCU.Library.Internal.DispatcherModels.Models.Requests;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices
{
public interface IJobFailureService
{
Task HandleJobFailureRequest(JobFailureRequest jobFailureRequest);
}
}
@@ -0,0 +1,7 @@
using System.Threading;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public interface IJobScheduleService
{
Task PerformSchedule(CancellationToken cancellationToken);
}
@@ -0,0 +1,10 @@
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices
{
public interface ISendErroredJobAlertService
{
Task HandleSendingErroredJobAlerts(List<BatchedErroredJobAlertRequest> batchedErroredJobAlertRequestList);
}
}
@@ -0,0 +1,159 @@
using LCU.Library.Internal.DispatcherModels.Enums;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public class IngestionService : IIngestionService
{
private readonly IJobService _jobService;
private readonly IWorkItemService _workItemService;
private readonly IJobErrorRecipientService _jobErrorRecipientService;
private readonly IJobTagService _jobTagService;
public IngestionService(IJobService jobService,
IWorkItemService workItemService,
IJobErrorRecipientService jobErrorRecipientService,
IJobTagService jobTagService)
{
_jobService = jobService;
_workItemService = workItemService;
_jobErrorRecipientService = jobErrorRecipientService;
_jobTagService = jobTagService;
}
public async Task HandleIngestionRequest(Job request)
{
try
{
ArgumentNullException.ThrowIfNull(request);
var result = await PerformIngestion(request);
if (!result)
{
throw new ServiceException("Failure to PerformIngestion.");
}
}
catch (Exception exception)
{
Log.Error(exception, $"Failed to perform execution.");
throw new ServiceException($"Failed to perform ingestion.");
}
}
public async Task<bool> PerformIngestion(Job request)
{
try
{
if (request.IsNewJob)
{
return await SaveNewRequest(request);
}
return await UpdateRequest(request);
}
catch (Exception ex)
{
Log.Information(ex, $"Failed to perform ingestion.");
return false;
}
}
public async Task<bool> SaveNewRequest(Job request)
{
request = await _jobService.SaveAsync(request);
if (request == null)
{
return false;
}
var updatedWorkItems = await _workItemService.PopulateActionFieldsAndUpdateList(request.WorkItems);
if (updatedWorkItems == null)
{
return false;
}
return true;
}
public async Task<bool> UpdateRequest(Job request)
{
var existingJob = await _jobService.GetByIdAsync(request.JobId);
await UpdateRequestWorkItems(request, existingJob);
UpdateRequestErrorRecipients(request, existingJob);
await UpdateRequestJobTags(request, existingJob);
request.JobStatus = (request.NextRunTime == null && request.CronSchedule == null) ? JobStatus.Inactive : existingJob.JobStatus;
var updatedJob = await _jobService.UpdateAsync(request);
if (updatedJob == null)
{
return false;
}
return true;
}
public async Task UpdateRequestWorkItems(Job request, Job existingJob)
{
var removedWorkItems = existingJob.WorkItems.Where(wi =>
!request.WorkItems.Any(newWI =>
newWI.WorkItemId == wi.WorkItemId)).ToList();
await _workItemService.DeleteList(removedWorkItems);
var newWorkItems = request.WorkItems.Where(wi => wi.IsNewWorkItem).ToList();
_workItemService.SaveList(newWorkItems);
var updatedWorkItems = await _workItemService.PopulateActionFieldsAndUpdateList(request.WorkItems);
if (updatedWorkItems == null)
{
throw new Exception($"WorkItem Error while Populating Action Fields and updating.");
}
}
public void UpdateRequestErrorRecipients(Job request, Job existingJob)
{
var removedJobErrorRecipients = existingJob.JobErrorRecipients.Where(recipient =>
!request.JobErrorRecipients.Any(newRecipient =>
newRecipient.Recipient == recipient.Recipient)).ToList();
_jobErrorRecipientService.DeleteList(removedJobErrorRecipients);
var newJobErrorRecipients = new List<JobErrorRecipient>();
foreach (var recipient in request.JobErrorRecipients)
{
var jobErrorRecipient = existingJob.JobErrorRecipients.FirstOrDefault(existing =>
existing.Recipient == recipient.Recipient);
if (jobErrorRecipient == null)
{
newJobErrorRecipients.Add(recipient);
}
}
_jobErrorRecipientService.SaveList(newJobErrorRecipients);
}
public async Task UpdateRequestJobTags(Job request, Job existingJob)
{
var removedJobTags = existingJob.JobTags.Where(tag =>
!request.JobTags.Any(jobTags =>
jobTags.TagId == tag.TagId)).ToList();
foreach (var tag in removedJobTags)
{
await _jobTagService.Delete(tag.JobTagId);
}
var newJobTags = new List<JobTag>();
foreach (var tag in request.JobTags)
{
var existingTag = existingJob.JobTags.FirstOrDefault(existing =>
existing.TagId == tag.TagId);
if (existingTag == null)
{
newJobTags.Add(tag);
var result = await _jobTagService.SaveAsync(tag);
}
}
}
}
@@ -0,0 +1,91 @@
using LCU.Api.Internal.Dispatcher.Services.QueueServices;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.DispatcherModels.Entities;
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Library.Internal.DispatcherModels.Models;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using LCU.Library.Internal.DocumentIngestorModels.Models.IndexFileDocuments;
using LCU.Library.Internal.Exceptions;
using System;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices
{
public class JobFailureService : IJobFailureService
{
private readonly IWorkItemService _workItemService;
private readonly IWorkItemExecutionService _workItemExecutionService;
private readonly IJobService _jobService;
private readonly IExecutionMessageService _executionMessageService;
private readonly IQueueDetermineOperationService _queueDetermineOperationService;
private readonly IQueueSendErroredJobAlertService _queueSendErroredJobAlertService;
public JobFailureService(IWorkItemService workItemService,
IWorkItemExecutionService workItemExecutionService,
IJobService jobService,
IExecutionMessageService executionMessageService,
IQueueDetermineOperationService queueDetermineOperationService,
IQueueSendErroredJobAlertService queueSendErroredJobAlertService)
{
_workItemService = workItemService;
_workItemExecutionService = workItemExecutionService;
_jobService = jobService;
_executionMessageService = executionMessageService;
_queueDetermineOperationService = queueDetermineOperationService;
_queueSendErroredJobAlertService = queueSendErroredJobAlertService;
}
public async Task HandleJobFailureRequest(JobFailureRequest jobFailureRequest)
{
try
{
var workItem = await _workItemService.GetByIdAsync(jobFailureRequest.WorkItemId);
var updatedWorkItemExecution = await _workItemExecutionService.UpdateFailedWorkItemExecution(workItem.CurrentWorkItemExecution);
if (workItem.CancelJobOnFailure)
{
workItem = await _workItemService.IncreaseCanceledJobCounter(workItem);
}
var job = await _jobService.GetByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(jobFailureRequest.WorkItemId);
if (workItem.RequiresJobCancellation)
{
var message = $"This Job will be canceled and if scheduleable, rescheduled. The " +
$"WorkItem's canceled Job counter is currently at {workItem.CanceledJobCounter} and has " +
$"not yet went over the cancel Job limit of {workItem.CancelJobLimit}.";
jobFailureRequest.ExecutionMessages.Add(message);
job.JobStatus = JobStatus.DeterminingOperation;
}
else
{
job.JobStatus = JobStatus.Failed;
}
await _executionMessageService.SaveExecutionMessages(workItem.CurrentWorkItemExecution.WorkItemExecutionId, jobFailureRequest.ExecutionMessages);
var updatedJob = await _jobService.UpdateAsync(job);
if (updatedJob.JobStatus == JobStatus.DeterminingOperation)
{
await _queueDetermineOperationService.QueueDetermineOperation(updatedJob, workItem, DetermineOperationType.Failed);
}
else
{
await _queueSendErroredJobAlertService.QueueSendErroredJobAlert(updatedJob, workItem);
}
}
catch (ServiceException serviceException)
{
Log.Error(serviceException, $"Dispatcher Job Failure Request Failure. " +
$"The Dispatcher Job Failure Request is placed on LCU.Dispatcher.JobFailure_error.");
throw new ServiceException($"Failed to handle JobFailureRequest.");
}
catch(Exception exception)
{
Log.Error(exception, $"Failed to handle JobFailureRequest.");
throw new ServiceException($"Failed to handle JobFailureRequest.");
}
}
}
}
@@ -0,0 +1,67 @@
using System.Threading;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Api.Internal.Dispatcher.Services.QueueServices;
using LCU.Library.Internal.DispatcherModels.Enums;
using System;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
public class JobScheduleService : IJobScheduleService
{
private readonly IQueueExecutionService _queueExecutionService;
private readonly IJobService _jobService;
private readonly IWorkItemService _workItemService;
private readonly IWorkItemExecutionService _workItemExecutionService;
public JobScheduleService(IQueueExecutionService queueExecutionService,
IJobService jobService,
IWorkItemService workItemService,
IWorkItemExecutionService workItemExecutionService)
{
_queueExecutionService = queueExecutionService;
_jobService = jobService;
_workItemService = workItemService;
_workItemExecutionService = workItemExecutionService;
}
public async Task PerformSchedule(CancellationToken cancellationToken)
{
try
{
var readyToScheduleJobs = await _jobService.GetJobsReadyToBeScheduled();
foreach (var job in readyToScheduleJobs)
{
var workItem = await _workItemService.GetFirstOrDefaultWorkItemByJobId(job.JobId);
if (workItem == null)
{
// THIS SHOULD NEVER HAPPEN
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error($"Failure in Dispatcher Job Scheduler. Attempting to schedule Job: {job.JobId} - {job.Name} and NO workItems were found to process.");
continue;
}
var workItemExecution = await _workItemExecutionService.CreateWorkItemExecution(workItem.WorkItemId);
job.JobStatus = JobStatus.PendingToRun;
if (job.SoftDeleteDate != null)
{
job.SoftDeleteDate = null;
}
await _jobService.UpdateAsync(job);
Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus);
await _queueExecutionService.QueueExecution(workItemExecution);
}
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(exception, $"Failed to perform schedule.");
}
}
}
@@ -0,0 +1,146 @@
using LCU.Api.Internal.Dispatcher.Configurations;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using System.Collections.Generic;
using System.IO;
namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices
{
public class SendErroredJobAlertService : ISendErroredJobAlertService
{
private readonly ApiConfiguration _apiConfiguration;
private readonly INotificationService _notificationService;
public SendErroredJobAlertService(ApiConfiguration apiConfiguration, INotificationService notificationService)
{
_apiConfiguration = apiConfiguration;
_notificationService = notificationService;
}
public Task HandleSendingErroredJobAlerts(List<BatchedErroredJobAlertRequest> batchedErroredJobAlertRequestList)
{
var erroredJobAlertRequests = CombineFromBatch(batchedErroredJobAlertRequestList);
return SendErroredJobsReport(erroredJobAlertRequests);
}
public async Task SendErroredJobsReport(List<ErroredJobAlertRequest> erroredJobAlertRequests)
{
var groupedJobsByRecipients = GroupJobsByRecipients(erroredJobAlertRequests);
var result = true;
foreach (var recipientGroup in groupedJobsByRecipients)
{
var successful = await HandleBatchErroredJobs(recipientGroup.Value, recipientGroup.Key);
if (!successful)
{
result = false;
}
}
if (!result)
{
throw new ServiceException($"Failure to Send Errored Jobs Report. Result: {result}");
}
}
public List<ErroredJobAlertRequest> CombineFromBatch(List<BatchedErroredJobAlertRequest> batchedErroredJobAlertRequestList)
{
var erroredJobAlertRequests = new List<ErroredJobAlertRequest>();
foreach (var batch in batchedErroredJobAlertRequestList)
{
erroredJobAlertRequests.AddRange(batch.ErroredJobAlertRequests);
}
return erroredJobAlertRequests;
}
public Dictionary<string, List<ErroredJobAlertRequest>> GroupJobsByRecipients(List<ErroredJobAlertRequest> erroredJobAlertRequests)
{
var groupedRecipients = new Dictionary<string, List<ErroredJobAlertRequest>>();
foreach (var erroredJobAlertRequest in erroredJobAlertRequests)
{
AddRecipients(erroredJobAlertRequest, groupedRecipients);
}
return groupedRecipients;
}
public void AddRecipients(ErroredJobAlertRequest erroredJbAlertRequest, Dictionary<string, List<ErroredJobAlertRequest>> groupedRecipients)
{
if (erroredJbAlertRequest.CurrentJob.JobErrorRecipients == null ||
erroredJbAlertRequest.CurrentJob.JobErrorRecipients.Count == 0)
{
AddRecipient(_apiConfiguration.DefaultErrorRecipient, erroredJbAlertRequest, groupedRecipients);
return;
}
foreach (var jobErrorRecipient in erroredJbAlertRequest.CurrentJob.JobErrorRecipients)
{
AddRecipient(jobErrorRecipient.Recipient, erroredJbAlertRequest, groupedRecipients);
}
}
public void AddRecipient(string defaultErrorRecipient, ErroredJobAlertRequest erroredJobAlertRequest, Dictionary<string, List<ErroredJobAlertRequest>> groupedRecipients)
{
var recipientLower = defaultErrorRecipient.ToLower();
if (!groupedRecipients.ContainsKey(recipientLower))
{
groupedRecipients.Add(recipientLower, new List<ErroredJobAlertRequest>());
}
groupedRecipients[recipientLower].Add(erroredJobAlertRequest);
}
public async Task<bool> HandleBatchErroredJobs(List<ErroredJobAlertRequest> erroredJobAlertRequests, string recipient)
{
string rowsOfErrors = "";
foreach (ErroredJobAlertRequest erroredJobAlertRequest in erroredJobAlertRequests)
{
var jobErrorDetail = SetUpJobErrorDetails(erroredJobAlertRequest);
if (jobErrorDetail != null)
{
rowsOfErrors += CreateErrorRow(jobErrorDetail);
}
}
var emailTemplate = CreateBatchErroredJobsEmail(rowsOfErrors);
var successfullySent = await _notificationService.SendEmailAsync(recipient,
_apiConfiguration.BatchErroredJobsSubject,
emailTemplate);
return successfullySent;
}
public JobErrorDetail SetUpJobErrorDetails(ErroredJobAlertRequest erroredJobAlertRequest)
{
var jobErrorDetail = new JobErrorDetail()
{
JobName = erroredJobAlertRequest?.CurrentJob?.Name,
WorkItemName = erroredJobAlertRequest?.CurrentWorkItem?.Name,
JobStatus = erroredJobAlertRequest?.CurrentJob?.JobStatus.ToString(),
StartTime = erroredJobAlertRequest?.CurrentWorkItem?.CurrentWorkItemExecution?.StartedTimestamp.ToString()
};
return jobErrorDetail;
}
public string CreateErrorRow(JobErrorDetail jobErrorDetails)
{
using var errorRowReader = new StreamReader(_apiConfiguration.ErrorRowTemplate);
string errorRowOutput = errorRowReader.ReadToEnd();
foreach (var property in jobErrorDetails.GetType().GetProperties())
{
string value = property.GetValue(jobErrorDetails) == null ? string.Empty :
property.GetValue(jobErrorDetails).ToString();
errorRowOutput = errorRowOutput.Replace($"{{{property.Name}}}", value);
}
return errorRowOutput;
}
public string CreateBatchErroredJobsEmail(string errorRows)
{
using var batchErroredJobsReader = new StreamReader(_apiConfiguration.BatchErroredJobsTemplate);
string batchErroredJobsOutput = batchErroredJobsReader.ReadToEnd();
return batchErroredJobsOutput.Replace("{TableRows}", errorRows);
}
}
}
@@ -0,0 +1,90 @@
using System.Collections.Generic;
using LCU.Api.Internal.DispatcherLib.Repositories;
namespace LCU.Api.Internal.Dispatcher.Services;
public class ExecutionMessageService : IExecutionMessageService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IExecutionMessageRepository _executionMessageRepository;
public ExecutionMessageService(IUnitOfWork unitOfWork,
IExecutionMessageRepository executionMessageRepository)
{
_unitOfWork = unitOfWork;
_executionMessageRepository = executionMessageRepository;
}
public async Task SaveExecutionMessages(List<ExecutionMessage> executionMessageList)
{
try
{
if (executionMessageList == null || !executionMessageList.Any())
{
return;
}
foreach (var message in executionMessageList)
{
if (string.IsNullOrWhiteSpace(message.Message))
{
continue;
}
var savedExecutionMessage = await SaveAsync(message);
ArgumentNullException.ThrowIfNull(savedExecutionMessage);
}
}
catch (Exception exception)
{
Log.Error(exception, "During SaveExecutionMessages there was an error. ");
throw new ServiceException($"During SaveExecutionMessages there was an error. " +
"ExceptionMessage: {exception.Message}");
}
}
public async Task SaveExecutionMessages(int workItemExecutionId, List<string> messageList)
{
try
{
if (messageList == null || !messageList.Any())
{
return;
}
foreach (var message in messageList)
{
if (string.IsNullOrWhiteSpace(message))
{
continue;
}
var executionMessage = new ExecutionMessage(workItemExecutionId, message);
var savedExecutionMessage = await SaveAsync(executionMessage);
ArgumentNullException.ThrowIfNull(savedExecutionMessage);
}
}
catch (Exception exception)
{
Log.Error(exception, $"During SaveExecutionMessages there was an error. " +
$"WorkItemExecutionId: {workItemExecutionId}");
throw new ServiceException($"During SaveExecutionMessages there was an error. " +
$"WorkItemExecutionId: {workItemExecutionId}, " +
$"ExceptionMessage: {exception.Message}");
}
}
public async Task<ExecutionMessage> SaveAsync(ExecutionMessage workItemExecution)
{
try
{
var result = await _executionMessageRepository.AddAsync(workItemExecution);
await _unitOfWork.CompleteAsync();
return result;
}
catch (Exception exception)
{
Log.Error(exception, "Failure saving dispatcher Execution Messages", workItemExecution);
throw new ServiceException($"Failure saving dispatcher Execution Messages. ExceptionMessage: { exception.Message }");
}
}
}
@@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services
{
public interface ICleanUpJobSettingService
{
Task<IEnumerable<CleanUpJobSetting>> GetAsync();
Task<CleanUpJobSetting> GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId);
Task<CleanUpJobSetting> SaveAsync(CleanUpJobSetting request);
Task<CleanUpJobSetting> UpdateAsync(CleanUpJobSetting request);
Task DeleteAsync(int cleanUpJobSettingId);
}
}
@@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface ICleanUpJobTagService
{
Task<IEnumerable<CleanUpJobTag>> GetAsync();
Task<CleanUpJobTag> GetByCleanUpJobTagIdAsync(int cleanUpJobTagId);
Task<IEnumerable<CleanUpJobTag>> GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId);
Task<CleanUpJobTag> SaveAsync(CleanUpJobTag request);
Task DeleteAsync(int cleanUpJobTagId);
Task DeleteByCleanUpJobSettingIdAsync(int cleanUpJobSettingId);
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface IExecutionMessageService
{
Task SaveExecutionMessages(List<ExecutionMessage> executionMessageList);
Task SaveExecutionMessages(int workItemExecutionId, List<string> messageList);
Task<ExecutionMessage> SaveAsync(ExecutionMessage workItemExecution);
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
using LCU.Library.Internal.DispatcherModels.Entities;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface IJobErrorRecipientService
{
void SaveList(ICollection<JobErrorRecipient> jobErrorRecipient);
void DeleteList(ICollection<JobErrorRecipient> jobErrorRecipient);
}
@@ -0,0 +1,35 @@
using System.Collections.Generic;
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using LCU.Library.Internal.DispatcherModels.Models.Results;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface IJobService
{
Task<PagedResult<Job>> GetFilteredJobs(Filter filter);
Task<IEnumerable<Job>> GetAsync();
Task<IEnumerable<Job>> GetInactiveAsync();
Task<IEnumerable<Job>> GetInactiveExcludeSoftDeletedTrackedAsync();
Task<IEnumerable<Job>> GetSoftDeletedTrackedAsync(DateTime cutOffDate);
Task<Job> GetByIdAsync(int id);
Task<Job> GetByIdExcludingWorkItemsAsync(int id);
Task<Job> GetByWorkItemIdAsync(int workItemId);
Task<Job> GetByWorkItemIdExcludingWorkItemsAsync(int workItemId);
Task<Job> GetByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(int workItemId);
Task<IEnumerable<Job>> GetJobsReadyToBeScheduled();
Task<IEnumerable<Job>> GetErrorMonitoringJobs();
Task<Job> SaveAsync(Job job);
Task CompleteJobAndWorkItem(Job job, WorkItemExecution currentWorkItemExecution, StatusType finalStatus);
Task CompleteWorkItemAndContinueProcessingJob(Job job, WorkItemExecution currentWorkItemExecution, StatusType finalStatus);
Task<Job> UpdateAsync(Job job);
Task<Job> UpdateExcludingWorkItemsAsyncWithoutSave(Job job);
Task<Job> RemoveSoftDeleteById(int id);
Task<Job> RunJobNowAsync(Job job);
Task<Job> RunJobNowByIdAsync(int id);
Task<Job> DeactivateJobAsync(Job job);
Task<Job> RescheduleJobAsync(Job job);
Task DeleteAsync(int jobId);
Task DeleteFromCleanupAsync(Job job);
Task<Job> CopyJobAsync(CopyJobRequest request);
}
@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface IJobTagService
{
Task<IEnumerable<JobTag>> GetAsync();
Task<IEnumerable<JobTag>> GetByTagIdAsync(int tagId);
Task<JobTag> SaveAsync(JobTag jobTag);
Task Delete(int jobTagId);
}
@@ -0,0 +1,7 @@
using System.Threading.Tasks;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface INotificationService
{
Task<bool> SendEmailAsync(string recipient, string subject, string emailTemplate);
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface ITagService
{
Task<IEnumerable<Tag>> GetAsync();
Task<Tag> GetByIdAsync(int tagId);
Task<Tag> SaveAsync(Tag tag);
Task<Tag> UpdateAsync(Tag tag);
Task DeleteAsync(int tagId);
}
@@ -0,0 +1,17 @@
using LCU.Library.Internal.DispatcherModels.Enums;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface IWorkItemExecutionService
{
Task<WorkItemExecution> GetByIdAsync(int workItemExecutionId);
Task<WorkItemExecution> UpdateCompleteWorkItemExecution(WorkItemExecution workItemExecution, StatusType workItemFinalStatus);
Task<WorkItemExecution> UpdateCompleteWorkItemExecutionWithoutSave(WorkItemExecution workItemExecution, StatusType workItemFinalStatus);
Task<WorkItemExecution> UpdateAsync(WorkItemExecution workItemExecution);
Task<WorkItemExecution> CreateWorkItemExecution(int workItemId);
Task<WorkItemExecution> SaveAsync(WorkItemExecution workItemExecution);
Task<WorkItemExecution> UpdateProcessingWorkItemExecution(WorkItemExecution workItemExecution);
Task<WorkItemExecution> UpdateFailedWorkItemExecution(WorkItemExecution workItemExecution);
Task<IEnumerable<WorkItemExecution>> GetAllUnFinishedWorkItemExecutions(int workItemExecutionId);
Task<IEnumerable<WorkItemExecution>> GetAllByWorkItemId(int workItemId);
}
@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services;
public interface IWorkItemService
{
Task<WorkItem> GetByIdAsync(int workItemId);
Task<IEnumerable<WorkItem>> GetByJobIdAsync(int jobId);
Task<IEnumerable<WorkItem>> GetByJobIdWithExecutionsAsync(int jobId);
Task<WorkItem> GetFirstOrDefaultWorkItemByJobId(int jobId);
Task<WorkItem> GetNextWorkItemByJobId(int jobId, int currentWorkItemOrder);
void SaveList(ICollection<WorkItem> workItems);
Task<ICollection<WorkItem>> PopulateActionFieldsAndUpdateList(ICollection<WorkItem> workItems);
Task<WorkItem> ResetCanceledJobCounter(WorkItem workItem);
Task<WorkItem> IncreaseCanceledJobCounter(WorkItem workItem);
Task<WorkItem> UpdateAsync(WorkItem workItem);
Task DeleteList(ICollection<WorkItem> workItems);
WorkItem PopulateActionFields(WorkItem workItem);
}
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using LCU.Api.Internal.DispatcherLib.Repositories;
namespace LCU.Api.Internal.Dispatcher.Services;
public class JobErrorRecipientService : IJobErrorRecipientService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IJobErrorRecipientRepository _jobErrorRecipientRepository;
public JobErrorRecipientService(IUnitOfWork unitOfWork,
IJobErrorRecipientRepository jobErrorRecipientRepository)
{
_unitOfWork = unitOfWork;
_jobErrorRecipientRepository = jobErrorRecipientRepository;
}
public void SaveList(ICollection<JobErrorRecipient> jobErrorRecipient)
{
try
{
_jobErrorRecipientRepository.AddList(jobErrorRecipient);
_unitOfWork.Complete();
}
catch (SqlException ex)
{
Log.Error(ex, $"SQL Failure saving list of job error recipients.");
throw new ServiceException($"SQL Failure saving list of job error recipients.");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure saving list of job error recipients.");
throw new ServiceException($"Failure saving list of job error recipients.");
}
}
public void DeleteList(ICollection<JobErrorRecipient> jobErrorRecipient)
{
try
{
_jobErrorRecipientRepository.RemoveList(jobErrorRecipient);
_unitOfWork.Complete();
}
catch (SqlException ex)
{
Log.Error(ex, $"SQL Failure removing list of job error recipients.");
throw new ServiceException($"SQL Failure removing list of job error recipients.");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure removing list of job error recipients.");
throw new ServiceException($"Failure removing list of job error recipients.");
}
}
}
@@ -0,0 +1,583 @@
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json;
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Api.Internal.DispatcherLib.Repositories;
using LCU.Api.Internal.DispatcherSharedLib.Utilities;
using LCU.Api.Internal.Dispatcher.Configurations;
using LCU.Library.Internal.TypeExtensions.ObjectExtensions;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using LCU.Library.Internal.DispatcherModels.Models.Results;
using LCU.Library.Internal.Logging.Performance;
namespace LCU.Api.Internal.Dispatcher.Services;
public class JobService : IJobService
{
private readonly IJobRepository _jobRepository;
private readonly IWorkItemService _workItemService;
private readonly IWorkItemExecutionService _workItemExecutionService;
private readonly IUnitOfWork _unitOfWork;
private readonly ApiConfiguration _apiConfiguration;
public JobService(IUnitOfWork unitOfWork,
IJobRepository jobRepository,
IWorkItemService workItemService,
IWorkItemExecutionService workItemExecutionService,
ApiConfiguration apiConfiguration)
{
_unitOfWork = unitOfWork;
_jobRepository = jobRepository;
_workItemService = workItemService;
_workItemExecutionService = workItemExecutionService;
_apiConfiguration = apiConfiguration;
}
public async Task<PagedResult<Job>> GetFilteredJobs(Filter filter)
{
try
{
var performanceMonitorId = Guid.NewGuid();
var getFilteredJobsQueryMonitor = new PerformanceMonitor("GetFilteredJobs", performanceMonitorId);
var filteredJobs = await _jobRepository.FilteredListAsync(filter,
_apiConfiguration.NumberOfWorkItemExecutions,
_apiConfiguration.NumberOfExecutionMessages);
getFilteredJobsQueryMonitor.LogPerformance();
return filteredJobs;
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving filtered dispatcher jobs.");
throw new ServiceException("Failure retrieving filtered dispatcher jobs.");
}
}
public async Task<IEnumerable<Job>> GetAsync()
{
try
{
return await _jobRepository.ListAsync(_apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages);
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving dispatcher jobs.");
throw new ServiceException("Failure retrieving dispatcher jobs.");
}
}
public async Task<IEnumerable<Job>> GetInactiveAsync()
{
try
{
return await _jobRepository.ListInactiveAsync(_apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages);
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving inactive dispatcher jobs.");
throw new ServiceException("Failure retrieving inactive dispatcher jobs.");
}
}
public async Task<IEnumerable<Job>> GetInactiveExcludeSoftDeletedTrackedAsync()
{
try
{
return await _jobRepository.ListInactiveExcludeSoftDeletedTrackedAsync(_apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages);
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving non-SoftDeleted inactive dispatcher jobs.");
throw new ServiceException("Failure retrieving non-SoftDeleted inactive dispatcher jobs.");
}
}
public async Task<IEnumerable<Job>> GetSoftDeletedTrackedAsync(DateTime cutOffDate)
{
try
{
return await _jobRepository.ListSoftDeletedTrackedAsync(_apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages, cutOffDate);
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving soft deleted dispatcher jobs.");
throw new ServiceException("Failure retrieving soft deleted dispatcher jobs.");
}
}
public async Task<Job> GetByIdAsync(int id)
{
try
{
var job = await _jobRepository.FindByIdAsync(id,
_apiConfiguration.NumberOfWorkItemExecutions,
_apiConfiguration.NumberOfExecutionMessages);
ArgumentNullException.ThrowIfNull(job);
return job;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher jobs with id {id}.");
throw new ServiceException($"Failure retrieving dispatcher jobs with id {id}.");
}
}
public async Task<Job> GetByIdExcludingWorkItemsAsync(int id)
{
try
{
var job = await _jobRepository.FindByIdExcludingWorkItemsAsync(id);
ArgumentNullException.ThrowIfNull(job);
return job;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher jobs with id {id}.");
throw new ServiceException($"Failure retrieving dispatcher jobs with id {id}.");
}
}
public async Task<Job> GetByWorkItemIdAsync(int workItemId)
{
try
{
var job = await _jobRepository.FindByWorkItemIdAsync(workItemId,
_apiConfiguration.NumberOfWorkItemExecutions,
_apiConfiguration.NumberOfExecutionMessages);
ArgumentNullException.ThrowIfNull(job);
return job;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher jobs by WorkItemId {workItemId}.");
throw new ServiceException($"Failure retrieving dispatcher jobs by WorkItemId {workItemId}.");
}
}
public async Task<Job> GetByWorkItemIdExcludingWorkItemsAsync(int workItemId)
{
try
{
var job = await _jobRepository.FindByWorkItemIdExcludingWorkItemsAsync(workItemId);
ArgumentNullException.ThrowIfNull(job);
return job;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher jobs excluding WorkItems by WorkItemId {workItemId}.");
throw new ServiceException($"Failure retrieving dispatcher jobs excluding WorkItems by WorkItemId {workItemId}.");
}
}
public async Task<Job> GetByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(int workItemId)
{
try
{
var job = await _jobRepository.FindByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(workItemId);
ArgumentNullException.ThrowIfNull(job);
return job;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher jobs excluding WorkItems by WorkItemId {workItemId}.");
throw new ServiceException($"Failure retrieving dispatcher jobs excluding WorkItems by WorkItemId {workItemId}.");
}
}
public async Task<IEnumerable<Job>> GetJobsReadyToBeScheduled()
{
try
{
var result = await _jobRepository.FindJobsReadyToBeScheduled(DateTime.Now);
return result;
}
catch (Exception exception)
{
Log.Error(exception, "Failure getting pending Jobs");
throw new ServiceException("Failure getting pending Jobs");
}
}
public async Task<IEnumerable<Job>> GetErrorMonitoringJobs()
{
try
{
var result = await _jobRepository.FindJobsPastDateByStatuses(DateTime.Now,
_apiConfiguration.NumberOfWorkItemExecutions,
_apiConfiguration.NumberOfExecutionMessages,
_apiConfiguration.JobStatusesToMonitorByTimeLimit);
ArgumentNullException.ThrowIfNull(result);
var erroredAndStuckJobs = result.Where(job => job.JobStatus == JobStatus.Failed).ToList();
if (_apiConfiguration.JobStatusesToMonitorByTimeLimit is not null && _apiConfiguration.JobStatusesToMonitorByTimeLimit.Count() > 0)
{
erroredAndStuckJobs.AddRange(GetJobsInStatusLongerThanTimeLimit(result.Except(erroredAndStuckJobs).ToList(),
_apiConfiguration.ProcessingLimitInMinutes));
}
return erroredAndStuckJobs;
}
catch (Exception exception)
{
Log.Error(exception, "Failure getting next errored and stuck in processing Jobs");
throw new ServiceException("Failure getting next errored and stuck in processing Jobs");
}
}
public List<Job> GetJobsInStatusLongerThanTimeLimit(List<Job> matchingStatusJobs, int processingLimitInMinutes)
{
var stuckInStatusJobs = new List<Job>();
foreach (var job in matchingStatusJobs)
{
var currentWorkItem = job.WorkItems.FirstOrDefault(wi => wi.CurrentWorkItemExecution != null);
if (currentWorkItem == null)
{
Log.Warning($"Current Work Item came back as null when attempting to get stuck in processing Jobs. JobId: {job.JobId}");
continue;
}
var startTime = Convert.ToDateTime(currentWorkItem.CurrentWorkItemExecution.StartedTimestamp);
var minutesInStatus = DateTime.Now.Subtract(startTime).TotalMinutes;
if (minutesInStatus > processingLimitInMinutes)
{
stuckInStatusJobs.Add(job);
}
}
return stuckInStatusJobs;
}
public async Task<Job> SaveAsync(Job job)
{
try
{
var result = await _jobRepository.AddAsync(job);
await _unitOfWork.CompleteAsync();
ArgumentNullException.ThrowIfNull(result);
return result;
}
catch (Exception exception)
{
Log.Error(exception, "Failure saving dispatcher job", job);
throw new ServiceException("Failure saving dispatcher job");
}
}
public async Task CompleteJobAndWorkItem(Job job, WorkItemExecution currentWorkItemExecution, StatusType workItemFinalStatus)
{
try
{
job.ResetRunTimeAndStatus();
CronHelpers.SetNextScheduledRunTime(ref job);
var updatedJob = await UpdateExcludingWorkItemsAsyncWithoutSave(job);
if (updatedJob == null)
{
throw new Exception($"Failure to setup update for job. Job:{JsonConvert.SerializeObject(job)}");
}
var updatedWorkItemExecution = await _workItemExecutionService.UpdateCompleteWorkItemExecutionWithoutSave(currentWorkItemExecution, workItemFinalStatus);
if (updatedWorkItemExecution == null)
{
throw new Exception($"Failure to setup update for WorkItemExecution. WorkItemExecution:{JsonConvert.SerializeObject(currentWorkItemExecution)}");
}
await _unitOfWork.CompleteAsync();
Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus);
}
catch (SqlException sqlException)
{
var message = $"SQL Failure to Complete Job and WorkItemExecution. JobId: {job.JobId}, WorkItemExecutionId: {currentWorkItemExecution.WorkItemExecutionId}.";
Log.Error(sqlException, message);
throw new ServiceException($"{message} ExceptionMessage: {sqlException.Message}");
}
catch (Exception exception)
{
var message = $"Failure to Complete Job and WorkItemExecution. JobId: {job.JobId}, WorkItemExecutionId: {currentWorkItemExecution.WorkItemExecutionId}.";
Log.Error(exception, message);
throw new ServiceException($"{message} ExceptionMessage: {exception.Message}");
}
}
public async Task CompleteWorkItemAndContinueProcessingJob(Job job, WorkItemExecution currentWorkItemExecution, StatusType workItemFinalStatus)
{
try
{
job.JobStatus = JobStatus.Processing;
var updatedJob = await UpdateExcludingWorkItemsAsyncWithoutSave(job);
if (updatedJob == null)
{
throw new Exception($"Failure to setup update for job. Job:{JsonConvert.SerializeObject(job)}");
}
var updatedWorkItemExecution = await _workItemExecutionService.UpdateCompleteWorkItemExecutionWithoutSave(currentWorkItemExecution, workItemFinalStatus);
if (updatedWorkItemExecution == null)
{
throw new Exception($"Failure to setup update for WorkItemExecution. WorkItemExecution:{JsonConvert.SerializeObject(currentWorkItemExecution)}");
}
await _unitOfWork.CompleteAsync();
Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus);
}
catch (SqlException sqlException)
{
var message = $"SQL Failure to Complete Job and WorkItemExecution. JobId: {job.JobId}, WorkItemExecutionId: {currentWorkItemExecution.WorkItemExecutionId}.";
Log.Error(sqlException, message);
throw new ServiceException($"{message} ExceptionMessage: {sqlException.Message}");
}
catch (Exception exception)
{
var message = $"Failure to Complete Job and WorkItemExecution. JobId: {job.JobId}, WorkItemExecutionId: {currentWorkItemExecution.WorkItemExecutionId}.";
Log.Error(exception, message);
throw new ServiceException($"{message} ExceptionMessage: {exception.Message}");
}
}
public async Task<Job> UpdateAsync(Job job)
{
try
{
var updatedJob = await _jobRepository.Update(job);
await _unitOfWork.CompleteAsync();
return updatedJob;
}
catch (SqlException sqlException)
{
Log.Error(sqlException, "SQL Failure updating dispatcher job", job);
throw new ServiceException("SQL Failure updating dispatcher job");
}
catch (Exception exception)
{
Log.Error(exception, "Failure updating dispatcher job", job);
throw new ServiceException("Failure updating dispatcher job");
}
}
public async Task<Job> UpdateExcludingWorkItemsAsyncWithoutSave(Job job)
{
return await _jobRepository.Update(job);
}
public async Task<Job> RemoveSoftDeleteById(int id)
{
try
{
var existing = await GetByIdAsync(id);
ArgumentNullException.ThrowIfNull(existing);
existing.SoftDeleteDate = null;
_jobRepository.Update(existing);
await _unitOfWork.CompleteAsync();
return existing;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure to remove soft delete date for dispatcher job. JobId: {id}");
throw new ServiceException($"Failure to remove soft delete date for dispatcher job. JobId: {id}");
}
}
public async Task DeleteAsync(int jobId)
{
try
{
var job = await _jobRepository.FindByIdAsync(jobId,
_apiConfiguration.NumberOfWorkItemExecutions,
_apiConfiguration.NumberOfExecutionMessages);
ArgumentNullException.ThrowIfNull(job);
_jobRepository.Remove(job);
await _unitOfWork.CompleteAsync();
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure removing dispatcher job. JobId: {jobId}");
throw new ServiceException($"SQL Failure removing dispatcher job. JobId: {jobId}");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure removing dispatcher job. JobId: {jobId}");
throw new ServiceException($"Failure removing dispatcher job. JobId: {jobId}");
}
}
public async Task DeleteFromCleanupAsync(Job job)
{
try
{
ArgumentNullException.ThrowIfNull(job);
_jobRepository.Remove(job);
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure removing dispatcher job. JobId: {job.JobId}");
throw new ServiceException($"SQL Failure removing dispatcher job. JobId: {job.JobId}");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure removing dispatcher job. JobId: {job.JobId}");
throw new ServiceException($"Failure removing dispatcher job. JobId: {job.JobId}");
}
}
public async Task<Job> RunJobNowAsync(Job job)
{
try
{
job = await ResetJob(job);
job.NextRunTime = DateTime.Now;
job.JobStatus = JobStatus.ReadyToSchedule;
var updatedJob = await UpdateAsync(job);
return updatedJob;
}
catch (Exception exception)
{
Log.Error(exception, "Failure to set job to run now.");
throw new ServiceException("Failure to set job to run now.");
}
}
public async Task<Job> RunJobNowByIdAsync(int id)
{
try
{
var job = await GetByIdAsync(id);
var updatedJob = await RunJobNowAsync(job);
return updatedJob;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure to set job to run now by id. Id: {id}");
throw new ServiceException($"Failure to set job to run now by id. Id: {id}");
}
}
public async Task<Job> DeactivateJobAsync(Job job)
{
try
{
job = await ResetJob(job);
var updatedJob = await UpdateAsync(job);
return updatedJob;
}
catch (Exception exception)
{
Log.Error(exception, "Failure to deactivate job.");
throw new ServiceException("Failure to deactivate job.");
}
}
public async Task<Job> RescheduleJobAsync(Job job)
{
try
{
job = await ResetJob(job);
CronHelpers.SetNextScheduledRunTime(ref job);
var updatedJob = await UpdateAsync(job);
Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus);
return updatedJob;
}
catch (Exception exception)
{
Log.Error(exception, "Failure to reschedule job.");
throw new ServiceException("Failure to reschedule job.");
}
}
public async Task<Job> ResetJob(Job job)
{
foreach (var workItem in job.WorkItems)
{
var executionsToReset = await _workItemExecutionService.GetAllUnFinishedWorkItemExecutions(workItem.WorkItemId);
foreach (var workItemExecution in executionsToReset)
{
workItemExecution.StartedTimestamp = workItemExecution.StartedTimestamp == null ?
DateTime.Now :
workItemExecution.StartedTimestamp;
workItemExecution.CompletedTimestamp = workItemExecution.CompletedTimestamp == null ?
DateTime.Now :
workItemExecution.CompletedTimestamp;
var updatedExecution = await _workItemExecutionService.UpdateAsync(workItemExecution);
ArgumentNullException.ThrowIfNull(updatedExecution);
}
}
job.ResetRunTimeAndStatus();
return job;
}
public async Task<Job> CopyJobAsync(CopyJobRequest request)
{
try
{
var job = (await GetByIdAsync(request.JobId)).DeepCopy();
if (job == null)
{
throw new Exception($"Job with id {request.JobId} not found.");
}
job.Name = $"(COPY) {job.Name}";
job.JobId = 0;
job.CronSchedule = null;
job.CreatedAt = DateTime.Now;
job.ModifiedDate = null;
job.ResetRunTimeAndStatus();
foreach (var errorRecipient in job.JobErrorRecipients)
{
errorRecipient.JobErrorRecipientId = 0;
errorRecipient.JobId = 0;
}
if (request.ChangeEnvironment)
{
job.JobTags = null;
foreach (var workItem in job.WorkItems)
{
workItem.WorkItemId = 0;
workItem.JobId = 0;
workItem.Url = null;
workItem.WorkItemExecutions = null;
}
return job;
}
foreach (var workItem in job.WorkItems)
{
workItem.WorkItemId = 0;
workItem.JobId = 0;
workItem.Url = null;
workItem.WorkItemExecutions = null;
}
foreach (var jobTag in job.JobTags)
{
jobTag.JobTagId = 0;
jobTag.JobId = 0;
jobTag.Tag = null;
}
var savedJob = await SaveAsync(job);
if (savedJob == null)
{
throw new Exception("Failure to save new job.");
}
return savedJob;
}
catch (Exception exception)
{
var message = $"Failure to copy dispatcher job. Request: {JsonConvert.SerializeObject(request)}";
Log.Error(exception, message);
throw new ServiceException($"{message}. Exception: {exception.Message}");
}
}
}
@@ -0,0 +1,81 @@
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using LCU.Api.Internal.DispatcherLib.Repositories;
namespace LCU.Api.Internal.Dispatcher.Services;
public class JobTagService : IJobTagService
{
private readonly IJobTagRepository _jobTagRepository;
private readonly IUnitOfWork _unitOfWork;
public JobTagService(IUnitOfWork unitOfWork,
IJobTagRepository jobTagRepository)
{
_unitOfWork = unitOfWork;
_jobTagRepository = jobTagRepository;
}
public async Task<IEnumerable<JobTag>> GetAsync()
{
try
{
return await _jobTagRepository.ListAsync();
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving JobTags.");
throw new ServiceException("Failure retrieving JobTags.");
}
}
public async Task<IEnumerable<JobTag>> GetByTagIdAsync(int tagId)
{
try
{
ArgumentNullException.ThrowIfNull(tagId);
return await _jobTagRepository.FindByTagIdAsync(tagId);
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher Job Tags. TagId: {tagId}");
throw new ServiceException($"Failure retrieving dispatcher Job Tags. TagId: {tagId}");
}
}
public async Task<JobTag> SaveAsync(JobTag jobTag)
{
try
{
var result = await _jobTagRepository.AddAsync(jobTag);
await _unitOfWork.CompleteAsync();
ArgumentNullException.ThrowIfNull(result);
return result;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure saving dispatcher JobTag. JobTagId: {jobTag.JobTagId}");
throw new ServiceException($"Failure saving dispatcher JobTag. JobTagId: {jobTag.JobTagId}");
}
}
public async Task Delete(int jobTagId)
{
try
{
var jobTag = await _jobTagRepository.FindByIdAsync(jobTagId);
ArgumentNullException.ThrowIfNull(jobTag);
_jobTagRepository.Remove(jobTag);
await _unitOfWork.CompleteAsync();
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure removing dispatcher JobTag. JobTagId: {jobTagId}");
throw new ServiceException($"SQL Failure removing dispatcher JobTag. JobTagId: {jobTagId}");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure removing dispatcher JobTag. JobTagId: {jobTagId}");
throw new ServiceException($"Failure removing dispatcher JobTag. JobTagId: {jobTagId}");
}
}
}
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using LCU.Library.Internal.NotificationsModels.Models;
using LCU.Api.Internal.DispatcherSharedLib.Configurations;
using LCU.Api.Internal.DispatcherSharedLib.Utilities;
using LCU.Api.Internal.Dispatcher.DataAccess;
namespace LCU.Api.Internal.Dispatcher.Services;
public class NotificationService : INotificationService
{
private readonly INotificationsApiDao _notificationsApiDao;
public NotificationService(INotificationsApiDao notificationsApiDao)
{
_notificationsApiDao = notificationsApiDao;
}
public async Task<bool> SendEmailAsync(string recipient, string subject, string emailTemplate)
{
try
{
var emailRequest = new EmailRequest
{
Recipient = new List<string>() { recipient },
Subject = subject,
Body = emailTemplate
};
await _notificationsApiDao.SendEmailAsync(emailRequest);
return true;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure to send email from Dispatcher");
return false;
}
}
}
@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices;
public interface IQueueCompletionService
{
Task QueueCompletion(DispatcherReturnResponse request, List<string> executionMessages = null, bool successful = true);
}
@@ -0,0 +1,10 @@
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices
{
public interface IQueueDetermineOperationService
{
Task QueueDetermineOperation(Job job, WorkItem workItem, DetermineOperationType type);
}
}
@@ -0,0 +1,7 @@
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices;
public interface IQueueExecutionService
{
Task QueueExecution(WorkItemExecution request);
Task QueueRetryExecution(WorkItemExecution request);
}
@@ -0,0 +1,10 @@
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices
{
public interface IQueueJobFailureService
{
public Task QueueJobFailure(int jobId, int workItemId, List<string> messages);
}
}
@@ -0,0 +1,10 @@
using LCU.Library.Internal.DispatcherModels.Models.Requests;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices
{
public interface IQueueSendErroredJobAlertService
{
public Task QueueSendErroredJobAlert(BatchedErroredJobAlertRequest request);
public Task QueueSendErroredJobAlert(Job job, WorkItem workItem);
}
}
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Threading;
using MassTransit;
using Newtonsoft.Json;
using ApiResponse = LCU.Library.Internal.ApiResponse.Models;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Api.Internal.Dispatcher.Configurations;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices;
public class QueueCompletionService : IQueueCompletionService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration;
public QueueCompletionService(IBusControl busControl,
ReceiveEndpointConfigurations queueReceiveEndpointsConfiguration)
{
_queueReceiveEndpointsConfiguration = queueReceiveEndpointsConfiguration;
_sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.CompletionReceiveEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueCompletion(DispatcherReturnResponse request, List<string> messages = null, bool successful = true)
{
try
{
ArgumentNullException.ThrowIfNull(request);
var response = new ApiResponse.Response()
{
Successful = successful,
ResponseStack = messages == null ? new List<string>() : messages,
ResponsePayload = JsonConvert.SerializeObject(request)
};
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await _sendEndpoint.Send(response, cancellationTokenSource.Token);
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Failed to queue completion workitem request. WorkItemId: {request?.WorkItemId}");
throw new ServiceException($"Failed to queue completion workitem request. WorkItemId: {request?.WorkItemId}");
}
}
}
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Threading;
using LCU.Api.Internal.Dispatcher.Configurations;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using MassTransit;
using Newtonsoft.Json;
using ApiResponse = LCU.Library.Internal.ApiResponse.Models;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices
{
public class QueueDetermineOperationService : IQueueDetermineOperationService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration;
public QueueDetermineOperationService(IBusControl busControl,
ReceiveEndpointConfigurations queueReceiveEndpointsConfiguration)
{
_queueReceiveEndpointsConfiguration = queueReceiveEndpointsConfiguration;
_sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.DetermineOperationReceiveEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueDetermineOperation(Job job, WorkItem workItem, DetermineOperationType type)
{
try
{
var request = new DetermineOperationRequest
{
CurrentJob = job,
CurrentWorkItem = workItem,
DetermineOperationType = type
};
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Failed to queue determine operation request. JobName: {job.Name} WorkItemId: {workItem.WorkItemId}");
throw new ServiceException($"Failed to queue determine operation request. JobName: {job.Name} WorkItemId: {workItem.WorkItemId}");
}
}
}
}
@@ -0,0 +1,55 @@
using System.Threading;
using MassTransit;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Api.Internal.Dispatcher.Configurations;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices;
public class QueueExecutionService : IQueueExecutionService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly IWorkItemExecutionService _workItemExecutionService;
private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration;
public QueueExecutionService(IBusControl busControl,
IWorkItemExecutionService workItemExecutionService,
ReceiveEndpointConfigurations queueReceiveEndpointsConfiguration)
{
_queueReceiveEndpointsConfiguration = queueReceiveEndpointsConfiguration;
_workItemExecutionService = workItemExecutionService;
_sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.ExecutionReceiveEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueExecution(WorkItemExecution request)
{
try
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Failed to queue workitemexecution request. WorkItemExecutionId: {request?.WorkItemExecutionId}");
throw new ServiceException($"Failed to queue workitemexecution request. WorkItemExecutionId: {request?.WorkItemExecutionId}");
}
}
public async Task QueueRetryExecution(WorkItemExecution request)
{
try
{
var updatedWorkItemExection = _workItemExecutionService.UpdateProcessingWorkItemExecution(request);
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Failed to queue workitemexecution request. WorkItemExecutionId: {request?.WorkItemExecutionId}");
throw new ServiceException($"Failed to queue workitemexecution request. WorkItemExecutionId: {request?.WorkItemExecutionId}");
}
}
}
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Threading;
using LCU.Api.Internal.Dispatcher.Configurations;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.ApiResponse.Models;
using LCU.Library.Internal.DispatcherModels.Entities;
using LCU.Library.Internal.DispatcherModels.Models;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using MassTransit;
using Newtonsoft.Json;
using ApiResponse = LCU.Library.Internal.ApiResponse.Models;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices
{
public class QueueJobFailureService : IQueueJobFailureService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration;
public QueueJobFailureService(IBusControl busControl,
ReceiveEndpointConfigurations queueReceiveEndpointsConfiguration)
{
_queueReceiveEndpointsConfiguration = queueReceiveEndpointsConfiguration;
_sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.JobFailureReceiveEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueJobFailure(int jobId, int workItemId, List<string> messages)
{
try
{
var request = new JobFailureRequest()
{
JobId = jobId,
WorkItemId = workItemId,
ExecutionMessages = messages
};
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Failed to queue job failure request. JobId: {jobId}, WorkItemId: {workItemId}");
throw new ServiceException($"Failed to queue job failure request. JobId: {jobId}, WorkItemId: {workItemId}");
}
}
}
}
@@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.Threading;
using LCU.Api.Internal.Dispatcher.Configurations;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.DispatcherModels.Models.Requests;
using MassTransit;
using Newtonsoft.Json;
using ApiResponse = LCU.Library.Internal.ApiResponse.Models;
namespace LCU.Api.Internal.Dispatcher.Services.QueueServices
{
public class QueueSendErroredJobAlertService : IQueueSendErroredJobAlertService
{
private readonly ISendEndpoint _sendEndpoint;
private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration;
public QueueSendErroredJobAlertService(IBusControl busControl,
ReceiveEndpointConfigurations queueReceiveEndpointConfigurations)
{
_queueReceiveEndpointsConfiguration = queueReceiveEndpointConfigurations;
_sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.SendErroredJobAlertReceiveEndpoint.ReceiveEndpointUri).Result;
}
public async Task QueueSendErroredJobAlert(BatchedErroredJobAlertRequest request)
{
try
{
ArgumentNullException.ThrowIfNull(request);
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
}
catch (Exception exception)
{
var workItemIds = string.Join(",",
request.ErroredJobAlertRequests
.Where(x => x.CurrentWorkItem != null)
.Select(x => x.CurrentWorkItem.WorkItemId));
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Failed to queue send errored job alert workitem requests. WorkItemIds: {workItemIds}");
throw new ServiceException($"Failed to queue send errored job alert workitem requests. WorkItemIds: {workItemIds}");
}
}
public async Task QueueSendErroredJobAlert(Job job, WorkItem workItem)
{
var request = new ErroredJobAlertRequest()
{
CurrentJob = job,
CurrentWorkItem = workItem
};
var requestList = new List<ErroredJobAlertRequest>()
{
request
};
var batchedRequest = new BatchedErroredJobAlertRequest()
{
ErroredJobAlertRequests = requestList
};
await QueueSendErroredJobAlert(batchedRequest);
}
}
}
@@ -0,0 +1,135 @@
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using LCU.Api.Internal.DispatcherLib.Repositories;
namespace LCU.Api.Internal.Dispatcher.Services;
public class TagService : ITagService
{
private readonly ITagRepository _tagRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IJobTagService _jobTagService;
public TagService(IUnitOfWork unitOfWork,
ITagRepository tagRepository, IJobTagService jobTagService)
{
_unitOfWork = unitOfWork;
_tagRepository = tagRepository;
_jobTagService = jobTagService;
}
public async Task<IEnumerable<Tag>> GetAsync()
{
try
{
return await _tagRepository.ListAsync();
}
catch (Exception exception)
{
Log.Error(exception, "Failure retrieving dispatcher Tags.");
throw new ServiceException("Failure retrieving dispatcher Tags.");
}
}
public async Task<Tag> GetByIdAsync(int id)
{
try
{
var tag = await _tagRepository.FindByIdAsync(id);
ArgumentNullException.ThrowIfNull(tag);
return tag;
}
catch (ArgumentNullException nullEx)
{
Log.Error(nullEx, $"Failure retrieving dispatcher Tags. TagId: {id}");
throw new ArgumentNullException($"Failure retrieving dispatcher Tags. TagId: {id}");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher Tags. TagId: {id}");
throw new ServiceException($"Failure retrieving dispatcher Tags. TagId: {id}");
}
}
public async Task<Tag> SaveAsync(Tag tag)
{
try
{
var result = await _tagRepository.AddAsync(tag);
await _unitOfWork.CompleteAsync();
ArgumentNullException.ThrowIfNull(result);
return result;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure saving dispatcher Tag. TagId: {tag.TagId}");
throw new ServiceException($"Failure saving dispatcher Tag. TagId: {tag.TagId}");
}
}
public async Task<Tag> UpdateAsync(Tag tag)
{
try
{
_tagRepository.Update(tag);
await _unitOfWork.CompleteAsync();
return tag;
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure updating dispatcher Tag. TagId: {tag.TagId}");
throw new ServiceException($"SQL Failure updating dispatcher Tag. TagId: {tag.TagId}");
}
catch (Exception exception)
{
Log.Error(exception, $"SQL Failure updating dispatcher Tag. TagId: {tag.TagId}");
throw new ServiceException($"SQL Failure updating dispatcher Tag. TagId: {tag.TagId}");
}
}
public async Task DeleteAsync(int tagId)
{
try
{
var jobTags = await _jobTagService.GetByTagIdAsync(tagId);
if (jobTags == null || !jobTags.Any())
{
await RemoveJobTags(jobTags.ToList());
}
var tag = await _tagRepository.FindByIdAsync(tagId);
ArgumentNullException.ThrowIfNull(tag);
_tagRepository.Remove(tag);
await _unitOfWork.CompleteAsync();
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure removing dispatcher Tag. TagId: {tagId}");
throw new ServiceException($"SQL Failure removing dispatcher Tag. TagId: {tagId}");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure removing dispatcher Tag. TagId: {tagId}");
throw new ServiceException($"Failure removing dispatcher Tag. TagId: {tagId}");
}
}
private async Task<bool> RemoveJobTags(List<JobTag> jobTags)
{
bool isSuccessful = false;
try
{
foreach (var jobTag in jobTags)
{
await _jobTagService.Delete(jobTag.JobTagId);
isSuccessful = true;
}
return isSuccessful;
}
catch (Exception exception)
{
Log.Error(exception, "Failure to remove job tags.");
throw new ServiceException("Failure to remove job tags.");
}
}
}
@@ -0,0 +1,150 @@
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using LCU.Library.Internal.DispatcherModels.Enums;
using LCU.Api.Internal.DispatcherLib.Repositories;
namespace LCU.Api.Internal.Dispatcher.Services;
public class WorkItemExecutionService : IWorkItemExecutionService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IWorkItemExecutionRepository _workItemExecutionRepository;
public WorkItemExecutionService(IUnitOfWork unitOfWork,
IWorkItemExecutionRepository workItemExecutionRepository)
{
_unitOfWork = unitOfWork;
_workItemExecutionRepository = workItemExecutionRepository;
}
public async Task<WorkItemExecution> GetByIdAsync(int id)
{
WorkItemExecution workItemExecution = null;
try
{
workItemExecution = await _workItemExecutionRepository.FindByIdAsync(id);
ArgumentNullException.ThrowIfNull(workItemExecution);
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher WorkItemExecution with id {id}.");
throw new ServiceException($"Failure retrieving dispatcher WorkItemExecution with id {id}.");
}
return workItemExecution;
}
public async Task<WorkItemExecution> UpdateCompleteWorkItemExecution(WorkItemExecution workItemExecution, StatusType finalStatus)
{
workItemExecution = WorkItemExecution.SetAsFinal(workItemExecution, finalStatus);
var updatedFailedWorkItem = await UpdateAsync(workItemExecution);
return updatedFailedWorkItem;
}
public async Task<WorkItemExecution> UpdateCompleteWorkItemExecutionWithoutSave(WorkItemExecution workItemExecution, StatusType finalStatus)
{
workItemExecution = WorkItemExecution.SetAsFinal(workItemExecution, finalStatus);
var updatedFailedWorkItem = await UpdateAsyncWithoutSave(workItemExecution);
return updatedFailedWorkItem;
}
public async Task<WorkItemExecution> UpdateProcessingWorkItemExecution(WorkItemExecution workItemExecution)
{
workItemExecution.Status = StatusType.Processing;
return await UpdateAsync(workItemExecution);
}
public async Task<WorkItemExecution> UpdateFailedWorkItemExecution(WorkItemExecution workItemExecution)
{
workItemExecution.Status = StatusType.Failed;
return await UpdateAsync(workItemExecution);
}
public async Task<WorkItemExecution> UpdateAsync(WorkItemExecution workItemExecution)
{
try
{
var updatedWorkItemExecution = await UpdateAsyncWithoutSave(workItemExecution);
await _unitOfWork.CompleteAsync();
return updatedWorkItemExecution;
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}");
throw new ServiceException($"SQL Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}");
throw new ServiceException($"Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}");
}
}
public async Task<WorkItemExecution> UpdateAsyncWithoutSave(WorkItemExecution workItemExecution)
{
try
{
_workItemExecutionRepository.Update(workItemExecution);
return workItemExecution;
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}");
throw new ServiceException($"SQL Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}");
throw new ServiceException($"Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}");
}
}
public async Task<WorkItemExecution> CreateWorkItemExecution(int workItemId)
{
var newWorkItemExecution = new WorkItemExecution(workItemId);
return await SaveAsync(newWorkItemExecution);
}
public async Task<WorkItemExecution> SaveAsync(WorkItemExecution workItemExecution)
{
try
{
var result = await _workItemExecutionRepository.AddAsync(workItemExecution);
await _unitOfWork.CompleteAsync();
return result;
}
catch (Exception exception)
{
Log.Error(exception, "Failure saving dispatcher WorkItemExecution", workItemExecution);
throw new ServiceException("Failure saving dispatcher WorkItemExecution");
}
}
public async Task<IEnumerable<WorkItemExecution>> GetAllUnFinishedWorkItemExecutions(int workItemId)
{
try
{
var result = await _workItemExecutionRepository.FindUnFinishedWorkItemExecutions(workItemId);
return result;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher WorkItemExecutions with id {workItemId}.");
throw new ServiceException($"Failure retrieving dispatcher WorkItemExecutions with id {workItemId}.");
}
}
public async Task<IEnumerable<WorkItemExecution>> GetAllByWorkItemId(int workItemId)
{
try
{
var result = await _workItemExecutionRepository.FindAllByWorkItemId(workItemId);
return result;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher WorkItemExecutions with WorkItemId {workItemId}.");
throw new ServiceException($"Failure retrieving dispatcher WorkItemExecutions with WorkItemId {workItemId}.");
}
}
}
@@ -0,0 +1,258 @@
using LCU.Api.Internal.Dispatcher.Configurations;
using LCU.Api.Internal.DispatcherLib.Repositories;
using LCU.Api.Internal.DispatcherSharedLib.BuilderServices;
using LCU.Library.Internal.ApiResponse.Models;
using LCU.Library.Internal.LcuMassTransitModels;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace LCU.Api.Internal.Dispatcher.Services;
public class WorkItemService : IWorkItemService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IWorkItemRepository _workItemRepository;
private readonly IWorkItemExecutionService _workItemExecutionService;
private readonly IActionBuilderService _actionBuilderService;
private readonly ConnectionConfiguration _connectionConfiguration;
private readonly ReceiveEndpointConfiguration _completionReceiveEndpoint;
private readonly ApiConfiguration _apiConfiguration;
public WorkItemService(IUnitOfWork unitOfWork,
IWorkItemRepository workItemRepository,
IWorkItemExecutionService workItemExecutionService,
IActionBuilderService actionBuilderService,
ConnectionConfiguration connectionConfiguration,
ReceiveEndpointConfigurations queueReceiveEndpointsConfigurations,
ApiConfiguration apiConfiguration)
{
_unitOfWork = unitOfWork;
_workItemRepository = workItemRepository;
_workItemExecutionService = workItemExecutionService;
_actionBuilderService = actionBuilderService;
_connectionConfiguration = connectionConfiguration;
_completionReceiveEndpoint = queueReceiveEndpointsConfigurations.CompletionReceiveEndpoint;
_apiConfiguration = apiConfiguration;
}
public async Task<WorkItem> GetByIdAsync(int id)
{
WorkItem workItem = null;
try
{
workItem = await _workItemRepository.FindByIdAsync(id);
ArgumentNullException.ThrowIfNull(workItem);
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher workitem with id {id}.");
throw new ServiceException($"Failure retrieving dispatcher workitem with id {id}.");
}
return workItem;
}
public async Task<IEnumerable<WorkItem>> GetByJobIdAsync(int jobId)
{
IEnumerable<WorkItem> workItems = null;
try
{
workItems = await _workItemRepository.FindByJobIdAsync(jobId);
ArgumentNullException.ThrowIfNull(workItems);
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher workitems with JobId {jobId}.");
throw new ServiceException($"Failure retrieving dispatcher workitems with JobId {jobId}.");
}
return workItems;
}
public async Task<IEnumerable<WorkItem>> GetByJobIdWithExecutionsAsync(int jobId)
{
IEnumerable<WorkItem> workItems = null;
try
{
workItems = await _workItemRepository.FindByJobIdWithExecutionsAsync(jobId,
_apiConfiguration.NumberOfWorkItemExecutions,
_apiConfiguration.NumberOfExecutionMessages);
ArgumentNullException.ThrowIfNull(workItems);
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving dispatcher workitems and executions with JobId {jobId}.");
throw new ServiceException($"Failure retrieving dispatcher workitems and executions with JobId {jobId}.");
}
return workItems;
}
public async Task<WorkItem> GetFirstOrDefaultWorkItemByJobId(int jobId)
{
WorkItem workItem = null;
try
{
workItem = await _workItemRepository.GetFirstOrDefaultWorkItemByJobId(jobId);
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving first or default workItem with JobId: {jobId}.");
throw new ServiceException($"Failure retrieving first or default workItem with JobId: {jobId}.");
}
return workItem;
}
public async Task<WorkItem> GetNextWorkItemByJobId(int jobId, int currentWorkItemOrder)
{
WorkItem workItem = null;
try
{
workItem = await _workItemRepository.GetNextWorkItemByJobId(jobId, currentWorkItemOrder);
}
catch (Exception exception)
{
Log.Error(exception, $"Failure retrieving next workItem with JobId: {jobId}.");
throw new ServiceException($"Failure retrieving next workItem with JobId: {jobId}.");
}
return workItem;
}
public async Task<ICollection<WorkItem>> PopulateActionFieldsAndUpdateList(ICollection<WorkItem> workItems)
{
try
{
var updatedWorkItems = new List<WorkItem>();
foreach (var workItem in workItems)
{
var newWorkItem = workItem;
newWorkItem = PopulateActionFields(newWorkItem);
newWorkItem = await UpdateAsync(newWorkItem);
updatedWorkItems.Add(newWorkItem);
}
return updatedWorkItems;
}
catch (Exception exception)
{
Log.Error(exception, $"Failure populating action fields and updating dispatcher workitems.");
}
return null;
}
public async Task<WorkItem> ResetCanceledJobCounter(WorkItem workItem)
{
var trackedWorkItem = await _workItemRepository.GetTrackedWorkItem(workItem.WorkItemId);
trackedWorkItem.CanceledJobCounter = 0;
await _unitOfWork.CompleteAsync();
return await _workItemRepository.FindByIdAsync(trackedWorkItem.WorkItemId);
}
public async Task<WorkItem> IncreaseCanceledJobCounter(WorkItem workItem)
{
var trackedWorkItem = await _workItemRepository.GetTrackedWorkItem(workItem.WorkItemId);
trackedWorkItem.CanceledJobCounter++;
await _unitOfWork.CompleteAsync();
return await _workItemRepository.FindByIdAsync(trackedWorkItem.WorkItemId);
}
public async Task<WorkItem> UpdateAsync(WorkItem workItem)
{
try
{
_workItemRepository.Update(workItem);
await _unitOfWork.CompleteAsync();
return workItem;
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure updating dispatcher workitem with id {workItem.WorkItemId}");
throw new ServiceException($"SQL Failure updating dispatcher workitem with id {workItem.WorkItemId}");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure updating dispatcher workitem with id {workItem.WorkItemId}");
throw new ServiceException($"Failure updating dispatcher workitem with id {workItem.WorkItemId}");
}
}
public void SaveList(ICollection<WorkItem> workItems)
{
try
{
_workItemRepository.AddList(workItems);
_unitOfWork.Complete();
}
catch (Exception exception)
{
Log.Error(exception, $"Failure saving list of workitems.");
throw new ServiceException($"Failure saving list of workitems.");
}
}
public async Task DeleteList(ICollection<WorkItem> workItems)
{
try
{
foreach (var workItem in workItems)
{
var workItemExecutions = await _workItemExecutionService.GetAllByWorkItemId(workItem.WorkItemId);
workItem.WorkItemExecutions = workItemExecutions.ToList();
}
_workItemRepository.RemoveList(workItems);
await _unitOfWork.CompleteAsync();
}
catch (SqlException sqlException)
{
Log.Error(sqlException, $"SQL Failure removing dispatcher workitems");
throw new ServiceException($"SQL Failure removing dispatcher workitems");
}
catch (Exception exception)
{
Log.Error(exception, $"Failure removing dispatcher workitems");
throw new ServiceException($"Failure removing dispatcher workitems");
}
}
public WorkItem PopulateActionFields(WorkItem workItem)
{
var actionBuilder = _actionBuilderService.BuildAction(workItem.ActionType);
var responseItem = CreateResponseItem(workItem.WorkItemId);
workItem = actionBuilder.AddUrlAndHttpMethod(workItem);
workItem = actionBuilder.AddWorkItemReturnResponse(workItem, responseItem);
return workItem;
}
private ResponseItem CreateResponseItem(int workItemId)
{
var dispatcherReturnResponse = new DispatcherReturnResponse
{
WorkItemId = workItemId
};
var serializeDispatcherReturnResponse = JsonConvert.SerializeObject(dispatcherReturnResponse);
var newReturnItem = new ResponseItem
{
ResponsePayload = serializeDispatcherReturnResponse,
ReturnQueueConnectionConfig = new QueueConnectionConfig()
{
Url = _connectionConfiguration.HostUrl,
Username = _connectionConfiguration.UserName,
Password = _connectionConfiguration.Password
},
ReturnQueueConsumerConfig = new QueueConsumerConfig()
{
ExchangeName = _completionReceiveEndpoint.ReceiveEndpoint
}
};
return newReturnItem;
}
}