diff --git a/src/bundles/Elsa.WorkflowServer.Web/Program.cs b/src/bundles/Elsa.WorkflowServer.Web/Program.cs index d7f8181d3..5d20b3b54 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/Program.cs +++ b/src/bundles/Elsa.WorkflowServer.Web/Program.cs @@ -19,7 +19,7 @@ using Proto.Persistence.SqlServer; const bool useMongoDb = false; const bool useSqlServer = false; const bool useDapper = false; -const bool useProtoActor = true; +const bool useProtoActor = false; const bool useHangfire = false; var builder = WebApplication.CreateBuilder(args); diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/FlowJoin.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/FlowJoin.cs index c1ab586dc..9023131f6 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/FlowJoin.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/FlowJoin.cs @@ -43,20 +43,24 @@ public class FlowJoin : Activity, IJoinNode switch (mode) { case FlowJoinMode.WaitAll: + { // If all left-inbound activities have executed, complete & continue. var haveAllInboundActivitiesExecuted = inboundActivities.All(x => flowScope.GetExecutionCount(x) > executionCount); if (haveAllInboundActivitiesExecuted) + { await context.CompleteActivityAsync(); - break; - case FlowJoinMode.WaitAny: - // If this activity was already executed, complete it with the AlreadyCompleted result. This will prevent the Flowchart activity from scheduling its siblings again. - var alreadyExecuted = inboundActivities.Max(x => flowScope.GetExecutionCount(x)) == executionCount; - var result = alreadyExecuted ? new AlreadyCompleted() : default; + await ClearBookmarksAsync(flowchart, context); + } - await context.CompleteActivityAsync(result); + break; + } + case FlowJoinMode.WaitAny: + { + await context.CompleteActivityAsync(); await ClearBookmarksAsync(flowchart, context); break; + } } } diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs index 48c9b7795..f404e9d06 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs @@ -6,6 +6,7 @@ using Elsa.Workflows.Core.Activities.Flowchart.Extensions; using Elsa.Workflows.Core.Activities.Flowchart.Models; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Core.Options; using Elsa.Workflows.Core.Signals; namespace Elsa.Workflows.Core.Activities.Flowchart.Activities; @@ -202,7 +203,15 @@ public class Flowchart : Container } else { - await flowchartContext.ScheduleActivityAsync(activity, OnChildCompletedAsync); + // Select an existing activity execution context for this activity, if any. + var joinContext = flowchartContext.WorkflowExecutionContext.ActiveActivityExecutionContexts.FirstOrDefault(x => x.ParentActivityExecutionContext == flowchartContext && x.Activity == activity); + var scheduleWorkOptions = new ScheduleWorkOptions + { + CompletionCallback = OnChildCompletedAsync, + ReuseActivityExecutionContextId = joinContext?.Id, + PreventDuplicateScheduling = true + }; + await flowchartContext.ScheduleActivityAsync(activity, scheduleWorkOptions); } } } diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityScheduler.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityScheduler.cs index 7ad3d9f6c..794177cf0 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IActivityScheduler.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IActivityScheduler.cs @@ -16,21 +16,25 @@ public interface IActivityScheduler /// /// Schedules a work item. /// - /// + /// The work item to schedule. void Schedule(ActivityWorkItem workItem); /// /// Takes the next work item from the scheduler. /// - /// ActivityWorkItem Take(); /// /// Returns a list of all work items in the scheduler. /// - /// IEnumerable List(); + /// + /// Returns true if there are any work items matching the specified predicate. + /// + /// The predicate to match. + bool Any(Func predicate); + /// /// Clears all work items from the scheduler. /// diff --git a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs index 600af62a0..e25afabc6 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs @@ -87,15 +87,22 @@ public static class WorkflowExecutionContextExtensions ActivityExecutionContext owner, ScheduleWorkOptions? options = default) { + var scheduler = workflowExecutionContext.Scheduler; + + if(options?.PreventDuplicateScheduling == true && scheduler.Any(x => x.ActivityId == activityNode.NodeId)) + return; + var activityInvoker = workflowExecutionContext.GetRequiredService(); var toolVersion = workflowExecutionContext.Workflow.WorkflowMetadata.ToolVersion; var activityId = toolVersion?.Major >= 3 ? activityNode.Activity.Id : activityNode.NodeId; var tag = options?.Tag; - var activityInvocationOptions = new ActivityInvocationOptions(owner, tag, options?.Variables); + var activityInvocationOptions = new ActivityInvocationOptions(owner, tag, options?.Variables, options?.ReuseActivityExecutionContextId); 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, tag); + scheduler.Schedule(workItem); + } /// diff --git a/src/modules/Elsa.Workflows.Core/Options/ActivityInvocationOptions.cs b/src/modules/Elsa.Workflows.Core/Options/ActivityInvocationOptions.cs index 1fbb3479a..e78be270f 100644 --- a/src/modules/Elsa.Workflows.Core/Options/ActivityInvocationOptions.cs +++ b/src/modules/Elsa.Workflows.Core/Options/ActivityInvocationOptions.cs @@ -8,4 +8,4 @@ namespace Elsa.Workflows.Core.Options; /// 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 +public record ActivityInvocationOptions(ActivityExecutionContext? Owner, object? Tag, IEnumerable? Variables, string? ReuseActivityExecutionContextId = default); \ 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 index b21795cda..8cdf3cf99 100644 --- a/src/modules/Elsa.Workflows.Core/Options/ScheduleWorkOptions.cs +++ b/src/modules/Elsa.Workflows.Core/Options/ScheduleWorkOptions.cs @@ -9,4 +9,9 @@ namespace Elsa.Workflows.Core.Options; /// 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, ICollection? Variables = default); \ No newline at end of file +public record ScheduleWorkOptions( + ActivityCompletionCallback? CompletionCallback = default, + object? Tag = default, + ICollection? Variables = default, + string? ReuseActivityExecutionContextId = default, + bool PreventDuplicateScheduling = false); \ 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 b6d4347bc..134347db8 100644 --- a/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs @@ -19,11 +19,21 @@ public class ActivityInvoker : IActivityInvoker /// public async Task InvokeAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, ActivityInvocationOptions? options = default) { - // Setup an activity execution context. - var activityExecutionContext = workflowExecutionContext.CreateActivityExecutionContext(activity, options); + // Setup an activity execution context, potentially reusing an existing one if requested. + var reuseActivityExecutionContextId = options?.ReuseActivityExecutionContextId; - // Add the activity context to the workflow context. - workflowExecutionContext.AddActivityExecutionContext(activityExecutionContext); + var activityExecutionContext = reuseActivityExecutionContextId != null + ? workflowExecutionContext.ActiveActivityExecutionContexts.FirstOrDefault(x => x.Id == reuseActivityExecutionContextId) + : default; + + if (activityExecutionContext == null) + { + // Create a new activity execution context. + activityExecutionContext = workflowExecutionContext.CreateActivityExecutionContext(activity, options); + + // Add the activity context to the workflow context. + workflowExecutionContext.AddActivityExecutionContext(activityExecutionContext); + } // Execute the activity execution pipeline. await InvokeAsync(activityExecutionContext); diff --git a/src/modules/Elsa.Workflows.Core/Services/QueueBasedActivityScheduler.cs b/src/modules/Elsa.Workflows.Core/Services/QueueBasedActivityScheduler.cs index d68611e17..26e8c28e1 100644 --- a/src/modules/Elsa.Workflows.Core/Services/QueueBasedActivityScheduler.cs +++ b/src/modules/Elsa.Workflows.Core/Services/QueueBasedActivityScheduler.cs @@ -24,6 +24,9 @@ public class QueueBasedActivityScheduler : IActivityScheduler /// public IEnumerable List() => _queue.ToList(); + /// + public bool Any(Func predicate) => _queue.Any(predicate); + /// public void Clear() => _queue.Clear(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/StackBasedActivityScheduler.cs b/src/modules/Elsa.Workflows.Core/Services/StackBasedActivityScheduler.cs index ec8d92adf..dda6d7fb1 100644 --- a/src/modules/Elsa.Workflows.Core/Services/StackBasedActivityScheduler.cs +++ b/src/modules/Elsa.Workflows.Core/Services/StackBasedActivityScheduler.cs @@ -24,6 +24,9 @@ public class StackBasedActivityScheduler : IActivityScheduler /// public IEnumerable List() => _stack.ToList(); + /// + public bool Any(Func predicate) => _stack.Any(predicate); + /// public void Clear() => _stack.Clear(); } \ No newline at end of file diff --git a/test/integration/Elsa.IntegrationTests/Elsa.IntegrationTests.csproj b/test/integration/Elsa.IntegrationTests/Elsa.IntegrationTests.csproj index 600d87b1d..63b2fe16c 100644 --- a/test/integration/Elsa.IntegrationTests/Elsa.IntegrationTests.csproj +++ b/test/integration/Elsa.IntegrationTests/Elsa.IntegrationTests.csproj @@ -96,12 +96,18 @@ Always - + Always Always + + Always + + + Always + diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/ExplicitJoinWaitAnyTests.cs b/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/ExplicitJoinWaitAnyTests.cs index 06e944b05..17f2754f2 100644 --- a/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/ExplicitJoinWaitAnyTests.cs +++ b/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/ExplicitJoinWaitAnyTests.cs @@ -18,14 +18,17 @@ public class ExplicitJoinWaitAnyTests _services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build(); } - [Fact(DisplayName = "Workflows with explicit joins using WaitAny wait for any of the specified activities to complete and complete the workflow.")] - public async Task Test1() + [Theory(DisplayName = "Workflows with explicit joins complete the workflow.")] + [InlineData("join-any-1.json", "Start; End")] + [InlineData("join-all-1.json", "Start; Line 1; Line 2; End")] + [InlineData("join-all-2.json", "Start; Line 1; Line 2; End")] + public async Task Test1(string workflowFileName, string expectedLines) { // Populate registries. await _services.PopulateRegistriesAsync(); // Import workflow. - var fileName = "Scenarios/ExplicitJoins/Workflows/flow-join-any.json"; + var fileName = $"Scenarios/ExplicitJoins/Workflows/{workflowFileName}"; var workflowDefinition = await _services.ImportWorkflowDefinitionAsync(fileName); // Execute. @@ -33,7 +36,8 @@ public class ExplicitJoinWaitAnyTests // Assert expected output. var lines = _capturingTextWriter.Lines.ToList(); - Assert.Equal(new[] { "Start", "End" }, lines); + var expectedLinesArray = expectedLines.Split(";", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(); + Assert.Equal(expectedLinesArray, lines); // Assert expected workflow status. Assert.Equal(WorkflowStatus.Finished, workflowState.Status); diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/join-all-1.json b/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/join-all-1.json new file mode 100644 index 000000000..fd1fb2847 --- /dev/null +++ b/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/join-all-1.json @@ -0,0 +1 @@ +{"id":"9948e541b0e540c384c6eb9fdcf1d544","definitionId":"296428cc22bd4f309e858f4b461dbb3d","name":"Join 1","createdAt":"2023-09-12T18:12:54.370149+00:00","version":2,"toolVersion":"3.0.0.0","variables":[],"inputs":[],"outputs":[],"outcomes":[],"customProperties":{},"isReadonly":false,"isLatest":true,"isPublished":true,"options":{"autoUpdateConsumingWorkflows":false},"root":{"type":"Elsa.Flowchart","version":1,"id":"sL581qWiIEOpRncv5Ez33A","metadata":{},"customProperties":{"source":"FlowchartJsonConverter.cs:46","notFoundConnections":[],"canStartWorkflow":false,"runAsynchronously":false},"activities":[{"mode":{"typeName":"Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core","expression":{"type":"Literal","value":"WaitAll"},"memoryReference":{"id":"aA4pfJOzyEqmLOY3U3vCoQ:input-0"}},"id":"aA4pfJOzyEqmLOY3U3vCoQ","name":"FlowJoin1","type":"Elsa.FlowJoin","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":198,"y":-266},"size":{"width":136.859375,"height":50}}}},{"text":{"typeName":"String","expression":{"type":"Literal","value":"Start"},"memoryReference":{"id":"FRXh0PlHekejZiWANAplUg:input-0"}},"id":"FRXh0PlHekejZiWANAplUg","name":"WriteLine1","type":"Elsa.WriteLine","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":-287,"y":-266},"size":{"width":102.21875,"height":50}},"displayText":"Start"}},{"text":{"typeName":"String","expression":{"type":"Literal","value":"Line 1"},"memoryReference":{"id":"GaVrCWqBDkS6YrvydxKvFA:input-0"}},"id":"GaVrCWqBDkS6YrvydxKvFA","name":"WriteLine2","type":"Elsa.WriteLine","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":-40,"y":-316},"size":{"width":110.65625,"height":50}},"displayText":"Line 1"}},{"text":{"typeName":"String","expression":{"type":"Literal","value":"Line 2"},"memoryReference":{"id":"9Z7bt98j00uRMt9TS2FD0g:input-0"}},"id":"9Z7bt98j00uRMt9TS2FD0g","name":"WriteLine3","type":"Elsa.WriteLine","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":-40,"y":-207},"size":{"width":110.65625,"height":50}},"displayText":"Line 2"}},{"text":{"typeName":"String","expression":{"type":"Literal","value":"End"},"memoryReference":{"id":"MplDrS_bUE-8td-XBUvWmA:input-0"}},"id":"MplDrS_bUE-8td-XBUvWmA","name":"WriteLine4","type":"Elsa.WriteLine","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":430,"y":-266},"size":{"width":139.296875,"height":50}}}}],"connections":[{"source":{"activity":"FRXh0PlHekejZiWANAplUg","port":"Done"},"target":{"activity":"GaVrCWqBDkS6YrvydxKvFA","port":"In"}},{"source":{"activity":"FRXh0PlHekejZiWANAplUg","port":"Done"},"target":{"activity":"9Z7bt98j00uRMt9TS2FD0g","port":"In"}},{"source":{"activity":"9Z7bt98j00uRMt9TS2FD0g","port":"Done"},"target":{"activity":"aA4pfJOzyEqmLOY3U3vCoQ","port":"In"}},{"source":{"activity":"GaVrCWqBDkS6YrvydxKvFA","port":"Done"},"target":{"activity":"aA4pfJOzyEqmLOY3U3vCoQ","port":"In"}},{"source":{"activity":"aA4pfJOzyEqmLOY3U3vCoQ","port":"Done"},"target":{"activity":"MplDrS_bUE-8td-XBUvWmA","port":"In"}}]}} \ No newline at end of file diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/join-all-2.json b/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/join-all-2.json new file mode 100644 index 000000000..de848a14d --- /dev/null +++ b/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/join-all-2.json @@ -0,0 +1 @@ +{"id":"14984c048db34cceaf5e15ece80f9703","definitionId":"64f8f2c852f04dc28f60707034d7a906","name":"Join 2","createdAt":"2023-09-12T18:12:43.392272+00:00","version":1,"toolVersion":"3.0.0.0","variables":[],"inputs":[],"outputs":[],"outcomes":[],"customProperties":{},"isReadonly":false,"isLatest":true,"isPublished":true,"options":{"autoUpdateConsumingWorkflows":false},"root":{"type":"Elsa.Flowchart","version":1,"id":"B2jpPDriPEe-zSOhVuAGeQ","metadata":{},"customProperties":{"source":"FlowchartJsonConverter.cs:46","notFoundConnections":[],"canStartWorkflow":false,"runAsynchronously":false},"activities":[{"text":{"typeName":"String","expression":{"type":"Literal","value":"Start"},"memoryReference":{"id":"2yPLgUudQEGPaschdLdkGg:input-0"}},"id":"2yPLgUudQEGPaschdLdkGg","name":"WriteLine1","type":"Elsa.WriteLine","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":-358.5,"y":-229},"size":{"width":139.296875,"height":50}},"displayText":"Start"}},{"body":{"type":"Elsa.Flowchart","version":1,"id":"zaAIlUtQDEaPqeITeRJ4_w","metadata":{},"customProperties":{"source":"FlowchartJsonConverter.cs:46","notFoundConnections":[],"canStartWorkflow":false,"runAsynchronously":false},"activities":[{"text":{"typeName":"String","expression":{"type":"Literal","value":"Line 2"},"memoryReference":{"id":"g3jVTdvU3EK7V5JqRqfqjQ:input-0"}},"id":"g3jVTdvU3EK7V5JqRqfqjQ","name":"WriteLine1","type":"Elsa.WriteLine","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":-279.5,"y":-294},"size":{"width":139.296875,"height":50}},"displayText":"Line 2"}}],"connections":[]},"id":"eHAEEQ-Iu0GwM3mRxZin0Q","name":"FlowNode1","type":"Elsa.FlowNode","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":-120,"y":-179},"size":{"width":211.15625,"height":120}},"displayText":"Line 2"}},{"text":{"typeName":"String","expression":{"type":"Literal","value":"Line 1"},"memoryReference":{"id":"7RQ2sbVHo0SxsyK0dvTKEA:input-0"}},"id":"7RQ2sbVHo0SxsyK0dvTKEA","name":"WriteLine2","type":"Elsa.WriteLine","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":-120,"y":-279},"size":{"width":139.296875,"height":50}},"displayText":"Line 1"}},{"mode":{"typeName":"Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core","expression":{"type":"Literal","value":"WaitAll"},"memoryReference":{"id":"dVUuwMm5XUiYpiYoGo5JKA:input-0"}},"id":"dVUuwMm5XUiYpiYoGo5JKA","name":"FlowJoin1","type":"Elsa.FlowJoin","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":180.5,"y":-229},"size":{"width":136.859375,"height":50}}}},{"text":{"typeName":"String","expression":{"type":"Literal","value":"End"},"memoryReference":{"id":"NpZ_yT_irUuE9ASEfYLxbw:input-0"}},"id":"NpZ_yT_irUuE9ASEfYLxbw","name":"WriteLine3","type":"Elsa.WriteLine","version":1,"customProperties":{"canStartWorkflow":false,"runAsynchronously":false},"metadata":{"designer":{"position":{"x":400,"y":-229},"size":{"width":139.296875,"height":50}},"displayText":"End"}}],"connections":[{"source":{"activity":"2yPLgUudQEGPaschdLdkGg","port":"Done"},"target":{"activity":"7RQ2sbVHo0SxsyK0dvTKEA","port":"In"}},{"source":{"activity":"2yPLgUudQEGPaschdLdkGg","port":"Done"},"target":{"activity":"eHAEEQ-Iu0GwM3mRxZin0Q","port":"In"}},{"source":{"activity":"7RQ2sbVHo0SxsyK0dvTKEA","port":"Done"},"target":{"activity":"dVUuwMm5XUiYpiYoGo5JKA","port":"In"}},{"source":{"activity":"eHAEEQ-Iu0GwM3mRxZin0Q","port":"Done"},"target":{"activity":"dVUuwMm5XUiYpiYoGo5JKA","port":"In"}},{"source":{"activity":"dVUuwMm5XUiYpiYoGo5JKA","port":"Done"},"target":{"activity":"NpZ_yT_irUuE9ASEfYLxbw","port":"In"}}]}} \ No newline at end of file diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/flow-join-any.json b/test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/join-any-1.json similarity index 100% rename from test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/flow-join-any.json rename to test/integration/Elsa.IntegrationTests/Scenarios/ExplicitJoins/Workflows/join-any-1.json