From 09f11c026e2c539c2a8dfb02e3de4b79cae4f633 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 11 May 2021 22:12:21 +0200 Subject: [PATCH] Signaling fixes and enhancements --- .../SendSignal/SendSignalBuilderExtensions.cs | 31 ++++++++++++ .../SendSignal/SendSignalExtensions.cs | 29 +++++++++++ .../SignalReceived/SignalReceivedBookmark.cs | 10 ++-- .../components/icons/signal-received-icon.tsx | 2 +- .../src/plugins/send-signal-plugin.tsx | 22 +++++++++ .../src/plugins/signal-received-plugin.tsx | 22 +++++++++ .../src/services/plugin-manager.ts | 4 ++ .../Elsa.Samples.SignalingConsole/Program.cs | 49 ++++++------------- .../SignalReceiverWorkflow.cs | 16 ++++++ .../SignalSenderWorkflow.cs | 17 +++++++ .../TrafficLightWorkflow.cs | 20 -------- 11 files changed, 164 insertions(+), 58 deletions(-) create mode 100644 src/core/Elsa.Core/Activities/Signaling/Activities/SendSignal/SendSignalBuilderExtensions.cs create mode 100644 src/core/Elsa.Core/Activities/Signaling/Activities/SendSignal/SendSignalExtensions.cs create mode 100644 src/designer/elsa-workflows-studio/src/plugins/send-signal-plugin.tsx create mode 100644 src/designer/elsa-workflows-studio/src/plugins/signal-received-plugin.tsx create mode 100644 src/samples/console/Elsa.Samples.SignalingConsole/SignalReceiverWorkflow.cs create mode 100644 src/samples/console/Elsa.Samples.SignalingConsole/SignalSenderWorkflow.cs delete mode 100644 src/samples/console/Elsa.Samples.SignalingConsole/TrafficLightWorkflow.cs diff --git a/src/core/Elsa.Core/Activities/Signaling/Activities/SendSignal/SendSignalBuilderExtensions.cs b/src/core/Elsa.Core/Activities/Signaling/Activities/SendSignal/SendSignalBuilderExtensions.cs new file mode 100644 index 000000000..8801d8415 --- /dev/null +++ b/src/core/Elsa.Core/Activities/Signaling/Activities/SendSignal/SendSignalBuilderExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Elsa.Builders; +using Elsa.Services.Models; + +// ReSharper disable ExplicitCallerInfoArgument +// ReSharper disable once CheckNamespace +namespace Elsa.Activities.Signaling +{ + public static class SendSignalBuilderExtensions + { + public static IActivityBuilder SendSignal(this IBuilder builder, Action> setup, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => + builder.Then(setup, null, lineNumber, sourceFile); + + public static IActivityBuilder SendSignal(this IBuilder builder, Func signal, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => + builder.SendSignal(activity => activity.WithSignal(signal), lineNumber, sourceFile); + + public static IActivityBuilder SendSignal(this IBuilder builder, Func> signal, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => + builder.SendSignal(activity => activity.WithSignal(signal!), lineNumber, sourceFile); + + public static IActivityBuilder SendSignal(this IBuilder builder, Func signal, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => + builder.SendSignal(activity => activity.WithSignal(signal!), lineNumber, sourceFile); + + public static IActivityBuilder SendSignal(this IBuilder builder, Func> signal, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => + builder.SendSignal(activity => activity.WithSignal(signal!), lineNumber, sourceFile); + + public static IActivityBuilder SendSignal(this IBuilder builder, string signal, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => + builder.SendSignal(activity => activity.WithSignal(signal!), lineNumber, sourceFile); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Signaling/Activities/SendSignal/SendSignalExtensions.cs b/src/core/Elsa.Core/Activities/Signaling/Activities/SendSignal/SendSignalExtensions.cs new file mode 100644 index 000000000..47939ec3b --- /dev/null +++ b/src/core/Elsa.Core/Activities/Signaling/Activities/SendSignal/SendSignalExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; +using Elsa.Builders; +using Elsa.Services.Models; + +// ReSharper disable once CheckNamespace +namespace Elsa.Activities.Signaling +{ + public static class SendSignalExtensions + { + public static ISetupActivity WithSignal(this ISetupActivity activity, Func> value) => activity.Set(x => x.Signal, value); + public static ISetupActivity WithSignal(this ISetupActivity activity, Func value) => activity.Set(x => x.Signal, value); + public static ISetupActivity WithSignal(this ISetupActivity activity, Func> value) => activity.Set(x => x.Signal, value); + public static ISetupActivity WithSignal(this ISetupActivity activity, Func value) => activity.Set(x => x.Signal, value); + public static ISetupActivity WithSignal(this ISetupActivity activity, string? value) => activity.Set(x => x.Signal, value); + + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, Func> value) => activity.Set(x => x.CorrelationId, value); + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, Func value) => activity.Set(x => x.CorrelationId, value); + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, Func> value) => activity.Set(x => x.CorrelationId, value); + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, Func value) => activity.Set(x => x.CorrelationId, value); + public static ISetupActivity WithCorrelationId(this ISetupActivity activity, string? value) => activity.Set(x => x.CorrelationId, value); + + public static ISetupActivity WithInput(this ISetupActivity activity, Func> value) => activity.Set(x => x.Input, value); + public static ISetupActivity WithInput(this ISetupActivity activity, Func value) => activity.Set(x => x.Input, value); + public static ISetupActivity WithInput(this ISetupActivity activity, Func> value) => activity.Set(x => x.Input, value); + public static ISetupActivity WithInput(this ISetupActivity activity, Func value) => activity.Set(x => x.Input, value); + public static ISetupActivity WithInput(this ISetupActivity activity, object? value) => activity.Set(x => x.Input, value); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/Signaling/Activities/SignalReceived/SignalReceivedBookmark.cs b/src/core/Elsa.Core/Activities/Signaling/Activities/SignalReceived/SignalReceivedBookmark.cs index 44f39e3e0..2b3c25ca8 100644 --- a/src/core/Elsa.Core/Activities/Signaling/Activities/SignalReceived/SignalReceivedBookmark.cs +++ b/src/core/Elsa.Core/Activities/Signaling/Activities/SignalReceived/SignalReceivedBookmark.cs @@ -14,14 +14,18 @@ namespace Elsa.Activities.Signaling public class SignalReceivedBookmarkProvider : BookmarkProvider { - public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) => - new[] + public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) + { + var workflowInstanceId = context.Mode == BookmarkIndexingMode.WorkflowBlueprint ? default : context.ActivityExecutionContext.WorkflowInstance.Id; + + return new[] { new SignalReceivedBookmark { Signal = (await context.Activity.GetPropertyValueAsync(x => x.Signal, cancellationToken))!, - WorkflowInstanceId = context.ActivityExecutionContext.WorkflowInstance.Id + WorkflowInstanceId = workflowInstanceId } }; + } } } \ No newline at end of file diff --git a/src/designer/elsa-workflows-studio/src/components/icons/signal-received-icon.tsx b/src/designer/elsa-workflows-studio/src/components/icons/signal-received-icon.tsx index 5d7f31940..2e8528585 100644 --- a/src/designer/elsa-workflows-studio/src/components/icons/signal-received-icon.tsx +++ b/src/designer/elsa-workflows-studio/src/components/icons/signal-received-icon.tsx @@ -4,7 +4,7 @@ export const SignalReceivedIcon = props => ( ` ` ); diff --git a/src/designer/elsa-workflows-studio/src/plugins/send-signal-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/send-signal-plugin.tsx new file mode 100644 index 000000000..79ef4d32e --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/plugins/send-signal-plugin.tsx @@ -0,0 +1,22 @@ +import {ElsaPlugin} from "../services/elsa-plugin"; +import {eventBus} from '../services/event-bus'; +import {ActivityDesignDisplayContext, EventTypes, SyntaxNames} from "../models"; +import {h} from "@stencil/core"; + +export class SendSignalPlugin implements ElsaPlugin { + constructor() { + eventBus.on(EventTypes.ActivityDesignDisplaying, this.onActivityDisplaying); + } + + onActivityDisplaying(context: ActivityDesignDisplayContext) { + const activityModel = context.activityModel; + + if (activityModel.type !== 'SendSignal') + return; + + const props = activityModel.properties || []; + const signalName = props.find(x => x.name == 'Signal') || { name: 'Signal', expressions: { 'Literal': '', syntax: SyntaxNames.Literal } }; + const syntax = signalName.syntax || SyntaxNames.Literal; + context.bodyDisplay = `

${signalName.expressions[syntax]}

`; + } +} diff --git a/src/designer/elsa-workflows-studio/src/plugins/signal-received-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/signal-received-plugin.tsx new file mode 100644 index 000000000..c676ec108 --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/plugins/signal-received-plugin.tsx @@ -0,0 +1,22 @@ +import {ElsaPlugin} from "../services/elsa-plugin"; +import {eventBus} from '../services/event-bus'; +import {ActivityDesignDisplayContext, EventTypes, SyntaxNames} from "../models"; +import {h} from "@stencil/core"; + +export class SignalReceivedPlugin implements ElsaPlugin { + constructor() { + eventBus.on(EventTypes.ActivityDesignDisplaying, this.onActivityDisplaying); + } + + onActivityDisplaying(context: ActivityDesignDisplayContext) { + const activityModel = context.activityModel; + + if (activityModel.type !== 'SignalReceived') + return; + + const props = activityModel.properties || []; + const signalName = props.find(x => x.name == 'Signal') || { name: 'Signal', expressions: { 'Literal': '', syntax: SyntaxNames.Literal } }; + const syntax = signalName.syntax || SyntaxNames.Literal; + context.bodyDisplay = `

${signalName.expressions[syntax]}

`; + } +} diff --git a/src/designer/elsa-workflows-studio/src/services/plugin-manager.ts b/src/designer/elsa-workflows-studio/src/services/plugin-manager.ts index b31995733..5944f0ab2 100644 --- a/src/designer/elsa-workflows-studio/src/services/plugin-manager.ts +++ b/src/designer/elsa-workflows-studio/src/services/plugin-manager.ts @@ -12,6 +12,8 @@ import {SwitchPlugin} from "../plugins/switch-plugin"; import {WhilePlugin} from "../plugins/while-plugin"; import {StartAtPlugin} from "../plugins/start-at-plugin"; import {CronPlugin} from "../plugins/cron-plugin"; +import {SignalReceivedPlugin} from "../plugins/signal-received-plugin"; +import {SendSignalPlugin} from "../plugins/send-signal-plugin"; export class PluginManager { @@ -29,6 +31,8 @@ export class PluginManager { new TimerPlugin(), new StartAtPlugin(), new CronPlugin(), + new SignalReceivedPlugin(), + new SendSignalPlugin(), new WriteLinePlugin(), new RunJavascriptPlugin(), new SendEmailPlugin() diff --git a/src/samples/console/Elsa.Samples.SignalingConsole/Program.cs b/src/samples/console/Elsa.Samples.SignalingConsole/Program.cs index 0f522f046..1ac0dad04 100644 --- a/src/samples/console/Elsa.Samples.SignalingConsole/Program.cs +++ b/src/samples/console/Elsa.Samples.SignalingConsole/Program.cs @@ -1,14 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using Elsa.Activities.Signaling; -using Elsa.Activities.Signaling.Models; using Elsa.Activities.Signaling.Services; -using Elsa.Dispatch; -using Elsa.Models; using Elsa.Services; -using MediatR; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Samples.SignalingConsole @@ -24,41 +17,29 @@ namespace Elsa.Samples.SignalingConsole var services = new ServiceCollection() .AddElsa(options => options .AddConsoleActivities() - .AddWorkflow()) + .AddWorkflowsFrom()) .BuildServiceProvider(); - // Run startup actions (not needed when registering Elsa with a Host). + // Run startup actions (will index triggers such as SignalReceived from workflows). var startupRunner = services.GetRequiredService(); await startupRunner.StartupAsync(); - + + Console.WriteLine("Press enter to send a signal from a workflow:"); + Console.ReadLine(); + // Get a workflow runner. var workflowRunner = services.GetRequiredService(); - - // Define a couple of cars so we can correlate workflows with them. - var cars = new[] { "Car 1", "Car 2" }; - - var workflowInstances = new List(); - - // Execute a workflow for each car. - foreach (var car in cars) - { - var workflowInstance = await workflowRunner.BuildAndStartWorkflowAsync(correlationId: car); - workflowInstances.Add(workflowInstance); - } - - Console.WriteLine("Hit enter to signal green light for Car 2."); + + // This workflow will send a signal, which will be handled by the `SignalReceiverWorkflow`. + await workflowRunner.BuildAndStartWorkflowAsync(); + + // We can also trigger a signal manually: + Console.WriteLine(); + Console.WriteLine("Press enter again to send a signal manually:"); Console.ReadLine(); - - // The workflows are now suspended at the red light. - // Trigger a green light signal for the first car. - var signal = "Green"; - var correlationId = "Car 2"; - var car2Workflow = workflowInstances.First(x => x.CorrelationId == correlationId); + var signaler = services.GetRequiredService(); - - await signaler.TriggerSignalAsync(signal, workflowInstanceId: car2Workflow.Id); - - // Notice that only the workflow correlated to the second car executed. + await signaler.TriggerSignalAsync("Demo Signal"); // Keep the application alive for the workflow scheduler to have enough time to resume the workflow. Console.ReadLine(); diff --git a/src/samples/console/Elsa.Samples.SignalingConsole/SignalReceiverWorkflow.cs b/src/samples/console/Elsa.Samples.SignalingConsole/SignalReceiverWorkflow.cs new file mode 100644 index 000000000..2d0772ed6 --- /dev/null +++ b/src/samples/console/Elsa.Samples.SignalingConsole/SignalReceiverWorkflow.cs @@ -0,0 +1,16 @@ +using Elsa.Activities.Console; +using Elsa.Activities.ControlFlow; +using Elsa.Builders; + +namespace Elsa.Samples.SignalingConsole +{ + public class SignalReceiverWorkflow : IWorkflow + { + public void Build(IWorkflowBuilder builder) + { + builder + .SignalReceived("Demo Signal") + .WriteLine(context => $"Signal received!"); + } + } +} \ No newline at end of file diff --git a/src/samples/console/Elsa.Samples.SignalingConsole/SignalSenderWorkflow.cs b/src/samples/console/Elsa.Samples.SignalingConsole/SignalSenderWorkflow.cs new file mode 100644 index 000000000..dd1c93015 --- /dev/null +++ b/src/samples/console/Elsa.Samples.SignalingConsole/SignalSenderWorkflow.cs @@ -0,0 +1,17 @@ +using Elsa.Activities.Console; +using Elsa.Activities.Signaling; +using Elsa.Builders; + +namespace Elsa.Samples.SignalingConsole +{ + public class SignalSenderWorkflow : IWorkflow + { + public void Build(IWorkflowBuilder builder) + { + builder + .WriteLine(context => $"Sending signal from workflow!") + .SendSignal("Demo Signal") + .WriteLine(context => $"Signal sent from workflow!"); + } + } +} \ No newline at end of file diff --git a/src/samples/console/Elsa.Samples.SignalingConsole/TrafficLightWorkflow.cs b/src/samples/console/Elsa.Samples.SignalingConsole/TrafficLightWorkflow.cs deleted file mode 100644 index a0ad59fb5..000000000 --- a/src/samples/console/Elsa.Samples.SignalingConsole/TrafficLightWorkflow.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Elsa.Activities.Console; -using Elsa.Activities.ControlFlow; -using Elsa.Builders; -using Elsa.Services.Models; - -namespace Elsa.Samples.SignalingConsole -{ - public class TrafficLightWorkflow : IWorkflow - { - public void Build(IWorkflowBuilder builder) - { - builder - .WriteLine(context => $"{GetCarName(context)} is approaching red traffic light...") - .SignalReceived("Green") - .WriteLine(context => $"Light turned green for {GetCarName(context)}. Hit that power pedal!"); - } - - private string GetCarName(ActivityExecutionContext context) => context.WorkflowExecutionContext.CorrelationId; - } -} \ No newline at end of file