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
This commit is contained in:
parent
997fb53b08
commit
fcfe7f08b4
|
|
@ -8,12 +8,12 @@ namespace Elsa.Activities.Console
|
||||||
{
|
{
|
||||||
public static class WriteLineBuilderExtensions
|
public static class WriteLineBuilderExtensions
|
||||||
{
|
{
|
||||||
public static IOutcomeBuilder WriteLine(this IBuilder builder, Action<ISetupActivity<WriteLine>> setup, string? name = default, string? id = default) => WriteLine(builder.Then(setup).WithName(name).WithId(id));
|
public static IActivityBuilder WriteLine(this IBuilder builder, Action<ISetupActivity<WriteLine>> setup) => builder.Then(setup);
|
||||||
public static IOutcomeBuilder WriteLine(this IBuilder builder, Func<ActivityExecutionContext, string> text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text), name, id);
|
public static IActivityBuilder WriteLine(this IBuilder builder, Func<ActivityExecutionContext, string> text) => builder.WriteLine(activity => activity.WithText(text));
|
||||||
public static IOutcomeBuilder WriteLine(this IBuilder builder, Func<ActivityExecutionContext, ValueTask<string>> text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text!), name, id);
|
public static IActivityBuilder WriteLine(this IBuilder builder, Func<ActivityExecutionContext, ValueTask<string>> text) => builder.WriteLine(activity => activity.WithText(text!));
|
||||||
public static IOutcomeBuilder WriteLine(this IBuilder builder, Func<string> text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text), name, id);
|
public static IActivityBuilder WriteLine(this IBuilder builder, Func<string> text) => builder.WriteLine(activity => activity.WithText(text!));
|
||||||
public static IOutcomeBuilder WriteLine(this IBuilder builder, Func<ValueTask<string>> text, string? name = default, string? id = default) => builder.WriteLine(activity => activity.Set(x => x.Text, text!), name, id);
|
public static IActivityBuilder WriteLine(this IBuilder builder, Func<ValueTask<string>> text) => builder.WriteLine(activity => activity.WithText(text!));
|
||||||
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);
|
public static IActivityBuilder WriteLine(this IBuilder builder, string text) => builder.WriteLine(activity => activity.WithText(text!));
|
||||||
private static IOutcomeBuilder WriteLine(IActivityBuilder writeLine) => writeLine.When(OutcomeNames.Done);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,16 +25,40 @@ namespace Elsa.ActivityResults
|
||||||
{
|
{
|
||||||
var outcomes = activityExecutionContext.Outcomes = Outcomes.ToList();
|
var outcomes = activityExecutionContext.Outcomes = Outcomes.ToList();
|
||||||
var workflowExecutionContext = activityExecutionContext.WorkflowExecutionContext;
|
var workflowExecutionContext = activityExecutionContext.WorkflowExecutionContext;
|
||||||
|
var nextConnections = GetNextConnections(workflowExecutionContext, activityExecutionContext.ActivityBlueprint.Id, outcomes).ToList();
|
||||||
|
|
||||||
var nextActivities = GetNextActivities(
|
var nextActivities =
|
||||||
workflowExecutionContext,
|
(
|
||||||
activityExecutionContext.ActivityBlueprint.Id,
|
from connection in nextConnections
|
||||||
outcomes).ToList();
|
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<string> GetNextActivities(
|
public static IEnumerable<string> GetNextActivities(
|
||||||
|
WorkflowExecutionContext workflowContext,
|
||||||
|
string sourceId,
|
||||||
|
IEnumerable<string> 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<IConnection> GetNextConnections(
|
||||||
WorkflowExecutionContext workflowContext,
|
WorkflowExecutionContext workflowContext,
|
||||||
string sourceId,
|
string sourceId,
|
||||||
IEnumerable<string> outcomes)
|
IEnumerable<string> outcomes)
|
||||||
|
|
@ -47,12 +71,10 @@ namespace Elsa.ActivityResults
|
||||||
let connectionOutcome = connection.Source.Outcome ?? OutcomeNames.Done
|
let connectionOutcome = connection.Source.Outcome ?? OutcomeNames.Done
|
||||||
let isConnectionOutcome = connectionOutcome.Equals(outcome.outcome, StringComparison.OrdinalIgnoreCase)
|
let isConnectionOutcome = connectionOutcome.Equals(outcome.outcome, StringComparison.OrdinalIgnoreCase)
|
||||||
where connection.Source.Activity.Id == sourceId && isConnectionOutcome
|
where connection.Source.Activity.Id == sourceId && isConnectionOutcome
|
||||||
from activityBlueprint in workflowContext.WorkflowBlueprint.Activities
|
orderby outcome.order
|
||||||
where activityBlueprint.Id == connection.Target.Activity.Id
|
select connection;
|
||||||
orderby outcome.order
|
|
||||||
select activityBlueprint.Id;
|
|
||||||
|
|
||||||
return query.Distinct();
|
return query;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Elsa.Services;
|
using Elsa.Services;
|
||||||
|
using Elsa.Services.Models;
|
||||||
|
|
||||||
namespace Elsa.Builders
|
namespace Elsa.Builders
|
||||||
{
|
{
|
||||||
|
|
@ -28,5 +29,6 @@ namespace Elsa.Builders
|
||||||
IActivityBuilder LoadWorkflowContext(bool value = true);
|
IActivityBuilder LoadWorkflowContext(bool value = true);
|
||||||
IActivityBuilder SaveWorkflowContext(bool value = true);
|
IActivityBuilder SaveWorkflowContext(bool value = true);
|
||||||
IActivityBuilder PersistWorkflow(bool value = true);
|
IActivityBuilder PersistWorkflow(bool value = true);
|
||||||
|
IWorkflowBlueprint Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Elsa.Services.Models
|
||||||
|
{
|
||||||
|
public interface IBranchingActivity
|
||||||
|
{
|
||||||
|
void Unwind(ActivityExecutionContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -67,7 +67,7 @@ namespace Elsa.Services.Models
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DeleteCompletedInstances => WorkflowBlueprint.DeleteCompletedInstances;
|
public bool DeleteCompletedInstances => WorkflowBlueprint.DeleteCompletedInstances;
|
||||||
public ICollection<string> ExecutionLog => new List<string>();
|
public IList<IConnection> ExecutionLog { get; } = new List<IConnection>();
|
||||||
public WorkflowStatus Status => WorkflowInstance.WorkflowStatus;
|
public WorkflowStatus Status => WorkflowInstance.WorkflowStatus;
|
||||||
public bool HasBlockingActivities => WorkflowInstance.BlockingActivities.Any();
|
public bool HasBlockingActivities => WorkflowInstance.BlockingActivities.Any();
|
||||||
public object? WorkflowContext { get; set; }
|
public object? WorkflowContext { get; set; }
|
||||||
|
|
@ -135,12 +135,6 @@ namespace Elsa.Services.Models
|
||||||
public void SetWorkflowContext(object? value) => WorkflowContext = value;
|
public void SetWorkflowContext(object? value) => WorkflowContext = value;
|
||||||
public T GetWorkflowContext<T>() => (T) WorkflowContext!;
|
public T GetWorkflowContext<T>() => (T) WorkflowContext!;
|
||||||
|
|
||||||
public async ValueTask<IEnumerable<RuntimeActivityInstance>> 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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove empty activity data to save on document size.
|
/// Remove empty activity data to save on document size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -156,4 +150,6 @@ namespace Elsa.Services.Models
|
||||||
return activityBlueprint != null && activityBlueprint.PersistOutput;
|
return activityBlueprint != null && activityBlueprint.PersistOutput;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record ExecutionLogEntry(string ActivityId, string Outcome);
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using Elsa.ActivityResults;
|
using Elsa.ActivityResults;
|
||||||
using Elsa.Attributes;
|
using Elsa.Attributes;
|
||||||
using Elsa.Services;
|
|
||||||
using Elsa.Services.Models;
|
using Elsa.Services.Models;
|
||||||
|
|
||||||
// ReSharper disable once CheckNamespace
|
// ReSharper disable once CheckNamespace
|
||||||
|
|
@ -12,7 +11,7 @@ namespace Elsa.Activities.ControlFlow
|
||||||
Description = "Iterate between two numbers.",
|
Description = "Iterate between two numbers.",
|
||||||
Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done }
|
Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done }
|
||||||
)]
|
)]
|
||||||
public class For : Activity
|
public class For : IteratingActivity
|
||||||
{
|
{
|
||||||
[ActivityProperty(Hint = "An expression that evaluates to the starting number.")]
|
[ActivityProperty(Hint = "An expression that evaluates to the starting number.")]
|
||||||
public long Start { get; set; }
|
public long Start { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace Elsa.Activities.ControlFlow
|
||||||
Description = "Iterate over a collection.",
|
Description = "Iterate over a collection.",
|
||||||
Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done }
|
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.")]
|
[ActivityProperty(Hint = "Enter an expression that evaluates to a collection of items to iterate over.")]
|
||||||
public ICollection<object> Items { get; set; } = new Collection<object>();
|
public ICollection<object> Items { get; set; } = new Collection<object>();
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Elsa.Activities.ControlFlow
|
||||||
get => GetState<int?>();
|
get => GetState<int?>();
|
||||||
set => SetState(value);
|
set => SetState(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
|
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
|
||||||
{
|
{
|
||||||
var collection = ItemsCopy;
|
var collection = ItemsCopy;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Elsa.ActivityResults;
|
using Elsa.ActivityResults;
|
||||||
using Elsa.Attributes;
|
using Elsa.Attributes;
|
||||||
using Elsa.Services;
|
using Elsa.Services;
|
||||||
|
|
@ -12,18 +14,58 @@ namespace Elsa.Activities.ControlFlow
|
||||||
Description = "Evaluate a Boolean expression and continue execution depending on the result.",
|
Description = "Evaluate a Boolean expression and continue execution depending on the result.",
|
||||||
Outcomes = new[] { True, False, OutcomeNames.Done }
|
Outcomes = new[] { True, False, OutcomeNames.Done }
|
||||||
)]
|
)]
|
||||||
public class IfElse : Activity
|
public class IfElse : Activity, IBranchingActivity
|
||||||
{
|
{
|
||||||
public const string True = "True";
|
public const string True = "True";
|
||||||
public const string False = "False";
|
public const string False = "False";
|
||||||
|
|
||||||
[ActivityProperty(Hint = "The condition to evaluate.")]
|
[ActivityProperty(Hint = "The condition to evaluate.")]
|
||||||
public bool Condition { get; set; }
|
public bool Condition { get; set; }
|
||||||
|
|
||||||
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
|
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
|
||||||
{
|
{
|
||||||
var outcome = Condition ? True : False;
|
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<string> GetNextActivities(WorkflowExecutionContext workflowExecutionContext, string currentActivityId) => OutcomeResult.GetNextActivities(workflowExecutionContext, currentActivityId, new[] { OutcomeNames.Done });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,12 +21,26 @@ namespace Elsa.Activities.ControlFlow
|
||||||
|
|
||||||
[ActivityProperty(Hint = "The conditions to evaluate.")]
|
[ActivityProperty(Hint = "The conditions to evaluate.")]
|
||||||
public SwitchMode Mode { get; set; } = SwitchMode.MatchFirst;
|
public SwitchMode Mode { get; set; } = SwitchMode.MatchFirst;
|
||||||
|
|
||||||
|
private bool Evaluated
|
||||||
|
{
|
||||||
|
get => GetState<bool>();
|
||||||
|
set => SetState(value);
|
||||||
|
}
|
||||||
|
|
||||||
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
|
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 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 results = Mode == SwitchMode.MatchFirst ? matches.Any() ? new[] { matches.First() } : new string[0] : matches.ToArray();
|
||||||
var outcomes = new[] { OutcomeNames.Done }.Concat(results);
|
var outcomes = new[] { OutcomeNames.Done }.Concat(results);
|
||||||
|
|
||||||
|
Evaluated = true;
|
||||||
return Outcomes(outcomes);
|
return Outcomes(outcomes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Elsa.Activities.ControlFlow
|
||||||
Description = "Execute while a given condition is true.",
|
Description = "Execute while a given condition is true.",
|
||||||
Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done }
|
Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done }
|
||||||
)]
|
)]
|
||||||
public class While : Activity
|
public class While : IteratingActivity
|
||||||
{
|
{
|
||||||
[ActivityProperty(Hint = "The condition to evaluate.")]
|
[ActivityProperty(Hint = "The condition to evaluate.")]
|
||||||
public bool Condition { get; set; }
|
public bool Condition { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Elsa.ActivityResults
|
||||||
if (previousEntry == null)
|
if (previousEntry == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var activityId = previousEntry;
|
var activityId = previousEntry.Source.Activity.Id;
|
||||||
workflowExecutionContext.ScheduleActivity(activityId, _input);
|
workflowExecutionContext.ScheduleActivity(activityId, _input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Elsa.Services;
|
using Elsa.Services;
|
||||||
|
using Elsa.Services.Models;
|
||||||
|
|
||||||
namespace Elsa.Builders
|
namespace Elsa.Builders
|
||||||
{
|
{
|
||||||
|
|
@ -108,5 +109,7 @@ namespace Elsa.Builders
|
||||||
PersistWorkflowEnabled = value;
|
PersistWorkflowEnabled = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IWorkflowBlueprint Build() => ((IWorkflowBuilder)WorkflowBuilder).BuildBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
<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_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_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_005Ccontrolflow_005Cifthen/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Ccontrolflow_005Cswitch/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cprimitives_005Csetname/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cprimitives_005Csetname/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Csignaling_005Cactivities_005Creceivesignal/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Csignaling_005Cactivities_005Creceivesignal/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Csignaling_005Cactivities_005Csignalreceived/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Csignaling_005Cactivities_005Csignalreceived/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using Elsa.Runtime;
|
||||||
using Elsa.Serialization;
|
using Elsa.Serialization;
|
||||||
using Elsa.Serialization.Converters;
|
using Elsa.Serialization.Converters;
|
||||||
using Elsa.Services;
|
using Elsa.Services;
|
||||||
|
using Elsa.Services.Models;
|
||||||
using Elsa.StartupTasks;
|
using Elsa.StartupTasks;
|
||||||
using Elsa.Triggers;
|
using Elsa.Triggers;
|
||||||
using Elsa.WorkflowProviders;
|
using Elsa.WorkflowProviders;
|
||||||
|
|
@ -54,6 +55,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
.AddSingleton(options.StorageFactory)
|
.AddSingleton(options.StorageFactory)
|
||||||
.AddStartupTask<CreateSubscriptions>();
|
.AddStartupTask<CreateSubscriptions>();
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddTransient<IBranchingActivity, IfElse>()
|
||||||
|
.AddTransient<IBranchingActivity, For>()
|
||||||
|
.AddTransient<IBranchingActivity, While>();
|
||||||
|
|
||||||
options
|
options
|
||||||
.AddWorkflowsCore()
|
.AddWorkflowsCore()
|
||||||
.AddCoreActivities();
|
.AddCoreActivities();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Elsa.Events;
|
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.
|
/// 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.
|
/// Also handles composite activity re-scheduling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RescheduleLoopsAndContainers : INotificationHandler<WorkflowExecutionBurstCompleted>
|
public class RescheduleBranchingActivitiesAndContainers : INotificationHandler<WorkflowExecutionBurstCompleted>
|
||||||
{
|
{
|
||||||
|
private readonly IEnumerable<IBranchingActivity> _branchingActivities;
|
||||||
|
|
||||||
|
public RescheduleBranchingActivitiesAndContainers(IEnumerable<IBranchingActivity> branchingActivities)
|
||||||
|
{
|
||||||
|
_branchingActivities = branchingActivities;
|
||||||
|
}
|
||||||
|
|
||||||
public Task Handle(WorkflowExecutionBurstCompleted notification, CancellationToken cancellationToken)
|
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);
|
ScheduleContainers(notification);
|
||||||
return Task.CompletedTask;
|
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)
|
private static void ScheduleContainers(WorkflowExecutionBurstCompleted notification)
|
||||||
{
|
{
|
||||||
var workflowExecutionContext = notification.WorkflowExecutionContext;
|
var workflowExecutionContext = notification.WorkflowExecutionContext;
|
||||||
|
|
@ -18,6 +18,7 @@ namespace Elsa.Services
|
||||||
var activity = _elsaOptions.ActivityFactory.CreateService(type, context.ServiceScope.ServiceProvider);
|
var activity = _elsaOptions.ActivityFactory.CreateService(type, context.ServiceScope.ServiceProvider);
|
||||||
activity.Data = context.GetData();
|
activity.Data = context.GetData();
|
||||||
activity.Id = context.ActivityId;
|
activity.Id = context.ActivityId;
|
||||||
|
|
||||||
await context.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.SetActivityPropertiesAsync(activity, context, context.CancellationToken);
|
await context.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.SetActivityPropertiesAsync(activity, context, context.CancellationToken);
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,6 @@ namespace Elsa.Services
|
||||||
await _mediator.Publish(new ActivityExecuted(activityExecutionContext), cancellationToken);
|
await _mediator.Publish(new ActivityExecuted(activityExecutionContext), cancellationToken);
|
||||||
await result.ExecuteAsync(activityExecutionContext, cancellationToken);
|
await result.ExecuteAsync(activityExecutionContext, cancellationToken);
|
||||||
workflowExecutionContext.WorkflowInstance.Output = activityExecutionContext.Output;
|
workflowExecutionContext.WorkflowInstance.Output = activityExecutionContext.Output;
|
||||||
workflowExecutionContext.ExecutionLog.Add(activity.Id);
|
|
||||||
workflowExecutionContext.PruneActivityData();
|
workflowExecutionContext.PruneActivityData();
|
||||||
activityOperation = Execute;
|
activityOperation = Execute;
|
||||||
workflowExecutionContext.CompletePass();
|
workflowExecutionContext.CompletePass();
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ namespace Elsa.Triggers
|
||||||
|
|
||||||
if (workflowInstanceId != null)
|
if (workflowInstanceId != null)
|
||||||
{
|
{
|
||||||
descriptors = _descriptors![workflowBlueprint.Id].ToList();
|
descriptors = _descriptors!.ContainsKey(workflowBlueprint.Id) ? _descriptors[workflowBlueprint.Id].ToList() : new List<TriggerDescriptor>();
|
||||||
descriptors.RemoveAll(x => x.WorkflowInstanceId == workflowInstanceId);
|
descriptors.RemoveAll(x => x.WorkflowInstanceId == workflowInstanceId);
|
||||||
|
|
||||||
var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken);
|
var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -17,23 +17,24 @@ namespace Elsa.Samples.IfElseConsole
|
||||||
public void Build(IWorkflowBuilder workflow)
|
public void Build(IWorkflowBuilder workflow)
|
||||||
{
|
{
|
||||||
workflow
|
workflow
|
||||||
.WriteLine("--POND OF HAPPINESS--", "Start")
|
.WriteLine("--POND OF HAPPINESS--")
|
||||||
.WriteLine("Throw some Rupees in and your wishes will surely come true.")
|
.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()
|
.ReadLine()
|
||||||
.IfElse(context => IsYes(context.Input),
|
.IfElse(context => IsYes(context.Input),
|
||||||
ifElse =>
|
ifElse =>
|
||||||
{
|
{
|
||||||
ifElse
|
ifElse
|
||||||
.When(IfElse.True)
|
.When(OutcomeNames.True)
|
||||||
.WriteLine(GetCurse)
|
.WriteLine(GetCurse)
|
||||||
.WriteLine("...")
|
.WriteLine("...")
|
||||||
.Then("Start");
|
.Then("Start");
|
||||||
|
|
||||||
ifElse
|
ifElse
|
||||||
.When(IfElse.False)
|
.When(OutcomeNames.False)
|
||||||
.WriteLine("Bye.");
|
.WriteLine("Keep your rupees.");
|
||||||
});
|
})
|
||||||
|
.WriteLine("--END--");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsYes(object? value)
|
private static bool IsYes(object? value)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Elsa.Samples.SwitchConsole
|
||||||
public void Build(IWorkflowBuilder workflow)
|
public void Build(IWorkflowBuilder workflow)
|
||||||
{
|
{
|
||||||
workflow
|
workflow
|
||||||
.WriteLine("--Grayscale Calculator--", "Start")
|
.WriteLine("--Grayscale Calculator--").WithName("Start")
|
||||||
.WriteLine("Enter a number between 0 and 100.")
|
.WriteLine("Enter a number between 0 and 100.")
|
||||||
.ReadLine()
|
.ReadLine()
|
||||||
.Switch(cases =>
|
.Switch(cases =>
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ namespace Elsa.Core.IntegrationTests.Workflows
|
||||||
activity => activity.Set(x => x.Branches, new HashSet<string>(new[] { "Branch 1", "Branch 2", "Branch 3" })),
|
activity => activity.Set(x => x.Branches, new HashSet<string>(new[] { "Branch 1", "Branch 2", "Branch 3" })),
|
||||||
fork =>
|
fork =>
|
||||||
{
|
{
|
||||||
fork.When("Branch 1").SignalReceived("Signal1").WriteLine("Branch 1 executed", "WriteLine1").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", "WriteLine2").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", "WriteLine3").Then("Join");
|
fork.When("Branch 3").SignalReceived("Signal3").WriteLine("Branch 3 executed").WithName("WriteLine3").Then("Join");
|
||||||
})
|
})
|
||||||
.Add<Join>(join => join.Set(x => x.Mode, _joinMode)).WithName("Join")
|
.Add<Join>(join => join.Set(x => x.Mode, _joinMode)).WithName("Join")
|
||||||
.WriteLine("Finished", "Finished");
|
.WriteLine("Finished").WithName("Finished");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Elsa.Core.IntegrationTests.Workflows
|
||||||
{
|
{
|
||||||
@while
|
@while
|
||||||
.When(OutcomeNames.Iterate)
|
.When(OutcomeNames.Iterate)
|
||||||
.WriteLine(context => $"Inside while loop. Counter = {context.GetVariable<int>(CounterVariableName)}", id: "WriteLoopCount")
|
.WriteLine(context => $"Inside while loop. Counter = {context.GetVariable<int>(CounterVariableName)}").WithId("WriteLoopCount")
|
||||||
.SetVariable(CounterVariableName, context => GetCounter(context) + 1);
|
.SetVariable(CounterVariableName, context => GetCounter(context) + 1);
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue