Work towards implementing a workflow template editor

This commit is contained in:
Colin Dawson 2026-02-09 22:07:37 +00:00
parent 17aea93370
commit af02ede4e3
21 changed files with 405 additions and 7871 deletions

View File

@ -0,0 +1,29 @@
using e_suite.Database.Core.Models;
using e_suite.Database.Core.Tables.Workflow;
namespace e_suite.API.Common;
public class GetWorkflowTemplate : IGeneralId
{
public GetWorkflowTemplate( Workflow workflow)
{
Id = workflow.Id;
Guid = workflow.Guid;
WorkflowName = workflow.Name;
Version = workflow.Versions.Max(x => x.Version);
Deleted = workflow.Deleted;
LastUpdated = workflow.LastUpdated;
}
public long Version { get; set; }
public DateTimeOffset LastUpdated { get; set; }
public bool Deleted { get; set; }
public string WorkflowName { get; set; }
public Guid Guid { get; set; }
public long Id { get; set; }
}

View File

@ -7,43 +7,43 @@ using eSuite.Core.Miscellaneous;
namespace e_suite.API.Common;
public class GetWorkflowTemplate : IGeneralId
public class GetWorkflowTemplateVersion : IGeneralId
{
public GetWorkflowTemplate()
public GetWorkflowTemplateVersion()
{
}
public GetWorkflowTemplate(WorkflowVersion workflowVersion)
public GetWorkflowTemplateVersion(WorkflowVersion workflowVersion)
{
Id = workflowVersion.Id;
Guid = workflowVersion.Guid;
Deleted = workflowVersion.Deleted;
ActivityNameTemplate = workflowVersion.ActivityNameTemplate;
Description = workflowVersion.Description;
DomainId = workflowVersion.Domain.ToGeneralIdRef();
DomainId = workflowVersion.Domain.ToGeneralIdRef()!;
LastUpdated = workflowVersion.LastUpdated;
Version = workflowVersion.Version;
WorkflowId = workflowVersion.Workflow.ToGeneralIdRef();
WorkflowId = workflowVersion.Workflow.ToGeneralIdRef()!;
WorkflowName = workflowVersion.Workflow.Name;
Tasks = workflowVersion.Tasks;
}
public List<TaskDefinition> Tasks { get; set; }
public List<TaskDefinition> Tasks { get; set; } = [];
public string WorkflowName { get; set; }
public string WorkflowName { get; set; } = string.Empty;
public GeneralIdRef? WorkflowId { get; set; }
public GeneralIdRef WorkflowId { get; set; } = null!;
public long Version { get; set; }
public DateTimeOffset LastUpdated { get; set; }
public GeneralIdRef? DomainId { get; set; }
public GeneralIdRef DomainId { get; set; } = null!;
public string Description { get; set; }
public string Description { get; set; } = string.Empty;
public string ActivityNameTemplate { get; set; }
public string ActivityNameTemplate { get; set; } = string.Empty;
public bool Deleted { get; set; }
@ -51,25 +51,23 @@ public class GetWorkflowTemplate : IGeneralId
public Guid Guid { get; set; }
}
public class EditWorkflowTemplate : IGeneralId
public class EditWorkflowTemplateVersion : IGeneralId
{
public long Id { get; set; }
public Guid Guid { get; set; }
public string WorkflowName { get; set; }
public GeneralIdRef? WorkflowId { get; set; }
public long Version { get; set; }
public GeneralIdRef? DomainId { get; set; }
public string Description { get; set; }
public string Description { get; set; } = String.Empty;
public string ActivityNameTemplate { get; set; }
public string ActivityNameTemplate { get; set; } = String.Empty;
public List<TaskDefinition> Tasks { get; set; } = [];
}
public class PatchWorkflowTemplate
public class PatchWorkflowTemplateVersion
{
public string? WorkflowName { get; set; }
@ -81,33 +79,29 @@ public class PatchWorkflowTemplate
public string? ActivityNameTemplate { get; set; }
}
public class CreateWorkflowTemplate
public class CreateWorkflowTemplateVersion
{
public List<TaskDefinition> Tasks { get; set; }
public List<TaskDefinition> Tasks { get; set; } = [];
public string WorkflowName { get; set; }
public GeneralIdRef? WorkflowId { get; set; }
public required GeneralIdRef WorkflowId { get; set; }
public long Version { get; set; }
public DateTimeOffset LastUpdated { get; set; }
public required GeneralIdRef DomainId { get; set; }
public GeneralIdRef? DomainId { get; set; }
public string Description { get; set; } = String.Empty;
public string Description { get; set; }
public string ActivityNameTemplate { get; set; }
public bool Deleted { get; set; }
public string ActivityNameTemplate { get; set; } = String.Empty;
}
public interface IWorkflowTemplateManager
{
Task<PaginatedData<GetWorkflowTemplate>> GetWorkflowTemplatesAsync(Paging paging, CancellationToken cancellationToken);
Task<GetWorkflowTemplate?> GetWorkflowTemplateAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken);
Task EditTemplate(AuditUserDetails auditUserDetails, EditWorkflowTemplate template, CancellationToken cancellationToken);
Task PatchTemplate(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, PatchWorkflowTemplate patchTemplate, CancellationToken cancellationToken);
Task PostTemplate(AuditUserDetails auditUserDetails, CreateWorkflowTemplate template, CancellationToken cancellationToken);
Task DeleteTemplate(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, CancellationToken cancellationToken);
Task<PaginatedData<GetWorkflowTemplateVersion>> GetWorkflowTemplateVersionsAsync(Paging paging, CancellationToken cancellationToken);
Task<GetWorkflowTemplateVersion?> GetWorkflowTemplateVersionAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken);
Task EditTemplateVersion(AuditUserDetails auditUserDetails, EditWorkflowTemplateVersion template, CancellationToken cancellationToken);
Task PatchTemplateVersion(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, PatchWorkflowTemplateVersion patchTemplate, CancellationToken cancellationToken);
Task PostTemplateVersion(AuditUserDetails auditUserDetails, CreateWorkflowTemplateVersion template, CancellationToken cancellationToken);
Task DeleteTemplateVersion(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, CancellationToken cancellationToken);
}

