From 62ae3b1f6e08cb8b1dec171703deb8d4eec79564 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 9 Aug 2023 22:35:44 +0200 Subject: [PATCH] Add ability to push variables to scheduled work (#4306) --- .../Models/ActivityExecutionContextState.cs | 7 ++ .../Stores/DapperWorkflowInboxStore.cs | 1 - .../Runtime/20230809080332_Initial.cs | 3 +- .../Runtime/20230809080343_Initial.cs | 3 +- .../Runtime/20230809080335_Initial.cs | 3 +- .../Modules/Runtime/Configurations.cs | 1 - .../Modules/Runtime/DbContext.cs | 1 - .../Modules/Runtime/WorkflowInboxStore.cs | 1 - .../WorkflowRuntimePersistenceFeature.cs | 1 - .../Services/JintJavaScriptEvaluator.cs | 10 +-- .../Modules/Runtime/CreateIndices.cs | 1 - .../WorkflowRuntimePersistenceFeature.cs | 1 - .../Elsa.Telnyx/Activities/AnswerCallBase.cs | 2 - .../Elsa.Telnyx/Activities/BridgeCallsBase.cs | 1 - .../Activities/GatherUsingSpeak.cs | 1 - .../Elsa.Telnyx/Activities/PlayAudioBase.cs | 1 - .../Handlers/TriggerAnswerCallActivities.cs | 1 - .../Handlers/TriggerCallAnsweredActivities.cs | 1 - .../Handlers/TriggerCallHangupActivities.cs | 1 - .../Handlers/TriggerIncomingCallActivities.cs | 1 - .../Activities/Parallel.cs | 1 - .../Activities/ParallelForEachT.cs | 24 ++++-- .../Contexts/ActivityExecutionContext.cs | 79 +++++++++++++------ .../Contexts/WorkflowExecutionContext.cs | 23 +++++- .../Contracts/IActivityInvoker.cs | 10 +-- .../Contracts/IExecutionContext.cs | 2 +- .../Elsa.Workflows.Core.csproj | 4 - .../ActivityExecutionContextExtensions.cs | 3 +- .../ExpressionExecutionContextExtensions.cs | 25 ++++-- .../WorkflowExecutionContextExtensions.cs | 10 ++- .../Options/ActivityInvocationOptions.cs | 11 +++ .../Options/ScheduleWorkOptions.cs | 12 +++ .../Services/ActivityInvoker.cs | 8 +- .../WorkflowExecutionContextMapper.cs | 5 +- .../State/ActivityExecutionContextState.cs | 7 ++ .../Contracts/IWorkflowInboxStore.cs | 1 - .../Features/WorkflowRuntimeFeature.cs | 1 - .../Filters/WorkflowInboxMessageFilter.cs | 1 - .../WorkflowInboxMessageReceived.cs | 1 - .../SubmitWorkflowInboxMessageResult.cs | 1 - .../Stores/MemoryWorkflowInboxStore.cs | 1 - 41 files changed, 172 insertions(+), 100 deletions(-) create mode 100644 src/modules/Elsa.Workflows.Core/Options/ActivityInvocationOptions.cs create mode 100644 src/modules/Elsa.Workflows.Core/Options/ScheduleWorkOptions.cs diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/ActivityExecutionContextState.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/ActivityExecutionContextState.cs index e6822074f..64181fab4 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/ActivityExecutionContextState.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/ActivityExecutionContextState.cs @@ -1,3 +1,5 @@ +using Elsa.Api.Client.Resources.WorkflowDefinitions.Models; + namespace Elsa.Api.Client.Resources.WorkflowInstances.Models; /// @@ -34,4 +36,9 @@ public class ActivityExecutionContextState /// The evaluated values of the activity's properties. /// public IDictionary? ActivityState { get; set; } + + /// + /// A list of dynamically created variables. + /// + public ICollection DynamicVariables { get; set; } = new List(); } \ No newline at end of file diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxStore.cs index 825437c45..54226593c 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxStore.cs @@ -8,7 +8,6 @@ using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Filters; -using Elsa.Workflows.Runtime.Models; namespace Elsa.Dapper.Modules.Runtime.Stores; diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20230809080332_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20230809080332_Initial.cs index 74c603679..8b4ccb3ce 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20230809080332_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20230809080332_Initial.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20230809080343_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20230809080343_Initial.cs index 7299662ec..c0285330f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20230809080343_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20230809080343_Initial.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20230809080335_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20230809080335_Initial.cs index eb8491684..9d9b527cd 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20230809080335_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20230809080335_Initial.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/Configurations.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/Configurations.cs index 24dec0424..da60e7d3b 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/Configurations.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/Configurations.cs @@ -1,7 +1,6 @@ using Elsa.Workflows.Core; using Elsa.Workflows.Core.State; using Elsa.Workflows.Runtime.Entities; -using Elsa.Workflows.Runtime.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/DbContext.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/DbContext.cs index bb1866598..218c60d07 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/DbContext.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/DbContext.cs @@ -1,7 +1,6 @@ using Elsa.EntityFrameworkCore.Common; using Elsa.Workflows.Core.State; using Elsa.Workflows.Runtime.Entities; -using Elsa.Workflows.Runtime.Models; using Microsoft.EntityFrameworkCore; namespace Elsa.EntityFrameworkCore.Modules.Runtime; diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowInboxStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowInboxStore.cs index 57d34468f..dea7fe9df 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowInboxStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowInboxStore.cs @@ -4,7 +4,6 @@ using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Filters; -using Elsa.Workflows.Runtime.Models; namespace Elsa.EntityFrameworkCore.Modules.Runtime; diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs index 7f39f19ad..69edb93db 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs @@ -3,7 +3,6 @@ using Elsa.Features.Attributes; using Elsa.Features.Services; using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Features; -using Elsa.Workflows.Runtime.Models; using Microsoft.Extensions.DependencyInjection; namespace Elsa.EntityFrameworkCore.Modules.Runtime; diff --git a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs index dcc111a25..d9f3c7164 100644 --- a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs +++ b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs @@ -127,13 +127,9 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator private static object GetVariableInScope(ExpressionExecutionContext context, string variableName) { - var q = from variable in EnumerateVariablesInScope(context) - where variable.Name == variableName - where variable.TryGet(context, out _) - select variable.Get(context); - - var value = q.FirstOrDefault(); - + var variable = context.GetVariable(variableName); + var value = variable?.Get(context); + return ConvertIEnumerableToArray(value); } diff --git a/src/modules/Elsa.MongoDb/Modules/Runtime/CreateIndices.cs b/src/modules/Elsa.MongoDb/Modules/Runtime/CreateIndices.cs index 278bceab6..6ebf912e3 100644 --- a/src/modules/Elsa.MongoDb/Modules/Runtime/CreateIndices.cs +++ b/src/modules/Elsa.MongoDb/Modules/Runtime/CreateIndices.cs @@ -1,7 +1,6 @@ using Elsa.MongoDb.Helpers; using Elsa.Workflows.Core.State; using Elsa.Workflows.Runtime.Entities; -using Elsa.Workflows.Runtime.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MongoDB.Driver; diff --git a/src/modules/Elsa.MongoDb/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs b/src/modules/Elsa.MongoDb/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs index 7f820d435..b73942a9f 100644 --- a/src/modules/Elsa.MongoDb/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs +++ b/src/modules/Elsa.MongoDb/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs @@ -3,7 +3,6 @@ using Elsa.Features.Services; using Elsa.MongoDb.Common; using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Features; -using Elsa.Workflows.Runtime.Models; using Microsoft.Extensions.DependencyInjection; namespace Elsa.MongoDb.Modules.Runtime; diff --git a/src/modules/Elsa.Telnyx/Activities/AnswerCallBase.cs b/src/modules/Elsa.Telnyx/Activities/AnswerCallBase.cs index 9030adb57..e620e6540 100644 --- a/src/modules/Elsa.Telnyx/Activities/AnswerCallBase.cs +++ b/src/modules/Elsa.Telnyx/Activities/AnswerCallBase.cs @@ -1,5 +1,4 @@ using Elsa.Extensions; -using Elsa.Telnyx.Attributes; using Elsa.Telnyx.Bookmarks; using Elsa.Telnyx.Client.Models; using Elsa.Telnyx.Client.Services; @@ -8,7 +7,6 @@ using Elsa.Telnyx.Payloads.Call; using Elsa.Workflows.Core; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Runtime.Contracts; using Refit; namespace Elsa.Telnyx.Activities; diff --git a/src/modules/Elsa.Telnyx/Activities/BridgeCallsBase.cs b/src/modules/Elsa.Telnyx/Activities/BridgeCallsBase.cs index bcd67ef3d..50abfb75e 100644 --- a/src/modules/Elsa.Telnyx/Activities/BridgeCallsBase.cs +++ b/src/modules/Elsa.Telnyx/Activities/BridgeCallsBase.cs @@ -9,7 +9,6 @@ using Elsa.Telnyx.Payloads.Call; using Elsa.Workflows.Core; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Runtime.Contracts; using JetBrains.Annotations; using Refit; diff --git a/src/modules/Elsa.Telnyx/Activities/GatherUsingSpeak.cs b/src/modules/Elsa.Telnyx/Activities/GatherUsingSpeak.cs index 30c19a3d3..3bb847f67 100644 --- a/src/modules/Elsa.Telnyx/Activities/GatherUsingSpeak.cs +++ b/src/modules/Elsa.Telnyx/Activities/GatherUsingSpeak.cs @@ -10,7 +10,6 @@ using Elsa.Workflows.Core; using Elsa.Workflows.Core.Activities.Flowchart.Attributes; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Runtime.Contracts; using Refit; namespace Elsa.Telnyx.Activities; diff --git a/src/modules/Elsa.Telnyx/Activities/PlayAudioBase.cs b/src/modules/Elsa.Telnyx/Activities/PlayAudioBase.cs index 2fb5c1197..76debca73 100644 --- a/src/modules/Elsa.Telnyx/Activities/PlayAudioBase.cs +++ b/src/modules/Elsa.Telnyx/Activities/PlayAudioBase.cs @@ -9,7 +9,6 @@ using Elsa.Workflows.Core; using Elsa.Workflows.Core.Activities.Flowchart.Attributes; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Runtime.Contracts; using Refit; namespace Elsa.Telnyx.Activities; diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerAnswerCallActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerAnswerCallActivities.cs index d4f972e2b..75b7634b7 100644 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerAnswerCallActivities.cs +++ b/src/modules/Elsa.Telnyx/Handlers/TriggerAnswerCallActivities.cs @@ -4,7 +4,6 @@ using Elsa.Telnyx.Activities; using Elsa.Telnyx.Bookmarks; using Elsa.Telnyx.Events; using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Abstract; using Elsa.Telnyx.Payloads.Call; using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Runtime.Contracts; diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerCallAnsweredActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerCallAnsweredActivities.cs index 4302e1e29..c067355b4 100644 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerCallAnsweredActivities.cs +++ b/src/modules/Elsa.Telnyx/Handlers/TriggerCallAnsweredActivities.cs @@ -4,7 +4,6 @@ using Elsa.Telnyx.Activities; using Elsa.Telnyx.Bookmarks; using Elsa.Telnyx.Events; using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Abstract; using Elsa.Telnyx.Payloads.Call; using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Runtime.Contracts; diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerCallHangupActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerCallHangupActivities.cs index 1567d0507..f8c213f3d 100644 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerCallHangupActivities.cs +++ b/src/modules/Elsa.Telnyx/Handlers/TriggerCallHangupActivities.cs @@ -4,7 +4,6 @@ using Elsa.Telnyx.Activities; using Elsa.Telnyx.Bookmarks; using Elsa.Telnyx.Events; using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Abstract; using Elsa.Telnyx.Payloads.Call; using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Runtime.Contracts; diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerIncomingCallActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerIncomingCallActivities.cs index ea06ba253..53acaae05 100644 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerIncomingCallActivities.cs +++ b/src/modules/Elsa.Telnyx/Handlers/TriggerIncomingCallActivities.cs @@ -4,7 +4,6 @@ using Elsa.Telnyx.Activities; using Elsa.Telnyx.Bookmarks; using Elsa.Telnyx.Events; using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Abstract; using Elsa.Telnyx.Payloads.Call; using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Runtime.Contracts; diff --git a/src/modules/Elsa.Workflows.Core/Activities/Parallel.cs b/src/modules/Elsa.Workflows.Core/Activities/Parallel.cs index b81e1b7ed..75ee922d1 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Parallel.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Parallel.cs @@ -3,7 +3,6 @@ using System.Runtime.CompilerServices; using Elsa.Extensions; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Contracts; -using JetBrains.Annotations; namespace Elsa.Workflows.Core.Activities; diff --git a/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs b/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs index 6b12df4a9..ccd795fbe 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs @@ -1,9 +1,11 @@ -using System.ComponentModel; using System.Runtime.CompilerServices; +using Elsa.Expressions.Helpers; using Elsa.Extensions; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Core.Memory; using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Core.Services; namespace Elsa.Workflows.Core.Activities; @@ -25,7 +27,7 @@ public class ParallelForEach : Activity /// /// The items to iterate. /// - [Input(Description = "The items to iterate.")] + [Input(Description = "The items to iterate through.")] public Input> Items { get; set; } = new(Array.Empty()); /// @@ -37,17 +39,27 @@ public class ParallelForEach : Activity /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { - var items = context.Get(Items)!.Reverse().ToList(); + var items = context.Get(Items)!.ToList(); var tags = new List(); foreach (var item in items) { - // TODO: For each item, declare a new block of memory to store the item into. + // For each item, declare a new variable the work to be scheduled. + var variable = new Variable("CurrentItem", item) + { + // TODO: This should be configurable, because this won't work for e.g. file streams and other non-serializable types. + StorageDriverType = typeof(WorkflowStorageDriver) + }; + + var variables = new List> + { + variable + }; // Schedule a body of work for each item. var tag = Guid.NewGuid(); tags.Add(tag); - await context.ScheduleActivityAsync(Body, OnChildCompleted, tag); + await context.ScheduleActivityAsync(Body, OnChildCompleted, tag, variables); } context.SetProperty(ScheduledTagsProperty, tags); @@ -57,7 +69,7 @@ public class ParallelForEach : Activity private async ValueTask OnChildCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) { var scheduledTags = context.GetProperty>(ScheduledTagsProperty)!; - var completedTag = (Guid)childContext.Tag!; + var completedTag = childContext.Tag.ConvertTo(); var completedTags = new HashSet(context.UpdateProperty>(CompletedTagsProperty, completedTags => { diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs index 98da388df..3e8478a9f 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs @@ -7,6 +7,7 @@ using Elsa.Extensions; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Memory; using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Core.Options; using Elsa.Workflows.Core.Services; namespace Elsa.Workflows.Core; @@ -87,7 +88,20 @@ public class ActivityExecutionContext : IExecutionContext public ExpressionExecutionContext ExpressionExecutionContext { get; } /// - public IEnumerable Variables => (Activity as IVariableContainer)?.Variables ?? Enumerable.Empty(); + public IEnumerable Variables + { + get + { + var containerVariables = (Activity as IVariableContainer)?.Variables ?? Enumerable.Empty(); + var dynamicVariables = DynamicVariables; + return containerVariables.Concat(dynamicVariables).DistinctBy(x => x.Name); + } + } + + /// + /// A list of variables that are dynamically added to the activity execution context. + /// + public ICollection DynamicVariables { get; set; } = new List(); /// /// The currently executing activity. @@ -159,47 +173,49 @@ public class ActivityExecutionContext : IExecutionContext /// Schedules the specified activity to be executed. /// /// The activity to schedule. - /// An optional callback that is invoked when the activity completes. - /// An optional tag that can be used to identify the activity. - public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityCompletionCallback? completionCallback = default, object? tag = default) + /// An optional callback to invoke when the activity completes. + /// An optional tag to associate with the activity execution. + /// An optional list of variables to declare with the activity execution. + public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityCompletionCallback? completionCallback, object? tag = default, IEnumerable? variables = default) { - var activityNode = activity != null ? WorkflowExecutionContext.FindNodeByActivity(activity) : default; - await ScheduleActivityAsync(activityNode, completionCallback, tag); + var options = new ScheduleWorkOptions(completionCallback, tag, variables); + await ScheduleActivityAsync(activity, options); } /// /// Schedules the specified activity to be executed. /// /// The activity to schedule. - /// The activity execution context that owns the activity. - /// An optional callback that is invoked when the activity completes. - /// An optional tag that can be used to identify the activity. - public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityExecutionContext owner, ActivityCompletionCallback? completionCallback = default, object? tag = default) + /// The options used to schedule the activity. + public async ValueTask ScheduleActivityAsync(IActivity? activity, ScheduleWorkOptions? options = default) { var activityNode = activity != null ? WorkflowExecutionContext.FindNodeByActivity(activity) : default; - await ScheduleActivityAsync(activityNode, this, completionCallback, tag); + await ScheduleActivityAsync(activityNode, this, options); } /// /// Schedules the specified activity to be executed. /// - /// The activity node to schedule. - /// An optional callback that is invoked when the activity completes. - /// An optional tag that can be used to identify the activity. - public async ValueTask ScheduleActivityAsync(ActivityNode? activityNode, ActivityCompletionCallback? completionCallback = default, object? tag = default) + /// The activity to schedule. + /// The activity execution context that owns the scheduled activity. + /// The options used to schedule the activity. + public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityExecutionContext? owner, ScheduleWorkOptions? options = default) { - await ScheduleActivityAsync(activityNode, this, completionCallback, tag); + var activityNode = activity != null ? WorkflowExecutionContext.FindNodeByActivity(activity) : default; + await ScheduleActivityAsync(activityNode, owner, options); } - + /// /// Schedules the specified activity to be executed. /// /// The activity node to schedule. - /// The activity execution context that owns the activity. - /// An optional callback that is invoked when the activity completes. - /// An optional tag that can be used to identify the activity. - public async ValueTask ScheduleActivityAsync(ActivityNode? activityNode, ActivityExecutionContext owner, ActivityCompletionCallback? completionCallback = default, object? tag = default) + /// The activity execution context that owns the scheduled activity. + /// The options used to schedule the activity. + public async ValueTask ScheduleActivityAsync(ActivityNode? activityNode, ActivityExecutionContext? owner = default, ScheduleWorkOptions? options = default) { + var completionCallback = options?.CompletionCallback; + owner ??= this; + if (activityNode == null) { if (completionCallback != null) @@ -209,7 +225,7 @@ public class ActivityExecutionContext : IExecutionContext return; } - WorkflowExecutionContext.Schedule(activityNode, owner, completionCallback, tag); + WorkflowExecutionContext.Schedule(activityNode, owner, options); } /// @@ -222,11 +238,24 @@ public class ActivityExecutionContext : IExecutionContext /// Schedules the specified activities to be executed. /// /// The activities to schedule. - /// An optional callback that is invoked for each activity when it completes. - public async ValueTask ScheduleActivities(IEnumerable activities, ActivityCompletionCallback? completionCallback = default) + /// The callback to invoke when the activities complete. + /// An optional tag to associate with the activity execution. + /// An optional list of variables to declare with the activity execution. + public async ValueTask ScheduleActivities(IEnumerable activities, ActivityCompletionCallback? completionCallback, object? tag = default, IEnumerable? variables = default) + { + var options = new ScheduleWorkOptions(completionCallback, tag, variables); + await ScheduleActivities(activities, options); + } + + /// + /// Schedules the specified activities to be executed. + /// + /// The activities to schedule. + /// The options used to schedule the activities. + public async ValueTask ScheduleActivities(IEnumerable activities, ScheduleWorkOptions? options = default) { foreach (var activity in activities) - await ScheduleActivityAsync(activity, completionCallback); + await ScheduleActivityAsync(activity, options); } /// diff --git a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs index 402538c64..407edd7bc 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs @@ -9,6 +9,7 @@ using Elsa.Common.Contracts; using Elsa.Workflows.Core.Activities; using Elsa.Workflows.Core.Memory; using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Core.Options; using JetBrains.Annotations; namespace Elsa.Workflows.Core; @@ -122,7 +123,7 @@ public class WorkflowExecutionContext : IExecutionContext /// The date and time the workflow execution context was created. /// public DateTimeOffset CreatedAt { get; set; } - + /// /// A flattened list of s from the . /// @@ -373,16 +374,34 @@ public class WorkflowExecutionContext : IExecutionContext /// /// Creates a new for the specified activity. /// - public ActivityExecutionContext CreateActivityExecutionContext(IActivity activity, ActivityExecutionContext? parentContext = default, object? tag = default) + public ActivityExecutionContext CreateActivityExecutionContext(IActivity activity, ActivityInvocationOptions? options = default) { var activityDescriptor = _activityRegistry.Find(activity) ?? throw new Exception($"Activity with type {activity.Type} not found in registry"); + var tag = options?.Tag; + var parentContext = options?.Owner; var parentExpressionExecutionContext = parentContext?.ExpressionExecutionContext ?? ExpressionExecutionContext; var properties = ExpressionExecutionContextExtensions.CreateActivityExecutionContextPropertiesFrom(this, Input); var memory = new MemoryRegister(); var now = _systemClock.UtcNow; var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, memory, parentExpressionExecutionContext, properties, CancellationToken); var activityExecutionContext = new ActivityExecutionContext(this, parentContext, expressionExecutionContext, activity, activityDescriptor, now, tag, _systemClock, CancellationToken); + var variablesToDeclare = options?.Variables ?? Array.Empty(); + var variableContainer = new[] { activityExecutionContext.ActivityNode }.Concat(activityExecutionContext.ActivityNode.Ancestors()).FirstOrDefault(x => x.Activity is IVariableContainer)?.Activity as IVariableContainer; expressionExecutionContext.TransientProperties[ExpressionExecutionContextExtensions.ActivityExecutionContextKey] = activityExecutionContext; + + if (variableContainer != null) + { + foreach (var variable in variablesToDeclare) + { + // Declare a dynamic variable on the activity execution context. + activityExecutionContext.DynamicVariables.RemoveWhere(x => x.Name == variable.Name); + activityExecutionContext.DynamicVariables.Add(variable); + + // Assign the variable to the expression execution context. + expressionExecutionContext.CreateVariable(variable.Name, variable.Value); + } + } + return activityExecutionContext; } diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityInvoker.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityInvoker.cs index 2b3a18a1c..a12b488ec 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IActivityInvoker.cs @@ -1,3 +1,5 @@ +using Elsa.Workflows.Core.Options; + namespace Elsa.Workflows.Core.Contracts; /// @@ -10,12 +12,8 @@ public interface IActivityInvoker /// /// The workflow execution context. /// The activity to invoke. - /// The activity execution context that owns the activity. - /// An optional tag to associate with the activity execution. - Task InvokeAsync(WorkflowExecutionContext workflowExecutionContext, - IActivity activity, - ActivityExecutionContext? owner = default, - object? tag = default); + /// + Task InvokeAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, ActivityInvocationOptions? options = default); /// /// Invokes the specified activity execution context. diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contracts/IExecutionContext.cs index 2566a9d13..249348bac 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IExecutionContext.cs @@ -19,7 +19,7 @@ public interface IExecutionContext ExpressionExecutionContext ExpressionExecutionContext { get; } /// - /// Returns variables declared the current execution context. + /// Returns variables declared in the current execution context. /// IEnumerable Variables { get; } diff --git a/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj b/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj index 9a50800f1..59409909c 100644 --- a/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj +++ b/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj @@ -27,8 +27,4 @@ - - - - diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs index a604ba050..84ce32023 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs @@ -230,8 +230,7 @@ public static class ActivityExecutionContextExtensions } /// - /// Schedules the specified activity with the provided callback. - /// If the activity is null, the callback is invoked immediately. + /// Schedules the specified activity. /// public static async Task ScheduleOutcomeAsync(this ActivityExecutionContext context, IActivity? activity, [CallerArgumentExpression("activity")] string portPropertyName = default!) { diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index e3a003fee..1d39d0806 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -47,7 +47,7 @@ public static class ExpressionExecutionContextExtensions public static object? Get(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value; public static T? GetVariable(this ExpressionExecutionContext context, string name) => (T?)context.GetVariable(name)?.Value; - private static Variable? GetVariable(this ExpressionExecutionContext context, string name) + public static Variable? GetVariable(this ExpressionExecutionContext context, string name) { foreach (var block in context.Memory.Blocks.Where(b => b.Value.Metadata is VariableBlockMetadata)) { @@ -59,9 +59,13 @@ public static class ExpressionExecutionContextExtensions return context.ParentContext?.GetVariable(name); } + /// + /// Creates a named variable in the context. + /// public static Variable CreateVariable(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = null, Action? configure = default) { var existingVariable = context.GetVariable(name); + if(existingVariable != null) throw new Exception($"Variable {name} already exists in the context."); @@ -74,17 +78,24 @@ public static class ExpressionExecutionContextExtensions return variable; } + /// + /// Sets the value of a named variable in the context. + /// public static Variable SetVariable(this ExpressionExecutionContext context, string name, T? value, Action? configure = default) { var variable = context.GetVariable(name); - if(variable is null) - throw new Exception($"Variable {name} not found in the context."); + + if(variable == null) + return CreateVariable(context, name, value, configure: configure); variable.Value = value; variable.Set(context, value, configure); return variable; } + /// + /// Sets the output to the specified value. + /// public static void Set(this ExpressionExecutionContext context, Output? output, object? value, Action? configure = default) { if (output != null) context.Set(output.MemoryBlockReference(), value, configure); @@ -107,11 +118,9 @@ public static class ExpressionExecutionContextExtensions while (currentContext != null) { var register = currentContext.Memory; - foreach (var entry in register.Blocks) - { - if (!memoryBlocks.ContainsKey(entry.Key)) - memoryBlocks.Add(entry.Key, entry.Value); - } + + foreach (var entry in register.Blocks) + memoryBlocks.TryAdd(entry.Key, entry.Value); currentContext = currentContext.ParentContext; } diff --git a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs index e89b73e0c..180154af8 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs @@ -1,7 +1,7 @@ using Elsa.Workflows.Core; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Core.Services; +using Elsa.Workflows.Core.Options; // ReSharper disable once CheckNamespace namespace Elsa.Extensions; @@ -98,13 +98,15 @@ public static class WorkflowExecutionContextExtensions this WorkflowExecutionContext workflowExecutionContext, ActivityNode activityNode, ActivityExecutionContext owner, - ActivityCompletionCallback? completionCallback = default, - object? tag = default) + ScheduleWorkOptions? options = default) { var activityInvoker = workflowExecutionContext.GetRequiredService(); var toolVersion = workflowExecutionContext.Workflow.ToolVersion; var activityId = toolVersion.Major >= 3 ? activityNode.Activity.Id : activityNode.NodeId; - var workItem = new ActivityWorkItem(activityId, owner.Id, async () => await activityInvoker.InvokeAsync(workflowExecutionContext, activityNode.Activity, owner, tag), tag); + var tag = options?.Tag; + var activityInvocationOptions = new ActivityInvocationOptions(owner, tag, options?.Variables); + var workItem = new ActivityWorkItem(activityId, owner.Id, async () => await activityInvoker.InvokeAsync(workflowExecutionContext, activityNode.Activity, activityInvocationOptions), tag); + var completionCallback = options?.CompletionCallback; workflowExecutionContext.Scheduler.Schedule(workItem); workflowExecutionContext.AddCompletionCallback(owner, activityNode, completionCallback); } diff --git a/src/modules/Elsa.Workflows.Core/Options/ActivityInvocationOptions.cs b/src/modules/Elsa.Workflows.Core/Options/ActivityInvocationOptions.cs new file mode 100644 index 000000000..1fbb3479a --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Options/ActivityInvocationOptions.cs @@ -0,0 +1,11 @@ +using Elsa.Workflows.Core.Memory; + +namespace Elsa.Workflows.Core.Options; + +/// +/// Represents options for invoking an activity. +/// +/// The activity execution context that owns this invocation. +/// An optional tag that can be used to identify the invocation. +/// The variables to declare in the activity execution context that will be created for this invocation. +public record ActivityInvocationOptions(ActivityExecutionContext? Owner, object? Tag, IEnumerable? Variables); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Options/ScheduleWorkOptions.cs b/src/modules/Elsa.Workflows.Core/Options/ScheduleWorkOptions.cs new file mode 100644 index 000000000..00ecec97b --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Options/ScheduleWorkOptions.cs @@ -0,0 +1,12 @@ +using Elsa.Workflows.Core.Memory; +using Elsa.Workflows.Core.Services; + +namespace Elsa.Workflows.Core.Options; + +/// +/// Represents options for scheduling a work item. +/// +/// The callback to invoke when the work item has completed. +/// A tag that can be used to identify the work item. +/// A collection of variables to declare in the activity execution context that will be created for this work item. +public record ScheduleWorkOptions(ActivityCompletionCallback? CompletionCallback = default, object? Tag = default, IEnumerable? Variables = default); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs index 9ba6d2576..b6d4347bc 100644 --- a/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs @@ -1,4 +1,5 @@ using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Core.Options; namespace Elsa.Workflows.Core.Services; @@ -16,13 +17,10 @@ public class ActivityInvoker : IActivityInvoker } /// - public async Task InvokeAsync(WorkflowExecutionContext workflowExecutionContext, - IActivity activity, - ActivityExecutionContext? owner, - object? tag) + public async Task InvokeAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, ActivityInvocationOptions? options = default) { // Setup an activity execution context. - var activityExecutionContext = workflowExecutionContext.CreateActivityExecutionContext(activity, owner, tag); + var activityExecutionContext = workflowExecutionContext.CreateActivityExecutionContext(activity, options); // Add the activity context to the workflow context. workflowExecutionContext.AddActivityExecutionContext(activityExecutionContext); diff --git a/src/modules/Elsa.Workflows.Core/Services/WorkflowExecutionContextMapper.cs b/src/modules/Elsa.Workflows.Core/Services/WorkflowExecutionContextMapper.cs index eca422cb1..02389a718 100644 --- a/src/modules/Elsa.Workflows.Core/Services/WorkflowExecutionContextMapper.cs +++ b/src/modules/Elsa.Workflows.Core/Services/WorkflowExecutionContextMapper.cs @@ -112,7 +112,9 @@ public class WorkflowExecutionContextMapper : IWorkflowExecutionContextMapper Status = activityExecutionContext.Status, StartedAt = activityExecutionContext.StartedAt, CompletedAt = activityExecutionContext.CompletedAt, - Tag = activityExecutionContext.Tag + Tag = activityExecutionContext.Tag, + DynamicVariables = activityExecutionContext.DynamicVariables + }; return activityExecutionContextState; } @@ -135,6 +137,7 @@ public class WorkflowExecutionContextMapper : IWorkflowExecutionContextMapper activityExecutionContext.StartedAt = activityExecutionContextState.StartedAt; activityExecutionContext.CompletedAt = activityExecutionContextState.CompletedAt; activityExecutionContext.Tag = activityExecutionContextState.Tag; + activityExecutionContext.DynamicVariables = activityExecutionContextState.DynamicVariables; return activityExecutionContext; } diff --git a/src/modules/Elsa.Workflows.Core/State/ActivityExecutionContextState.cs b/src/modules/Elsa.Workflows.Core/State/ActivityExecutionContextState.cs index 0adad9e03..32ab6bd72 100644 --- a/src/modules/Elsa.Workflows.Core/State/ActivityExecutionContextState.cs +++ b/src/modules/Elsa.Workflows.Core/State/ActivityExecutionContextState.cs @@ -1,3 +1,5 @@ +using Elsa.Workflows.Core.Memory; + namespace Elsa.Workflows.Core.State; /// @@ -43,6 +45,11 @@ public class ActivityExecutionContextState /// The evaluated values of the activity's properties. /// public IDictionary? ActivityState { get; set; } + + /// + /// A list of dynamically created variables. + /// + public ICollection DynamicVariables { get; set; } = new List(); /// /// The status of the activity. diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInboxStore.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInboxStore.cs index fbbfdaaee..6aeaa1615 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInboxStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInboxStore.cs @@ -1,6 +1,5 @@ using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Filters; -using Elsa.Workflows.Runtime.Models; namespace Elsa.Workflows.Runtime.Contracts; diff --git a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs index c2c8177f3..173eb2476 100644 --- a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs @@ -14,7 +14,6 @@ using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Handlers; using Elsa.Workflows.Runtime.HostedServices; -using Elsa.Workflows.Runtime.Models; using Elsa.Workflows.Runtime.Options; using Elsa.Workflows.Runtime.Providers; using Elsa.Workflows.Runtime.Services; diff --git a/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowInboxMessageFilter.cs b/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowInboxMessageFilter.cs index 28d8aa9eb..5ddc8617a 100644 --- a/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowInboxMessageFilter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowInboxMessageFilter.cs @@ -1,5 +1,4 @@ using Elsa.Workflows.Runtime.Entities; -using Elsa.Workflows.Runtime.Models; namespace Elsa.Workflows.Runtime.Filters; diff --git a/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowInboxMessageReceived.cs b/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowInboxMessageReceived.cs index 94149677c..f8a9f22e9 100644 --- a/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowInboxMessageReceived.cs +++ b/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowInboxMessageReceived.cs @@ -1,7 +1,6 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; -using Elsa.Workflows.Runtime.Models; namespace Elsa.Workflows.Runtime.Notifications; diff --git a/src/modules/Elsa.Workflows.Runtime/Results/SubmitWorkflowInboxMessageResult.cs b/src/modules/Elsa.Workflows.Runtime/Results/SubmitWorkflowInboxMessageResult.cs index bde41c003..0c28a4694 100644 --- a/src/modules/Elsa.Workflows.Runtime/Results/SubmitWorkflowInboxMessageResult.cs +++ b/src/modules/Elsa.Workflows.Runtime/Results/SubmitWorkflowInboxMessageResult.cs @@ -1,6 +1,5 @@ using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; -using Elsa.Workflows.Runtime.Models; namespace Elsa.Workflows.Runtime.Results; diff --git a/src/modules/Elsa.Workflows.Runtime/Stores/MemoryWorkflowInboxStore.cs b/src/modules/Elsa.Workflows.Runtime/Stores/MemoryWorkflowInboxStore.cs index e80ec6805..cde5839db 100644 --- a/src/modules/Elsa.Workflows.Runtime/Stores/MemoryWorkflowInboxStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/Stores/MemoryWorkflowInboxStore.cs @@ -2,7 +2,6 @@ using Elsa.Common.Services; using Elsa.Workflows.Runtime.Contracts; using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Filters; -using Elsa.Workflows.Runtime.Models; namespace Elsa.Workflows.Runtime.Stores;