diff --git a/src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs b/src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs
index 67d1bfa48..7a7ab30b9 100644
--- a/src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs
+++ b/src/common/Elsa.Testing.Shared/ServiceProviderExtensions.cs
@@ -53,10 +53,11 @@ public static class ServiceProviderExtensions
///
/// The services.
/// The ID of the workflow definition.
+ /// An optional dictionary of input values.
/// The workflow state.
- public static async Task RunWorkflowUntilEndAsync(this IServiceProvider services, string workflowDefinitionId)
+ public static async Task RunWorkflowUntilEndAsync(this IServiceProvider services, string workflowDefinitionId, IDictionary? input = default)
{
- var startWorkflowOptions = new StartWorkflowRuntimeOptions(null, new Dictionary(), VersionOptions.Published);
+ var startWorkflowOptions = new StartWorkflowRuntimeOptions(null, input, VersionOptions.Published);
var workflowRuntime = services.GetRequiredService();
var result = await workflowRuntime.StartWorkflowAsync(workflowDefinitionId, startWorkflowOptions);
var bookmarks = new Stack(result.Bookmarks);
@@ -67,7 +68,7 @@ public static class ServiceProviderExtensions
var resumeOptions = new ResumeWorkflowRuntimeOptions(BookmarkId: bookmark.Id);
var resumeResult = await workflowRuntime.ResumeWorkflowAsync(result.WorkflowInstanceId, resumeOptions);
- foreach (var newBookmark in resumeResult.Bookmarks)
+ foreach (var newBookmark in resumeResult!.Bookmarks)
bookmarks.Push(newBookmark);
}
diff --git a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs
index a18e31c7d..877d84b0b 100644
--- a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs
+++ b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs
@@ -9,6 +9,7 @@ using Elsa.JavaScript.Extensions;
using Elsa.JavaScript.Notifications;
using Elsa.JavaScript.Options;
using Elsa.Mediator.Contracts;
+using Elsa.Workflows.Core.Activities;
using Elsa.Workflows.Core.Memory;
using Humanizer;
using Jint;
@@ -67,12 +68,14 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
engine.SetValue("getInput", (Func)(name => context.GetWorkflowExecutionContext().Input.GetValue(name)));
engine.SetValue("getOutputFrom", (Func)((activityIdOrNodeId, outputName) => GetOutput(context, activityIdOrNodeId, outputName)));
engine.SetValue("getLastResult", (Func)(() => GetLastResult(context)));
-
- // Create workflow input accessors.
- CreateWorkflowInputAccessors(engine, context);
// Create variable getters and setters for each variable.
CreateVariableAccessors(engine, context);
+
+ // Create workflow input accessors - only if the current activity is not part of a composite activity definition.
+ // Otherwise, the workflow input accessors will hide the composite activity input accessors which rely on variable accessors created above.
+ if(!IsInsideCompositeActivity(context))
+ CreateWorkflowInputAccessors(engine, context);
// Create output getters for each activity.
CreateOutputAccessors(engine, context);
@@ -103,6 +106,17 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
return engine;
}
+ private static bool IsInsideCompositeActivity(ExpressionExecutionContext context)
+ {
+ if(!context.TryGetActivityExecutionContext(out var activityExecutionContext))
+ return false;
+
+ // If the first workflow definition in the ancestor hierarchy and that workflow definition has a parent, then we are inside a composite activity.
+ var firstWorkflowContext = activityExecutionContext.GetAncestors().FirstOrDefault(x => x.Activity is Workflow);
+
+ return firstWorkflowContext?.ParentActivityExecutionContext != null;
+ }
+
private static object? GetLastResult(ExpressionExecutionContext context)
{
var workflowExecutionContext = context.GetWorkflowExecutionContext();
@@ -123,6 +137,11 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
private void CreateWorkflowInputAccessors(Engine engine, ExpressionExecutionContext context)
{
+ if(!context.TryGetActivityExecutionContext(out var activityExecutionContext))
+ return;
+
+ // Only create workflow input accessors if the current activity is not part of a composite activity definition.
+
if(context.TryGetWorkflowExecutionContext(out var workflowExecutionContext))
{
var input = workflowExecutionContext.Input;
diff --git a/test/integration/Elsa.IntegrationTests/Elsa.IntegrationTests.csproj b/test/integration/Elsa.IntegrationTests/Elsa.IntegrationTests.csproj
index e3d941252..e8ccb4ac4 100644
--- a/test/integration/Elsa.IntegrationTests/Elsa.IntegrationTests.csproj
+++ b/test/integration/Elsa.IntegrationTests/Elsa.IntegrationTests.csproj
@@ -87,8 +87,18 @@
Always
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Tests.cs b/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Tests.cs
new file mode 100644
index 000000000..85adf1c23
--- /dev/null
+++ b/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Tests.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Elsa.Testing.Shared;
+using Elsa.Workflows.Core;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Elsa.IntegrationTests.Scenarios.ParentChildInputs;
+
+///
+/// Tests for the flowchart completion feature using various workflow setups.
+///
+public class Tests
+{
+ private readonly CapturingTextWriter _capturingTextWriter = new();
+ private readonly IServiceProvider _services;
+
+ public Tests(ITestOutputHelper testOutputHelper)
+ {
+ _services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
+ }
+
+ [Fact(DisplayName = "Child activity receives workflow input from parent event if same name.")]
+ public async Task Test1()
+ {
+ // Populate registries.
+ await _services.PopulateRegistriesAsync();
+
+ // Import child workflow.
+ var childFileName = $"Scenarios/ParentChildInputs/Workflows/child.json";
+ await _services.ImportWorkflowDefinitionAsync(childFileName);
+
+ // Import parent workflow.
+ var parentFileName = $"Scenarios/ParentChildInputs/Workflows/parent1.json";
+ var workflowDefinition = await _services.ImportWorkflowDefinitionAsync(parentFileName);
+
+ // Execute.
+ var input = new Dictionary { ["Input1"] = "Foo" };
+ await _services.RunWorkflowUntilEndAsync(workflowDefinition.DefinitionId, input);
+
+ // Assert expected output.
+ var lines = _capturingTextWriter.Lines.ToList();
+ Assert.Equal(new[] { "Parent: Foo", "Child: Bar" }, lines);
+ }
+
+ [Fact(DisplayName = "Child activity receives input from parent event if same name and does not use global workflow input.")]
+ public async Task Test2()
+ {
+ // Populate registries.
+ await _services.PopulateRegistriesAsync();
+
+ // Import child workflow.
+ var childFileName = $"Scenarios/ParentChildInputs/Workflows/child.json";
+ await _services.ImportWorkflowDefinitionAsync(childFileName);
+
+ // Import parent workflow.
+ var parentFileName = $"Scenarios/ParentChildInputs/Workflows/parent2.json";
+ var workflowDefinition = await _services.ImportWorkflowDefinitionAsync(parentFileName);
+
+ // Execute.
+ var input = new Dictionary { ["Input1"] = "Foo" };
+ await _services.RunWorkflowUntilEndAsync(workflowDefinition.DefinitionId, input);
+
+ // Assert expected output.
+ var lines = _capturingTextWriter.Lines.ToList();
+ Assert.Equal(new[] { "Parent: Foo", "Child: Foo" }, lines);
+ }
+}
\ No newline at end of file
diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/child.json b/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/child.json
new file mode 100644
index 000000000..2f89aac94
--- /dev/null
+++ b/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/child.json
@@ -0,0 +1,139 @@
+{
+ "id": "228c0150ab7b4745b038b950be574935",
+ "definitionId": "25b097966de245818e9a5c0d04f3323e",
+ "name": "Child",
+ "createdAt": "2023-09-06T09:57:32.944622+00:00",
+ "version": 5,
+ "variables": [],
+ "inputs": [
+ {
+ "uiHint": "single-line",
+ "storageDriverType": "Elsa.Workflows.Core.Services.WorkflowStorageDriver, Elsa.Workflows.Core",
+ "type": "Object",
+ "name": "Input1",
+ "displayName": "Input1",
+ "description": "",
+ "category": "",
+ "isArray": false
+ }
+ ],
+ "outputs": [
+ {
+ "type": "Object",
+ "name": "Output1",
+ "displayName": "Output1",
+ "description": "",
+ "isArray": false
+ }
+ ],
+ "outcomes": [],
+ "customProperties": {},
+ "isReadonly": false,
+ "isLatest": true,
+ "isPublished": true,
+ "options": {
+ "usableAsActivity": true,
+ "autoUpdateConsumingWorkflows": true
+ },
+ "root": {
+ "type": "Elsa.Flowchart",
+ "version": 1,
+ "id": "Flowchart1",
+ "metadata": {},
+ "customProperties": {
+ "source": "FlowchartJsonConverter.cs:45",
+ "NotFoundConnectionsKey": [],
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "start": "WriteLine1",
+ "activities": [
+ {
+ "outputName": {
+ "typeName": "String",
+ "expression": {
+ "type": "Literal",
+ "value": "Output1"
+ },
+ "memoryReference": {
+ "id": "ca8473b5-83ec-4f75-a2f1-dfba71f349d1"
+ }
+ },
+ "outputValue": {
+ "typeName": "Object",
+ "expression": {
+ "type": "JavaScript",
+ "value": "return getInput1();"
+ },
+ "memoryReference": {
+ "id": "06c6243d-8a57-45e3-ae54-f209b95d2bb2"
+ }
+ },
+ "id": "SetOutput1",
+ "name": null,
+ "type": "Elsa.SetOutput",
+ "version": 1,
+ "customProperties": {
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "metadata": {
+ "designer": {
+ "position": {
+ "x": 636,
+ "y": 250.01388549804688
+ },
+ "size": {
+ "width": 107.734375,
+ "height": 50
+ }
+ }
+ }
+ },
+ {
+ "text": {
+ "typeName": "String",
+ "expression": {
+ "type": "JavaScript",
+ "value": "\u0022Child: \u0022 \u002B getInput1();"
+ },
+ "memoryReference": {
+ "id": "3f726554-0fa1-4e54-b5e7-74b18807437b"
+ }
+ },
+ "id": "WriteLine1",
+ "name": null,
+ "type": "Elsa.WriteLine",
+ "version": 1,
+ "customProperties": {
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "metadata": {
+ "designer": {
+ "position": {
+ "x": 332.01385498046875,
+ "y": 222.01388549804688
+ },
+ "size": {
+ "width": 139.296875,
+ "height": 50
+ }
+ }
+ }
+ }
+ ],
+ "connections": [
+ {
+ "source": {
+ "activity": "WriteLine1",
+ "port": "Done"
+ },
+ "target": {
+ "activity": "SetOutput1",
+ "port": "In"
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/parent1.json b/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/parent1.json
new file mode 100644
index 000000000..0fe003b43
--- /dev/null
+++ b/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/parent1.json
@@ -0,0 +1,138 @@
+{
+ "id": "e49f81715e29460187671ee298bbd0b2",
+ "definitionId": "6a4f5aeecbf94fc09dbba65363c5cb57",
+ "name": "Parent1",
+ "createdAt": "2023-09-06T11:38:10.701838+00:00",
+ "version": 8,
+ "variables": [],
+ "inputs": [
+ {
+ "uiHint": "single-line",
+ "storageDriverType": "Elsa.Workflows.Core.Services.WorkflowStorageDriver, Elsa.Workflows.Core",
+ "type": "Object",
+ "name": "Input1",
+ "displayName": "Input1",
+ "description": "",
+ "category": "",
+ "isArray": false
+ }
+ ],
+ "outputs": [
+ {
+ "type": "Object",
+ "name": "Output1",
+ "displayName": "Output1",
+ "description": "",
+ "isArray": false
+ }
+ ],
+ "outcomes": [],
+ "customProperties": {},
+ "isReadonly": false,
+ "isLatest": true,
+ "isPublished": false,
+ "options": {
+ "autoUpdateConsumingWorkflows": false
+ },
+ "root": {
+ "type": "Elsa.Flowchart",
+ "version": 1,
+ "id": "Flowchart1",
+ "metadata": {},
+ "customProperties": {
+ "source": "FlowchartJsonConverter.cs:45",
+ "NotFoundConnectionsKey": [],
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "start": "WriteLine1",
+ "activities": [
+ {
+ "text": {
+ "typeName": "String",
+ "expression": {
+ "type": "JavaScript",
+ "value": "\u0022Parent: \u0022 \u002B getInput1();"
+ },
+ "memoryReference": {
+ "id": "3070bee4-8309-44e3-a805-a9ec7fdd7ac2"
+ }
+ },
+ "id": "WriteLine1",
+ "name": null,
+ "type": "Elsa.WriteLine",
+ "version": 1,
+ "customProperties": {
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "metadata": {
+ "designer": {
+ "position": {
+ "x": 160,
+ "y": 280
+ },
+ "size": {
+ "width": 139.296875,
+ "height": 50
+ }
+ }
+ }
+ },
+ {
+ "workflowDefinitionId": "25b097966de245818e9a5c0d04f3323e",
+ "workflowDefinitionVersionId": "228c0150ab7b4745b038b950be574935",
+ "latestAvailablePublishedVersion": 5,
+ "latestAvailablePublishedVersionId": "228c0150ab7b4745b038b950be574935",
+ "id": "22jW7xiYM0K30H99EDWztQ",
+ "name": "Child1",
+ "type": "Child",
+ "version": 5,
+ "customProperties": {
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "metadata": {
+ "designer": {
+ "position": {
+ "x": 405.3984375,
+ "y": 204.0078125
+ },
+ "size": {
+ "width": 94.765625,
+ "height": 116.015625
+ }
+ }
+ },
+ "input1": {
+ "typeName": "Object",
+ "expression": {
+ "type": "JavaScript",
+ "value": "\u0060Bar\u0060"
+ },
+ "memoryReference": {
+ "id": "22jW7xiYM0K30H99EDWztQ:input-0"
+ }
+ },
+ "output1": {
+ "typeName": "Object",
+ "memoryReference": {
+ "id": "Output1"
+ }
+ }
+ }
+ ],
+ "connections": [
+ {
+ "source": {
+ "activity": "WriteLine1",
+ "port": "Done"
+ },
+ "target": {
+ "activity": "22jW7xiYM0K30H99EDWztQ",
+ "port": "In"
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/parent2.json b/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/parent2.json
new file mode 100644
index 000000000..948b8eeed
--- /dev/null
+++ b/test/integration/Elsa.IntegrationTests/Scenarios/ParentChildInputs/Workflows/parent2.json
@@ -0,0 +1,138 @@
+{
+ "id": "c5aa339e124f4486b14dbfdc133a45d1",
+ "definitionId": "8ad20b5ff9ee4ff59b8dfcfb6d4e29a0",
+ "name": "Parent2",
+ "createdAt": "2023-09-06T11:38:30.217265+00:00",
+ "version": 1,
+ "variables": [],
+ "inputs": [
+ {
+ "uiHint": "single-line",
+ "storageDriverType": "Elsa.Workflows.Core.Services.WorkflowStorageDriver, Elsa.Workflows.Core",
+ "type": "Object",
+ "name": "Input1",
+ "displayName": "Input1",
+ "description": "",
+ "category": "",
+ "isArray": false
+ }
+ ],
+ "outputs": [
+ {
+ "type": "Object",
+ "name": "Output1",
+ "displayName": "Output1",
+ "description": "",
+ "isArray": false
+ }
+ ],
+ "outcomes": [],
+ "customProperties": {},
+ "isReadonly": false,
+ "isLatest": true,
+ "isPublished": true,
+ "options": {
+ "autoUpdateConsumingWorkflows": false
+ },
+ "root": {
+ "type": "Elsa.Flowchart",
+ "version": 1,
+ "id": "Flowchart1",
+ "metadata": {},
+ "customProperties": {
+ "source": "FlowchartJsonConverter.cs:45",
+ "NotFoundConnectionsKey": [],
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "start": "WriteLine1",
+ "activities": [
+ {
+ "text": {
+ "typeName": "String",
+ "expression": {
+ "type": "JavaScript",
+ "value": "\u0022Parent: \u0022 \u002B getInput1();"
+ },
+ "memoryReference": {
+ "id": "3070bee4-8309-44e3-a805-a9ec7fdd7ac2"
+ }
+ },
+ "id": "WriteLine1",
+ "name": null,
+ "type": "Elsa.WriteLine",
+ "version": 1,
+ "customProperties": {
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "metadata": {
+ "designer": {
+ "position": {
+ "x": 160,
+ "y": 280
+ },
+ "size": {
+ "width": 139.296875,
+ "height": 50
+ }
+ }
+ }
+ },
+ {
+ "workflowDefinitionId": "25b097966de245818e9a5c0d04f3323e",
+ "workflowDefinitionVersionId": "228c0150ab7b4745b038b950be574935",
+ "latestAvailablePublishedVersion": 5,
+ "latestAvailablePublishedVersionId": "228c0150ab7b4745b038b950be574935",
+ "id": "22jW7xiYM0K30H99EDWztQ",
+ "name": "Child1",
+ "type": "Child",
+ "version": 5,
+ "customProperties": {
+ "canStartWorkflow": false,
+ "runAsynchronously": false
+ },
+ "metadata": {
+ "designer": {
+ "position": {
+ "x": 405.3984375,
+ "y": 204.0078125
+ },
+ "size": {
+ "width": 135.390625,
+ "height": 120
+ }
+ }
+ },
+ "input1": {
+ "typeName": "Object",
+ "expression": {
+ "type": "JavaScript",
+ "value": "getInput1()"
+ },
+ "memoryReference": {
+ "id": "22jW7xiYM0K30H99EDWztQ:input-0"
+ }
+ },
+ "output1": {
+ "typeName": "Object",
+ "memoryReference": {
+ "id": "Output1"
+ }
+ }
+ }
+ ],
+ "connections": [
+ {
+ "source": {
+ "activity": "WriteLine1",
+ "port": "Done"
+ },
+ "target": {
+ "activity": "22jW7xiYM0K30H99EDWztQ",
+ "port": "In"
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file