View File

@ -6,6 +6,7 @@ namespace e_suite.API.Common.repository;
public interface IWorkflowTemplateRepository : IRepository
{
public IQueryable<Workflow> GetWorkflows();
IQueryable<WorkflowVersion> GetWorkflowVersions();
Task EditWorkflowVersionAsync(AuditUserDetails auditUserDetails, WorkflowVersion workflowVersion, CancellationToken cancellationToken);
}

View File

@ -27,6 +27,7 @@ public class WorkflowTemplateController : ESuiteControllerBase
_workflowTemplateManager = workflowTemplateManager;
}
/// <summary>
/// Get a list of workflow templates
/// </summary>
@ -37,30 +38,46 @@ public class WorkflowTemplateController : ESuiteControllerBase
[Route("templates")]
[AccessKey(SecurityAccess.ViewWorkflowTemplates)]
[HttpGet]
public async Task<IActionResult> GetWorkflowTemplates([FromQuery] Paging paging,CancellationToken cancellationToken = default!)
public async Task<IActionResult> GetWorkflowTemplates([FromQuery] Paging paging, CancellationToken cancellationToken = default!)
{
var result = await _workflowTemplateManager.GetWorkflowTemplatesAsync(paging, cancellationToken);
return Ok(result);
}
/// <summary>
/// Get a list of workflow templates
/// </summary>
/// <param name="paging"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
[Route("templateVersions")]
[AccessKey(SecurityAccess.ViewWorkflowTemplates)]
[HttpGet]
public async Task<IActionResult> GetWorkflowTemplateVersions([FromQuery] Paging paging,CancellationToken cancellationToken = default!)
{
var result = await _workflowTemplateManager.GetWorkflowTemplateVersionsAsync(paging, cancellationToken);
return Ok(result);
}
/// <summary>
/// Get the details of specific template
/// </summary>
/// <param name="generalIdRef"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[Route("template")]
[Route("templateVersion")]
[HttpGet]
[AccessKey(SecurityAccess.ViewWorkflowTemplates)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetWorkflowTemplate(
public async Task<IActionResult> GetWorkflowTemplateVersion(
[FromQuery] GeneralIdRef generalIdRef,
CancellationToken cancellationToken = default!
)
{
var result = await _workflowTemplateManager.GetWorkflowTemplateAsync(generalIdRef, cancellationToken);
var result = await _workflowTemplateManager.GetWorkflowTemplateVersionAsync(generalIdRef, cancellationToken);
return Ok(result);
}
@ -70,14 +87,14 @@ public class WorkflowTemplateController : ESuiteControllerBase
/// <param name="user"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[Route("template")]
[Route("templateVersion")]
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[AccessKey(SecurityAccess.EditWorkflowTemplate)]
public async Task<IActionResult> EditTemplate(EditWorkflowTemplate template, CancellationToken cancellationToken = default!)
public async Task<IActionResult> EditTemplateVersion(EditWorkflowTemplateVersion template, CancellationToken cancellationToken = default!)
{
await _workflowTemplateManager.EditTemplate(AuditUserDetails, template, cancellationToken);
await _workflowTemplateManager.EditTemplateVersion(AuditUserDetails, template, cancellationToken);
return Ok();
}
@ -87,14 +104,14 @@ public class WorkflowTemplateController : ESuiteControllerBase
/// <param name="user"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[Route("template")]
[Route("templateVersion")]
[HttpPatch]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[AccessKey(SecurityAccess.EditWorkflowTemplate)]
public async Task<IActionResult> PatchWorkflowTemplate([FromQuery] IGeneralIdRef templateId, [FromBody] PatchWorkflowTemplate patchTemplate, CancellationToken cancellationToken = default!)
public async Task<IActionResult> PatchWorkflowTemplateVersion([FromQuery] IGeneralIdRef templateId, [FromBody] PatchWorkflowTemplateVersion patchTemplate, CancellationToken cancellationToken = default!)
{
await _workflowTemplateManager.PatchTemplate(AuditUserDetails, templateId, patchTemplate, cancellationToken);
await _workflowTemplateManager.PatchTemplateVersion(AuditUserDetails, templateId, patchTemplate, cancellationToken);
return Ok();
}
@ -105,17 +122,17 @@ public class WorkflowTemplateController : ESuiteControllerBase
/// <param name="userRegistration">Contains the details that need to be supplied to create the user.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[Route("template")]
[Route("templateVersion")]
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))]
[AccessKey(SecurityAccess.AddWorkflowTemplate)]
public async Task<IActionResult> CreateWorkflowTemplate(
[FromBody] CreateWorkflowTemplate template,
public async Task<IActionResult> CreateWorkflowTemplateVersion(
[FromBody] CreateWorkflowTemplateVersion template,
CancellationToken cancellationToken = default!
)
{
await _workflowTemplateManager.PostTemplate(AuditUserDetails, template, cancellationToken);
await _workflowTemplateManager.PostTemplateVersion(AuditUserDetails, template, cancellationToken);
return Ok();
}
@ -124,18 +141,18 @@ public class WorkflowTemplateController : ESuiteControllerBase
/// </summary>
/// <param name="email"></param>
/// <param name="cancellationToken"></param>
[Route("template")]
[Route("templateVersion")]
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ProblemDetails))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))]
[AccessKey(SecurityAccess.DeleteWorkflowTemplate)]
public async Task<IActionResult> DeleteWorkflowTemplate(
public async Task<IActionResult> DeleteWorkflowTemplateVersion(
[FromBody] IGeneralIdRef templateId,
CancellationToken cancellationToken = default!
)
{
await _workflowTemplateManager.DeleteTemplate(AuditUserDetails, templateId, cancellationToken);
await _workflowTemplateManager.DeleteTemplateVersion(AuditUserDetails, templateId, cancellationToken);
return Ok();
}
}

