using System.ComponentModel; using e_suite.API.Common; using e_suite.API.Common.exceptions; using e_suite.API.Common.repository; using e_suite.Database.Audit; using e_suite.Database.Core.Extensions; using e_suite.Utilities.Pagination; using e_suite.Workflow.Core.Attributes; using e_suite.Workflow.Core.Extensions; using e_suite.Workflow.Core.Interfaces; using eSuite.Core.Miscellaneous; using System.Linq.Expressions; using System.Reflection; using e_suite.Database.Core.Tables.Workflow; //using WorkflowVersion = e_suite.Workflow.Core.WorkflowVersion; namespace e_suite.Modules.WorkflowTemplatesManager; public class WorkflowTemplateManager : IWorkflowTemplateManager { private readonly IWorkflowTemplateRepository _workflowTemplateRepository; private readonly IWorkflowConverter _workflowConverter; private readonly IDomainRepository _domainRepository; private readonly IPatchFactory _patchFactory; public WorkflowTemplateManager(IWorkflowTemplateRepository workflowTemplateRepository, IPatchFactory patchFactory, IDomainRepository domainRepository, IWorkflowConverter workflowConverter) { _workflowTemplateRepository = workflowTemplateRepository; _patchFactory = patchFactory; _domainRepository = domainRepository; _workflowConverter = workflowConverter; } public async Task> GetWorkflowTemplatesAsync(Paging paging, CancellationToken cancellationToken) { var ssoProviders = _workflowTemplateRepository.GetWorkflows().Where(x => x.Deleted == false); var paginatedData = await PaginatedData.Paginate(ssoProviders, paging, WorkflowKeySelector, cancellationToken); return paginatedData; } private Expression> WorkflowKeySelector(string sortKey) { return sortKey?.ToLowerInvariant() switch { "id" => x => x.Id, "guid" => x => x.Guid, "name" => x => x.Name, "lastupdated" => x => x.LastUpdated, _ => x => x.Id }; } public async Task> GetWorkflowTemplateVersionsAsync(Paging paging, CancellationToken cancellationToken) { var ssoProviders = _workflowTemplateRepository.GetWorkflowVersions().Where(x => x.Deleted == false); var paginatedData = await PaginatedData.Paginate(ssoProviders, paging, KeySelector, cancellationToken); return paginatedData; } private Expression> KeySelector(string sortKey) { return sortKey?.ToLowerInvariant() switch { "id" => x => x.Id, "activitynametemplate" => x => x.ActivityNameTemplate, "description" => x => x.Description, "domain.name" => x => x.Domain.Name, "workflow.name" => x => x.Workflow.Name, _ => x => x.Id }; } public async Task GetWorkflowTemplateAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken) { var workflow = await _workflowTemplateRepository.GetWorkflows().FindByGeneralIdRefAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("Unable to find Workflow"); var dbWorkflowTemplate = _workflowTemplateRepository.GetWorkflowVersions().Where(x => x.WorkflowId == workflow.Id).OrderByDescending( x => x.Version).First(); //var dbWorkflowTemplate = await _workflowTemplateRepository.GetWorkflowVersions().FindByGeneralIdRefAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("Unable to find Workflow Version"); //var workflowTemplate = _workflowConverter.DeserialiseFromDatabase(dbWorkflowTemplate); return new GetWorkflowTemplateVersion(dbWorkflowTemplate); } public async Task EditTemplateVersion( AuditUserDetails auditUserDetails, GetWorkflowTemplateVersion template, CancellationToken cancellationToken ) { await AlterWorkflowTemplateVersionAsync(auditUserDetails, template.ToGeneralIdRef()!, async version => { var domain = await _domainRepository.GetDomainById(template.DomainId!, cancellationToken) ?? throw new NotFoundException("Unable to find Domain with provided id"); var workflow = version.Workflow; workflow.Name = template.WorkflowName; var newVersion = new e_suite.Database.Core.Tables.Workflow.WorkflowVersion { Domain = domain, DomainId = domain.Id, Description = template.Description, ActivityNameTemplate = template.ActivityNameTemplate, Deleted = false, Version = template.Version + 1, Tasks = template.Tasks, Workflow = workflow }; return newVersion; }, cancellationToken); } public async Task PatchTemplateVersion( AuditUserDetails auditUserDetails, IGeneralIdRef templateId, PatchWorkflowTemplateVersion patchTemplate, CancellationToken cancellationToken ) { var patch = _patchFactory.Create(patchTemplate); await AlterWorkflowTemplateVersionAsync(auditUserDetails, templateId, async version => { await patch.ApplyTo(version); return version; }, cancellationToken); } public async Task PostTemplateVersion( AuditUserDetails auditUserDetails, CreateWorkflowTemplateVersion template, CancellationToken cancellationToken ) { await _workflowTemplateRepository.TransactionAsync(async () => { if (_workflowTemplateRepository.GetWorkflows().FirstOrDefault(x => x.Name == template.WorkflowName) != null) { throw new ExistsException("Workflow already exists"); } var workflow = new Database.Core.Tables.Workflow.Workflow { Name = template.WorkflowName, Guid = Guid.NewGuid() }; await _workflowTemplateRepository.AddWorkflow(auditUserDetails, workflow, cancellationToken); var workflowDomain = await _domainRepository.GetDomainById(template.DomainId, cancellationToken) ?? throw new NotFoundException("Unable to find domain"); var dbWorkflowTemplate = new WorkflowVersion { ActivityNameTemplate = template.ActivityNameTemplate, Description = template.Description, Domain = workflowDomain, Tasks = template.Tasks, Workflow = workflow, }; //This will attempt to parse the data onto the internal workflow structure. Workflow.Core.WorkflowVersion? workflowTemplate = _workflowConverter.DeserialiseFromDatabase(dbWorkflowTemplate); if (workflowTemplate is null) { throw new InvalidDataException("The workflow template is not valid"); } await _workflowTemplateRepository.AddWorkflowVersion(auditUserDetails, dbWorkflowTemplate, cancellationToken); } ); } public async Task DeleteTemplateVersion(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, CancellationToken cancellationToken) { await AlterWorkflowTemplateVersionAsync(auditUserDetails, templateId, async version => { version.Deleted = true; return version; }, cancellationToken); } private async Task AlterWorkflowTemplateVersionAsync( AuditUserDetails auditUserDetails, IGeneralIdRef generalIdRef, Func> applyChanges, CancellationToken cancellationToken ) { await _workflowTemplateRepository.TransactionAsync(async () => { var workflowVersion = await _workflowTemplateRepository.GetWorkflowVersions() .FindByGeneralIdRefAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("WorkflowVersion with this id does not exists"); var newVersion = await applyChanges(workflowVersion); if (newVersion != workflowVersion) await _workflowTemplateRepository.AddWorkflowVersion(auditUserDetails, newVersion, cancellationToken); else await _workflowTemplateRepository.EditWorkflowVersionAsync(auditUserDetails, workflowVersion, cancellationToken); }); } private static string FormatInterfaceName(Type interfaceType) { if (!interfaceType.IsGenericType) return interfaceType.Name; var genericName = interfaceType.Name; var tickIndex = genericName.IndexOf('`'); if (tickIndex > 0) genericName = genericName.Substring(0, tickIndex); var genericArgs = interfaceType.GetGenericArguments(); var formattedArgs = string.Join(", ", genericArgs.Select(a => a.Name)); return $"{genericName}<{formattedArgs}>"; } public Task> GetAllowedTaskMetadataList(string? taskType, CancellationToken cancellationToken) { var taskTypeAttribute = StageExtensions.GetTaskAttributeType(taskType); var allowedTypes = StageExtensions.GetAllowedTaskTypes(taskTypeAttribute); var result = allowedTypes .Select(t => { var interfaces = t.GetInterfaces(); var capabilities = interfaces .Where(i => i.GetCustomAttribute() != null) .Select(FormatInterfaceName) .ToList(); var taskAttribute = t.GetCustomAttributes(typeof(TaskTypeAttribute), inherit: true).FirstOrDefault() as TaskTypeAttribute; var newTaskMetadata = new TaskMetadata { TaskType = t.FullName!, DisplayName = t.Name, Icon = taskAttribute?.TaskIcon ?? string.Empty }; newTaskMetadata.Capabilities.AddRange(capabilities); var outcomeInterface = interfaces .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOutcome<>)); var outcomeEnumType = outcomeInterface.GetGenericArguments()[0]; newTaskMetadata.OutcomeLabel = outcomeEnumType .GetCustomAttribute()? .Description ?? outcomeEnumType.Name; newTaskMetadata.Outcomes = Enum.GetNames(outcomeEnumType).Select( x => $"{outcomeEnumType.Name}.{x}").ToList(); return newTaskMetadata; }) .ToList(); return Task.FromResult(result); } private async Task AlterWorkflowTemplateAsync( AuditUserDetails auditUserDetails, IGeneralIdRef generalIdRef, Func> applyChanges, CancellationToken cancellationToken ) { await _workflowTemplateRepository.TransactionAsync(async () => { var workflow = await _workflowTemplateRepository.GetWorkflows() .FindByGeneralIdRefAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("Workflow with this id does not exists"); var newWorkflow = await applyChanges(workflow); if (newWorkflow != workflow) await _workflowTemplateRepository.AddWorkflow(auditUserDetails, newWorkflow, cancellationToken); else await _workflowTemplateRepository.EditWorkflowAsync(auditUserDetails, workflow, cancellationToken); }); } public async Task DeleteTemplate(AuditUserDetails auditUserDetails, GeneralIdRef templateId, CancellationToken cancellationToken) { await AlterWorkflowTemplateAsync(auditUserDetails, templateId, async version => { version.Deleted = true; return version; }, cancellationToken); } }