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 DisplayName { get; set; }
public List<string> Capabilities { get; set; } = [];
public string OutcomeLabel { get; set; }
public List<string> Outcomes { get; set; } = [];
}
public interface IWorkflowTemplateManager

View File

@ -143,7 +143,7 @@ public class WorkflowTemplateController : ESuiteControllerBase
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))]
[AccessKey(SecurityAccess.DeleteWorkflowTemplate)]
public async Task<IActionResult> DeleteWorkflowTemplateVersion(
[FromBody] IGeneralIdRef templateId,
[FromBody] GeneralIdRef templateId,
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.repository;
using e_suite.Database.Audit;
@ -251,6 +252,17 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager
};
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;
})
.ToList();

View File

@ -4,7 +4,6 @@ using e_suite.Database.Core.Tables.Activity;
using e_suite.Messaging.Common;
using e_suite.Workflow.Core;
using e_suite.Workflow.Core.Extensions;
using e_suite.Workflow.Core.Interfaces;
using eSuite.Core.Clock;
using eSuite.Core.Enums;
using eSuite.Core.Miscellaneous;
@ -60,7 +59,17 @@ public class WorkflowProcessor : IWorkflowProcessor
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;

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
{
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
{
Complete

View File

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

View File

@ -2,11 +2,22 @@
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]
public interface IOutcome<T>
{
//Todo runtime only property.
//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 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

@ -9,5 +9,6 @@ public class ApprovalStep : TaskBase, IAssignees<ApprovalTaskAssignee>, IOutcome
{
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 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]
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 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 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)]
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)
yield return "FormIdRef is required.";
}
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]
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]
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)
{
@ -21,4 +21,6 @@ public class MilestoneTask : TaskBase, IOutcome<DefaultOutcome>
activityTask.AddOutcome(DefaultOutcome.Complete);
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 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]
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]
public class VisualBriefUploadTask : TaskBase, IOutcome<DefaultOutcome>
{
public Dictionary<DefaultOutcome, Guid> OutcomeActions { get; set; } = [];
public List<OutcomeAction<DefaultOutcome>> OutcomeActions { get; set; } = [];
}