From 31092f8d817154a57c43e34743b93b09cbda1f3e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 4 Apr 2024 16:07:32 +0200 Subject: [PATCH] Enhance BulkDispatchWorkflows Activity with Support for Multiple Input Keys and Customizable Input Key (#5182) * Add PublicAPI attribute to ActivityExecutionContext The 'PublicAPI' attribute has been added to the ActivityExecutionContext class. This signals that this class is part of our public API and should not be modified in a way that violates backward compatibility. Furthermore, the 'RequiresUnreferencedCode' attributes were removed from the 'CompleteActivityAsync' and 'CompleteActivityWithOutcomesAsync' methods, as these methods no longer need them. * Add merge function to DictionaryExtensions A new merge function has been added to the DictionaryExtensions. This function combines the contents of two dictionaries, prioritizing the keys and values from the second dictionary whenever there is a collision. * Add default item input key in BulkDispatchWorkflows The code has been adjusted to include a default input item key in the BulkDispatchWorkflows module. This key will be used for the item input when dispatching workflows, unless the Items field contains a list of dictionaries. Additionally, modifications have been made to convert the item into an input dictionary if it isn't already one and merge this with the input dictionary. * Update Merge method documentation in DictionaryExtensions The commit adds a critical detail about dictionary operation in the method summary. It clearly states that during the merge operation, the value from the 'other' dictionary overwrites the value in the target dictionary when the same key exists in both. --- .../ActivityExecutionContext.Complete.cs | 3 --- .../Contexts/ActivityExecutionContext.cs | 2 ++ .../Extensions/DictionaryExtensions.cs | 10 +++++++ .../Activities/BulkDispatchWorkflows.cs | 27 ++++++++++++------- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Complete.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Complete.cs index f3963ed91..28aa711ae 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Complete.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Complete.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using Elsa.Extensions; using Elsa.Workflows.Activities.Flowchart.Models; using Elsa.Workflows.Contracts; @@ -11,7 +10,6 @@ public partial class ActivityExecutionContext /// /// Complete the current activity. This should only be called by activities that explicitly suppress automatic-completion. /// - [RequiresUnreferencedCode("The activity may be serialized and executed in a different context.")] public async ValueTask CompleteActivityAsync(object? result = default) { var outcomes = result as Outcomes; @@ -93,7 +91,6 @@ public partial class ActivityExecutionContext /// /// Complete the current activity with the specified outcomes. /// - [RequiresUnreferencedCode("The activity may be serialized and executed in a different context.")] public ValueTask CompleteActivityWithOutcomesAsync(params string[] outcomes) { return CompleteActivityAsync(new Outcomes(outcomes)); diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs index 2be9c0b3d..d33473646 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs @@ -10,12 +10,14 @@ using Elsa.Workflows.Memory; using Elsa.Workflows.Models; using Elsa.Workflows.Options; using Elsa.Workflows.Services; +using JetBrains.Annotations; namespace Elsa.Workflows; /// /// Represents the context of an activity execution. /// +[PublicAPI] public partial class ActivityExecutionContext : IExecutionContext { private readonly ISystemClock _systemClock; diff --git a/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs index 3698b239c..c95267384 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs @@ -65,6 +65,16 @@ public static class DictionaryExtensions dictionary.Add(key, value); return dictionary; } + + /// + /// Merges the specified dictionary with the other dictionary. + /// When a key exists in both dictionaries, the value in the other dictionary will overwrite the value in the specified dictionary. + /// + public static void Merge(this IDictionary dictionary, IDictionary other) + { + foreach (var (key, value) in other) + dictionary[key] = value; + } private static T? ConvertValue(object? value) => value.ConvertTo(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs index eade3320c..569f53647 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs @@ -55,19 +55,25 @@ public class BulkDispatchWorkflows : Activity [Input(Description = "The data source to use for dispatching the workflows.")] public Input Items { get; set; } = default!; + /// + /// The default key to use for the item input. Will not be used if the Items contain a list of dictionaries. + /// + [Input(Description = "The default key to use for the input name when sending the current item to the dispatched workflow. Will not be used if the Items field contain a list of dictionaries", DefaultValue = "Item")] + public Input DefaultItemInputKey { get; set; } = new("Item"); + /// /// The correlation ID to associate the workflow with. /// [Input( DisplayName = "Correlation ID Function", - Description = "A function to compute the correlation ID to associate a dispatched workflow with. Receives the current item as an argument called Item.", + Description = "A function to compute the correlation ID to associate a dispatched workflow with.", AutoEvaluate = false)] public Input? CorrelationIdFunction { get; set; } /// /// The input to send to the workflows. /// - [Input(Description = """Additional input to send to the workflows being dispatched. The "Item" key is reserved and should not be used.""")] + [Input(Description = "Additional input to send to the workflows being dispatched.")] public Input?> Input { get; set; } = default!; /// @@ -102,7 +108,6 @@ public class BulkDispatchWorkflows : Activity public IActivity? ChildFaulted { get; set; } /// - [RequiresUnreferencedCode("Calls Elsa.Expressions.Helpers.ObjectConverter.ConvertTo(ObjectConverterOptions)")] protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var waitForCompletion = WaitForCompletion.GetOrDefault(context); @@ -162,20 +167,24 @@ public class BulkDispatchWorkflows : Activity var parentInstanceId = context.WorkflowExecutionContext.Id; var input = Input.GetOrDefault(context) ?? new Dictionary(); var channelName = ChannelName.GetOrDefault(context); + var defaultInputItemKey = DefaultItemInputKey.GetOrDefault(context, () => "Item")!; var properties = new Dictionary { ["ParentInstanceId"] = parentInstanceId }; + + var itemAsInputDictionary = item as IDictionary ?? new Dictionary + { + [defaultInputItemKey] = item + }; + var evaluatorOptions = new ExpressionEvaluatorOptions { - Arguments = new Dictionary - { - ["Item"] = item - } + Arguments = itemAsInputDictionary }; input["ParentInstanceId"] = parentInstanceId; - input["Item"] = item; + input.Merge(itemAsInputDictionary); var workflowDispatcher = context.GetRequiredService(); var identityGenerator = context.GetRequiredService(); @@ -198,7 +207,7 @@ public class BulkDispatchWorkflows : Activity }; var dispatchResponse = await workflowDispatcher.DispatchAsync(request, options, context.CancellationToken); - + if (!dispatchResponse.Succeeded) throw new FaultException(dispatchResponse.ErrorMessage);