Refactored the IOutcomes to be completely data driven on the UI now.

This commit is contained in:
Colin Dawson 2026-03-12 18:23:10 +00:00
parent 88d6a1f136
commit 44ada3f332
21 changed files with 99 additions and 51 deletions

View File

@ -81,6 +81,9 @@ public class TaskMetadata
public string TaskType { get; set; } public string TaskType { get; set; }
public string DisplayName { get; set; } public string DisplayName { get; set; }
public List<string> Capabilities { get; set; } = []; public List<string> Capabilities { get; set; } = [];
public string OutcomeLabel { get; set; }
public List<string> Outcomes { get; set; } = [];
} }
public interface IWorkflowTemplateManager public interface IWorkflowTemplateManager

View File

@ -143,7 +143,7 @@ public class WorkflowTemplateController : ESuiteControllerBase
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))]
[AccessKey(SecurityAccess.DeleteWorkflowTemplate)] [AccessKey(SecurityAccess.DeleteWorkflowTemplate)]
public async Task<IActionResult> DeleteWorkflowTemplateVersion( public async Task<IActionResult> DeleteWorkflowTemplateVersion(
[FromBody] IGeneralIdRef templateId, [FromBody] GeneralIdRef templateId,
CancellationToken cancellationToken = default! CancellationToken cancellationToken = default!
) )
{ {

View File

@ -1,4 +1,5 @@
using e_suite.API.Common; using System.ComponentModel;
using e_suite.API.Common;
using e_suite.API.Common.exceptions; using e_suite.API.Common.exceptions;
using e_suite.API.Common.repository; using e_suite.API.Common.repository;
using e_suite.Database.Audit; using e_suite.Database.Audit;
@ -251,6 +252,17 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager
}; };
newTaskMetadata.Capabilities.AddRange(capabilities); newTaskMetadata.Capabilities.AddRange(capabilities);
var outcomeInterface = interfaces
.FirstOrDefault(i => i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IOutcome<>));
var outcomeEnumType = outcomeInterface.GetGenericArguments()[0];
newTaskMetadata.OutcomeLabel = outcomeEnumType
.GetCustomAttribute<DescriptionAttribute>()?
.Description ?? outcomeEnumType.Name;
newTaskMetadata.Outcomes = Enum.GetNames(outcomeEnumType).Select( x => $"{outcomeEnumType.Name}.{x}").ToList();
return newTaskMetadata; return newTaskMetadata;
}) })
.ToList(); .ToList();

View File

@ -4,7 +4,6 @@ using e_suite.Database.Core.Tables.Activity;
using e_suite.Messaging.Common; using e_suite.Messaging.Common;
using e_suite.Workflow.Core; using e_suite.Workflow.Core;
using e_suite.Workflow.Core.Extensions; using e_suite.Workflow.Core.Extensions;
using e_suite.Workflow.Core.Interfaces;
using eSuite.Core.Clock; using eSuite.Core.Clock;
using eSuite.Core.Enums; using eSuite.Core.Enums;
using eSuite.Core.Miscellaneous; using eSuite.Core.Miscellaneous;
@ -60,7 +59,17 @@ public class WorkflowProcessor : IWorkflowProcessor
return; return;
} }
var workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion); WorkflowVersion workflowVersion;
try
{
workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion);
}
catch (Exception e)
{
_logger.LogError("{DateTime}: Failed to Deserialise Json {messageId} - {messageId}", _clock.GetNow, activityId, e.ToString());
throw;
}
var hasCompletableTask = false; var hasCompletableTask = false;

View File

