Signaling fixes and enhancements

This commit is contained in:
Sipke Schoorstra 2021-05-11 22:12:21 +02:00
parent 37c07be05c
commit 09f11c026e
11 changed files with 164 additions and 58 deletions

View file

@ -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<ISetupActivity<SendSignal>> setup, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
builder.Then(setup, null, lineNumber, sourceFile);
public static IActivityBuilder SendSignal(this IBuilder builder, Func<ActivityExecutionContext, string> 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<ActivityExecutionContext, ValueTask<string>> 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<string> 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<ValueTask<string>> 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);
}
}

View file

@ -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<SendSignal> WithSignal(this ISetupActivity<SendSignal> activity, Func<ActivityExecutionContext, ValueTask<string?>> value) => activity.Set(x => x.Signal, value);
public static ISetupActivity<SendSignal> WithSignal(this ISetupActivity<SendSignal> activity, Func<ActivityExecutionContext, string?> value) => activity.Set(x => x.Signal, value);
public static ISetupActivity<SendSignal> WithSignal(this ISetupActivity<SendSignal> activity, Func<ValueTask<string?>> value) => activity.Set(x => x.Signal, value);
public static ISetupActivity<SendSignal> WithSignal(this ISetupActivity<SendSignal> activity, Func<string?> value) => activity.Set(x => x.Signal, value);
public static ISetupActivity<SendSignal> WithSignal(this ISetupActivity<SendSignal> activity, string? value) => activity.Set(x => x.Signal, value);
public static ISetupActivity<SendSignal> WithCorrelationId(this ISetupActivity<SendSignal> activity, Func<ActivityExecutionContext, ValueTask<string?>> value) => activity.Set(x => x.CorrelationId, value);
public static ISetupActivity<SendSignal> WithCorrelationId(this ISetupActivity<SendSignal> activity, Func<ActivityExecutionContext, string?> value) => activity.Set(x => x.CorrelationId, value);
public static ISetupActivity<SendSignal> WithCorrelationId(this ISetupActivity<SendSignal> activity, Func<ValueTask<string?>> value) => activity.Set(x => x.CorrelationId, value);
public static ISetupActivity<SendSignal> WithCorrelationId(this ISetupActivity<SendSignal> activity, Func<string?> value) => activity.Set(x => x.CorrelationId, value);
public static ISetupActivity<SendSignal> WithCorrelationId(this ISetupActivity<SendSignal> activity, string? value) => activity.Set(x => x.CorrelationId, value);
public static ISetupActivity<SendSignal> WithInput(this ISetupActivity<SendSignal> activity, Func<ActivityExecutionContext, ValueTask<object?>> value) => activity.Set(x => x.Input, value);
public static ISetupActivity<SendSignal> WithInput(this ISetupActivity<SendSignal> activity, Func<ActivityExecutionContext, object?> value) => activity.Set(x => x.Input, value);
public static ISetupActivity<SendSignal> WithInput(this ISetupActivity<SendSignal> activity, Func<ValueTask<object?>> value) => activity.Set(x => x.Input, value);
public static ISetupActivity<SendSignal> WithInput(this ISetupActivity<SendSignal> activity, Func<object?> value) => activity.Set(x => x.Input, value);
public static ISetupActivity<SendSignal> WithInput(this ISetupActivity<SendSignal> activity, object? value) => activity.Set(x => x.Input, value);
}
}

View file

@ -14,14 +14,18 @@ namespace Elsa.Activities.Signaling
public class SignalReceivedBookmarkProvider : BookmarkProvider<SignalReceivedBookmark, SignalReceived>
{
public override async ValueTask<IEnumerable<IBookmark>> GetBookmarksAsync(BookmarkProviderContext<SignalReceived> context, CancellationToken cancellationToken) =>
new[]
public override async ValueTask<IEnumerable<IBookmark>> GetBookmarksAsync(BookmarkProviderContext<SignalReceived> 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
}
};
}
}
}

View file

@ -4,7 +4,7 @@ export const SignalReceivedIcon = props =>
(
`<span class="${`rounded-lg inline-flex p-3 bg-rose-50 text-rose-700 ring-4 ring-white`}">
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke="none" d="M0 0h24v24H0z"/> <polyline points="21 12 17 12 14 20 10 4 7 12 3 12"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
</span>`
);

View file

@ -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 = `<p>${signalName.expressions[syntax]}</p>`;
}
}

View file

@ -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 = `<p>${signalName.expressions[syntax]}</p>`;
}
}

View file

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

View file

@ -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<TrafficLightWorkflow>())
.AddWorkflowsFrom<SignalReceiverWorkflow>())
.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<IStartupRunner>();
await startupRunner.StartupAsync();
Console.WriteLine("Press enter to send a signal from a workflow:");
Console.ReadLine();
// Get a workflow runner.
var workflowRunner = services.GetRequiredService<IBuildsAndStartsWorkflow>();
// Define a couple of cars so we can correlate workflows with them.
var cars = new[] { "Car 1", "Car 2" };
var workflowInstances = new List<WorkflowInstance>();
// Execute a workflow for each car.
foreach (var car in cars)
{
var workflowInstance = await workflowRunner.BuildAndStartWorkflowAsync<TrafficLightWorkflow>(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<SignalSenderWorkflow>();
// 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<ISignaler>();
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();

View file

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

View file

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

View file

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