Add support for composite activities to return outcomes

This commit is contained in:
Sipke Schoorstra 2020-12-02 22:20:13 +01:00
parent 055482b935
commit dadee53d2d
10 changed files with 54 additions and 36 deletions

View file

@ -16,18 +16,18 @@ namespace Elsa.ActivityResults
Outcomes = outcomeList;
}
public IReadOnlyCollection<string> Outcomes { get; }
public IEnumerable<string> 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);
}

View file

@ -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<bool> CanExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnCanExecuteAsync(context, cancellationToken);
public ValueTask<IActivityExecutionResult> ExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnExecuteAsync(context, cancellationToken);
public ValueTask<IActivityExecutionResult> ResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnResumeAsync(context, cancellationToken);
protected virtual ValueTask<bool> OnCanExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new ValueTask<bool>(OnCanExecute(context));
protected virtual ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new ValueTask<IActivityExecutionResult>(OnExecute(context));
protected virtual ValueTask<IActivityExecutionResult> OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new ValueTask<IActivityExecutionResult>(OnResume(context));
protected virtual ValueTask<bool> OnCanExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new(OnCanExecute(context));
protected virtual ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => new(OnExecute(context));
protected virtual ValueTask<IActivityExecutionResult> 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<string> outcomes) => new OutcomeResult(outcomes);
protected OutcomeResult Outcomes(IEnumerable<string> outcomes) => new(outcomes);
protected OutcomeResult Outcomes(params string[] outcomes) => Outcomes((IEnumerable<string>)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<string> 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<string> activityIds, object input) => new(activityIds, input);
protected ScheduleActivitiesResult Schedule(string activityId, object input) => Schedule(new[] { activityId }, input);
protected ScheduleActivitiesResult Schedule(IEnumerable<ScheduledActivity> activities) => new ScheduleActivitiesResult(activities);
protected PostScheduleActivitiesResult PostSchedule(params string[] activityIds) => new PostScheduleActivitiesResult(activityIds);
protected CombinedResult Combine(IEnumerable<IActivityExecutionResult> 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<ScheduledActivity> activities) => new(activities);
protected PostScheduleActivitiesResult PostSchedule(params string[] activityIds) => new(activityIds);
protected CombinedResult Combine(IEnumerable<IActivityExecutionResult> results) => new(results);
protected CombinedResult Combine(params IActivityExecutionResult[] results) => new(results);
protected FaultResult Fault(LocalizedString message) => new(message);
protected T GetState<T>(Func<T>? defaultValue = null, [CallerMemberName] string name = null!) => Data.GetState(name, defaultValue);
protected T GetState<T>(Type type, Func<T>? defaultValue = null, [CallerMemberName] string name = null!) => Data.GetState(type, name, defaultValue);

View file

@ -11,11 +11,24 @@ namespace Elsa.Services
{
}
private bool IsScheduled
{
get => GetState<bool>();
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();
}
}

View file

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

View file

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

View file

@ -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(

View file

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

View file

@ -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
{
/// <summary>
/// Custom activities that inherit from <seealso cref="CompositeActivity"/> declare their own mini-workflow.
/// </summary>
[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)!);
}
}

View file

@ -23,7 +23,7 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole
await startupRunner.StartupAsync();
// Get a workflow host.
var workflowRunner = services.GetService<IWorkflowRunner>();
var workflowRunner = services.GetRequiredService<IWorkflowRunner>();
// Execute the workflow.
await workflowRunner.RunWorkflowAsync<CompositionWorkflow>();

View file

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