From dadee53d2d227ddca9e3fe03e50bf32224bcb093 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 2 Dec 2020 22:20:13 +0100 Subject: [PATCH] Add support for composite activities to return outcomes --- .../ActivityResults/OutcomeResult.cs | 10 +++---- .../Elsa.Abstractions/Services/Activity.cs | 30 +++++++++---------- .../Services/CompositeActivity.cs | 15 +++++++++- .../Models/WorkflowExecutionContext.cs | 6 +--- .../Activities/ControlFlow/Finish/Finish.cs | 3 +- .../Builders/CompositeActivityBuilder.cs | 2 +- src/core/Elsa.Core/Services/WorkflowRunner.cs | 1 + .../Activities/CountDownActivity.cs | 13 ++++++-- .../Program.cs | 2 +- .../Workflows/CompositionWorkflow.cs | 8 +++-- 10 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/core/Elsa.Abstractions/ActivityResults/OutcomeResult.cs b/src/core/Elsa.Abstractions/ActivityResults/OutcomeResult.cs index 191963a77..85235ebfc 100644 --- a/src/core/Elsa.Abstractions/ActivityResults/OutcomeResult.cs +++ b/src/core/Elsa.Abstractions/ActivityResults/OutcomeResult.cs @@ -16,18 +16,18 @@ namespace Elsa.ActivityResults Outcomes = outcomeList; } - - public IReadOnlyCollection Outcomes { get; } + + public IEnumerable Outcomes { get; } protected override void Execute(ActivityExecutionContext activityExecutionContext) { - activityExecutionContext.Outcomes = Outcomes.ToList(); - + var outcomes = activityExecutionContext.Outcomes = Outcomes.ToList(); var workflowExecutionContext = activityExecutionContext.WorkflowExecutionContext; + var nextActivities = GetNextActivities( workflowExecutionContext, activityExecutionContext.ActivityBlueprint.Id, - Outcomes).ToList(); + outcomes).ToList(); workflowExecutionContext.ScheduleActivities(nextActivities, activityExecutionContext.Output); } diff --git a/src/core/Elsa.Abstractions/Services/Activity.cs b/src/core/Elsa.Abstractions/Services/Activity.cs index c126ef8cc..5d78b4123 100644 --- a/src/core/Elsa.Abstractions/Services/Activity.cs +++ b/src/core/Elsa.Abstractions/Services/Activity.cs @@ -21,35 +21,35 @@ namespace Elsa.Services public bool PersistWorkflow { get; set; } public bool LoadWorkflowContext { get; set; } public bool SaveWorkflowContext { get; set; } - public JObject Data { get; set; } = new JObject(); + public JObject Data { get; set; } = new(); public ValueTask CanExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnCanExecuteAsync(context, cancellationToken); public ValueTask ExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnExecuteAsync(context, cancellationToken); public ValueTask ResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnResumeAsync(context, cancellationToken); - protected virtual ValueTask OnCanExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new ValueTask(OnCanExecute(context)); - protected virtual ValueTask OnExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new ValueTask(OnExecute(context)); - protected virtual ValueTask OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new ValueTask(OnResume(context)); + protected virtual ValueTask OnCanExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new(OnCanExecute(context)); + protected virtual ValueTask OnExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new(OnExecute(context)); + protected virtual ValueTask OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new(OnResume(context)); protected virtual bool OnCanExecute(ActivityExecutionContext context) => true; protected virtual IActivityExecutionResult OnExecute(ActivityExecutionContext context) => OnExecute(); protected virtual IActivityExecutionResult OnExecute() => Done(); protected virtual IActivityExecutionResult OnResume(ActivityExecutionContext context) => OnResume(); protected virtual IActivityExecutionResult OnResume() => Done(); - protected NoopResult Noop() => new NoopResult(); + protected NoopResult Noop() => new(); protected OutcomeResult Done() => Outcome(OutcomeNames.Done); protected CombinedResult Done(object? output) => Combine(Output(output), Done()); - protected OutcomeResult Outcomes(IEnumerable outcomes) => new OutcomeResult(outcomes); + protected OutcomeResult Outcomes(IEnumerable outcomes) => new(outcomes); protected OutcomeResult Outcomes(params string[] outcomes) => Outcomes((IEnumerable)outcomes); protected OutcomeResult Outcome(string outcome) => Outcomes(outcome); protected CombinedResult Outcome(string outcome, object? output) => Combine(Output(output), Outcome(outcome)); - protected OutputResult Output(object? output) => new OutputResult(output); - protected SuspendResult Suspend() => new SuspendResult(); - protected ScheduleActivitiesResult Schedule(params string[] activityIds) => new ScheduleActivitiesResult(activityIds); - protected ScheduleActivitiesResult Schedule(IEnumerable activityIds, object input) => new ScheduleActivitiesResult(activityIds, input); + protected OutputResult Output(object? output) => new(output); + protected SuspendResult Suspend() => new(); + protected ScheduleActivitiesResult Schedule(params string[] activityIds) => new(activityIds); + protected ScheduleActivitiesResult Schedule(IEnumerable activityIds, object input) => new(activityIds, input); protected ScheduleActivitiesResult Schedule(string activityId, object input) => Schedule(new[] { activityId }, input); - protected ScheduleActivitiesResult Schedule(IEnumerable activities) => new ScheduleActivitiesResult(activities); - protected PostScheduleActivitiesResult PostSchedule(params string[] activityIds) => new PostScheduleActivitiesResult(activityIds); - protected CombinedResult Combine(IEnumerable results) => new CombinedResult(results); - protected CombinedResult Combine(params IActivityExecutionResult[] results) => new CombinedResult(results); - protected FaultResult Fault(LocalizedString message) => new FaultResult(message); + protected ScheduleActivitiesResult Schedule(IEnumerable activities) => new(activities); + protected PostScheduleActivitiesResult PostSchedule(params string[] activityIds) => new(activityIds); + protected CombinedResult Combine(IEnumerable results) => new(results); + protected CombinedResult Combine(params IActivityExecutionResult[] results) => new(results); + protected FaultResult Fault(LocalizedString message) => new(message); protected T GetState(Func? defaultValue = null, [CallerMemberName] string name = null!) => Data.GetState(name, defaultValue); protected T GetState(Type type, Func? defaultValue = null, [CallerMemberName] string name = null!) => Data.GetState(type, name, defaultValue); diff --git a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs index 41c7d2cf7..383f3e459 100644 --- a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs +++ b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs @@ -11,11 +11,24 @@ namespace Elsa.Services { } + private bool IsScheduled + { + get => GetState(); + set => SetState(value); + } + protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { + if (IsScheduled) + return Complete(context); + var compositeActivityBlueprint = (ICompositeActivityBlueprint)context.ActivityBlueprint; var startActivities = compositeActivityBlueprint.GetStartActivities().Select(x => x.Id).ToList(); - return Combine(Done(), Schedule(startActivities, null!)); + context.WorkflowExecutionContext.PostScheduleActivity(Id); + IsScheduled = true; + return Schedule(startActivities, null!); } + + protected virtual IActivityExecutionResult Complete(ActivityExecutionContext context) => Done(); } } \ 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 dc520386f..8ca0c6df9 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs +++ b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs @@ -89,11 +89,7 @@ namespace Elsa.Services.Models WorkflowFault = new WorkflowFault(activityId, message); } - public void Complete(object? output = default) - { - WorkflowInstance.Status = WorkflowStatus.Finished; - WorkflowInstance.Output = output; - } + public void Complete() => WorkflowInstance.Status = WorkflowStatus.Finished; public IActivityBlueprint? GetActivityBlueprintById(string id) => WorkflowBlueprint.Activities.FirstOrDefault(x => x.Id == id); public IActivityBlueprint? GetActivityBlueprintByName(string name) => WorkflowBlueprint.Activities.FirstOrDefault(x => x.Name == name); diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Finish/Finish.cs b/src/core/Elsa.Core/Activities/ControlFlow/Finish/Finish.cs index 7e8c994ee..de36cc998 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/Finish/Finish.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/Finish/Finish.cs @@ -18,9 +18,8 @@ namespace Elsa.Activities.ControlFlow protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { context.WorkflowExecutionContext.WorkflowInstance.BlockingActivities.Clear(); - context.WorkflowExecutionContext.Complete(OutputValue); - return Combine(Done(OutputValue)); + return Done(OutputValue); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs b/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs index 4faae8df2..d8b631096 100644 --- a/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs +++ b/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs @@ -85,7 +85,7 @@ namespace Elsa.Builders where T : class, IActivity { var activityBuilder = new ActivityBuilder(typeof(T), this, propertyValueProviders); - return Add(activityBuilder); + return Add(activityBuilder, branch); } public IActivityBuilder Add( diff --git a/src/core/Elsa.Core/Services/WorkflowRunner.cs b/src/core/Elsa.Core/Services/WorkflowRunner.cs index 4fb015d19..c63f3b04c 100644 --- a/src/core/Elsa.Core/Services/WorkflowRunner.cs +++ b/src/core/Elsa.Core/Services/WorkflowRunner.cs @@ -280,6 +280,7 @@ namespace Elsa.Services await _mediator.Publish(new ActivityExecuting(activityExecutionContext), cancellationToken); await result.ExecuteAsync(activityExecutionContext, cancellationToken); await _mediator.Publish(new ActivityExecuted(activityExecutionContext), cancellationToken); + workflowExecutionContext.WorkflowInstance.Output = activityExecutionContext.Output; workflowExecutionContext.WorkflowInstance.ContextId = await SaveWorkflowContextAsync(workflowExecutionContext, WorkflowContextFidelity.Activity, activityBlueprint.SaveWorkflowContext, cancellationToken); activityOperation = Execute; diff --git a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Activities/CountDownActivity.cs b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Activities/CountDownActivity.cs index fe3f1093b..33ab80ab5 100644 --- a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Activities/CountDownActivity.cs +++ b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Activities/CountDownActivity.cs @@ -1,20 +1,27 @@ using Elsa.Activities.Console; +using Elsa.Activities.ControlFlow; +using Elsa.ActivityResults; +using Elsa.Attributes; using Elsa.Builders; using Elsa.Services; +using Elsa.Services.Models; namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Activities { /// /// Custom activities that inherit from declare their own mini-workflow. /// + [Action(Outcomes = new[] { "Left", "Right" })] public class CountDownActivity : CompositeActivity { public override void Build(ICompositeActivityBuilder composite) { composite - .WriteLine("3!") - .WriteLine("2!") - .WriteLine("1!"); + .WriteLine("Turn left or right?") + .ReadLine() + .Finish(context => (string) context.Input); } + + protected override IActivityExecutionResult Complete(ActivityExecutionContext context) => Outcome(((string) context.WorkflowExecutionContext.WorkflowInstance.Output)!); } } \ No newline at end of file diff --git a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Program.cs b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Program.cs index b31b2118a..4890f1521 100644 --- a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Program.cs +++ b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Program.cs @@ -23,7 +23,7 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole await startupRunner.StartupAsync(); // Get a workflow host. - var workflowRunner = services.GetService(); + var workflowRunner = services.GetRequiredService(); // Execute the workflow. await workflowRunner.RunWorkflowAsync(); diff --git a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Workflows/CompositionWorkflow.cs b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Workflows/CompositionWorkflow.cs index 9cbd4c97c..d986f3867 100644 --- a/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Workflows/CompositionWorkflow.cs +++ b/src/samples/console/Elsa.Samples.ProgrammaticCompositeActivitiesConsole/Workflows/CompositionWorkflow.cs @@ -13,8 +13,10 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Workflows .WriteLine("Welcome to the Composite Activities demo workflow!") // A custom, composite activity - .Then() - - .WriteLine("Done!"); + .Then(countDown => + { + countDown.When("Left").WriteLine("We're going left."); + countDown.When("Right").WriteLine("We're going right."); + }); } } \ No newline at end of file