elsa-workflows / elsa-core

A .NET workflows library
https://v3.elsaworkflows.io/
MIT License
6.23k stars 1.14k forks source link

[ENH] Support lists and dictionaries of activity inputs #5421

Open bobhauser opened 3 months ago

bobhauser commented 3 months ago

Enhancement Request

Enhancement Overview

We would like the ability to develop activities that contain lists of inputs and dictionaries of inputs.

For example: public List<Input<string>>? Recipients { get; set; } = default;

public Dictionary<string, Input<string>>? Arguments { get; set; } = default;

The existing activity input evaluation does not support either of these usages.

Note: we are not using the designer, so as part of this enhancement I am not proposing designer changes.

Proposed Enhancement

The enhancement I am proposing is to basically support lists and dictionaries of inputs. This can be accomplished with small modifications to a few classes (including Elsa.Workflows.Models.InputDescriptor and Elsa.Workflows.Services.ActivityDescriber).

I have a solution for this that we have been using for a few weeks from which I will be able to create a PR.

Alternative Solutions

We have attempted various workarounds such as using an input of an array of strings (Input<string[]>) but this did not provide the flexibility we require (which is to map the entries of the collections separately).

Use Cases

List of Input We have an activity which sends a message to a list of users. When configuring the recipients, we require the ability to hard-code some users (Literal) while mapping some of the recipients to the results of prior activities (Variable or JavaScript)..

Dictionary of Input We have an activity where a user can select a script to run, with each script containing it's own unique set of named arguments. We need to be able to map each of the arguments separately.

Impact of Enhancement

Users of Elsa would be able to create activities with more complicated mapping of inputs. Although not being proposed at this time, I could envision future enhancements to the designer being made to surface these new capabilities.

Additional Context

This is an example activity which makes use of a list of inputs when writing to the Console.

[Activity("Sample", "Console", "Write a multiple lines of text to the console.")]
public class WriteMultiLine : CodeActivity
{
    /// <inheritdoc />
    [JsonConstructor]
    private WriteMultiLine(string? source = default, int? line = default) : base(source, line)
    {
    }

