From f14c22d5d4a17323335d89c5f0c8ae0463ecd6ed Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 2 Jan 2021 15:39:38 +0100 Subject: [PATCH] Improve interaction between Finish and composite activities This update enabled activities within composite activity containers to control the outcome of the container --- .../Elsa.Abstractions/Models/FinishOutput.cs | 4 ++ .../Services/CompositeActivity.cs | 44 +++++++++++++++++-- .../Activities/ControlFlow/Finish/Finish.cs | 11 +++-- .../Finish/FinishBuilderExtensions.cs | 12 +++-- .../ControlFlow/Finish/IfElseExtensions.cs | 21 +++++++++ .../Elsa.Core/Elsa.Core.csproj.DotSettings | 1 + .../Handlers/RescheduleLoopsAndContainers.cs | 7 ++- .../Activities/CountDownActivity.cs | 10 ++--- .../Workflows/CompositionWorkflow.cs | 6 ++- 9 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Models/FinishOutput.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/Finish/IfElseExtensions.cs diff --git a/src/core/Elsa.Abstractions/Models/FinishOutput.cs b/src/core/Elsa.Abstractions/Models/FinishOutput.cs new file mode 100644 index 000000000..885d56d6c --- /dev/null +++ b/src/core/Elsa.Abstractions/Models/FinishOutput.cs @@ -0,0 +1,4 @@ +namespace Elsa.Models +{ + public record FinishOutput(object? Output, string? Outcome); +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs index 8951db1c5..d7733fe96 100644 --- a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs +++ b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs @@ -1,5 +1,8 @@ -using Elsa.ActivityResults; +using System.Collections.Generic; +using System.Threading.Tasks; +using Elsa.ActivityResults; using Elsa.Builders; +using Elsa.Models; using Elsa.Services.Models; namespace Elsa.Services @@ -18,16 +21,51 @@ namespace Elsa.Services set => SetState(value); } - protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) + protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context) { if (!IsScheduled) { IsScheduled = true; + await OnEnterAsync(context); return Outcome(Enter); } IsScheduled = false; - return Done(); + await OnExitAsync(context); + + var finishOutput = context.GetInput(); + var outcomes = new List { OutcomeNames.Done }; + var output = default(object?); + + if (finishOutput != null) + { + if(!string.IsNullOrWhiteSpace(finishOutput.Outcome)) + outcomes.Add(finishOutput.Outcome!); + + output = finishOutput.Output; + } + + return Combine(Outcomes(outcomes), Output(output)); + } + + protected virtual ValueTask OnEnterAsync(ActivityExecutionContext context) + { + OnEnter(context); + return new(); + } + + protected virtual ValueTask OnExitAsync(ActivityExecutionContext context) + { + OnExit(context); + return new(); + } + + protected virtual void OnEnter(ActivityExecutionContext context) + { + } + + protected virtual void OnExit(ActivityExecutionContext context) + { } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Finish/Finish.cs b/src/core/Elsa.Core/Activities/ControlFlow/Finish/Finish.cs index 3cf8169a7..5a8c623f7 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/Finish/Finish.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/Finish/Finish.cs @@ -1,6 +1,7 @@ using System.Linq; using Elsa.ActivityResults; using Elsa.Attributes; +using Elsa.Models; using Elsa.Services; using Elsa.Services.Models; @@ -13,8 +14,11 @@ namespace Elsa.Activities.ControlFlow )] public class Finish : Activity { - [ActivityProperty(Hint = "The value to set as the workflow's output'")] + [ActivityProperty(Hint = "The value to set as the workflow's output")] public object? OutputValue { get; set; } + + [ActivityProperty(Hint = "The outcome to set on the container activity")] + public string? Outcome { get; set; } protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { @@ -24,8 +28,9 @@ namespace Elsa.Activities.ControlFlow var containedBlockingActivityIds = parentBlueprint == null ? blockingActivityIds : parentBlueprint.Activities.Where(x => blockingActivityIds.Contains(x.Id)).Select(x => x.Id).ToList(); blockingActivities.RemoveWhere(x => containedBlockingActivityIds.Contains(x.ActivityId)); - - return Done(OutputValue); + var output = new FinishOutput(OutputValue, Outcome); + context.WorkflowExecutionContext.WorkflowInstance.Output = output; + return Done(output); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Finish/FinishBuilderExtensions.cs b/src/core/Elsa.Core/Activities/ControlFlow/Finish/FinishBuilderExtensions.cs index 036e8c7aa..2dcb0b17c 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/Finish/FinishBuilderExtensions.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/Finish/FinishBuilderExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Elsa.Builders; using Elsa.Services.Models; @@ -8,8 +9,13 @@ namespace Elsa.Activities.ControlFlow public static class FinishBuilderExtensions { public static IActivityBuilder Finish(this IBuilder builder, Action>? setup = default) => builder.Then(setup); - public static IActivityBuilder Finish(this IBuilder builder, Func output) => builder.Finish(activity => activity.Set(x => x.OutputValue, output)); - public static IActivityBuilder Finish(this IBuilder builder, Func output) => builder.Finish(activity => activity.Set(x => x.OutputValue, output)); - public static IActivityBuilder Finish(this IBuilder builder, object? output) => builder.Finish(activity => activity.Set(x => x.OutputValue, output)); + public static IActivityBuilder Finish(this IBuilder builder, Func> output) => builder.Finish(activity => activity.WithOutput(output)); + public static IActivityBuilder Finish(this IBuilder builder, Func output) => builder.Finish(activity => activity.WithOutput(output)); + public static IActivityBuilder Finish(this IBuilder builder, Func output) => builder.Finish(activity => activity.WithOutput(output)); + + public static IActivityBuilder Finish(this IBuilder builder, Func> outcome) => builder.Finish(activity => activity.WithOutcome(outcome)); + public static IActivityBuilder Finish(this IBuilder builder, Func outcome) => builder.Finish(activity => activity.WithOutcome(outcome)); + public static IActivityBuilder Finish(this IBuilder builder, Func outcome) => builder.Finish(activity => activity.WithOutcome(outcome)); + public static IActivityBuilder Finish(this IBuilder builder, string? outcome) => builder.Finish(activity => activity.WithOutcome(outcome)); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Finish/IfElseExtensions.cs b/src/core/Elsa.Core/Activities/ControlFlow/Finish/IfElseExtensions.cs new file mode 100644 index 000000000..7efac0756 --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/Finish/IfElseExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading.Tasks; +using Elsa.Builders; +using Elsa.Services.Models; + +// ReSharper disable once CheckNamespace +namespace Elsa.Activities.ControlFlow +{ + public static class FinishExtensions + { + public static ISetupActivity WithOutput(this ISetupActivity activity, Func> value) => activity.Set(x => x.OutputValue, value); + public static ISetupActivity WithOutput(this ISetupActivity activity, Func value) => activity.Set(x => x.OutputValue, value); + public static ISetupActivity WithOutput(this ISetupActivity activity, Func value) => activity.Set(x => x.OutputValue, value); + public static ISetupActivity WithOutput(this ISetupActivity activity, object? value) => activity.Set(x => x.OutputValue, value); + + public static ISetupActivity WithOutcome(this ISetupActivity activity, Func> value) => activity.Set(x => x.Outcome, value); + public static ISetupActivity WithOutcome(this ISetupActivity activity, Func value) => activity.Set(x => x.Outcome, value); + public static ISetupActivity WithOutcome(this ISetupActivity activity, Func value) => activity.Set(x => x.Outcome, value); + public static ISetupActivity WithOutcome(this ISetupActivity activity, string? value) => activity.Set(x => x.Outcome, value); + } +} \ 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 2117cfad5..915674e13 100644 --- a/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings +++ b/src/core/Elsa.Core/Elsa.Core.csproj.DotSettings @@ -1,5 +1,6 @@  True + True True True True diff --git a/src/core/Elsa.Core/Handlers/RescheduleLoopsAndContainers.cs b/src/core/Elsa.Core/Handlers/RescheduleLoopsAndContainers.cs index 4ae1c53c1..00924da2d 100644 --- a/src/core/Elsa.Core/Handlers/RescheduleLoopsAndContainers.cs +++ b/src/core/Elsa.Core/Handlers/RescheduleLoopsAndContainers.cs @@ -1,8 +1,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Elsa.Activities.ControlFlow; using Elsa.Events; using Elsa.Models; +using Elsa.Services.Models; using MediatR; namespace Elsa.Handlers @@ -58,8 +60,11 @@ namespace Elsa.Handlers // Re-schedule the parent activity, if any. if (activityBlueprint.Parent != null && workflowBlueprint.GetActivity(activityBlueprint.Parent.Id) != null) { - workflowExecutionContext.ScheduleActivity(activityBlueprint.Parent.Id); + var output = GetFinishOutput(workflowExecutionContext); + workflowExecutionContext.ScheduleActivity(activityBlueprint.Parent.Id, output); } } + + private static FinishOutput? GetFinishOutput(WorkflowExecutionContext workflowExecutionContext) => workflowExecutionContext.WorkflowInstance.Output as FinishOutput; } } \ No newline at end of file diff --git a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Activities/CountDownActivity.cs b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Activities/CountDownActivity.cs index 05bd13e27..21dff6b79 100644 --- a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Activities/CountDownActivity.cs +++ b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Activities/CountDownActivity.cs @@ -1,10 +1,10 @@ -using System; -using Elsa.Activities.Console; +using Elsa.Activities.Console; using Elsa.Activities.ControlFlow; using Elsa.Attributes; using Elsa.Builders; using Elsa.Services; using Elsa.Services.Models; +using NetBox.Extensions; namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Activities { @@ -20,11 +20,7 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Activities .StartWith(GetInstructions) .WriteLine(context => (string)context.Input) .ReadLine() - .IfElse(context => string.Equals(context.GetInput(), "left", StringComparison.CurrentCultureIgnoreCase), ifElse => - { - ifElse.When(IfElse.True).WriteLine("We're going left"); - ifElse.When(IfElse.False).WriteLine("We're going right"); - }); + .Finish(context => context.GetInput().Capitalize()); } private static void GetInstructions(ActivityExecutionContext context) => context.Output = "Turn left or right?"; diff --git a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Workflows/CompositionWorkflow.cs b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Workflows/CompositionWorkflow.cs index f23dc0c21..18827394a 100644 --- a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Workflows/CompositionWorkflow.cs +++ b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Workflows/CompositionWorkflow.cs @@ -13,7 +13,11 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Workflows .WriteLine("Welcome to the Composite Activities demo workflow!") // A custom, composite activity - .Then() + .Then(countDown => + { + countDown.When("Left").WriteLine("Where going left!"); + countDown.When("Right").WriteLine("Where going right!"); + }) .WriteLine("Done") ; }