View File

@ -60,6 +60,7 @@
<ProjectReference Include="..\..\e-suite.Modules.SpecificationManager\e-suite.Modules.SpecificationManager\e-suite.Modules.SpecificationManager.csproj" />
<ProjectReference Include="..\..\e-suite.Modules.SSOManager\e-suite.Modules.SSOManager\e-suite.Modules.SSOManager.csproj" />
<ProjectReference Include="..\..\e-suite.Modules.UserManager\e-suite.Modules.UserManager\e-suite.Modules.UserManager.csproj" />
<ProjectReference Include="..\..\e-suite.Modules.WorkflowTemplatesManager\e-suite.Modules.WorkflowTemplatesManager.csproj" />
<ProjectReference Include="..\..\e-suite.Service.CustomFieldValidation\e-suite.Service.CustomFieldValidation\e-suite.Service.CustomFieldValidation.csproj" />
<ProjectReference Include="..\..\e-suite.Service.Mail\e-suite.Service.Mail\e-suite.Service.Mail.csproj" />
<ProjectReference Include="..\..\e-suite.Service.Sentinel\e-suite.Service.Sentinel\e-suite.Service.Sentinel.csproj" />

View File

@ -3,6 +3,5 @@
public class TaskDefinition
{
public string Type { get; set; } = string.Empty;
public int Order { get; set; }
public Dictionary<string, object>? Config { get; set; }
}