    /// <inheritdoc />
    public WriteMultiLine(List<Input<string>> text, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => Text = text;

    /// <summary>
    /// The multiple lines of text to write.
    /// </summary>
    [Description("The text to write.")]
    public List<Input<string>>? Text { get; set; } = default;

    /// <inheritdoc />
    protected override void Execute(ActivityExecutionContext context)
    {
        var provider = context.GetService<IStandardOutStreamProvider>() ?? new StandardOutStreamProvider(System.Console.Out);
        var textWriter = provider.GetTextWriter();

        Text?.ForEach(t => {
            var text = context.Get(t);
            textWriter.WriteLine(text);
        });
    }
}
glime-ai[bot] commented 3 months ago

Based on the context provided, it seems you are looking to enhance the Elsa Workflow activities to support lists and dictionaries of inputs. Your proposed enhancement involves modifying classes such as InputDescriptor and ActivityDescriber to accommodate these complex input types. You have also mentioned that you have a working solution and are ready to create a pull request.

Given that you are not using the designer and are not proposing designer changes at this time, the enhancement would primarily benefit users who are developing workflows programmatically. The use cases you've described, such as sending messages to a list of users with varying input types (Literal, Variable, JavaScript) and running scripts with unique sets of named arguments, are clear examples of the flexibility this enhancement would provide.

The impact of this enhancement would allow for more sophisticated input mapping in activities, which could be beneficial for workflows that require dynamic input configurations. Although this does not currently involve changes to the designer, it could potentially lead to future enhancements that make these capabilities available in the designer interface.

Your additional context with the example activity WriteMultiLine demonstrates how a list of inputs could be used in practice.

If you have already been using this solution for a few weeks and it is stable, creating a pull request would be the next step. It would be important to ensure that your changes are well-documented and include tests to verify the new functionality. Additionally, it would be beneficial to consider how these changes might be integrated with the designer in the future, even if that is not part of the current proposal.

Before proceeding, it would be advisable to discuss this enhancement with the Elsa Workflow maintainers to ensure that it aligns with the project's roadmap and to gather any feedback they might have.

This comment was generated by Glime.

rboylesDev commented 3 months ago

I don't understand why this doesn't meet what you are wanting?

public Input<List<string>> Recipients { get; set; } = default;

It's just a list of strings anyways and is effectively the same thing you're proposing just without extra steps on the consumer.

bobhauser commented 3 months ago

Hey @rboylesDev, thanks for your comment. For sure what you suggest would work if a simple list of names is available. But if the list were to come from a variety of sources, then I believe it would be easier to specify the inputs separately. I added a sample workflow showing both of the new input constructs that I am suggesting at the bottom of this message.

The RunScript activity has an input called arguments which consists of two string arguments (one Literal, one JavaScript) as well as a boolean argument.

The SendMessage activity has an input called to which consists of three string elements (one Literal, one JavaScript, and one Variable).

Does this help demonstrate why a simple Input<List<string>> would not be sufficient?

Sample Workflow Definition

``` { "type": "Elsa.Flowchart", "version": 1, "id": "flowchart", "nodeId": "120f407f-bff7-4b3a-a8b6-c57ee625dc42:flowchart", "metadata": {}, "customProperties": { "source": "WorkflowMapper.cs:61" }, "activities": [ { "id": "Event_1hehqwh", "nodeId": "120f407f-bff7-4b3a-a8b6-c57ee625dc42:flowchart:Event_1hehqwh", "name": "", "type": "Elsa.Start", "version": 1, "customProperties": { "source": "WorkflowMapper.cs:121" }, "metadata": {} }, { "id": "Event_01xeq36", "nodeId": "120f407f-bff7-4b3a-a8b6-c57ee625dc42:flowchart:Event_01xeq36", "name": "", "type": "Elsa.End", "version": 1, "customProperties": { "source": "WorkflowMapper.cs:126" }, "metadata": {} }, { "scriptDependency": { "type": "Script", "name": "LookupCompanyContactEmail", "scope": "Private", "onlyAllowPublic": false }, "arguments": { "CompanyName": { "typeName": "String", "expression": { "type": "Literal", "value": "Apex Inc" } }, "ContactName": { "typeName": "String", "expression": { "type": "JavaScript", "value": "getInput(\"ContactName\");" } }, "SomeBooleanArg": { "typeName": "Boolean", "expression": { "type": "Literal", "value": "true" } } }, "result": { "typeName": "Object", "memoryReference": { "id": "9d8307aafd7641f88a0ce53a5e58fedb" } }, "id": "Activity_10k4yr3", "nodeId": "120f407f-bff7-4b3a-a8b6-c57ee625dc42:flowchart:Activity_10k4yr3", "name": "RunScript", "type": "Sample.RunScript", "version": 1, "customProperties": {}, "metadata": {} }, { "subject": { "typeName": "String", "expression": { "type": "Literal", "value": "This is a test message" } }, "body": { "typeName": "String", "expression": { "type": "JavaScript", "value": "getInput(\"MessageBody\");" } }, "to": [ { "typeName": "String", "expression": { "type": "Literal", "value": "admin" } }, { "typeName": "String", "expression": { "type": "JavaScript", "value": "getInput(\"MessageRecipient\");" } }, { "typeName": "String", "expression": { "type": "Variable", "value": { "id": "9d8307aafd7641f88a0ce53a5e58fedb", "name": "AnotherRecipient", "typeName": "String", "value": "ralph", "storageDriverTypeName": "Elsa.Workflows.Services.WorkflowStorageDriver, Elsa.Workflows.Core" } } } ], "cc": [], "bcc": [], "id": "Activity_1ndpz3j", "nodeId": "120f407f-bff7-4b3a-a8b6-c57ee625dc42:flowchart:Activity_1ndpz3j", "name": "SendMessage", "type": "Sample.SendMessage", "version": 1, "customProperties": {}, "metadata": {} } ], "connections": [ { "source": { "activity": "Event_1hehqwh" }, "target": { "activity": "Activity_10k4yr3" } }, { "source": { "activity": "Activity_10k4yr3" }, "target": { "activity": "Activity_1ndpz3j" } }, { "source": { "activity": "Activity_1ndpz3j" }, "target": { "activity": "Event_01xeq36" } } ] } ```

rboylesDev commented 3 months ago

@bobhauser, ah I see. We have a few of these cases and the way we've handled (worked around?) it is to have a javascript activity, or Set Variable activity with value input using Javascript, that combines all the inputs into a single list before passing to our activity. While it's not natively taking in multiple inputs, it does work now without additional Elsa changes.