**BREAKING API** Update Builder API to allow to connect to named activities as well as activities by type name

This replaces the `Then(string activityName)` method with `ThenNamed(string activityName)`.
A new method is added to allow connecting to activities by type name: `ThenTypeNamed(string activityTypeName)`.

The reason for this change and addition is to allow workflow builders to connect to activity types that do not have a corresponding type.
For example, the Telnyx project has an activity type provider that *dynamically* provides activity types based on Telnyx webhook callback types.
This commit is contained in:
Sipke Schoorstra 2021-03-23 09:48:17 +01:00
parent 5d8d05aec5
commit 6fef9874da
30 changed files with 341 additions and 208 deletions

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
using Elsa.Activities.ControlFlow;
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
using Elsa.Attributes;
using Elsa.Builders;
using Elsa.Design;
@ -85,7 +86,7 @@ namespace Elsa.Activities.Telnyx.Activities
.WithFrom(() => From)
.WithFromDisplayName(() => FromDisplayName)
)
.Then();
.ThenTypeNamed(CallAnsweredPayload.ActivityTypeName);
}
object IActivityPropertyDefaultValueProvider.GetDefaultValue(PropertyInfo property) => Duration.FromSeconds(20);

View file

@ -2,10 +2,11 @@
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, "TelnyxCallAnswered", "Call Answered", "Triggered when an incoming call is answered.")]
[Payload(EventType, ActivityTypeName, "Call Answered", "Triggered when an incoming call is answered.")]
public sealed record CallAnsweredPayload : CallPayload
{
public const string EventType = "call.answered";
public const string ActivityTypeName = "CallAnswered";
public string From { get; init; } = default!;
public string To { get; init; } = default!;
public string State { get; init; } = default!;

View file

@ -2,10 +2,11 @@
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, "TelnyxCallDtmfReceived", "Call DTMF Received", "Triggered when DTMF input is received.")]
public sealed record CallDtmfReceived : CallPayload
[Payload(EventType, ActivityTypeName, "Call DTMF Received", "Triggered when DTMF input is received.")]
public sealed record CallDtmfReceivedPayload : CallPayload
{
public const string EventType = "call.dtmf.received";
public const string ActivityTypeName = "CallDtmfReceived";
public string Digit { get; set; }
public string From { get; set; }
public string To { get; set; }

View file

@ -2,10 +2,11 @@
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, "TelnyxCallGatherEnded", "Call Gather Ended", "Triggered when an call gather has ended.")]
public sealed record CallGatherEnded : CallPayload
[Payload(EventType, ActivityTypeName, "Call Gather Ended", "Triggered when an call gather has ended.")]
public sealed record CallGatherEndedPayload : CallPayload
{
public const string EventType = "call.gather.ended";
public const string ActivityTypeName = "CallGatherEnded";
public string Digits { get; set; }
public string From { get; set; }
public string To { get; set; }

View file

@ -3,10 +3,11 @@ using NodaTime;
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, "TelnyxCallHangup", "Call Hangup", "Triggered when an incoming call was hangup.")]
[Payload(EventType, ActivityTypeName, "Call Hangup", "Triggered when an incoming call was hangup.")]
public sealed record CallHangupPayload : CallPayload
{
public const string EventType = "call.hangup";
public const string ActivityTypeName = "CallHangup";
public Instant StartTime { get; init; }
public Instant EndTime { get; init; }
public string SipHangupCause { get; init; } = default!;

View file

@ -2,10 +2,11 @@
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, "TelnyxCallInitiated", "Call Initiated", "Triggered when an incoming call is received.")]
[Payload(EventType, ActivityTypeName, "Call Initiated", "Triggered when an incoming call is received.")]
public sealed record CallInitiatedPayload : CallPayload
{
public const string EventType = "call.initiated";
public const string ActivityTypeName = "CallInitiated";
public string Direction { get; init; } = default!;
public string State { get; init; } = default!;
}

View file

@ -1,11 +0,0 @@
using Elsa.Activities.Telnyx.Webhooks.Attributes;
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, "TelnyxCallPlaybackEnded", "Call Playback Ended", "Triggered when an audio playback has ended.")]
public sealed record CallPlaybackEnded : CallPlayback
{
public const string EventType = "call.playback.ended";
public string Status { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using Elsa.Activities.Telnyx.Webhooks.Attributes;
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, ActivityTypeName, "Call Playback Ended", "Triggered when an audio playback has ended.")]
public sealed record CallPlaybackEndedPayload : CallPlayback
{
public const string EventType = "call.playback.ended";
public const string ActivityTypeName = "CallPlaybackEnded";
public string Status { get; set; }
}
}

View file

@ -1,10 +0,0 @@
using Elsa.Activities.Telnyx.Webhooks.Attributes;
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, "TelnyxCallPlaybackStarted", "Call Playback Started", "Triggered when an audio playback has started.")]
public sealed record CallPlaybackStarted : CallPlayback
{
public const string EventType = "call.playback.started";
}
}

View file

@ -0,0 +1,11 @@
using Elsa.Activities.Telnyx.Webhooks.Attributes;
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Payload(EventType, ActivityTypeName, "Call Playback Started", "Triggered when an audio playback has started.")]
public sealed record CallPlaybackStartedPayload : CallPlayback
{
public const string EventType = "call.playback.started";
public const string ActivityTypeName = "CallPlaybackStarted";
}
}

View file

@ -10,6 +10,7 @@ namespace Elsa.Builders
{
ICompositeActivityBuilder WorkflowBuilder { get; set; }
public Type ActivityType { get; }
public string ActivityTypeName { get; }
string ActivityId { get; set; }
public string? Name { get; set; }
public string? DisplayName { get; set; }
@ -20,10 +21,10 @@ namespace Elsa.Builders
bool SaveWorkflowContextEnabled { get; set; }
bool PersistOutputEnabled { get; set; }
string? Source { get; }
IActivityBuilder Add<T>(Action<ISetupActivity<T>>? setup = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder Add<T>(string activityTypeName, Action<ISetupActivity<T>>? setup = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IOutcomeBuilder When(string outcome);
IActivityBuilder Then(IActivityBuilder targetActivity);
IConnectionBuilder Then(string activityName);
IConnectionBuilder ThenNamed(string activityName);
IActivityBuilder WithId(string? value);
IActivityBuilder WithName(string? value);
IActivityBuilder WithDisplayName(string? value);

View file

@ -6,7 +6,9 @@ namespace Elsa.Builders
{
public interface IBuilder
{
IActivityBuilder Then<T>(Action<ISetupActivity<T>>? setup, Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder Then<T>(string activityTypeName, Action<ISetupActivity<T>>? setup, Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
where T : class, IActivity;
IActivityBuilder Then<T>(string activityTypeName, Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) where T : class, IActivity;
}
}

View file

@ -12,34 +12,37 @@ namespace Elsa.Builders
IReadOnlyCollection<IActivityBuilder> Activities { get; }
IActivityBuilder New<T>(
string activityTypeName,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity;
IActivityBuilder New<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder StartWith<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder StartWith<T>(Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder StartWith<T>(string activityTypeName, Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder Add<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder Add<T>(
string activityTypeName,
Action<IActivityBuilder>? branch = default,
string? activityType = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)

View file

@ -17,11 +17,11 @@ namespace Elsa.Builders
public static class SetupActivityExtensions
{
// public static ISetupActivity<T> Set<T, TProperty>(
// this ISetupActivity<T> builder,
// Expression<Func<T, TProperty?>> propertyAccessor,
// Func<ActivityExecutionContext, ValueTask<TProperty?>> valueFactory) where T : IActivity =>
// builder.Set(propertyAccessor, async context => await valueFactory(context));
public static ISetupActivity<T> Set<T, TProperty>(
this ISetupActivity<T> builder,
Expression<Func<T, TProperty?>> propertyAccessor,
Func<ActivityExecutionContext, ValueTask<TProperty?>> valueFactory) where T : IActivity =>
builder.Set(propertyAccessor, async context => await valueFactory(context));
public static ISetupActivity<T> Set<T, TProperty>(
this ISetupActivity<T> builder,

View file

@ -6,20 +6,21 @@ using System.Runtime.CompilerServices;
using Elsa.Services;
using Elsa.Services.Models;
// ReSharper disable ExplicitCallerInfoArgument
namespace Elsa.Builders
{
public class ActivityBuilder : IActivityBuilder
{
public ActivityBuilder(
Type activityType,
string? activityTypeName,
string activityTypeName,
ICompositeActivityBuilder workflowBuilder,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders,
int lineNumber,
string? sourceFile)
{
ActivityType = activityType;
ActivityTypeName = activityTypeName ?? activityType.Name;
ActivityTypeName = activityTypeName;
WorkflowBuilder = workflowBuilder;
PropertyValueProviders = propertyValueProviders;
LineNumber = lineNumber;
@ -31,7 +32,7 @@ namespace Elsa.Builders
}
public Type ActivityType { get; protected set; } = default!;
public string ActivityTypeName { get; set; } = default!;
public string ActivityTypeName { get; protected set; } = default!;
public ICompositeActivityBuilder WorkflowBuilder { get; set; } = default!;
public string ActivityId { get; set; } = default!;
public string? Name { get; set; }
@ -47,27 +48,27 @@ namespace Elsa.Builders
public string? Source => SourceFile != null && LineNumber != default ? $"{Path.GetFileName(SourceFile)}:{LineNumber}" : default;
public IActivityBuilder Add<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup = default,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
WorkflowBuilder.Add(setup, null, activityType, lineNumber, sourceFile);
WorkflowBuilder.Add(activityTypeName, setup, null, lineNumber, sourceFile);
public IOutcomeBuilder When(string outcome) => new OutcomeBuilder(WorkflowBuilder, this, outcome);
public virtual IActivityBuilder Then<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup = null,
Action<IActivityBuilder>? branch = null,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
When(OutcomeNames.Done).Then(setup, branch, act, lineNumber, sourceFile);
When(OutcomeNames.Done).Then(activityTypeName, setup, branch, lineNumber, sourceFile);
public virtual IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = null, string? activityTypeName = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
public virtual IActivityBuilder Then<T>(string activityTypeName, Action<IActivityBuilder>? branch = null, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
When(OutcomeNames.Done).Then<T>(branch, lineNumber, sourceFile);
When(OutcomeNames.Done).Then<T>(activityTypeName, branch, lineNumber, sourceFile);
public virtual IActivityBuilder Then(IActivityBuilder targetActivity)
{
@ -75,7 +76,7 @@ namespace Elsa.Builders
return this;
}
public virtual IConnectionBuilder Then(string activityName) =>
public virtual IConnectionBuilder ThenNamed(string activityName) =>
WorkflowBuilder.Connect(
() => this,
() => WorkflowBuilder.Activities.First(x => x.Name == activityName));

View file

@ -1,65 +1,18 @@
using System;
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Elsa.Activities.Primitives;
using Elsa.ActivityResults;
using Elsa.Services.Models;
using Elsa.Services;
// ReSharper disable ExplicitCallerInfoArgument
namespace Elsa.Builders
{
public static class ActivityBuilderExtensions
{
public static IActivityBuilder Then(
this IBuilder builder,
Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> activity,
public static IActivityBuilder Add<T>(
this IActivityBuilder activityBuilder,
Action<ISetupActivity<T>>? setup = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.Then<Inline>(inline => inline.Set(x => x.Function, RunInline(activity)), null, lineNumber, sourceFile);
public static IActivityBuilder
Then(this IBuilder builder, Func<ActivityExecutionContext, ValueTask> activity, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.Then<Inline>(inline => inline.Set(x => x.Function, RunInline(activity)), null, lineNumber, sourceFile);
public static IActivityBuilder Then(this IBuilder builder, Action<ActivityExecutionContext> activity, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.Then<Inline>(inline => inline.Set(x => x.Function, RunInline(activity)), null, lineNumber, sourceFile);
public static IActivityBuilder Then(this IBuilder builder, Action activity) =>
builder.Then<Inline>(inline => inline.Set(x => x.Function, RunInline(activity)));
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(
Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> activity) =>
async context => await activity(context);
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(
Func<ActivityExecutionContext, ValueTask> activity) =>
async context =>
{
await activity(context);
return new OutcomeResult();
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(
Action<ActivityExecutionContext> activity) =>
context =>
{
activity(context);
return new ValueTask<IActivityExecutionResult>(new OutcomeResult());
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(
Func<ActivityExecutionContext, Task> activity) =>
async context =>
{
await activity(context);
return new OutcomeResult();
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Action activity) =>
context =>
{
activity();
return new ValueTask<IActivityExecutionResult>(new OutcomeResult());
};
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
activityBuilder.Add(typeof(T).Name, setup, lineNumber, sourceFile);
}
}

View file

@ -0,0 +1,42 @@
using System;
using System.Runtime.CompilerServices;
using Elsa.Services;
// ReSharper disable ExplicitCallerInfoArgument
namespace Elsa.Builders
{
public static class BuilderExtensions
{
public static IActivityBuilder Then<T>(
this IBuilder builder,
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity => builder.Then<T>(typeof(T).Name, setup, branch, lineNumber, sourceFile);
public static IActivityBuilder ThenTypeNamed(
this IBuilder builder,
string activityTypeName,
Action<ISetupActivity<IActivity>>? setup,
Action<IActivityBuilder>? branch = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
=> builder.Then(activityTypeName, setup, branch, lineNumber, sourceFile);
public static IActivityBuilder Then<T>(
this IBuilder builder,
Action<IActivityBuilder>? branch = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity =>
builder.Then<T>(typeof(T).Name, branch, lineNumber, sourceFile);
public static IActivityBuilder ThenTypeNamed(
this IBuilder builder,
string activityTypeName,
Action<IActivityBuilder>? branch = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.Then<IActivity>(activityTypeName, branch, lineNumber, sourceFile);
}
}

View file

@ -14,12 +14,11 @@ namespace Elsa.Builders
public class CompositeActivityBuilder : ActivityBuilder, ICompositeActivityBuilder
{
private readonly Func<ICompositeActivityBuilder> _compositeActivityBuilderFactory;
private readonly IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider;
private readonly IGetsStartActivitiesForCompositeActivityBlueprint _startingActivitiesProvider;
public CompositeActivityBuilder(IServiceProvider serviceProvider, IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider)
{
this.startingActivitiesProvider = startingActivitiesProvider ?? throw new ArgumentNullException(nameof(startingActivitiesProvider));
_startingActivitiesProvider = startingActivitiesProvider ?? throw new ArgumentNullException(nameof(startingActivitiesProvider));
ServiceProvider = serviceProvider;
ActivityBuilders = new List<IActivityBuilder>();
ConnectionBuilders = new List<IConnectionBuilder>();
@ -40,7 +39,7 @@ namespace Elsa.Builders
public IActivityBuilder New(
Type activityType,
string? activityTypeName = default,
string activityTypeName,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
@ -50,7 +49,7 @@ namespace Elsa.Builders
}
public IActivityBuilder New<T>(
string? activityTypeName = default,
string activityTypeName,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
@ -58,8 +57,8 @@ namespace Elsa.Builders
New(typeof(T), activityTypeName, propertyValueProviders, lineNumber, sourceFile);
public IActivityBuilder New<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup = default,
string? activityTypeName = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity
{
@ -74,62 +73,58 @@ namespace Elsa.Builders
}
public IActivityBuilder StartWith<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default,
string? activityTypeName = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity
{
var activityBuilder = New(setup, activityTypeName, lineNumber, sourceFile);
var activityBuilder = New(activityTypeName, setup, lineNumber, sourceFile);
return Add(activityBuilder, branch);
}
public IActivityBuilder StartWith<T>(Action<IActivityBuilder>? branch = default, string? activityTypeName = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
public IActivityBuilder StartWith<T>(string activityTypeName, Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
Add<T>(branch, activityTypeName, null, lineNumber, sourceFile);
Add<T>(activityTypeName, branch, null, lineNumber, sourceFile);
public IActivityBuilder Add<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity
{
var activityBuilder = New(setup, activityType, lineNumber, sourceFile);
var activityBuilder = New(activityTypeName, setup, lineNumber, sourceFile);
return Add(activityBuilder, branch);
}
public IActivityBuilder Add<T>(
string activityTypeName,
Action<IActivityBuilder>? branch = default,
string? activityType = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity
{
var activityBuilder = new ActivityBuilder(typeof(T), activityType, this, propertyValueProviders, lineNumber, sourceFile);
var activityBuilder = new ActivityBuilder( typeof(T), activityTypeName, this, propertyValueProviders, lineNumber, sourceFile);
return Add(activityBuilder, branch);
}
public IActivityBuilder Add(IActivityBuilder activityBuilder, Action<IActivityBuilder>? branch = default)
public IActivityBuilder Add(
IActivityBuilder activityBuilder,
Action<IActivityBuilder>? branch = default)
{
branch?.Invoke(activityBuilder);
ActivityBuilders.Add(activityBuilder);
return activityBuilder;
}
public override IActivityBuilder Then<T>(
Action<ISetupActivity<T>>? setup = null,
Action<IActivityBuilder>? branch = null,
string? activityTypeName = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
StartWith(setup, branch, activityTypeName, lineNumber, sourceFile);
public override IActivityBuilder Then<T>(string activityTypeName, Action<ISetupActivity<T>>? setup = null, Action<IActivityBuilder>? branch = null, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
StartWith(activityTypeName,setup, branch, lineNumber, sourceFile);
public override IActivityBuilder Then(IActivityBuilder targetActivity) => Add(targetActivity);
public override IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = null, string? activityTypeName = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
where T : class, IActivity => StartWith<T>(branch, activityTypeName, lineNumber, sourceFile);
public override IActivityBuilder Then<T>(string activityTypeName, Action<IActivityBuilder>? branch = null, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => StartWith<T>(activityTypeName, branch, lineNumber, sourceFile);
public IConnectionBuilder Connect(
IActivityBuilder source,
@ -215,18 +210,18 @@ namespace Elsa.Builders
compositeActivityBlueprint.ActivityPropertyProviders = compositeActivityBlueprint.ActivityPropertyProviders;
// Connect the composite activity to its starting activities.
var startActivities = startingActivitiesProvider.GetStartActivities(compositeActivityBlueprint).ToList();
var startActivities = _startingActivitiesProvider.GetStartActivities(compositeActivityBlueprint).ToList();
connections.AddRange(startActivities.Select(x => new Connection(compositeActivityBlueprint, x, CompositeActivity.Enter)));
}
}
private IActivityBlueprint BuildActivityBlueprint(IActivityBuilder builder, ICompositeActivityBlueprint parent)
private static IActivityBlueprint BuildActivityBlueprint(IActivityBuilder builder, ICompositeActivityBlueprint parent)
{
var isComposite = typeof(CompositeActivity).IsAssignableFrom(builder.ActivityType);
return isComposite
? new CompositeActivityBlueprint(builder.ActivityId, parent, builder.Name, builder.DisplayName, builder.Description, builder.ActivityType.Name, builder.PersistWorkflowEnabled, builder.LoadWorkflowContextEnabled,
? new CompositeActivityBlueprint(builder.ActivityId, parent, builder.Name, builder.DisplayName, builder.Description, builder.ActivityTypeName, builder.PersistWorkflowEnabled, builder.LoadWorkflowContextEnabled,
builder.SaveWorkflowContextEnabled, builder.PersistOutputEnabled, builder.Source)
: new ActivityBlueprint(builder.ActivityId, parent, builder.Name, builder.DisplayName, builder.Description, builder.ActivityType.Name, builder.PersistWorkflowEnabled, builder.LoadWorkflowContextEnabled,
: new ActivityBlueprint(builder.ActivityId, parent, builder.Name, builder.DisplayName, builder.Description, builder.ActivityTypeName, builder.PersistWorkflowEnabled, builder.LoadWorkflowContextEnabled,
builder.SaveWorkflowContextEnabled, builder.PersistOutputEnabled, builder.Source);
}
}

View file

@ -1,72 +1,52 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Elsa.Activities.Primitives;
using Elsa.ActivityResults;
using Elsa.Services.Models;
using Elsa.Services;
// ReSharper disable ExplicitCallerInfoArgument
namespace Elsa.Builders
{
public static class CompositeActivityBuilderExtensions
{
public static IActivityBuilder StartWith(this ICompositeActivityBuilder builder, Action action) => builder.StartWith<Inline>(inline => inline.Set(x => x.Function, RunInline(action)));
public static IActivityBuilder
StartWith(this ICompositeActivityBuilder builder, Action<ActivityExecutionContext> action, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.StartWith<Inline>(inline => inline.Set(x => x.Function, RunInline(action)), null, lineNumber, sourceFile);
public static IActivityBuilder StartWith(
this ICompositeActivityBuilder builder,
Func<ActivityExecutionContext, ValueTask> action,
public static IActivityBuilder New<T>(
this ICompositeActivityBuilder compositeActivityBuilder,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.StartWith<Inline>(inline => inline.Set(x => x.Function, RunInline(action)), null, lineNumber, sourceFile);
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity => compositeActivityBuilder.New<T>(typeof(T).Name, propertyValueProviders, lineNumber, sourceFile);
public static IActivityBuilder StartWith(
this ICompositeActivityBuilder builder,
Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> action,
public static IActivityBuilder New<T>(
this ICompositeActivityBuilder compositeActivityBuilder,
Action<ISetupActivity<T>>? setup,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.StartWith<Inline>(inline => inline.Set(x => x.Function, RunInline(action)), null, lineNumber, sourceFile);
[CallerFilePath] string? sourceFile = default) where T : class, IActivity => compositeActivityBuilder.New<T>(typeof(T).Name, setup, lineNumber, sourceFile);
public static IActivityBuilder SetVariable(this ICompositeActivityBuilder builder, string variableName, object? value, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.StartWith<SetVariable>(
activity =>
{
activity.Set(x => x.VariableName, _ => variableName);
activity.Set(x => x.Value, _ => value);
}, null, lineNumber, sourceFile);
public static IActivityBuilder SetVariable(
this ICompositeActivityBuilder builder,
string variableName,
Func<object?> value,
public static IActivityBuilder StartWith<T>(
this ICompositeActivityBuilder compositeActivityBuilder,
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.SetVariable(variableName, value(), lineNumber, sourceFile);
[CallerFilePath] string? sourceFile = default) where T : class, IActivity => compositeActivityBuilder.StartWith(typeof(T).Name, setup, branch, lineNumber, sourceFile);
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Action action) =>
_ =>
{
action();
return new ValueTask<IActivityExecutionResult>(new DoneResult());
};
public static IActivityBuilder StartWith<T>(
this ICompositeActivityBuilder compositeActivityBuilder,
Action<IActivityBuilder>? branch = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity => compositeActivityBuilder.StartWith<T>(typeof(T).Name, branch, lineNumber, sourceFile);
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Action<ActivityExecutionContext> action) =>
context =>
{
action(context);
return new ValueTask<IActivityExecutionResult>(new DoneResult());
};
public static IActivityBuilder Add<T>(
this ICompositeActivityBuilder compositeActivityBuilder,
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity => compositeActivityBuilder.Add(typeof(T).Name, setup, branch, lineNumber, sourceFile);
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Func<ActivityExecutionContext, ValueTask> action) =>
async context =>
{
await action(context);
return new DoneResult();
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> action) => async context => await action(context);
public static IActivityBuilder Add<T>(
this ICompositeActivityBuilder compositeActivityBuilder,
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity => compositeActivityBuilder.Add<T>(typeof(T).Name, branch, propertyValueProviders, lineNumber, sourceFile);
}
}

View file

@ -0,0 +1,65 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Elsa.Activities.Primitives;
using Elsa.ActivityResults;
using Elsa.Services.Models;
// ReSharper disable ExplicitCallerInfoArgument
namespace Elsa.Builders
{
public static class InlineActivityBuilderExtensions
{
public static IActivityBuilder Then(
this IBuilder builder,
Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> activity,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.Then<Inline>(inline => inline.Set(x => x.Function, RunInline(activity)), null, lineNumber, sourceFile);
public static IActivityBuilder
Then(this IBuilder builder, Func<ActivityExecutionContext, ValueTask> activity, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.Then<Inline>(inline => inline.Set(x => x.Function, RunInline(activity)), null, lineNumber, sourceFile);
public static IActivityBuilder Then(this IBuilder builder, Action<ActivityExecutionContext> activity, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.Then<Inline>(inline => inline.Set(x => x.Function, RunInline(activity)), null, lineNumber, sourceFile);
public static IActivityBuilder Then(this IBuilder builder, Action activity) =>
builder.Then<Inline>(inline => inline.Set(x => x.Function, RunInline(activity)));
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(
Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> activity) =>
async context => await activity(context);
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(
Func<ActivityExecutionContext, ValueTask> activity) =>
async context =>
{
await activity(context);
return new OutcomeResult();
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(
Action<ActivityExecutionContext> activity) =>
context =>
{
activity(context);
return new ValueTask<IActivityExecutionResult>(new OutcomeResult());
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(
Func<ActivityExecutionContext, Task> activity) =>
async context =>
{
await activity(context);
return new OutcomeResult();
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Action activity) =>
context =>
{
activity();
return new ValueTask<IActivityExecutionResult>(new OutcomeResult());
};
}
}

View file

@ -0,0 +1,56 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Elsa.Activities.Primitives;
using Elsa.ActivityResults;
using Elsa.Services.Models;
// ReSharper disable ExplicitCallerInfoArgument
namespace Elsa.Builders
{
public static class InlineCompositeActivityBuilderExtensions
{
public static IActivityBuilder StartWith(this ICompositeActivityBuilder builder, Action action) => builder.StartWith<Inline>(inline => inline.Set(x => x.Function, RunInline(action)));
public static IActivityBuilder
StartWith(this ICompositeActivityBuilder builder, Action<ActivityExecutionContext> action, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.StartWith<Inline>(inline => inline.Set(x => x.Function, RunInline(action)), null, lineNumber, sourceFile);
public static IActivityBuilder StartWith(
this ICompositeActivityBuilder builder,
Func<ActivityExecutionContext, ValueTask> action,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.StartWith<Inline>(inline => inline.Set(x => x.Function, RunInline(action)), null, lineNumber, sourceFile);
public static IActivityBuilder StartWith(
this ICompositeActivityBuilder builder,
Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> action,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.StartWith<Inline>(inline => inline.Set(x => x.Function, RunInline(action)), null, lineNumber, sourceFile);
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Action action) =>
_ =>
{
action();
return new ValueTask<IActivityExecutionResult>(new DoneResult());
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Action<ActivityExecutionContext> action) =>
context =>
{
action(context);
return new ValueTask<IActivityExecutionResult>(new DoneResult());
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Func<ActivityExecutionContext, ValueTask> action) =>
async context =>
{
await action(context);
return new DoneResult();
};
private static Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> RunInline(Func<ActivityExecutionContext, ValueTask<IActivityExecutionResult>> action) => async context => await action(context);
}
}

View file

@ -21,21 +21,21 @@ namespace Elsa.Builders
public string Outcome { get; }
public IActivityBuilder Then<T>(
string activityTypeName,
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity
{
var activityBuilder = WorkflowBuilder.Add(setup, default, activityType, lineNumber, sourceFile);
var activityBuilder = WorkflowBuilder.Add(activityTypeName, setup, null, lineNumber, sourceFile);
Then(activityBuilder, branch);
return activityBuilder;
}
public IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
public IActivityBuilder Then<T>(string activityTypeName, Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
where T : class, IActivity
{
var activityBuilder = WorkflowBuilder.Add<T>(branch, null, lineNumber, sourceFile);
var activityBuilder = WorkflowBuilder.Add<T>(activityTypeName, branch, null, lineNumber, sourceFile);
Then(activityBuilder);
return activityBuilder;
}

View file

@ -0,0 +1,26 @@
using System;
using System.Runtime.CompilerServices;
using Elsa.Activities.Primitives;
// ReSharper disable ExplicitCallerInfoArgument
namespace Elsa.Builders
{
public static class VariableCompositeActivityBuilderExtensions
{
public static IActivityBuilder SetVariable(this ICompositeActivityBuilder builder, string variableName, object? value, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.StartWith<SetVariable>(
activity =>
{
activity.Set(x => x.VariableName, _ => variableName);
activity.Set(x => x.Value, _ => value);
}, null, lineNumber, sourceFile);
public static IActivityBuilder SetVariable(
this ICompositeActivityBuilder builder,
string variableName,
Func<object?> value,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
builder.SetVariable(variableName, value(), lineNumber, sourceFile);
}
}

View file

@ -17,6 +17,7 @@ namespace Elsa.Builders
CustomAttributes = new Variables();
ActivityId = idGenerator.Generate();
ActivityType = typeof(Workflow);
ActivityTypeName = nameof(Workflow);
WorkflowBuilder = this;
PropertyValueProviders = new Dictionary<string, IActivityPropertyValueProvider>();
PersistenceBehavior = WorkflowPersistenceBehavior.WorkflowBurst;

View file

@ -35,13 +35,13 @@ namespace Elsa.Samples.ForkJoinTimerAndSignalHttp.Workflows
.When("Timer")
.Timer(_timeOut)
.SetVariable("CompletedVia", "Timer")
.Then("Join");
.ThenNamed("Join");
fork
.When("Signal")
.SignalReceived("hurry")
.SetVariable("CompletedVia", "Signal")
.Then("Join");
.ThenNamed("Join");
})
.Add<Join>(x => x.WithMode(Join.JoinMode.WaitAny)).WithName("Join")
.WriteLine(context => $"Demo {GetCorrelationId(context)} completed successfully via {context.GetVariable<string>("CompletedVia")}!");

View file

@ -17,17 +17,17 @@ namespace Elsa.Samples.UserTaskConsole
userTask
.When("Accept")
.WriteLine("Great! Your work has been accepted.")
.Then("Exit");
.ThenNamed("Exit");
userTask
.When("Reject")
.WriteLine("Sorry! Your work has been rejected.")
.Then("Exit");
.ThenNamed("Exit");
userTask
.When("Needs Work")
.WriteLine("So close! Your work needs a little bit more work.")
.Then("Exit");
.ThenNamed("Exit");
}
)
.WriteLine("Workflow finished.").WithName("Exit");

View file

@ -19,20 +19,20 @@ namespace Elsa.Samples.Temporal.Activities
.While(true, @while => @while
.Timer(Duration.FromSeconds(1))
.WriteLine("Timer A went off"))
.Then("Join1");
.ThenNamed("Join1");
fork
.When("B")
.While(true, @while => @while
.Timer(Duration.FromSeconds(5))
.WriteLine("Timer B went off"))
.Then("Join1");
.ThenNamed("Join1");
fork
.When("C")
.Timer(Duration.FromSeconds(6))
.WriteLine("Timer C went off")
.Then("Join1");
.ThenNamed("Join1");
})
.Add<Join>(join => join.WithMode(Join.JoinMode.WaitAny)).WithName("Join1")
.WriteLine("Container 1 Joined!")

View file

@ -18,14 +18,14 @@ namespace Elsa.Samples.Temporal.Activities
.When("D")
.Timer(Duration.FromSeconds(20))
.WriteLine("Timer D went off. Exiting fork")
.Then("Join2");
.ThenNamed("Join2");
fork
.When("E")
.While(true, iterate => iterate
.Timer(Duration.FromSeconds(5))
.WriteLine("Timer E went off. Looping."))
.Then("Join2");
.ThenNamed("Join2");
})
.Add<Join>(join => join.WithMode(Join.JoinMode.WaitAny)).WithName("Join2")
.WriteLine("Container 2 Joined!")

View file

@ -20,7 +20,7 @@ namespace Elsa.Samples.Temporal.Workflows
.When("A")
.Timer(Duration.FromSeconds(5)).WithId("timer-a")
.WriteLine("Timer A went off. Exiting fork").WithId("write-line-a")
.Then("Join2");
.ThenNamed("Join2");
fork
.When("B")
@ -29,7 +29,7 @@ namespace Elsa.Samples.Temporal.Workflows
.Timer(Duration.FromSeconds(1)).WithId("timer-b")
.WriteLine("Timer B went off. Looping.").WithId("write-line-b"))
).WithId("while-true")
.Then("Join2");
.ThenNamed("Join2");
})
.Add<Join>(join => join.WithMode(Join.JoinMode.WaitAny)).WithName("Join2")
).WithId("for-2"))

View file

@ -20,9 +20,9 @@ namespace Elsa.Core.IntegrationTests.Workflows
activity => activity.Set(x => x.Branches, new HashSet<string>(new[] { "Branch 1", "Branch 2", "Branch 3" })),
fork =>
{
fork.When("Branch 1").SignalReceived("Signal1").WriteLine("Branch 1 executed").WithName("WriteLine1").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").WithName("WriteLine3").Then("Join");
fork.When("Branch 1").SignalReceived("Signal1").WriteLine("Branch 1 executed").WithName("WriteLine1").ThenNamed("Join");
fork.When("Branch 2").SignalReceived("Signal2").WriteLine("Branch 2 executed").WithName("WriteLine2").ThenNamed("Join");
fork.When("Branch 3").SignalReceived("Signal3").WriteLine("Branch 3 executed").WithName("WriteLine3").ThenNamed("Join");
})
.Add<Join>(join => join.Set(x => x.Mode, _joinMode)).WithName("Join")
.WriteLine("Finished").WithName("Finished");