View File

@ -1,5 +1,6 @@
using e_suite.Database.Audit.Attributes;
using e_suite.Database.Core.Models;
using e_suite.Database.Core.Tables.Forms;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
@ -30,4 +31,6 @@ public class Workflow : IGeneralId, ISoftDeletable
[AuditLastUpdated]
public DateTimeOffset LastUpdated { get; set; }
public ICollection<WorkflowVersion> Versions { get; set; } = [];
}

View File

@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Primary constructors do not create readonly fields, it creates writable properties instead, which is bad practice", Scope = "module")]

View File

@ -0,0 +1,17 @@
using Autofac;
using e_suite.API.Common;
using e_suite.API.Common.repository;
using e_suite.DependencyInjection;
using e_suite.Modules.WorkflowTemplatesManager.Repository;
namespace e_suite.Modules.WorkflowTemplatesManager;
public class IocRegistration : IIocRegistration
{
public void RegisterTypes(ContainerBuilder builder)
{
builder.RegisterType<WorkflowTemplateRepository>().As<IWorkflowTemplateRepository>().InstancePerLifetimeScope();
builder.RegisterType<WorkflowTemplateManager>().As<IWorkflowTemplateManager>().InstancePerLifetimeScope();
builder.RegisterType<WorkflowConverter>().As<IWorkflowConverter>().InstancePerLifetimeScope();
}
}

View File

