Backend/e-suite.Workflow.Core/Extensions/TaskExtensions.cs

295 lines
9.5 KiB
C#

using e_suite.Database.Core.Models;
using e_suite.Database.Core.Tables.Activity;
using e_suite.Workflow.Core.Attributes;
using e_suite.Workflow.Core.Interfaces;
using eSuite.Core.Clock;
using eSuite.Core.Enums;
using System.Collections;
using System.Reflection;
using System.Text.Json;
namespace e_suite.Workflow.Core.Extensions;
public static class TaskExtensions
{
extension(ITask task)
{
private Dictionary<string, object?> ToConfigDictionary()
{
var dictionary = new Dictionary<string, object?>();
var type = task.GetType();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
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, JsonSerializerOptions jsonSerializerOptions)
{
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, jsonSerializerOptions);
return instance;
}
public static void FromConfigDictionary(this object obj, Dictionary<string, object?> dict, JsonSerializerOptions jsonSerializerOptions)
{
var type = obj.GetType();
foreach (var kvp in dict)
{
var prop = type.GetProperty(
kvp.Key,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase
);
if (prop == null || !prop.CanWrite)
continue;
var targetType = prop.PropertyType;
var value = kvp.Value;
// If the value is a JsonElement, convert it to the target type
if (value is JsonElement je)
{
if (targetType.IsEnum)
{
// Enums come through as strings
value = Enum.Parse(targetType, je.GetString()!, ignoreCase: true);
}
else
{
// Deserialize JSON into the target type (handles lists, objects, primitives)
value = JsonSerializer.Deserialize(je.GetRawText(), targetType, jsonSerializerOptions);
}
}
prop.SetValue(obj, value);
}
}
private static Dictionary<string, object> ExtractConfig(object task)
{
var dict = new Dictionary<string, object>();
foreach (var prop in task.GetType().GetProperties())
{
if (!prop.CanRead) continue;
if (prop.GetIndexParameters().Length > 0) continue; // skip indexers
if (prop.IsDefined(typeof(RuntimeOnlyAttribute), inherit: true))
continue;
var value = prop.GetValue(task);
if (value != null)
dict[prop.Name] = value;
}
return dict;
}
extension(ITask task)
{
public TaskDefinition ToDefinition()
{
if (task is null)
throw new NullReferenceException();
return new TaskDefinition
{
Type = task.GetType().FullName!,
Config = task.ToConfigDictionary()!
};
}
private (Type outcomeType, IEnumerable outcomeList)? GetOutcomeList()
{
var outcomeInterface = task.GetType()
.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IOutcome<>));
if (outcomeInterface == null)
return null;
var outcomeType = outcomeInterface.GetGenericArguments()[0];
var prop = outcomeInterface.GetProperty("OutcomeActions");
var list = (IEnumerable)prop!.GetValue(task)!;
return (outcomeType, list);
}
public IEnumerable<Guid> GetTargetGuids()
{
var info = task.GetOutcomeList();
if (info == null)
yield break;
foreach (var item in info.Value.outcomeList.Cast<IOutcomeAction>())
yield return item.Task;
}
public IEnumerable<Guid> GetTargetGuids(IEnumerable<string> outcomes)
{
var info = task.GetOutcomeList();
if (info == null)
return Enumerable.Empty<Guid>();
var (outcomeType, list) = info.Value;
var typedOutcomes = outcomes.Select(o =>
outcomeType.IsEnum
? Enum.Parse(outcomeType, o, true)
: Convert.ChangeType(o, outcomeType)
).ToHashSet();
return list
.Cast<object>()
.Where(item =>
{
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)
{
var now = clock.GetNow;
activityTask.StartDateTime = now;
activityTask.SetState(ActivityState.Active);
foreach (var assignment in activityTask.Assignments)
{
assignment.StartDateTime = now;
assignment.SetState(ActivityState.Active);
}
await task.OnStartedAsync(activityTask);
}
public async Task<bool> ProgressTask(ActivityTask activityTask, ICollection<ActivityTask> tasks, IStage stage, IClock clock)
{
var hasCompletableTask = false;
if (activityTask.ActivityState == ActivityState.ReadyToComplete)
{
activityTask.FinishDateTime = clock.GetNow;
activityTask.SetState(ActivityState.Completed);
await task.OnCompleteAsync(activityTask);
var targetGuids = task.GetTargetGuids(activityTask.Outcomes).ToList();
foreach (var t in tasks)
{
if (targetGuids.Contains(t.TaskGuid))
{
var taskDefinition = stage.FindTask(t.TaskGuid)!;
await taskDefinition.StartTask(t, clock);
if (t.ActivityState == ActivityState.ReadyToComplete)
{
hasCompletableTask = true;
}
}
}
}
return hasCompletableTask;
}
}
//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;
// 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)}");
// }
// return task;
//}
//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 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);
// return Convert.ChangeType(value, targetType);
//}
//Todo can only be on a run-time instance
//public static bool IsCompleted(this ITask task)
//{
// return task.TaskState.In(TaskState.Completed, TaskState.Cancelled);
//}
//public static bool ReadyToActivate(this ITask task)
//{
// foreach (var predecessor in task.Predecessors)
// {
// if (!predecessor.IsCompleted())
// {
// return false;
// }
// }
// return true;
//}
}