< Summary

Information
Class: Elsa.Workflows.WorkflowStateExtractor
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs
Line coverage
89%
Covered lines: 147
Uncovered lines: 18
Coverable lines: 165
Total lines: 281
Line coverage: 89%
Branch coverage
65%
Covered branches: 38
Total branches: 58
Branch coverage: 65.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs

#LineLine coverage
 1using Elsa.Extensions;
 2using Elsa.Workflows.Models;
 3using Elsa.Workflows.Services;
 4using Elsa.Workflows.State;
 5using Microsoft.Extensions.Logging;
 6
 7namespace Elsa.Workflows;
 8
 9/// <inheritdoc />
 58810public class WorkflowStateExtractor(ILogger<WorkflowStateExtractor> logger) : IWorkflowStateExtractor
 11{
 12    /// <inheritdoc />
 13    public WorkflowState Extract(WorkflowExecutionContext workflowExecutionContext)
 14    {
 51515        var state = new WorkflowState
 51516        {
 51517            Id = workflowExecutionContext.Id,
 51518            DefinitionId = workflowExecutionContext.Workflow.Identity.DefinitionId,
 51519            DefinitionVersionId = workflowExecutionContext.Workflow.Identity.Id,
 51520            DefinitionVersion = workflowExecutionContext.Workflow.Identity.Version,
 51521            CorrelationId = workflowExecutionContext.CorrelationId,
 51522            Name = workflowExecutionContext.Name,
 51523            ParentWorkflowInstanceId = workflowExecutionContext.ParentWorkflowInstanceId,
 51524            Status = workflowExecutionContext.Status,
 51525            SubStatus = workflowExecutionContext.SubStatus,
 51526            IsExecuting = workflowExecutionContext.IsExecuting,
 51527            Bookmarks = workflowExecutionContext.Bookmarks,
 51528            ExecutionLogSequence = workflowExecutionContext.ExecutionLogSequence,
 51529            Input = GetPersistableInput(workflowExecutionContext),
 51530            Output = workflowExecutionContext.Output,
 51531            Incidents = workflowExecutionContext.Incidents,
 51532            IsSystem = workflowExecutionContext.Workflow.IsSystem,
 51533            CreatedAt = workflowExecutionContext.CreatedAt,
 51534            UpdatedAt = workflowExecutionContext.UpdatedAt,
 51535            FinishedAt = workflowExecutionContext.FinishedAt,
 51536        };
 37
 51538        ExtractProperties(state, workflowExecutionContext);
 51539        ExtractActiveActivityExecutionContexts(state, workflowExecutionContext);
 51540        ExtractCompletionCallbacks(state, workflowExecutionContext);
 51541        ExtractScheduledActivities(state, workflowExecutionContext);
 42
 51543        return state;
 44    }
 45
 46    /// <inheritdoc />
 47    public async Task<WorkflowExecutionContext> ApplyAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowSt
 48    {
 13949        workflowExecutionContext.Id = state.Id;
 13950        workflowExecutionContext.CorrelationId = state.CorrelationId;
 13951        workflowExecutionContext.Name = state.Name;
 13952        workflowExecutionContext.ParentWorkflowInstanceId = state.ParentWorkflowInstanceId;
 13953        workflowExecutionContext.SubStatus = state.SubStatus;
 13954        workflowExecutionContext.IsExecuting = state.IsExecuting;
 13955        workflowExecutionContext.Bookmarks = state.Bookmarks;
 13956        workflowExecutionContext.Output = state.Output;
 13957        workflowExecutionContext.ExecutionLogSequence = state.ExecutionLogSequence;
 13958        workflowExecutionContext.CreatedAt = state.CreatedAt;
 13959        workflowExecutionContext.UpdatedAt = state.UpdatedAt;
 13960        workflowExecutionContext.FinishedAt = state.FinishedAt;
 13961        ApplyInput(state, workflowExecutionContext);
 13962        ApplyProperties(state, workflowExecutionContext);
 13963        await ApplyActivityExecutionContextsAsync(state, workflowExecutionContext);
 13964        ApplyCompletionCallbacks(state, workflowExecutionContext);
 13965        ApplyScheduledActivities(state, workflowExecutionContext);
 13966        return workflowExecutionContext;
 13967    }
 68
 69    private void ApplyInput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
 70    {
 71        // Only add input from state if the input doesn't already exist on the workflow execution context.
 35672        foreach (var inputItem in state.Input)
 3973            if (!workflowExecutionContext.Input.ContainsKey(inputItem.Key))
 074                workflowExecutionContext.Input.Add(inputItem.Key, inputItem.Value);
 13975    }
 76
 77    private IDictionary<string, object> GetPersistableInput(WorkflowExecutionContext workflowExecutionContext)
 78    {
 79        // TODO: This is a temporary solution. We need to find a better way to handle this.
 53280        var persistableInput = workflowExecutionContext.Workflow.Inputs.Where(x => x.StorageDriverType == typeof(Workflo
 51581        var input = workflowExecutionContext.Input;
 51582        var filteredInput = new Dictionary<string, object>();
 83
 103084        foreach (var inputDefinition in persistableInput)
 85        {
 086            if (input.TryGetValue(inputDefinition.Name, out var value))
 087                filteredInput.Add(inputDefinition.Name, value);
 88        }
 89
 51590        return filteredInput;
 91    }
 92
 93    private void ExtractProperties(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
 94    {
 51595        state.Properties = workflowExecutionContext.Properties;
 51596    }
 97
 98    private void ApplyProperties(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
 99    {
 100        // Merge properties.
 354101        foreach (var property in state.Properties)
 38102            workflowExecutionContext.Properties[property.Key] = property.Value;
 139103    }
 104
 105    private async Task ApplyActivityExecutionContextsAsync(WorkflowState state, WorkflowExecutionContext workflowExecuti
 106    {
 107        var activityExecutionContexts = (await Task.WhenAll(state.ActivityExecutionContexts.Select(async item => await C
 108
 109        var lookup = activityExecutionContexts.ToDictionary(x => x.Id);
 110
 111        // Reconstruct hierarchy.
 112        foreach (var contextState in state.ActivityExecutionContexts.Where(x => !string.IsNullOrWhiteSpace(x.ParentConte
 113        {
 105114            var parentContextId = contextState.ParentContextId;
 105115            if (parentContextId == null || !lookup.TryGetValue(parentContextId, out var parentContext))
 116            {
 0117                logger.LogWarning("Parent context with ID '{ParentContextId}' not found for context with ID '{ContextId}
 0118                continue; // Skip if parent context is not found.
 119            }
 120
 105121            var contextId = contextState.Id;
 122
 105123            if (lookup.TryGetValue(contextId, out var context))
 124            {
 105125                context.ExpressionExecutionContext.ParentContext = parentContext.ExpressionExecutionContext;
 105126                context.ParentActivityExecutionContext = parentContext;
 127            }
 128        }
 129
 130        // Assign root expression execution context.
 131        var rootActivityExecutionContexts = activityExecutionContexts.Where(x => x.ExpressionExecutionContext.ParentCont
 132
 278133        foreach (var rootActivityExecutionContext in rootActivityExecutionContexts)
 0134            rootActivityExecutionContext.ExpressionExecutionContext.ParentContext = workflowExecutionContext.ExpressionE
 135
 139136        workflowExecutionContext.ActivityExecutionContexts = activityExecutionContexts;
 139137        return;
 138
 139        async Task<ActivityExecutionContext?> CreateActivityExecutionContextAsync(ActivityExecutionContextState activity
 140        {
 150141            var activity = workflowExecutionContext.FindActivityByNodeId(activityExecutionContextState.ScheduledActivity
 142
 143            // Activity can be null in case the workflow instance was migrated to a newer version that no longer contain
 150144            if (activity == null)
 0145                return null;
 146
 150147            var properties = activityExecutionContextState.Properties;
 150148            var metadata = activityExecutionContextState.Metadata;
 150149            var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity);
 150150            activityExecutionContext.Id = activityExecutionContextState.Id;
 150151            activityExecutionContext.CallStackDepth = activityExecutionContextState.CallStackDepth;
 150152            activityExecutionContext.Properties.Merge(properties);
 150153            activityExecutionContext.Metadata.Merge(metadata);
 154
 150155            if(activityExecutionContextState.ActivityState != null)
 150156                activityExecutionContext.ActivityState.Merge(activityExecutionContextState.ActivityState);
 157
 150158            activityExecutionContext.TransitionTo(activityExecutionContextState.Status);
 150159            activityExecutionContext.IsExecuting = activityExecutionContextState.IsExecuting;
 150160            activityExecutionContext.AggregateFaultCount = activityExecutionContextState.FaultCount;
 150161            activityExecutionContext.StartedAt = activityExecutionContextState.StartedAt;
 150162            activityExecutionContext.CompletedAt = activityExecutionContextState.CompletedAt;
 150163            activityExecutionContext.Tag = activityExecutionContextState.Tag;
 150164            activityExecutionContext.DynamicVariables = activityExecutionContextState.DynamicVariables;
 165
 150166            return activityExecutionContext;
 150167        }
 139168    }
 169
 170    private static void ApplyCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
 171    {
 488172        foreach (var completionCallbackEntry in state.CompletionCallbacks)
 173        {
 415174            var ownerActivityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x
 105175            if (ownerActivityExecutionContext != null)
 176            {
 105177                var childNode = workflowExecutionContext.FindNodeById(completionCallbackEntry.ChildNodeId);
 178
 105179                if (childNode == null)
 180                    continue;
 181
 105182                var callbackName = completionCallbackEntry.MethodName;
 105183                var callbackDelegate = !string.IsNullOrEmpty(callbackName) ? ownerActivityExecutionContext.Activity.GetA
 105184                var tag = completionCallbackEntry.Tag;
 105185                workflowExecutionContext.AddCompletionCallback(ownerActivityExecutionContext, childNode, callbackDelegat
 186            }
 187        }
 139188    }
 189
 190    private void ApplyScheduledActivities(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
 191    {
 278192        foreach (var activityWorkItemState in state.ScheduledActivities)
 193        {
 0194            var activity = workflowExecutionContext.FindActivityByNodeId(activityWorkItemState.ActivityNodeId);
 195
 0196            if (activity == null)
 197                continue;
 198
 0199            var ownerContext = workflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x.Id == activityWo
 0200            var existingActivityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x =
 0201            var variables = activityWorkItemState.Variables;
 0202            var input = activityWorkItemState.Input;
 0203            var tag = activityWorkItemState.Tag;
 0204            var workItem = new ActivityWorkItem(activity, ownerContext, tag, variables, existingActivityExecutionContext
 0205            workflowExecutionContext.Scheduler.Schedule(workItem);
 206        }
 139207    }
 208
 209    private static void ExtractCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContex
 210    {
 211        // Assert that all referenced owner contexts exist.
 515212        var activeContexts = workflowExecutionContext.GetActiveActivityExecutionContexts().ToList();
 1312213        foreach (var completionCallback in workflowExecutionContext.CompletionCallbacks)
 214        {
 408215            var ownerContext = activeContexts.FirstOrDefault(x => x == completionCallback.Owner);
 216
 141217            if (ownerContext == null)
 0218                throw new("Lost an owner context");
 219        }
 220
 656221        var completionCallbacks = workflowExecutionContext.CompletionCallbacks.Select(x => new CompletionCallbackState(x
 222
 515223        state.CompletionCallbacks = completionCallbacks.ToList();
 515224    }
 225
 226    private static void ExtractActiveActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExe
 227    {
 228        ActivityExecutionContextState CreateActivityExecutionContextState(ActivityExecutionContext activityExecutionCont
 229        {
 672230            var parentId = activityExecutionContext.ParentActivityExecutionContext?.Id;
 231
 672232            if (parentId != null)
 233            {
 544234                var parentContext = activityExecutionContext.WorkflowExecutionContext.ActivityExecutionContexts.FirstOrD
 235
 157236                if (parentContext == null)
 0237                    throw new("We lost a context. This could indicate a bug in a parent activity that completed before (
 238            }
 239
 672240            var activityExecutionContextState = new ActivityExecutionContextState
 672241            {
 672242                Id = activityExecutionContext.Id,
 672243                CallStackDepth = activityExecutionContext.CallStackDepth,
 672244                ParentContextId = activityExecutionContext.ParentActivityExecutionContext?.Id,
 672245                ScheduledActivityNodeId = activityExecutionContext.NodeId,
 672246                OwnerActivityNodeId = activityExecutionContext.ParentActivityExecutionContext?.NodeId,
 672247                Properties = activityExecutionContext.Properties,
 672248                Metadata = activityExecutionContext.Metadata,
 672249                ActivityState = activityExecutionContext.ActivityState,
 672250                Status = activityExecutionContext.Status,
 672251                IsExecuting = activityExecutionContext.IsExecuting,
 672252                FaultCount = activityExecutionContext.AggregateFaultCount,
 672253                StartedAt = activityExecutionContext.StartedAt,
 672254                CompletedAt = activityExecutionContext.CompletedAt,
 672255                Tag = activityExecutionContext.Tag,
 672256                DynamicVariables = activityExecutionContext.DynamicVariables,
 672257            };
 672258            return activityExecutionContextState;
 259        }
 260
 261        // Only persist non-completed contexts.
 515262        state.ActivityExecutionContexts = workflowExecutionContext.GetActiveActivityExecutionContexts().Reverse().Select
 515263    }
 264
 265    private void ExtractScheduledActivities(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
 266    {
 515267        var scheduledActivities = workflowExecutionContext
 515268            .Scheduler.List()
 515269            .Select(x => new ActivityWorkItemState
 515270            {
 515271                ActivityNodeId = x.Activity.NodeId,
 515272                OwnerContextId = x.Owner?.Id,
 515273                Tag = x.Tag,
 515274                Variables = x.Variables?.ToList(),
 515275                ExistingActivityExecutionContextId = x.ExistingActivityExecutionContext?.Id,
 515276                Input = x.Input,
 515277            });
 278
 515279        state.ScheduledActivities = scheduledActivities.ToList();
 515280    }
 281}