@ -1,5 +1,8 @@
namespace e_suite.Workflow.Core.Enums; using System.ComponentModel;
namespace e_suite.Workflow.Core.Enums;
[Description("Verdict")]
public enum ApprovalVerdict public enum ApprovalVerdict
{ {
None, None,

View File

@ -1,5 +1,8 @@
namespace e_suite.Workflow.Core.Enums; using System.ComponentModel;
namespace e_suite.Workflow.Core.Enums;
[Description("Outcome")]
public enum DefaultOutcome public enum DefaultOutcome
{ {
Complete Complete

View File

@ -83,8 +83,15 @@ public static class TaskExtensions
else else
{ {
// Deserialize JSON into the target type (handles lists, objects, primitives) // Deserialize JSON into the target type (handles lists, objects, primitives)
try
{
value = JsonSerializer.Deserialize(je.GetRawText(), targetType, jsonSerializerOptions); value = JsonSerializer.Deserialize(je.GetRawText(), targetType, jsonSerializerOptions);
} }
catch (Exception e)
{
throw;
}
}
} }
prop.SetValue(obj, value); prop.SetValue(obj, value);
@ -125,7 +132,7 @@ public static class TaskExtensions
}; };
} }
private (Type outcomeType, IDictionary dict)? GetOutcomeDictionary() private (Type outcomeType, IEnumerable outcomeList)? GetOutcomeList()
{ {
var outcomeInterface = task.GetType() var outcomeInterface = task.GetType()
.GetInterfaces() .GetInterfaces()
@ -137,49 +144,45 @@ public static class TaskExtensions
var outcomeType = outcomeInterface.GetGenericArguments()[0]; var outcomeType = outcomeInterface.GetGenericArguments()[0];
var prop = outcomeInterface.GetProperty("OutcomeActions"); var prop = outcomeInterface.GetProperty("OutcomeActions");
var dict = (IDictionary)prop!.GetValue(task)!;
return (outcomeType, dict); var list = (IEnumerable)prop!.GetValue(task)!;
return (outcomeType, list);
} }
public IEnumerable<Guid> GetTargetGuids() public IEnumerable<Guid> GetTargetGuids()
{ {
var info = task.GetOutcomeDictionary(); var info = task.GetOutcomeList();
if (info == null) if (info == null)
return Enumerable.Empty<Guid>(); yield break;
return info.Value.dict.Values.Cast<Guid>(); foreach (var item in info.Value.outcomeList.Cast<IOutcomeAction>())
yield return item.Task;
} }
public IEnumerable<Guid> GetTargetGuids(IEnumerable<string> outcomes) public IEnumerable<Guid> GetTargetGuids(IEnumerable<string> outcomes)
{ {
var info = task.GetOutcomeDictionary(); var info = task.GetOutcomeList();
if (info == null) if (info == null)
return Enumerable.Empty<Guid>(); return Enumerable.Empty<Guid>();
var (outcomeType, dict) = info.Value; var (outcomeType, list) = info.Value;
var results = new List<Guid>();
foreach (var outcomeString in outcomes) var typedOutcomes = outcomes.Select(o =>
{ outcomeType.IsEnum
object? typedKey; ? Enum.Parse(outcomeType, o, true)
: Convert.ChangeType(o, outcomeType)
).ToHashSet();
if (outcomeType.IsEnum) return list
.Cast<object>()
.Where(item =>
{ {
typedKey = Enum.Parse(outcomeType, outcomeString); var outcomeProp = item.GetType().GetProperty("Outcome");
} var outcomeValue = outcomeProp!.GetValue(item);
else return typedOutcomes.Contains(outcomeValue);
{ })
typedKey = Convert.ChangeType(outcomeString, outcomeType); .Select(item => ((IOutcomeAction)item).Task);
}
if (dict.Contains(typedKey))
{
results.Add((Guid)dict[typedKey]!);
}
}
return results;
} }
public async Task StartTask(ActivityTask activityTask, IClock clock) public async Task StartTask(ActivityTask activityTask, IClock clock)

View File

@ -2,11 +2,22 @@
namespace e_suite.Workflow.Core.Interfaces; namespace e_suite.Workflow.Core.Interfaces;
public interface IOutcomeAction
{
Guid Task { get; }
}
public class OutcomeAction<T> : IOutcomeAction
{
public T Outcome { get; set; }
public Guid Task { get; set; }
}
[TaskCapability] [TaskCapability]
public interface IOutcome<T> public interface IOutcome<T>
{ {
//Todo runtime only property. //Todo runtime only property.
//public T? TaskOutcome { get; set; } //public T? TaskOutcome { get; set; }
Dictionary<T, Guid> OutcomeActions { get; set; } List<OutcomeAction<T>> OutcomeActions { get; set; }
} }

View File

@ -9,6 +9,5 @@ public class AdhocApprovalTask : TaskBase, IAssignees<ApprovalTaskAssignee>, IOu
{ {
public List<ApprovalTaskAssignee> Assignees { get; set; } = []; public List<ApprovalTaskAssignee> Assignees { get; set; } = [];
public ApprovalVerdict TaskOutcome { get; set; } public ApprovalVerdict TaskOutcome { get; set; }
public Dictionary<ApprovalVerdict, Guid> OutcomeActions { get; set; } = []; public List<OutcomeAction<ApprovalVerdict>> OutcomeActions { get; set; } = [];
public bool OverrideDefaultTaskProgression { get; set; }
} }

View File