@ -1,22 +1,113 @@
using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Core;
using e_suite.Database.Core.Tables.Workflow;
using e_suite.Database.Core.Extensions;
using e_suite.Workflow.Core;
using e_suite.Workflow.Core.Extensions;
using Microsoft.EntityFrameworkCore;
namespace e_suite.Modules.WorkflowTemplatesManager.Repository;
public interface IWorkflowConverter
{
WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion);
Task<e_suite.Database.Core.Tables.Workflow.WorkflowVersion> SerialiseToDatabase(
WorkflowVersion runtime,
e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null,
CancellationToken cancellationToken = default
);
}
public class WorkflowConverter : IWorkflowConverter
{
private readonly IDomainRepository _domainRepository;
public WorkflowConverter(IDomainRepository domainRepository)
{
_domainRepository = domainRepository;
}
public WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion)
{
var runtime = new WorkflowVersion
{
Id = dbVersion.Id,
Guid = dbVersion.Guid,
Version = dbVersion.Version,
ActivityNameTemplate = dbVersion.ActivityNameTemplate,
Description = dbVersion.Description,
Domain = dbVersion.Domain.ToGeneralIdRef()!,
Template = new WorkflowTemplate
{
Name = "Need to fix",
Id = 1,
Guid = Guid.Empty
} // however you load templates
};
foreach (var def in dbVersion.Tasks)
{
var task = def.ToTask();
runtime.Tasks.Add(task);
}
return runtime;
}
public async Task<e_suite.Database.Core.Tables.Workflow.WorkflowVersion> SerialiseToDatabase(WorkflowVersion runtime, e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null, CancellationToken cancellationToken = default)
{
if (runtime is null)
throw new NullReferenceException();
var domain = await _domainRepository.GetDomainById(runtime.Domain, cancellationToken);
if (domain is null)
throw new Exception($"Domain with id {runtime.Domain} not found.");
var dbVersion = dbObject ?? new e_suite.Database.Core.Tables.Workflow.WorkflowVersion();
if (dbObject == null)
{
dbVersion.Id = runtime.Id;
dbVersion.Guid = runtime.Guid;
}
else
{
//note cannot move a version from one workflow to another, that requires a new version.
//todo make sure that the Workflow is populated here.
//dbVersion.Workflow = runtime.Template.ToGeneralIdRef()
}
dbVersion.Version = runtime.Version; //todo make sure that the version number get incremented somewhere logical.
dbVersion.ActivityNameTemplate = runtime.ActivityNameTemplate;
dbVersion.Description = runtime.Description;
dbVersion.DomainId = domain.Id;
dbVersion.Tasks = runtime.Tasks
.Select(t => t.ToDefinition())
.ToList();
return dbVersion;
}
}
public class WorkflowTemplateRepository : RepositoryBase, IWorkflowTemplateRepository
{
public WorkflowTemplateRepository(IEsuiteDatabaseDbContext databaseDbContext) : base(databaseDbContext)
{
}
public IQueryable<WorkflowVersion> GetWorkflowVersions()
public IQueryable<e_suite.Database.Core.Tables.Workflow.Workflow> GetWorkflows()
{
return DatabaseDbContext.Workflows
.Include( x => x.Versions);
}
public IQueryable<e_suite.Database.Core.Tables.Workflow.WorkflowVersion> GetWorkflowVersions()
{
return DatabaseDbContext.WorkflowVersions;
}
public async Task EditWorkflowVersionAsync(AuditUserDetails auditUserDetails, WorkflowVersion workflowVersion, CancellationToken cancellationToken)
public async Task EditWorkflowVersionAsync(AuditUserDetails auditUserDetails, e_suite.Database.Core.Tables.Workflow.WorkflowVersion workflowVersion, CancellationToken cancellationToken)
{
await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken);
}

View File

