From 79b5bfc378762af2429988d17891328325de7600 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Mon, 23 Sep 2024 11:35:43 +0200 Subject: [PATCH] Add OpenTelemetry support for tracing workflow execution This commit introduces the Elsa.OpenTelemetry module to provide OpenTelemetry sources for tracing workflow and activity execution. Additionally, it updates the program to include default workflow and activity execution pipelines with tracing middleware, and improves pipeline builder extensions to support middleware insertion. --- .github/workflows/elsa-server-and-studio.yml | 4 +- .github/workflows/elsa-server.yml | 4 +- .github/workflows/elsa-studio.yml | 4 +- .github/workflows/packages.yml | 15 +----- Elsa.sln | 7 +++ .../Elsa.Server.Web/Elsa.Server.Web.csproj | 1 + src/bundles/Elsa.Server.Web/Program.cs | 6 +++ .../Elsa.OpenTelemetry.csproj | 14 ++++++ .../Features/OpenTelemetryFeature.cs | 11 +++++ .../Elsa.OpenTelemetry/FodyWeavers.xml | 3 ++ .../Helpers/OpenTelemetryHelpers.cs | 8 +++ ...metryTracingActivityExecutionMiddleware.cs | 42 ++++++++++++++++ ...metryTracingWorkflowExecutionMiddleware.cs | 49 +++++++++++++++++++ .../IActivityExecutionPipelineBuilder.cs | 3 ++ .../IWorkflowExecutionPipelineBuilder.cs | 3 ++ .../ActivityExecutionMiddlewareExtensions.cs | 26 ++++++++-- ...ctivityExecutionPipelinePipelineBuilder.cs | 7 +++ .../WorkflowExecutionMiddlewareExtensions.cs | 22 ++++----- .../WorkflowExecutionPipelineBuilder.cs | 8 +++ .../PipelineWorkflowsFeatureExtensions.cs | 35 +++++++++++++ .../Extensions/WorkflowsFeatureExtensions.cs | 1 + 21 files changed, 238 insertions(+), 35 deletions(-) create mode 100644 src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj create mode 100644 src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs create mode 100644 src/modules/Elsa.OpenTelemetry/FodyWeavers.xml create mode 100644 src/modules/Elsa.OpenTelemetry/Helpers/OpenTelemetryHelpers.cs create mode 100644 src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs create mode 100644 src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs create mode 100644 src/modules/Elsa.Workflows.Runtime/Extensions/PipelineWorkflowsFeatureExtensions.cs diff --git a/.github/workflows/elsa-server-and-studio.yml b/.github/workflows/elsa-server-and-studio.yml index 8f58dbcc2..cbca2daf3 100644 --- a/.github/workflows/elsa-server-and-studio.yml +++ b/.github/workflows/elsa-server-and-studio.yml @@ -3,7 +3,7 @@ on: workflow_dispatch: push: branches: - - patch/3.2.x + - blueberry jobs: push_to_registry: @@ -29,7 +29,7 @@ jobs: with: # list of Docker images to use as base name for tags images: | - elsaworkflows/elsa-server-and-studio-v3-2-1-preview + elsaworkflows/elsa-server-and-studio-v3-2-1-blueberry flavor: | latest=true # generate Docker tags based on the following events/attributes diff --git a/.github/workflows/elsa-server.yml b/.github/workflows/elsa-server.yml index 82a92a31b..e305d5072 100644 --- a/.github/workflows/elsa-server.yml +++ b/.github/workflows/elsa-server.yml @@ -3,7 +3,7 @@ on: workflow_dispatch: push: branches: - - patch/3.2.x + - blueberry jobs: push_to_registry: @@ -29,7 +29,7 @@ jobs: with: # list of Docker images to use as base name for tags images: | - elsaworkflows/elsa-server-v3-2-1-preview + elsaworkflows/elsa-server-v3-2-1-blueberry flavor: | latest=true # generate Docker tags based on the following events/attributes diff --git a/.github/workflows/elsa-studio.yml b/.github/workflows/elsa-studio.yml index a8673a0dd..a275af27d 100644 --- a/.github/workflows/elsa-studio.yml +++ b/.github/workflows/elsa-studio.yml @@ -3,7 +3,7 @@ on: workflow_dispatch: push: branches: - - patch/3.2.x + - blueberry jobs: push_to_registry: @@ -29,7 +29,7 @@ jobs: with: # list of Docker images to use as base name for tags images: | - elsaworkflows/elsa-studio-v3-2-1-preview + elsaworkflows/elsa-studio-v3-2-1-blueberry flavor: | latest=true # generate Docker tags based on the following events/attributes diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index c4461693c..2869399e3 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -3,18 +3,7 @@ on: workflow_dispatch: push: branches: - - 'main' - - 'feature/*' - - 'feat/*' - - 'issue/*' - - 'bug/*' - - 'enhancement/*' - - 'enh/*' - - 'patch/*' - - 'fix/*' - - 'perf/*' - - 'hotfix/*' - - 'chore/*' + - 'blueberry' release: types: [ prereleased, published ] env: @@ -62,7 +51,7 @@ jobs: TAG_NAME=${TAG_NAME#refs/tags/} # remove the refs/tags/ prefix echo "VERSION=${TAG_NAME}" >> $GITHUB_ENV else - echo "VERSION=3.2.1-preview.${{github.run_number}}" >> $GITHUB_ENV + echo "VERSION=3.2.1-blueberry.${{github.run_number}}" >> $GITHUB_ENV fi - name: Set up JDK 17 uses: actions/setup-java@v2 diff --git a/Elsa.sln b/Elsa.sln index 3d9bca0c0..58f882779 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -355,6 +355,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Workflows.PerformanceT EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.MongoDb.UnitTests", "test\unit\Elsa.MongoDb.UnitTests\Elsa.MongoDb.UnitTests.csproj", "{56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.OpenTelemetry", "src\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj", "{25880971-403E-4872-93A1-D33089E07C91}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -844,6 +846,10 @@ Global {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Debug|Any CPU.Build.0 = Debug|Any CPU {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Release|Any CPU.Build.0 = Release|Any CPU + {25880971-403E-4872-93A1-D33089E07C91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25880971-403E-4872-93A1-D33089E07C91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25880971-403E-4872-93A1-D33089E07C91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25880971-403E-4872-93A1-D33089E07C91}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -991,6 +997,7 @@ Global {CBB515F3-A0EF-43B5-A907-FD4E652DD66E} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} {90CD37A9-C866-4D90-A3B1-8C87F53B845E} = {CBB515F3-A0EF-43B5-A907-FD4E652DD66E} {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1} = {18453B51-25EB-4317-A4B3-B10518252E92} + {25880971-403E-4872-93A1-D33089E07C91} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E} diff --git a/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj index 71cb87c96..ff8b01a9d 100644 --- a/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj +++ b/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj @@ -12,6 +12,7 @@ + diff --git a/src/bundles/Elsa.Server.Web/Program.cs b/src/bundles/Elsa.Server.Web/Program.cs index 33328dace..d72e04aca 100644 --- a/src/bundles/Elsa.Server.Web/Program.cs +++ b/src/bundles/Elsa.Server.Web/Program.cs @@ -20,6 +20,7 @@ using Elsa.MongoDb.Modules.Alterations; using Elsa.MongoDb.Modules.Identity; using Elsa.MongoDb.Modules.Management; using Elsa.MongoDb.Modules.Runtime; +using Elsa.OpenTelemetry.Middleware; using Elsa.Server.Web; using Elsa.Workflows; using Elsa.Workflows.Management.Compression; @@ -131,6 +132,11 @@ services identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options)); }) .UseDefaultAuthentication() + .UseWorkflows(workflows => + { + workflows.WithDefaultWorkflowExecutionPipeline(pipeline => pipeline.UseWorkflowExecutionTracing()); + workflows.WithDefaultActivityExecutionPipeline(pipeline => pipeline.UseActivityExecutionTracing()); + }) .UseWorkflowManagement(management => { if (useMongoDb) diff --git a/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj b/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj new file mode 100644 index 000000000..dc06e309b --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj @@ -0,0 +1,14 @@ + + + + + Provides OpenTelemetry sources for tracing workflow and activity execution. + + elsa module open-telemetry + + + + + + + \ No newline at end of file diff --git a/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs b/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs new file mode 100644 index 000000000..67201c44d --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/Features/OpenTelemetryFeature.cs @@ -0,0 +1,11 @@ +using Elsa.Features.Abstractions; +using Elsa.Features.Services; + +namespace Elsa.OpenTelemetry.Features; + +public class OpenTelemetryFeature(IModule module) : FeatureBase(module) +{ + public override void Configure() + { + } +} \ No newline at end of file diff --git a/src/modules/Elsa.OpenTelemetry/FodyWeavers.xml b/src/modules/Elsa.OpenTelemetry/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/modules/Elsa.OpenTelemetry/Helpers/OpenTelemetryHelpers.cs b/src/modules/Elsa.OpenTelemetry/Helpers/OpenTelemetryHelpers.cs new file mode 100644 index 000000000..302659456 --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/Helpers/OpenTelemetryHelpers.cs @@ -0,0 +1,8 @@ +using System.Diagnostics; + +namespace Elsa.OpenTelemetry.Helpers; + +public class ElsaOpenTelemetry +{ + public static readonly ActivitySource ActivitySource = new("Elsa.Workflows"); +} \ No newline at end of file diff --git a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs new file mode 100644 index 000000000..cd389bdb7 --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs @@ -0,0 +1,42 @@ +using System.Diagnostics; +using Elsa.OpenTelemetry.Helpers; +using Elsa.Workflows; +using Elsa.Workflows.Contracts; +using Elsa.Workflows.Pipelines.ActivityExecution; +using Elsa.Workflows.Pipelines.WorkflowExecution; +using JetBrains.Annotations; +using Activity = System.Diagnostics.Activity; +using ActivityKind = System.Diagnostics.ActivityKind; + +namespace Elsa.OpenTelemetry.Middleware; + +/// +[UsedImplicitly] +public class OpenTelemetryTracingActivityExecutionMiddleware(ActivityMiddlewareDelegate next) : IActivityExecutionMiddleware +{ + /// + public async ValueTask InvokeAsync(ActivityExecutionContext context) + { + var activity = context.Activity; + using var span = ElsaOpenTelemetry.ActivitySource.StartActivity($"ActivityExecution {context.ActivityDescriptor.TypeName}", ActivityKind.Internal, Activity.Current?.Context ?? default); + span?.AddTag("activity.nodeId", activity.NodeId); + span?.AddTag("activity.type", activity.Type); + span?.AddTag("activity.name", activity.Name); + span?.AddTag("activityInstance.id", context.Id); + span?.AddTag("activityInstance.originalStatus", context.Status.ToString()); + span?.AddEvent(new ActivityEvent("Executing")); + await next(context); + span?.AddEvent(new ActivityEvent("Executed")); + span?.AddTag("activityInstance.newStatus", context.Status.ToString()); + } +} + +/// +/// Contains extension methods for . +/// +[UsedImplicitly] +public static class OpenTelemetryTracingActivityExecutionMiddlewareExtensions +{ + /// Installs the component in the workflow execution pipeline. + public static IActivityExecutionPipelineBuilder UseActivityExecutionTracing(this IActivityExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.Insert(0); +} diff --git a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs new file mode 100644 index 000000000..ae6d67572 --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs @@ -0,0 +1,49 @@ +using System.Diagnostics; +using Elsa.OpenTelemetry.Helpers; +using Elsa.Workflows; +using Elsa.Workflows.Contracts; +using Elsa.Workflows.Pipelines.WorkflowExecution; +using JetBrains.Annotations; +using Activity = System.Diagnostics.Activity; +using ActivityKind = System.Diagnostics.ActivityKind; + +namespace Elsa.OpenTelemetry.Middleware; + +/// +/// Middleware that traces workflow execution using OpenTelemetry. +/// +[UsedImplicitly] +public class OpenTelemetryTracingWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next) : WorkflowExecutionMiddleware(next) +{ + /// + public override async ValueTask InvokeAsync(WorkflowExecutionContext context) + { + var workflowInstanceId = context.Id; + var workflow = context.Workflow; + using var activity = ElsaOpenTelemetry.ActivitySource.StartActivity($"WorkflowExecution {workflow.WorkflowMetadata.Name}", ActivityKind.Internal, Activity.Current?.Context ?? default); + + if(!string.IsNullOrWhiteSpace(context.CorrelationId)) + activity?.AddTag("correlationId", context.CorrelationId); + + activity?.AddTag("workflowInstance.id", workflowInstanceId); + activity?.AddTag("workflowDefinition.definitionId", workflow.Identity.DefinitionId); + activity?.AddTag("workflowDefinition.version", workflow.Identity.Version); + activity?.AddTag("workflowInstance.originalStatus", context.Status.ToString()); + activity?.AddTag("workflowInstance.originalSubStatus", context.SubStatus.ToString()); + activity?.AddEvent(new ActivityEvent("Executing")); + await Next(context); + activity?.AddEvent(new ActivityEvent("Executed")); + activity?.AddTag("workflowInstance.newStatus", context.Status.ToString()); + activity?.AddTag("workflowInstance.newSubStatus", context.SubStatus.ToString()); + } +} + +/// +/// Contains extension methods for . +/// +[UsedImplicitly] +public static class OpenTelemetryWorkflowExecutionMiddlewareExtensions +{ + /// Installs the component in the workflow execution pipeline. + public static IWorkflowExecutionPipelineBuilder UseWorkflowExecutionTracing(this IWorkflowExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.Insert(0); +} diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionPipelineBuilder.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionPipelineBuilder.cs index 7c7968451..6cf6c926d 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionPipelineBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionPipelineBuilder.cs @@ -19,6 +19,9 @@ public interface IActivityExecutionPipelineBuilder /// The current . IActivityExecutionPipelineBuilder Use(Func middleware); + /// Inserts the middleware component at the specified index. + IActivityExecutionPipelineBuilder Insert(int index, Func middleware); + /// /// Constructs the final delegate that invokes each installed middleware component. /// diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowExecutionPipelineBuilder.cs b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowExecutionPipelineBuilder.cs index a2f37c9b0..04ce5ff62 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowExecutionPipelineBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowExecutionPipelineBuilder.cs @@ -36,6 +36,9 @@ public interface IWorkflowExecutionPipelineBuilder /// Clears the current pipeline. /// IWorkflowExecutionPipelineBuilder Reset(); + + /// Inserts the middleware component at the specified index. + IWorkflowExecutionPipelineBuilder Insert(int index, Func middleware); /// /// Replaces the middleware component at the specified index with the specified delegate. diff --git a/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs b/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs index 487b41340..c42152859 100644 --- a/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Elsa.Workflows.Contracts; using Microsoft.Extensions.DependencyInjection; @@ -5,16 +6,33 @@ namespace Elsa.Workflows.Pipelines.ActivityExecution; public static class ActivityExecutionMiddlewareExtensions { - public static IActivityExecutionPipelineBuilder UseMiddleware(this IActivityExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IActivityExecutionMiddleware + public static IActivityExecutionPipelineBuilder UseMiddleware<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>(this IActivityExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IActivityExecutionMiddleware + { + var delegateFactory = CreateMiddlewareDelegateFactory(pipelineBuilder, args); + return pipelineBuilder.Use(delegateFactory); + } + + public static IActivityExecutionPipelineBuilder Insert<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TMiddleware>(this IActivityExecutionPipelineBuilder pipelineBuilder, int index, params object[] args) where TMiddleware : IActivityExecutionMiddleware + { + var delegateFactory = CreateMiddlewareDelegateFactory(pipelineBuilder, args); + return pipelineBuilder.Insert(index, delegateFactory); + } + + /// Creates a middleware delegate for the specified middleware component. + public static Func CreateMiddlewareDelegateFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>( + this IActivityExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IActivityExecutionMiddleware { var middleware = typeof(TMiddleware); - return pipelineBuilder.Use(next => + return next => { var invokeMethod = MiddlewareHelpers.GetInvokeMethod(middleware); - var ctorArgs = new[] { next }.Concat(args).Select(x => x!).ToArray(); + var ctorArgs = new[] + { + next + }.Concat(args).Select(x => x).ToArray(); var instance = ActivatorUtilities.CreateInstance(pipelineBuilder.ServiceProvider, middleware, ctorArgs); return (ActivityMiddlewareDelegate)invokeMethod.CreateDelegate(typeof(ActivityMiddlewareDelegate), instance); - }); + }; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionPipelinePipelineBuilder.cs b/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionPipelinePipelineBuilder.cs index 971ebfbca..b64289f03 100644 --- a/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionPipelinePipelineBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionPipelinePipelineBuilder.cs @@ -25,6 +25,13 @@ public class ActivityExecutionPipelinePipelineBuilder : IActivityExecutionPipeli return this; } + /// + public IActivityExecutionPipelineBuilder Insert(int index, Func middleware) + { + _components.Insert(index, middleware); + return this; + } + /// public ActivityMiddlewareDelegate Build() { diff --git a/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionMiddlewareExtensions.cs b/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionMiddlewareExtensions.cs index 4e7221c11..4007c8a16 100644 --- a/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionMiddlewareExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionMiddlewareExtensions.cs @@ -4,44 +4,42 @@ using Microsoft.Extensions.DependencyInjection; namespace Elsa.Workflows.Pipelines.WorkflowExecution; -/// /// Provides extensions to that adds support for installing components. -/// public static class WorkflowExecutionMiddlewareExtensions { - /// /// Installs the specified middleware component into the pipeline being built. - /// public static IWorkflowExecutionPipelineBuilder UseMiddleware<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>( this IWorkflowExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware { var delegateFactory = CreateMiddlewareDelegateFactory(pipelineBuilder, args); return pipelineBuilder.Use(delegateFactory); } + + /// Installs the specified middleware component into the pipeline being built. + public static IWorkflowExecutionPipelineBuilder Insert<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>( + this IWorkflowExecutionPipelineBuilder pipelineBuilder, int index, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware + { + var delegateFactory = CreateMiddlewareDelegateFactory(pipelineBuilder, args); + return pipelineBuilder.Insert(index, delegateFactory); + } - /// /// Replaces the terminal middleware component with the specified middleware component. - /// public static IWorkflowExecutionPipelineBuilder ReplaceTerminal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>( this IWorkflowExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware { var index = pipelineBuilder.Components.Count() - 1; return pipelineBuilder.Replace(index, args); } - - /// + /// Replaces the middleware component at the specified index with the specified middleware component. - /// public static IWorkflowExecutionPipelineBuilder Replace<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>( this IWorkflowExecutionPipelineBuilder pipelineBuilder, int index, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware { var delegateFactory = CreateMiddlewareDelegateFactory(pipelineBuilder, args); return pipelineBuilder.Replace(index, delegateFactory); } - - /// + /// Creates a middleware delegate for the specified middleware component. - /// public static Func CreateMiddlewareDelegateFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TMiddleware>( this IWorkflowExecutionPipelineBuilder pipelineBuilder, params object[] args) where TMiddleware : IWorkflowExecutionMiddleware { diff --git a/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs b/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs index c61c6c4e5..940461e8f 100644 --- a/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs @@ -54,6 +54,14 @@ public class WorkflowExecutionPipelineBuilder : IWorkflowExecutionPipelineBuilde return this; } + /// + public IWorkflowExecutionPipelineBuilder Insert(int index, Func middleware) + { + _components.Insert(index, middleware); + return this; + } + + /// public IWorkflowExecutionPipelineBuilder Replace(int index, Func middleware) { _components[index] = middleware; diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/PipelineWorkflowsFeatureExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/PipelineWorkflowsFeatureExtensions.cs new file mode 100644 index 000000000..b68a86779 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/PipelineWorkflowsFeatureExtensions.cs @@ -0,0 +1,35 @@ +using Elsa.Workflows.Contracts; +using Elsa.Workflows.Features; +using Elsa.Workflows.Middleware.Activities; + +// ReSharper disable once CheckNamespace +namespace Elsa.Extensions; + +/// Adds an extension method to the that installs a default workflow runtime execution pipeline. +public static class PipelineWorkflowsFeatureExtensions +{ + /// Installs a default workflow runtime execution pipeline. + public static WorkflowsFeature WithDefaultWorkflowExecutionPipeline(this WorkflowsFeature workflowsFeature, Action? configurePipeline = null) + { + return workflowsFeature.WithWorkflowExecutionPipeline(pipeline => + { + pipeline.UseDefaultPipeline(); + configurePipeline?.Invoke(pipeline); + }); + } + + /// Installs an activity invoker that can run activities asynchronously in the background. + public static WorkflowsFeature WithDefaultActivityExecutionPipeline(this WorkflowsFeature workflowsFeature, Action? configurePipeline = null) + { + return workflowsFeature.WithActivityExecutionPipeline(pipeline => + { + pipeline + .UseExceptionHandling() + .UseExecutionLogging() + .UseNotifications() + .UseBackgroundActivityInvoker(); + + configurePipeline?.Invoke(pipeline); + }); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowsFeatureExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowsFeatureExtensions.cs index 9d8bda420..7031ca4a1 100644 --- a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowsFeatureExtensions.cs +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowsFeatureExtensions.cs @@ -6,6 +6,7 @@ using Elsa.Workflows.Middleware.Activities; // ReSharper disable once CheckNamespace using Elsa.Workflows.Features; +// ReSharper disable once CheckNamespace namespace Elsa.Extensions; ///