using Elsa.Extensions; using Elsa.Workflows.Activities; using Elsa.Workflows.Management.Options; using Elsa.Workflows.Models; using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.State; using Humanizer; using Microsoft.Extensions.Options; namespace Elsa.Workflows.Runtime; /// public class DefaultActivityExecutionMapper(IOptions options) : IActivityExecutionMapper { private const string LogPersistenceModeKey = "logPersistenceMode"; /// public ActivityExecutionRecord Map(ActivityExecutionContext source) { /* * { * "logPersistenceMode": { * "default": "default", * "inputs": { k : v }, * "outputs": { k: v } * } * } */ var workflow = (Workflow?)source.GetAncestors().FirstOrDefault(x => x.Activity is Workflow)?.Activity ?? source.WorkflowExecutionContext.Workflow; var workflowPersistenceProperty = GetDefaultPersistenceMode(workflow.CustomProperties, () => options.Value.LogPersistenceMode); var activityPersistenceProperties = source.Activity.CustomProperties.GetValueOrDefault>(LogPersistenceModeKey, () => new Dictionary()); var activityPersistencePropertyDefault = GetDefaultPersistenceMode(source.Activity.CustomProperties, () => workflowPersistenceProperty); // Get any outcomes that were added to the activity execution context. var outcomes = source.JournalData.TryGetValue("Outcomes", out var resultValue) ? resultValue as string[] : default; var payload = new Dictionary(); if (outcomes != null) payload.Add("Outcomes", outcomes); // Get any outputs that were added to the activity execution context. var activity = source.Activity; var expressionExecutionContext = source.ExpressionExecutionContext; var activityDescriptor = source.ActivityDescriptor; var outputDescriptors = activityDescriptor.Outputs; var outputs = outputDescriptors.ToDictionary(x => x.Name, x => { if (x.IsSerializable == false) return "(not serializable)"; var cachedValue = activity.GetOutput(expressionExecutionContext, x.Name); if (cachedValue != default) return cachedValue; if (x.ValueGetter(activity) is Output output && source.TryGet(output.MemoryBlockReference(), out var outputValue)) return outputValue; return default; }); outputs = StorePropertyUsingPersistenceMode(outputs, activityPersistenceProperties!.GetValueOrDefault("outputs", () => new Dictionary())!, activityPersistencePropertyDefault); var activityState = StorePropertyUsingPersistenceMode(source.ActivityState, activityPersistenceProperties!.GetValueOrDefault("inputs", () => new Dictionary())!, activityPersistencePropertyDefault); return new ActivityExecutionRecord { Id = source.Id, ActivityId = source.Activity.Id, ActivityNodeId = source.NodeId, WorkflowInstanceId = source.WorkflowExecutionContext.Id, ActivityType = source.Activity.Type, ActivityName = source.Activity.Name, ActivityState = activityState, Outputs = outputs, Properties = source.Properties, Payload = payload, Exception = ExceptionState.FromException(source.Exception), ActivityTypeVersion = source.Activity.Version, StartedAt = source.StartedAt, HasBookmarks = source.Bookmarks.Any(), Status = GetAggregateStatus(source), CompletedAt = source.CompletedAt }; } private static LogPersistenceMode GetDefaultPersistenceMode(IDictionary customProperties, Func defaultFactory) { var properties = customProperties.GetValueOrDefault>(LogPersistenceModeKey, () => new Dictionary()); var persistencePropertyDefault = properties!.GetValueOrDefault("default", defaultFactory); if (persistencePropertyDefault == LogPersistenceMode.Inherit) return defaultFactory(); return persistencePropertyDefault; } private static Dictionary StorePropertyUsingPersistenceMode(IDictionary inputs, IDictionary persistenceModeConfiguration, LogPersistenceMode defaultLogPersistenceMode) { var result = new Dictionary(); foreach (var input in inputs) { var persistence = persistenceModeConfiguration.GetValueOrDefault(input.Key.Camelize(), () => defaultLogPersistenceMode); if (persistence.Equals(LogPersistenceMode.Include) || (persistence.Equals(LogPersistenceMode.Inherit) && defaultLogPersistenceMode is LogPersistenceMode.Include or LogPersistenceMode.Inherit)) result.Add(input.Key, input.Value); } return result; } private ActivityStatus GetAggregateStatus(ActivityExecutionContext context) { // If any child activity is faulted, the aggregate status is faulted. var descendantContexts = context.GetDescendants().ToList(); if (descendantContexts.Any(x => x.Status == ActivityStatus.Faulted)) return ActivityStatus.Faulted; return context.Status; } }