Improve interaction between Finish and composite activities

This update enabled activities within composite activity containers to control the outcome of the container
This commit is contained in:
Sipke Schoorstra 2021-01-02 15:39:38 +01:00
parent 7c2e9f9342
commit f14c22d5d4
9 changed files with 98 additions and 18 deletions

View file

@ -0,0 +1,4 @@
namespace Elsa.Models
{
public record FinishOutput(object? Output, string? Outcome);
}

View file

@ -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<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
{
if (!IsScheduled)
{
IsScheduled = true;
await OnEnterAsync(context);
return Outcome(Enter);
}
IsScheduled = false;
return Done();
await OnExitAsync(context);
var finishOutput = context.GetInput<FinishOutput>();
var outcomes = new List<string> { 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)
{
}
}
}

View file

@ -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);
}
}
}

View file

@ -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<ISetupActivity<Finish>>? setup = default) => builder.Then(setup);
public static IActivityBuilder Finish(this IBuilder builder, Func<ActivityExecutionContext, object?> output) => builder.Finish(activity => activity.Set(x => x.OutputValue, output));
public static IActivityBuilder Finish(this IBuilder builder, Func<object?> 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<ActivityExecutionContext, ValueTask<object?>> output) => builder.Finish(activity => activity.WithOutput(output));
public static IActivityBuilder Finish(this IBuilder builder, Func<ActivityExecutionContext, object?> output) => builder.Finish(activity => activity.WithOutput(output));
public static IActivityBuilder Finish(this IBuilder builder, Func<object?> output) => builder.Finish(activity => activity.WithOutput(output));
public static IActivityBuilder Finish(this IBuilder builder, Func<ActivityExecutionContext, ValueTask<string?>> outcome) => builder.Finish(activity => activity.WithOutcome(outcome));
public static IActivityBuilder Finish(this IBuilder builder, Func<ActivityExecutionContext, string?> outcome) => builder.Finish(activity => activity.WithOutcome(outcome));
public static IActivityBuilder Finish(this IBuilder builder, Func<string?> outcome) => builder.Finish(activity => activity.WithOutcome(outcome));
public static IActivityBuilder Finish(this IBuilder builder, string? outcome) => builder.Finish(activity => activity.WithOutcome(outcome));
}
}

View file

@ -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<Finish> WithOutput(this ISetupActivity<Finish> activity, Func<ActivityExecutionContext, ValueTask<object?>> value) => activity.Set(x => x.OutputValue, value);
public static ISetupActivity<Finish> WithOutput(this ISetupActivity<Finish> activity, Func<ActivityExecutionContext, object?> value) => activity.Set(x => x.OutputValue, value);
public static ISetupActivity<Finish> WithOutput(this ISetupActivity<Finish> activity, Func<object?> value) => activity.Set(x => x.OutputValue, value);
public static ISetupActivity<Finish> WithOutput(this ISetupActivity<Finish> activity, object? value) => activity.Set(x => x.OutputValue, value);
public static ISetupActivity<Finish> WithOutcome(this ISetupActivity<Finish> activity, Func<ActivityExecutionContext, ValueTask<string?>> value) => activity.Set(x => x.Outcome, value);
public static ISetupActivity<Finish> WithOutcome(this ISetupActivity<Finish> activity, Func<ActivityExecutionContext, string?> value) => activity.Set(x => x.Outcome, value);
public static ISetupActivity<Finish> WithOutcome(this ISetupActivity<Finish> activity, Func<string?> value) => activity.Set(x => x.Outcome, value);
public static ISetupActivity<Finish> WithOutcome(this ISetupActivity<Finish> activity, string? value) => activity.Set(x => x.Outcome, value);
}
}

View file

@ -1,5 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Ccontrolflow_005Ccondition/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Ccontrolflow_005Cfinish/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Ccontrolflow_005Cfor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Ccontrolflow_005Cifthen/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cprimitives_005Csetname/@EntryIndexedValue">True</s:Boolean>

View file

@ -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;
}
}

View file

@ -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<string>(), "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<string>().Capitalize());
}
private static void GetInstructions(ActivityExecutionContext context) => context.Output = "Turn left or right?";

View file

@ -13,7 +13,11 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Workflows
.WriteLine("Welcome to the Composite Activities demo workflow!")
// A custom, composite activity
.Then<CountDownActivity>()
.Then<CountDownActivity>(countDown =>
{
countDown.When("Left").WriteLine("Where going left!");
countDown.When("Right").WriteLine("Where going right!");
})
.WriteLine("Done")
;
}