@ -3,16 +3,19 @@ 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.Database.Core.Tables.Workflow;
using e_suite.Utilities.Pagination;
using eSuite.Core.Miscellaneous;
using System.Linq.Expressions;
using e_suite.Modules.WorkflowTemplatesManager.Repository;
//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;
@ -24,16 +27,38 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager
}
public async Task<PaginatedData<GetWorkflowTemplate>> GetWorkflowTemplatesAsync(Paging paging, CancellationToken cancellationToken)
{
var ssoProviders = _workflowTemplateRepository.GetWorkflows().Where(x => x.Deleted == false);
var paginatedData = await PaginatedData.Paginate<e_suite.Database.Core.Tables.Workflow.Workflow, GetWorkflowTemplate>(ssoProviders, paging,
WorkflowKeySelector, cancellationToken);
return paginatedData;
}
private Expression<Func<e_suite.Database.Core.Tables.Workflow.Workflow, object>> 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<PaginatedData<GetWorkflowTemplateVersion>> GetWorkflowTemplateVersionsAsync(Paging paging, CancellationToken cancellationToken)
{
var ssoProviders = _workflowTemplateRepository.GetWorkflowVersions().Where(x => x.Deleted == false);
var paginatedData = await PaginatedData.Paginate<WorkflowVersion, GetWorkflowTemplate>(ssoProviders, paging,
var paginatedData = await PaginatedData.Paginate<e_suite.Database.Core.Tables.Workflow.WorkflowVersion, GetWorkflowTemplateVersion>(ssoProviders, paging,
KeySelector, cancellationToken);
return paginatedData;
}
private Expression<Func<WorkflowVersion, object>> KeySelector(string sortKey)
private Expression<Func<e_suite.Database.Core.Tables.Workflow.WorkflowVersion, object>> KeySelector(string sortKey)
{
return sortKey?.ToLowerInvariant() switch
{
@ -46,24 +71,23 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager
};
}
public async Task<GetWorkflowTemplate?> GetWorkflowTemplateAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
public async Task<GetWorkflowTemplateVersion?> GetWorkflowTemplateVersionAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
{
var workflowTemplate = await _workflowTemplateRepository.GetWorkflowVersions().FindByGeneralIdRefAsync(generalIdRef, cancellationToken)
?? throw new NotFoundException("Unable to find Workflow Version");
return new GetWorkflowTemplate(workflowTemplate);
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 EditTemplate(
public async Task EditTemplateVersion(
AuditUserDetails auditUserDetails,
EditWorkflowTemplate template,
EditWorkflowTemplateVersion template,
CancellationToken cancellationToken
)
{
await AlterWorkflowTemplateVersionAsync(auditUserDetails, template.ToGeneralIdRef()!, async version =>
{
var domain = await _domainRepository.GetDomainById(template.DomainId, cancellationToken);
if (domain is null)
throw new NotFoundException("Unable to find Domain with provided id");
var domain = await _domainRepository.GetDomainById(template.DomainId!, cancellationToken) ?? throw new NotFoundException("Unable to find Domain with provided id");
version.Domain = domain;
version.DomainId = domain.Id;
@ -71,34 +95,37 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager
version.ActivityNameTemplate = template.ActivityNameTemplate;
version.Deleted = false;
version.Version = template.Version;
//version.WorkflowId
version.Tasks = template.Tasks;
}, cancellationToken);
}
public async Task PatchTemplate(
public async Task PatchTemplateVersion(
AuditUserDetails auditUserDetails,
IGeneralIdRef templateId,
PatchWorkflowTemplate patchTemplate,
PatchWorkflowTemplateVersion patchTemplate,
CancellationToken cancellationToken
)
{
var patch = _patchFactory.Create(patchTemplate);
await AlterWorkflowTemplateVersionAsync(auditUserDetails, templateId, async version =>
{
patch.ApplyTo(version);
await patch.ApplyTo(version);
}, cancellationToken);
}
public async Task PostTemplate(
public async Task PostTemplateVersion(
AuditUserDetails auditUserDetails,
CreateWorkflowTemplate template,
CreateWorkflowTemplateVersion template,
CancellationToken cancellationToken
)
{
//var workflowTemplate = _workflowConverter.DeserialiseFromDatabase(dbWorkflowTemplate);
throw new NotImplementedException();
}
public async Task DeleteTemplate(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, CancellationToken cancellationToken)
public async Task DeleteTemplateVersion(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, CancellationToken cancellationToken)
{
await AlterWorkflowTemplateVersionAsync(auditUserDetails, templateId, async version =>
{
@ -109,15 +136,13 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager
private async Task AlterWorkflowTemplateVersionAsync(
AuditUserDetails auditUserDetails,
IGeneralIdRef generalIdRef,
Func<WorkflowVersion, Task> applyChanges,
Func<e_suite.Database.Core.Tables.Workflow.WorkflowVersion, Task> applyChanges,
CancellationToken cancellationToken
)
{
await _workflowTemplateRepository.TransactionAsync(async () =>
{
var workflowVersion = await _workflowTemplateRepository.GetWorkflowVersions().FindByGeneralIdRefAsync(generalIdRef, cancellationToken);
if (workflowVersion is null)
throw new NotFoundException("SsoProvider with this id does not exists");
var workflowVersion = await _workflowTemplateRepository.GetWorkflowVersions().FindByGeneralIdRefAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("SsoProvider with this id does not exists");
await applyChanges(workflowVersion);

View File

@ -1,38 +1,105 @@
using e_suite.Workflow.Core.Attributes;
using e_suite.Workflow.Core.Enums;
using e_suite.Database.Core.Models;
using e_suite.Workflow.Core.Attributes;
using e_suite.Workflow.Core.Interfaces;
using e_suite.Workflow.Core.Tasks;
using System.Reflection;
namespace e_suite.Workflow.Core.Extensions;
//copied from Database.core.
public class TaskDefinition
{
public string Type { get; set; } = string.Empty;
public int Order { get; set; }
public Dictionary<string, object>? Config { get; set; }
}
public static class TaskExtensions
{
public static TaskDefinition ToDefinition(this ITask task, int order)
private static Dictionary<string, object?> ToConfigDictionary(this ITask task)
{
if (task is ITemplateValidatable v)
var dictionary = new Dictionary<string, object?>();
var type = task.GetType();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
var errors = v.ValidateForTemplate().ToList();
if (errors.Count != 0)
throw new InvalidOperationException(
$"Task {task.GetType().Name} is invalid: {string.Join("; ", errors)}");
if (!prop.CanRead)
continue;
if (!prop.CanWrite)
continue;
var value = prop.GetValue(task);
dictionary[prop.Name] = value;
}
return dictionary;
}
public static ITask ToTask(this TaskDefinition definition)
{
var type = Type.GetType(definition.Type);
if (type == null)
throw new InvalidOperationException($"Unknown task type '{definition.Type}'.");
var instance = Activator.CreateInstance(type) as ITask;
if (instance == null)
throw new InvalidOperationException($"Type '{definition.Type}' does not implement ITask.");
FromConfigDictionary(instance, definition.Config);
return instance;
}
public static void FromConfigDictionary(this object obj, Dictionary<string, object?> dict)
{
var type = obj.GetType();
foreach (var kvp in dict)
{
var prop = type.GetProperty(kvp.Key);
if (prop == null || !prop.CanWrite)
continue;
var targetType = prop.PropertyType;
var value = kvp.Value;
// Handle enums (the only case where JSON gives you a string)
if (value != null && targetType.IsEnum)
{
value = Enum.Parse(targetType, value.ToString()!, ignoreCase: true);
}
prop.SetValue(obj, value);
}
}
public static TaskDefinition ToDefinition(this ITask task)
{
if (task is null)
throw new NullReferenceException();
return new TaskDefinition
{
Type = task.GetType().AssemblyQualifiedName!,
Order = order,
Config = ExtractConfig(task)
Type = task.GetType().AssemblyQualifiedName,
Config = task.ToConfigDictionary()
};
}
//public static TaskDefinition ToDefinition(this ITask task)
//{
// if (task is ITemplateValidatable v)
// {
// var errors = v.ValidateForTemplate().ToList();
// if (errors.Count != 0)
// throw new InvalidOperationException(
// $"Task {task.GetType().Name} is invalid: {string.Join("; ", errors)}");
// }
// return new TaskDefinition
// {
// Type = task.GetType().AssemblyQualifiedName!,
// Config = ExtractConfig(task)
// };
//}
private static Dictionary<string, object> ExtractConfig(object task)
{
var dict = new Dictionary<string, object>();
@ -52,53 +119,53 @@ public static class TaskExtensions
return dict;
}
public static ITask ToTask(this TaskDefinition def)
{
var type = Type.GetType(def.Type, throwOnError: true)!;
var task = (ITask)Activator.CreateInstance(type)!;
//public static ITask ToTask(this TaskDefinition def)
//{
// var type = Type.GetType(def.Type, throwOnError: true)!;
// var task = (ITask)Activator.CreateInstance(type)!;
if (def.Config != null)
foreach (var kvp in def.Config)
{
var prop = type.GetProperty(kvp.Key);
if (prop == null || !prop.CanWrite) continue;
if (prop.IsDefined(typeof(RuntimeOnlyAttribute), inherit: true))
continue;
// if (def.Config != null)
// foreach (var kvp in def.Config)
// {
// var prop = type.GetProperty(kvp.Key);
// if (prop == null || !prop.CanWrite) continue;
// if (prop.IsDefined(typeof(RuntimeOnlyAttribute), inherit: true))
// continue;
object? converted = ConvertValue(kvp.Value, prop.PropertyType);
prop.SetValue(task, converted);
}
// object? converted = ConvertValue(kvp.Value, prop.PropertyType);
// prop.SetValue(task, converted);
// }
if (task is ITemplateValidatable v)
{
var errors = v.ValidateForTemplate().ToList();
if (errors.Count != 0)
throw new InvalidOperationException(
$"Task {task.GetType().Name} is invalid: {string.Join("; ", errors)}");
}
// if (task is ITemplateValidatable v)
// {
// var errors = v.ValidateForTemplate().ToList();
// if (errors.Count != 0)
// throw new InvalidOperationException(
// $"Task {task.GetType().Name} is invalid: {string.Join("; ", errors)}");
// }
return task;
}
// return task;
//}
private static object? ConvertValue(object? value, Type targetType)
{
if (value == null) return null;
//private static object? ConvertValue(object? value, Type targetType)
//{
// if (value == null) return null;
// Handle enums
if (targetType.IsEnum)
return Enum.Parse(targetType, value.ToString()!);
// // Handle enums
// if (targetType.IsEnum)
// return Enum.Parse(targetType, value.ToString()!);
// Handle Guid
if (targetType == typeof(Guid))
return Guid.Parse(value.ToString()!);
// // Handle Guid
// if (targetType == typeof(Guid))
// return Guid.Parse(value.ToString()!);
// Handle nullable types
var underlying = Nullable.GetUnderlyingType(targetType);
if (underlying != null)
return Convert.ChangeType(value, underlying);
// // Handle nullable types
// var underlying = Nullable.GetUnderlyingType(targetType);
// if (underlying != null)
// return Convert.ChangeType(value, underlying);
return Convert.ChangeType(value, targetType);
}
// return Convert.ChangeType(value, targetType);
//}
//Todo can only be on a run-time instance

View File

@ -1,17 +1,17 @@
using e_suite.Workflow.Core.Interfaces;
namespace e_suite.Workflow.Core.Extensions;
public static class WorkflowExtensions
public static class WorkflowVersionExtensions
{
/// <summary>
/// Returns true once the workflow has met any requirements that are needed to change the state to active.
/// For example, earliest start time has passed.
/// Or required starting information is now available.
/// </summary>
public static bool ReadyToActivate(this IWorkflow workflow)
public static bool ReadyToActivate(this IWorkflowVersion workflow)
{
//todo Check to see if the StartDate has passed.
return true;
}
}

View File

@ -12,7 +12,7 @@ public interface ITask
/// <summary>
/// ID of the parent of this task. It could be either a Task or a Workflow.
/// </summary>
ITask Parent { get; }
ITask Parent { get; set; }
/// <summary>
/// Name of the task as seen by users, must be unique in the workflow.

View File

@ -4,7 +4,7 @@ using eSuite.Core.Miscellaneous;
namespace e_suite.Workflow.Core.Interfaces;
public interface IWorkflow : IStage<GeneralTaskAttribute>
public interface IWorkflowVersion : IStage<GeneralTaskAttribute>
{
/// <summary>
/// Client domain to which the workflow belongs.

View File

@ -16,7 +16,7 @@ public class WorkflowTemplate: IGeneralId
public required string Name { get; set; }
}
public class WorkflowVersion : IGeneralId, IWorkflow
public class WorkflowVersion : IGeneralId, IWorkflowVersion
{
public long Id { get; set; }
public Guid Guid { get; set; }
@ -25,7 +25,7 @@ public class WorkflowVersion : IGeneralId, IWorkflow
public required long Version { get; set; }
public ICollection<ITask> Tasks { get; } = new List<ITask>(); //Serialise to Json.
public ICollection<ITask> Tasks { get; } = []; //Serialise to Json.
public required IGeneralIdRef Domain { get; set; }
public WorkflowState CurrentState { get; set; } = WorkflowState.Pending;
public required string ActivityNameTemplate { get; set; }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=authorisation/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Authorised/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deserialise/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=execption/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=jwks/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=organisation/@EntryIndexedValue">True</s:Boolean>