From fcfe7f08b4e4b4d63b2c7ff4b5cb995da9786617 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 12 Jan 2021 09:28:48 +0100 Subject: [PATCH] Change For/While/ForEach/IfElse behavior Before this change, the aforementioned activities would always schedule the Done outcome. After this change, the Done outcome is scheduled **after** the Iterate branch and True or False branches have completed --- .../WriteLine/WriteLineBuilderExtensions.cs | 14 +++--- .../ActivityResults/OutcomeResult.cs | 44 ++++++++++++----- .../Builders/IActivityBuilder.cs | 2 + .../Services/Models/IBranchingActivity.cs | 7 +++ .../Models/WorkflowExecutionContext.cs | 10 ++-- .../Activities/ControlFlow/For/For.cs | 3 +- .../Activities/ControlFlow/ForEach/ForEach.cs | 4 +- .../Activities/ControlFlow/IfElse/IfElse.cs | 48 +++++++++++++++++-- .../ControlFlow/IteratingActivity.cs | 29 +++++++++++ .../ControlFlow/{IfThen => Switch}/Switch.cs | 14 ++++++ .../SwitchBuilderExtensions.cs | 0 .../{IfThen => Switch}/SwitchCase.cs | 0 .../{IfThen => Switch}/SwitchCaseBuilder.cs | 0 .../{IfThen => Switch}/SwitchExtensions.cs | 0 .../{IfThen => Switch}/SwitchMode.cs | 0 .../Activities/ControlFlow/While/While.cs | 2 +- .../Elsa.Core/ActivityResults/GoBackResult.cs | 2 +- .../Elsa.Core/Builders/ActivityBuilder.cs | 3 ++ .../Elsa.Core/Elsa.Core.csproj.DotSettings | 1 + .../ElsaServiceCollectionExtensions.cs | 6 +++ ...heduleBranchingActivitiesAndContainers.cs} | 47 ++++++++---------- .../Elsa.Core/Services/ActivityActivator.cs | 1 + src/core/Elsa.Core/Services/WorkflowRunner.cs | 1 - .../Elsa.Core/Triggers/WorkflowSelector.cs | 2 +- .../HappinessWorkflow.cs | 13 ++--- .../GrayscaleWorkflow.cs | 2 +- .../Workflows/ForkJoinWorkflow.cs | 8 ++-- .../Workflows/WhileWorkflow.cs | 2 +- 28 files changed, 190 insertions(+), 75 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Services/Models/IBranchingActivity.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/IteratingActivity.cs rename src/core/Elsa.Core/Activities/ControlFlow/{IfThen => Switch}/Switch.cs (80%) rename src/core/Elsa.Core/Activities/ControlFlow/{IfThen => Switch}/SwitchBuilderExtensions.cs (100%) rename src/core/Elsa.Core/Activities/ControlFlow/{IfThen => Switch}/SwitchCase.cs (100%) rename src/core/Elsa.Core/Activities/ControlFlow/{IfThen => Switch}/SwitchCaseBuilder.cs (100%) rename src/core/Elsa.Core/Activities/ControlFlow/{IfThen => Switch}/SwitchExtensions.cs (100%) rename src/core/Elsa.Core/Activities/ControlFlow/{IfThen => Switch}/SwitchMode.cs (100%) rename src/core/Elsa.Core/Handlers/{RescheduleLoopsAndContainers.cs => RescheduleBranchingActivitiesAndContainers.cs} (61%) diff --git a/src/activities/Elsa.Activities.Console/Activities/WriteLine/WriteLineBuilderExtensions.cs b/src/activities/Elsa.Activities.Console/Activities/WriteLine/WriteLineBuilderExtensions.cs index b13f9d9a7..315c30fd3 100644 --- a/src/activities/Elsa.Activities.Console/Activities/WriteLine/WriteLineBuilderExtensions.cs +++ b/src/activities/Elsa.Activities.Console/Activities/WriteLine/WriteLineBuilderExtensions.cs @@ -8,12 +8,12 @@ namespace Elsa.Activities.Console { public static class WriteLineBuilderExtensions { - public static IOutcomeBuilder WriteLine(this IBuilder builder, Action> setup, string? name = default, string? id = default) => WriteLine(builder.Then(setup).WithName(name).WithId(id)); - public static IOutcomeBuilder WriteLine(this IBuilder builder, Func text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text), name, id); - public static IOutcomeBuilder WriteLine(this IBuilder builder, Func> text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text!), name, id); - public static IOutcomeBuilder WriteLine(this IBuilder builder, Func text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text), name, id); - public static IOutcomeBuilder WriteLine(this IBuilder builder, Func> text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text!), name, id); - public static IOutcomeBuilder WriteLine(this IBuilder builder, string text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text), name, id); - private static IOutcomeBuilder WriteLine(IActivityBuilder writeLine) => writeLine.When(OutcomeNames.Done); + public static IActivityBuilder WriteLine(this IBuilder builder, Action> setup) => builder.Then(setup); + public static IActivityBuilder WriteLine(this IBuilder builder, Func text) => builder.WriteLine(activity => activity.WithText(text)); + public static IActivityBuilder WriteLine(this IBuilder builder, Func> text) => builder.WriteLine(activity => activity.WithText(text!)); + public static IActivityBuilder WriteLine(this IBuilder builder, Func text) => builder.WriteLine(activity => activity.WithText(text!)); + public static IActivityBuilder WriteLine(this IBuilder builder, Func> text) => builder.WriteLine(activity => activity.WithText(text!)); + public static IActivityBuilder WriteLine(this IBuilder builder, string text) => builder.WriteLine(activity => activity.WithText(text!)); + } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/ActivityResults/OutcomeResult.cs b/src/core/Elsa.Abstractions/ActivityResults/OutcomeResult.cs index 02df77ae4..83805cb9e 100644 --- a/src/core/Elsa.Abstractions/ActivityResults/OutcomeResult.cs +++ b/src/core/Elsa.Abstractions/ActivityResults/OutcomeResult.cs @@ -25,16 +25,40 @@ namespace Elsa.ActivityResults { var outcomes = activityExecutionContext.Outcomes = Outcomes.ToList(); var workflowExecutionContext = activityExecutionContext.WorkflowExecutionContext; + var nextConnections = GetNextConnections(workflowExecutionContext, activityExecutionContext.ActivityBlueprint.Id, outcomes).ToList(); - var nextActivities = GetNextActivities( - workflowExecutionContext, - activityExecutionContext.ActivityBlueprint.Id, - outcomes).ToList(); + var nextActivities = + ( + from connection in nextConnections + from activityBlueprint in workflowExecutionContext.WorkflowBlueprint.Activities + where activityBlueprint.Id == connection.Target.Activity.Id + select activityBlueprint.Id + ) + .Distinct(); - workflowExecutionContext.ScheduleActivities(nextActivities, activityExecutionContext.Output); + foreach (var nextConnection in nextConnections) + workflowExecutionContext.ExecutionLog.Add(nextConnection); + + workflowExecutionContext.ScheduleActivities(nextActivities, activityExecutionContext.Output); } - private IEnumerable GetNextActivities( + public static IEnumerable GetNextActivities( + WorkflowExecutionContext workflowContext, + string sourceId, + IEnumerable outcomes) + { + var nextConnections = GetNextConnections(workflowContext, sourceId, outcomes); + + var query = + from connection in nextConnections + from activityBlueprint in workflowContext.WorkflowBlueprint.Activities + where activityBlueprint.Id == connection.Target.Activity.Id + select activityBlueprint.Id; + + return query.Distinct(); + } + + public static IEnumerable GetNextConnections( WorkflowExecutionContext workflowContext, string sourceId, IEnumerable outcomes) @@ -47,12 +71,10 @@ namespace Elsa.ActivityResults let connectionOutcome = connection.Source.Outcome ?? OutcomeNames.Done let isConnectionOutcome = connectionOutcome.Equals(outcome.outcome, StringComparison.OrdinalIgnoreCase) where connection.Source.Activity.Id == sourceId && isConnectionOutcome - from activityBlueprint in workflowContext.WorkflowBlueprint.Activities - where activityBlueprint.Id == connection.Target.Activity.Id - orderby outcome.order - select activityBlueprint.Id; + orderby outcome.order + select connection; - return query.Distinct(); + return query; } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs b/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs index dc70373da..b6f9e7739 100644 --- a/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Elsa.Services; +using Elsa.Services.Models; namespace Elsa.Builders { @@ -28,5 +29,6 @@ namespace Elsa.Builders IActivityBuilder LoadWorkflowContext(bool value = true); IActivityBuilder SaveWorkflowContext(bool value = true); IActivityBuilder PersistWorkflow(bool value = true); + IWorkflowBlueprint Build(); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/IBranchingActivity.cs b/src/core/Elsa.Abstractions/Services/Models/IBranchingActivity.cs new file mode 100644 index 000000000..dd251f753 --- /dev/null +++ b/src/core/Elsa.Abstractions/Services/Models/IBranchingActivity.cs @@ -0,0 +1,7 @@ +namespace Elsa.Services.Models +{ + public interface IBranchingActivity + { + void Unwind(ActivityExecutionContext context); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs index fe22ab5d3..d5e25801b 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs +++ b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs @@ -67,7 +67,7 @@ namespace Elsa.Services.Models } public bool DeleteCompletedInstances => WorkflowBlueprint.DeleteCompletedInstances; - public ICollection ExecutionLog => new List(); + public IList ExecutionLog { get; } = new List(); public WorkflowStatus Status => WorkflowInstance.WorkflowStatus; public bool HasBlockingActivities => WorkflowInstance.BlockingActivities.Any(); public object? WorkflowContext { get; set; } @@ -135,12 +135,6 @@ namespace Elsa.Services.Models public void SetWorkflowContext(object? value) => WorkflowContext = value; public T GetWorkflowContext() => (T) WorkflowContext!; - public async ValueTask> ActivateActivitiesAsync(CancellationToken cancellationToken = default) - { - var activityExecutionContexts = WorkflowBlueprint.Activities.Select(x => new ActivityExecutionContext(ServiceScope, this, x, null, CancellationToken.None)); - return await Task.WhenAll(activityExecutionContexts.Select(async x => await x.ActivateActivityAsync(cancellationToken))); - } - /// /// Remove empty activity data to save on document size. /// @@ -156,4 +150,6 @@ namespace Elsa.Services.Models return activityBlueprint != null && activityBlueprint.PersistOutput; } } + + public record ExecutionLogEntry(string ActivityId, string Outcome); } \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/For/For.cs b/src/core/Elsa.Core/Activities/ControlFlow/For/For.cs index c6169bf82..0ae6085c9 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/For/For.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/For/For.cs @@ -1,7 +1,6 @@ using System; using Elsa.ActivityResults; using Elsa.Attributes; -using Elsa.Services; using Elsa.Services.Models; // ReSharper disable once CheckNamespace @@ -12,7 +11,7 @@ namespace Elsa.Activities.ControlFlow Description = "Iterate between two numbers.", Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done } )] - public class For : Activity + public class For : IteratingActivity { [ActivityProperty(Hint = "An expression that evaluates to the starting number.")] public long Start { get; set; } diff --git a/src/core/Elsa.Core/Activities/ControlFlow/ForEach/ForEach.cs b/src/core/Elsa.Core/Activities/ControlFlow/ForEach/ForEach.cs index a9cb61fc6..0be7fe23e 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/ForEach/ForEach.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/ForEach/ForEach.cs @@ -14,7 +14,7 @@ namespace Elsa.Activities.ControlFlow Description = "Iterate over a collection.", Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done } )] - public class ForEach : Activity + public class ForEach : IteratingActivity { [ActivityProperty(Hint = "Enter an expression that evaluates to a collection of items to iterate over.")] public ICollection Items { get; set; } = new Collection(); @@ -30,7 +30,7 @@ namespace Elsa.Activities.ControlFlow get => GetState(); set => SetState(value); } - + protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { var collection = ItemsCopy; diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IfElse/IfElse.cs b/src/core/Elsa.Core/Activities/ControlFlow/IfElse/IfElse.cs index 7d4644a35..7b387c83c 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/IfElse/IfElse.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/IfElse/IfElse.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using Elsa.ActivityResults; using Elsa.Attributes; using Elsa.Services; @@ -12,18 +14,58 @@ namespace Elsa.Activities.ControlFlow Description = "Evaluate a Boolean expression and continue execution depending on the result.", Outcomes = new[] { True, False, OutcomeNames.Done } )] - public class IfElse : Activity + public class IfElse : Activity, IBranchingActivity { public const string True = "True"; public const string False = "False"; - + [ActivityProperty(Hint = "The condition to evaluate.")] public bool Condition { get; set; } protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { var outcome = Condition ? True : False; - return Outcomes(OutcomeNames.Done, outcome); + return Outcome(outcome); } + + public void Unwind(ActivityExecutionContext context) + { + var workflowExecutionContext = context.WorkflowExecutionContext; + var workflowBlueprint = workflowExecutionContext.WorkflowBlueprint; + var activityExecutionContext = context; + var currentActivityBlueprint = activityExecutionContext.ActivityBlueprint; + var currentActivityId = currentActivityBlueprint.Id; + + // An IfElse activity completed within a burst of execution, which means its outcome (true or false) did not yield child activities to execute (an empty branch). + // Schedule its child activities connected to the "Done" outcome. + if (currentActivityBlueprint.Type == nameof(IfElse)) + { + var nextActivities = GetNextActivities(workflowExecutionContext, currentActivityId); + workflowExecutionContext.ScheduleActivities(nextActivities); + } + else + { + // Get all incoming connections. + var inboundConnections = workflowBlueprint.GetInboundConnectionPath(currentActivityId).ToList(); + + // Filter out those connections who have a source of IfElse. + var query = + from inboundConnection in inboundConnections + let parentActivityBlueprint = inboundConnection.Source.Activity + where inboundConnection.Source.Activity.Type == nameof(IfElse) + select inboundConnection; + + var firstMatch = query.FirstOrDefault(); + + if (firstMatch != null && firstMatch.Source.Outcome != OutcomeNames.Done) + { + var parentActivityBlueprint = firstMatch.Source.Activity; + var nextActivities = OutcomeResult.GetNextActivities(workflowExecutionContext, parentActivityBlueprint.Id, new[] { OutcomeNames.Done }).ToList(); + workflowExecutionContext.ScheduleActivities(nextActivities); + } + } + } + + private IEnumerable GetNextActivities(WorkflowExecutionContext workflowExecutionContext, string currentActivityId) => OutcomeResult.GetNextActivities(workflowExecutionContext, currentActivityId, new[] { OutcomeNames.Done }); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IteratingActivity.cs b/src/core/Elsa.Core/Activities/ControlFlow/IteratingActivity.cs new file mode 100644 index 000000000..a8a4d20e5 --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/IteratingActivity.cs @@ -0,0 +1,29 @@ +using System.Linq; +using Elsa.Services; +using Elsa.Services.Models; + +// ReSharper disable once CheckNamespace +namespace Elsa.Activities.ControlFlow +{ + public abstract class IteratingActivity : Activity, IBranchingActivity + { + public void Unwind(ActivityExecutionContext context) + { + var workflowExecutionContext = context.WorkflowExecutionContext; + var workflowBlueprint = workflowExecutionContext.WorkflowBlueprint; + var currentActivityId = context.ActivityBlueprint.Id; + var inboundConnections = workflowBlueprint.GetInboundConnectionPath(currentActivityId).ToList(); + + var query = + from inboundConnection in inboundConnections + let parentActivityBlueprint = inboundConnection.Source.Activity + where inboundConnection.Source.Activity.Type == Type + select inboundConnection; + + var firstMatch = query.FirstOrDefault(); + + if(firstMatch != null && firstMatch.Source.Outcome == OutcomeNames.Iterate) + workflowExecutionContext.ScheduleActivity(firstMatch.Source.Activity.Id); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IfThen/Switch.cs b/src/core/Elsa.Core/Activities/ControlFlow/Switch/Switch.cs similarity index 80% rename from src/core/Elsa.Core/Activities/ControlFlow/IfThen/Switch.cs rename to src/core/Elsa.Core/Activities/ControlFlow/Switch/Switch.cs index c7aad379b..dfd018389 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/IfThen/Switch.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/Switch/Switch.cs @@ -21,12 +21,26 @@ namespace Elsa.Activities.ControlFlow [ActivityProperty(Hint = "The conditions to evaluate.")] public SwitchMode Mode { get; set; } = SwitchMode.MatchFirst; + + private bool Evaluated + { + get => GetState(); + set => SetState(value); + } protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { + if (Evaluated) + { + Evaluated = false; + return Done(); + } + var matches = Cases.Where(x => x.Condition).Select(x => x.Name).ToList(); var results = Mode == SwitchMode.MatchFirst ? matches.Any() ? new[] { matches.First() } : new string[0] : matches.ToArray(); var outcomes = new[] { OutcomeNames.Done }.Concat(results); + + Evaluated = true; return Outcomes(outcomes); } } diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchBuilderExtensions.cs b/src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchBuilderExtensions.cs similarity index 100% rename from src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchBuilderExtensions.cs rename to src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchBuilderExtensions.cs diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchCase.cs b/src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchCase.cs similarity index 100% rename from src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchCase.cs rename to src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchCase.cs diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchCaseBuilder.cs b/src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchCaseBuilder.cs similarity index 100% rename from src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchCaseBuilder.cs rename to src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchCaseBuilder.cs diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchExtensions.cs b/src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchExtensions.cs similarity index 100% rename from src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchExtensions.cs rename to src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchExtensions.cs diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchMode.cs b/src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchMode.cs similarity index 100% rename from src/core/Elsa.Core/Activities/ControlFlow/IfThen/SwitchMode.cs rename to src/core/Elsa.Core/Activities/ControlFlow/Switch/SwitchMode.cs diff --git a/src/core/Elsa.Core/Activities/ControlFlow/While/While.cs b/src/core/Elsa.Core/Activities/ControlFlow/While/While.cs index ae4728a43..820930f22 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/While/While.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/While/While.cs @@ -11,7 +11,7 @@ namespace Elsa.Activities.ControlFlow Description = "Execute while a given condition is true.", Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done } )] - public class While : Activity + public class While : IteratingActivity { [ActivityProperty(Hint = "The condition to evaluate.")] public bool Condition { get; set; } diff --git a/src/core/Elsa.Core/ActivityResults/GoBackResult.cs b/src/core/Elsa.Core/ActivityResults/GoBackResult.cs index cd89bcb29..f2b7f8477 100644 --- a/src/core/Elsa.Core/ActivityResults/GoBackResult.cs +++ b/src/core/Elsa.Core/ActivityResults/GoBackResult.cs @@ -25,7 +25,7 @@ namespace Elsa.ActivityResults if (previousEntry == null) return; - var activityId = previousEntry; + var activityId = previousEntry.Source.Activity.Id; workflowExecutionContext.ScheduleActivity(activityId, _input); } } diff --git a/src/core/Elsa.Core/Builders/ActivityBuilder.cs b/src/core/Elsa.Core/Builders/ActivityBuilder.cs index 510d57a5c..4b9b33154 100644 --- a/src/core/Elsa.Core/Builders/ActivityBuilder.cs +++ b/src/core/Elsa.Core/Builders/ActivityBuilder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Elsa.Services; +using Elsa.Services.Models; namespace Elsa.Builders { @@ -108,5 +109,7 @@ namespace Elsa.Builders PersistWorkflowEnabled = value; return this; } + + public IWorkflowBlueprint Build() => ((IWorkflowBuilder)WorkflowBuilder).BuildBlueprint(); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings b/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings index 3d434196f..c50530f5f 100644 --- a/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings +++ b/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings @@ -3,6 +3,7 @@ True True True + True True True True diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 86fd4e128..35ad2fb18 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -21,6 +21,7 @@ using Elsa.Runtime; using Elsa.Serialization; using Elsa.Serialization.Converters; using Elsa.Services; +using Elsa.Services.Models; using Elsa.StartupTasks; using Elsa.Triggers; using Elsa.WorkflowProviders; @@ -54,6 +55,11 @@ namespace Microsoft.Extensions.DependencyInjection .AddSingleton(options.StorageFactory) .AddStartupTask(); + services + .AddTransient() + .AddTransient() + .AddTransient(); + options .AddWorkflowsCore() .AddCoreActivities(); diff --git a/src/core/Elsa.Core/Handlers/RescheduleLoopsAndContainers.cs b/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs similarity index 61% rename from src/core/Elsa.Core/Handlers/RescheduleLoopsAndContainers.cs rename to src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs index c7c9dcc22..a3773b093 100644 --- a/src/core/Elsa.Core/Handlers/RescheduleLoopsAndContainers.cs +++ b/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Elsa.Events; @@ -12,39 +12,32 @@ namespace Elsa.Handlers /// Walks up the tee of inbound connections along the "Iterate" outcome of the looping construct (While/For/ForEach) and re-schedules the looping activity. /// Also handles composite activity re-scheduling. /// - public class RescheduleLoopsAndContainers : INotificationHandler + public class RescheduleBranchingActivitiesAndContainers : INotificationHandler { + private readonly IEnumerable _branchingActivities; + + public RescheduleBranchingActivitiesAndContainers(IEnumerable branchingActivities) + { + _branchingActivities = branchingActivities; + } + public Task Handle(WorkflowExecutionBurstCompleted notification, CancellationToken cancellationToken) { - ScheduleLoops(notification); + var workflowExecutionContext = notification.WorkflowExecutionContext; + var activityExecutionContext = notification.ActivityExecutionContext; + + foreach (var branchingActivity in _branchingActivities) + { + if (workflowExecutionContext.HasScheduledActivities || workflowExecutionContext.Status != WorkflowStatus.Running) + break; + + branchingActivity.Unwind(activityExecutionContext); + } + ScheduleContainers(notification); return Task.CompletedTask; } - private static void ScheduleLoops(WorkflowExecutionBurstCompleted notification) - { - var workflowExecutionContext = notification.WorkflowExecutionContext; - - // If no suspension has been instructed, re-schedule any post-scheduled activities. - if (workflowExecutionContext.HasScheduledActivities || workflowExecutionContext.Status != WorkflowStatus.Running) - return; - - var workflowBlueprint = workflowExecutionContext.WorkflowBlueprint; - var currentActivityId = notification.ActivityExecutionContext.ActivityBlueprint.Id; - var inboundConnections = workflowBlueprint.GetInboundConnectionPath(currentActivityId).ToList(); - - var query = - from inboundConnection in inboundConnections - let parentActivityBlueprint = inboundConnection.Source.Activity - where inboundConnection.Source.Outcome == OutcomeNames.Iterate - select parentActivityBlueprint; - - var firstLoop = query.FirstOrDefault(); - - if(firstLoop != null) - workflowExecutionContext.ScheduleActivity(firstLoop.Id); - } - private static void ScheduleContainers(WorkflowExecutionBurstCompleted notification) { var workflowExecutionContext = notification.WorkflowExecutionContext; diff --git a/src/core/Elsa.Core/Services/ActivityActivator.cs b/src/core/Elsa.Core/Services/ActivityActivator.cs index 9c48d473a..28292ea1d 100644 --- a/src/core/Elsa.Core/Services/ActivityActivator.cs +++ b/src/core/Elsa.Core/Services/ActivityActivator.cs @@ -18,6 +18,7 @@ namespace Elsa.Services var activity = _elsaOptions.ActivityFactory.CreateService(type, context.ServiceScope.ServiceProvider); activity.Data = context.GetData(); activity.Id = context.ActivityId; + await context.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.SetActivityPropertiesAsync(activity, context, context.CancellationToken); return activity; } diff --git a/src/core/Elsa.Core/Services/WorkflowRunner.cs b/src/core/Elsa.Core/Services/WorkflowRunner.cs index 81c649f37..30879a80f 100644 --- a/src/core/Elsa.Core/Services/WorkflowRunner.cs +++ b/src/core/Elsa.Core/Services/WorkflowRunner.cs @@ -290,7 +290,6 @@ namespace Elsa.Services await _mediator.Publish(new ActivityExecuted(activityExecutionContext), cancellationToken); await result.ExecuteAsync(activityExecutionContext, cancellationToken); workflowExecutionContext.WorkflowInstance.Output = activityExecutionContext.Output; - workflowExecutionContext.ExecutionLog.Add(activity.Id); workflowExecutionContext.PruneActivityData(); activityOperation = Execute; workflowExecutionContext.CompletePass(); diff --git a/src/core/Elsa.Core/Triggers/WorkflowSelector.cs b/src/core/Elsa.Core/Triggers/WorkflowSelector.cs index eb2f6f4db..11f65b23e 100644 --- a/src/core/Elsa.Core/Triggers/WorkflowSelector.cs +++ b/src/core/Elsa.Core/Triggers/WorkflowSelector.cs @@ -146,7 +146,7 @@ namespace Elsa.Triggers if (workflowInstanceId != null) { - descriptors = _descriptors![workflowBlueprint.Id].ToList(); + descriptors = _descriptors!.ContainsKey(workflowBlueprint.Id) ? _descriptors[workflowBlueprint.Id].ToList() : new List(); descriptors.RemoveAll(x => x.WorkflowInstanceId == workflowInstanceId); var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken); diff --git a/src/samples/console/Elsa.Samples.IfElseConsole/HappinessWorkflow.cs b/src/samples/console/Elsa.Samples.IfElseConsole/HappinessWorkflow.cs index 2b7ed5762..f77a18fa6 100644 --- a/src/samples/console/Elsa.Samples.IfElseConsole/HappinessWorkflow.cs +++ b/src/samples/console/Elsa.Samples.IfElseConsole/HappinessWorkflow.cs @@ -17,23 +17,24 @@ namespace Elsa.Samples.IfElseConsole public void Build(IWorkflowBuilder workflow) { workflow - .WriteLine("--POND OF HAPPINESS--", "Start") + .WriteLine("--POND OF HAPPINESS--") .WriteLine("Throw some Rupees in and your wishes will surely come true.") - .WriteLine("Do you want to throw Rupees?") + .WriteLine("Do you want to throw Rupees?").WithName("Start") .ReadLine() .IfElse(context => IsYes(context.Input), ifElse => { ifElse - .When(IfElse.True) + .When(OutcomeNames.True) .WriteLine(GetCurse) .WriteLine("...") .Then("Start"); ifElse - .When(IfElse.False) - .WriteLine("Bye."); - }); + .When(OutcomeNames.False) + .WriteLine("Keep your rupees."); + }) + .WriteLine("--END--"); } private static bool IsYes(object? value) diff --git a/src/samples/console/Elsa.Samples.SwitchConsole/GrayscaleWorkflow.cs b/src/samples/console/Elsa.Samples.SwitchConsole/GrayscaleWorkflow.cs index d91d69e1d..32aff209a 100644 --- a/src/samples/console/Elsa.Samples.SwitchConsole/GrayscaleWorkflow.cs +++ b/src/samples/console/Elsa.Samples.SwitchConsole/GrayscaleWorkflow.cs @@ -17,7 +17,7 @@ namespace Elsa.Samples.SwitchConsole public void Build(IWorkflowBuilder workflow) { workflow - .WriteLine("--Grayscale Calculator--", "Start") + .WriteLine("--Grayscale Calculator--").WithName("Start") .WriteLine("Enter a number between 0 and 100.") .ReadLine() .Switch(cases => diff --git a/test/integration/Elsa.Core.IntegrationTests/Workflows/ForkJoinWorkflow.cs b/test/integration/Elsa.Core.IntegrationTests/Workflows/ForkJoinWorkflow.cs index 2e24a530c..b76faeebc 100644 --- a/test/integration/Elsa.Core.IntegrationTests/Workflows/ForkJoinWorkflow.cs +++ b/test/integration/Elsa.Core.IntegrationTests/Workflows/ForkJoinWorkflow.cs @@ -20,12 +20,12 @@ namespace Elsa.Core.IntegrationTests.Workflows activity => activity.Set(x => x.Branches, new HashSet(new[] { "Branch 1", "Branch 2", "Branch 3" })), fork => { - fork.When("Branch 1").SignalReceived("Signal1").WriteLine("Branch 1 executed", "WriteLine1").Then("Join"); - fork.When("Branch 2").SignalReceived("Signal2").WriteLine("Branch 2 executed", "WriteLine2").Then("Join"); - fork.When("Branch 3").SignalReceived("Signal3").WriteLine("Branch 3 executed", "WriteLine3").Then("Join"); + fork.When("Branch 1").SignalReceived("Signal1").WriteLine("Branch 1 executed").WithName("WriteLine1").Then("Join"); + fork.When("Branch 2").SignalReceived("Signal2").WriteLine("Branch 2 executed").WithName("WriteLine2").Then("Join"); + fork.When("Branch 3").SignalReceived("Signal3").WriteLine("Branch 3 executed").WithName("WriteLine3").Then("Join"); }) .Add(join => join.Set(x => x.Mode, _joinMode)).WithName("Join") - .WriteLine("Finished", "Finished"); + .WriteLine("Finished").WithName("Finished"); } } } \ No newline at end of file diff --git a/test/integration/Elsa.Core.IntegrationTests/Workflows/WhileWorkflow.cs b/test/integration/Elsa.Core.IntegrationTests/Workflows/WhileWorkflow.cs index 1bbed7701..2db6588e0 100644 --- a/test/integration/Elsa.Core.IntegrationTests/Workflows/WhileWorkflow.cs +++ b/test/integration/Elsa.Core.IntegrationTests/Workflows/WhileWorkflow.cs @@ -25,7 +25,7 @@ namespace Elsa.Core.IntegrationTests.Workflows { @while .When(OutcomeNames.Iterate) - .WriteLine(context => $"Inside while loop. Counter = {context.GetVariable(CounterVariableName)}", id: "WriteLoopCount") + .WriteLine(context => $"Inside while loop. Counter = {context.GetVariable(CounterVariableName)}").WithId("WriteLoopCount") .SetVariable(CounterVariableName, context => GetCounter(context) + 1); })