@ -9,5 +9,6 @@ public class ApprovalStep : TaskBase, IAssignees<ApprovalTaskAssignee>, IOutcome
{ {
public List<ApprovalTaskAssignee> Assignees { get; set; } = []; public List<ApprovalTaskAssignee> Assignees { get; set; } = [];
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = [];
public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }

View File

@ -9,6 +9,6 @@ public class ApprovalTask : TaskBase, IStage<ApprovalTaskAttribute>, IOutcome<Ap
{ {
public ICollection<ITask> Tasks { get; } = new List<ITask>(); public ICollection<ITask> Tasks { get; } = new List<ITask>();
public ApprovalVerdict TaskOutcome { get; set; } public ApprovalVerdict TaskOutcome { get; set; }
public Dictionary<ApprovalVerdict, Guid> OutcomeActions { get; set; } = [];
public bool OverrideDefaultTaskProgression { get; set; } public List<OutcomeAction<ApprovalVerdict>> OutcomeActions { get; set; } = [];
} }

View File

@ -7,5 +7,5 @@ namespace e_suite.Workflow.Core.Tasks;
[GeneralTask] [GeneralTask]
public class AssetUploadTask : TaskBase, IOutcome<DefaultOutcome> public class AssetUploadTask : TaskBase, IOutcome<DefaultOutcome>
{ {
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = []; public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }

View File

@ -12,5 +12,6 @@ namespace e_suite.Workflow.Core.Tasks;
public class BasicTask : TaskBase, IAssignees<TaskAssignee>, IOutcome<DefaultOutcome> public class BasicTask : TaskBase, IAssignees<TaskAssignee>, IOutcome<DefaultOutcome>
{ {
public List<TaskAssignee> Assignees { get; set; } = []; public List<TaskAssignee> Assignees { get; set; } = [];
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = [];
public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }

View File

@ -11,5 +11,6 @@ namespace e_suite.Workflow.Core.Tasks;
public class ContentCollationTask : TaskBase, IAssignees<TaskAssignee>, IOutcome<DefaultOutcome> public class ContentCollationTask : TaskBase, IAssignees<TaskAssignee>, IOutcome<DefaultOutcome>
{ {
public List<TaskAssignee> Assignees { get; set; } = []; public List<TaskAssignee> Assignees { get; set; } = [];
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = [];
public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }

View File

@ -10,5 +10,5 @@ namespace e_suite.Workflow.Core.Tasks;
[GeneralTask(allowMultiple: false)] [GeneralTask(allowMultiple: false)]
public class FileReleaseTask : TaskBase, IOutcome<DefaultOutcome> public class FileReleaseTask : TaskBase, IOutcome<DefaultOutcome>
{ {
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = []; public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }

View File

@ -17,6 +17,5 @@ public class FormDataInputTask : TaskBase, IAssignees<TaskAssignee>, IFormTempla
if (FormIdRef == null) if (FormIdRef == null)
yield return "FormIdRef is required."; yield return "FormIdRef is required.";
} }
public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = [];
} }

View File

@ -7,5 +7,5 @@ namespace e_suite.Workflow.Core.Tasks;
[GeneralTask] [GeneralTask]
public class LinkActivityTask : TaskBase, IOutcome<DefaultOutcome> public class LinkActivityTask : TaskBase, IOutcome<DefaultOutcome>
{ {
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = []; public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }

View File

@ -13,7 +13,7 @@ namespace e_suite.Workflow.Core.Tasks;
[GeneralTask] [GeneralTask]
public class MilestoneTask : TaskBase, IOutcome<DefaultOutcome> public class MilestoneTask : TaskBase, IOutcome<DefaultOutcome>
{ {
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = []; public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
public override async Task OnStartedAsync(ActivityTask activityTask) public override async Task OnStartedAsync(ActivityTask activityTask)
{ {
@ -21,4 +21,6 @@ public class MilestoneTask : TaskBase, IOutcome<DefaultOutcome>
activityTask.AddOutcome(DefaultOutcome.Complete); activityTask.AddOutcome(DefaultOutcome.Complete);
activityTask.SetState(ActivityState.ReadyToComplete); activityTask.SetState(ActivityState.ReadyToComplete);
} }
} }

View File

@ -10,5 +10,6 @@ public class StageTask : TaskBase, IStage<GeneralTaskAttribute>, IBypassable, IO
public ICollection<ITask> Tasks { get; } = new List<ITask>(); public ICollection<ITask> Tasks { get; } = new List<ITask>();
public bool Bypassable { get; set; } public bool Bypassable { get; set; }
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = [];
public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }

View File

@ -7,5 +7,5 @@ namespace e_suite.Workflow.Core.Tasks;
[GeneralTask] [GeneralTask]
public class VisualBriefReviewTask : TaskBase, IOutcome<DefaultOutcome> public class VisualBriefReviewTask : TaskBase, IOutcome<DefaultOutcome>
{ {
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = []; public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }

View File

@ -7,5 +7,5 @@ namespace e_suite.Workflow.Core.Tasks;
[GeneralTask] [GeneralTask]
public class VisualBriefUploadTask : TaskBase, IOutcome<DefaultOutcome> public class VisualBriefUploadTask : TaskBase, IOutcome<DefaultOutcome>
{ {
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = []; public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
} }