diff --git a/Elsa.sln b/Elsa.sln index de7cd82fb..239e86f9b 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -373,6 +373,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.ServerAndStudio.Web", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Studio.Web", "src\apps\Elsa.Studio.Web\Elsa.Studio.Web.csproj", "{169A82A5-2DB3-40EA-801E-14C08D743DF7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.OpenTelemetry", "src\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj", "{2CDF3E1C-267D-4198-B1C7-7E1F548FC120}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -885,6 +887,10 @@ Global {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Release|Any CPU.Build.0 = Release|Any CPU + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1039,6 +1045,7 @@ Global {690B0274-291F-4D9E-BA76-54EFF7D3E4BC} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} {169A82A5-2DB3-40EA-801E-14C08D743DF7} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E} diff --git a/docker/ElsaServer-Datadog.Dockerfile b/docker/ElsaServer-Datadog.Dockerfile index 3af4d992a..207e69e5d 100644 --- a/docker/ElsaServer-Datadog.Dockerfile +++ b/docker/ElsaServer-Datadog.Dockerfile @@ -36,7 +36,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ rm -rf /var/lib/apt/lists/* # Set PYTHONNET_PYDLL environment variable -ENV PYTHONNET_PYDLL /usr/lib/aarch64-linux-gnu/libpython3.11.so +ENV PYTHONNET_PYDLL=/usr/lib/aarch64-linux-gnu/libpython3.11.so # Copy the tracer from build target COPY --from=build /tmp/datadog-dotnet-apm.deb /tmp/datadog-dotnet-apm.deb diff --git a/docker/ElsaServer.Dockerfile b/docker/ElsaServer.Dockerfile index 5c6ca57c1..219a46a74 100644 --- a/docker/ElsaServer.Dockerfile +++ b/docker/ElsaServer.Dockerfile @@ -28,7 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ rm -rf /var/lib/apt/lists/* # Set PYTHONNET_PYDLL environment variable -ENV PYTHONNET_PYDLL /usr/lib/aarch64-linux-gnu/libpython3.11.so +ENV PYTHONNET_PYDLL=/usr/lib/aarch64-linux-gnu/libpython3.11.so EXPOSE 80/tcp EXPOSE 443/tcp diff --git a/docker/ElsaServerAndStudio.Dockerfile b/docker/ElsaServerAndStudio.Dockerfile index bcfdcba90..2de69c82e 100644 --- a/docker/ElsaServerAndStudio.Dockerfile +++ b/docker/ElsaServerAndStudio.Dockerfile @@ -29,7 +29,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ rm -rf /var/lib/apt/lists/* # Set PYTHONNET_PYDLL environment variable -ENV PYTHONNET_PYDLL /usr/lib/aarch64-linux-gnu/libpython3.11.so +ENV PYTHONNET_PYDLL=/usr/lib/aarch64-linux-gnu/libpython3.11.so EXPOSE 80/tcp EXPOSE 443/tcp diff --git a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj index 7622f41c1..1e3e878f0 100644 --- a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj +++ b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj @@ -5,6 +5,7 @@ + diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs index 3ea373136..c77607f0e 100644 --- a/src/apps/Elsa.Server.Web/Program.cs +++ b/src/apps/Elsa.Server.Web/Program.cs @@ -21,13 +21,17 @@ 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.Tenants.Extensions; using Elsa.Workflows; using Elsa.Workflows.Api; using Elsa.Workflows.Management.Compression; using Elsa.Workflows.Management.Stores; +using Elsa.Workflows.Pipelines.ActivityExecution; +using Elsa.Workflows.Pipelines.WorkflowExecution; using Elsa.Workflows.Runtime.Distributed.Extensions; +using Elsa.Workflows.Runtime.Extensions; using Elsa.Workflows.Runtime.Stores; using JetBrains.Annotations; using Medallion.Threading.FileSystem; @@ -133,6 +137,11 @@ services identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options)); }) .UseDefaultAuthentication() + .UseWorkflows(workflows => + { + workflows.WithDefaultWorkflowExecutionPipeline(pipeline => pipeline.UseWorkflowExecutionTracing()); + workflows.WithDefaultActivityExecutionPipeline(pipeline => pipeline.UseActivityExecutionTracing()); + }) .UseWorkflowManagement(management => { if (persistenceProvider == PersistenceProvider.MongoDb) diff --git a/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj b/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj new file mode 100644 index 000000000..8660d883e --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj @@ -0,0 +1,18 @@ + + + + + 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..4c8e1e1aa --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using Elsa.OpenTelemetry.Helpers; +using Elsa.Workflows; +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()); + } +} + +[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..4876aded3 --- /dev/null +++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs @@ -0,0 +1,38 @@ +using System.Diagnostics; +using Elsa.OpenTelemetry.Helpers; +using Elsa.Workflows; +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 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); + activity?.AddTag("workflowInstance.id", workflowInstanceId); + activity?.AddTag("workflowDefinition.definitionId", workflow.Identity.DefinitionId); + activity?.AddTag("workflowDefinition.version", workflow.Identity.Version); + activity?.AddTag("tenantId", workflow.Identity.TenantId); + 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()); + } +} + +[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.WorkflowContexts/Extensions/WorkflowExecutionBuilderExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionBuilderExtensions.cs index 7f78ccd3b..03fc7c966 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionBuilderExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionBuilderExtensions.cs @@ -1,4 +1,5 @@ using Elsa.WorkflowContexts.Middleware; +using Elsa.Workflows; using Elsa.Workflows.Contracts; using Elsa.Workflows.Pipelines.ActivityExecution; using Elsa.Workflows.Pipelines.WorkflowExecution; diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionMiddleware.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionMiddleware.cs index 6094ed7e5..bb786f283 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionMiddleware.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionMiddleware.cs @@ -1,4 +1,4 @@ -namespace Elsa.Workflows.Contracts; +namespace Elsa.Workflows; public interface IActivityExecutionMiddleware { diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionPipelineBuilder.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionPipelineBuilder.cs index 7c7968451..3d60e9f68 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionPipelineBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IActivityExecutionPipelineBuilder.cs @@ -1,6 +1,6 @@ using Elsa.Workflows.Pipelines.ActivityExecution; -namespace Elsa.Workflows.Contracts; +namespace Elsa.Workflows; /// /// Builds an activity execution pipeline. @@ -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..f0de095d9 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowExecutionPipelineBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowExecutionPipelineBuilder.cs @@ -1,41 +1,30 @@ using Elsa.Workflows.Pipelines.WorkflowExecution; -namespace Elsa.Workflows.Contracts; +namespace Elsa.Workflows; -/// /// Builds a workflow execution pipeline. -/// public interface IWorkflowExecutionPipelineBuilder { - /// /// A general-purpose dictionary of values that can be used by middleware components. - /// public IDictionary Properties { get; } - /// /// The middleware components that have been installed. - /// public IEnumerable> Components { get; } - /// /// The current service provider to resolve services from. - /// IServiceProvider ServiceProvider { get; } - /// /// Installs the specified delegate as a middleware component. - /// IWorkflowExecutionPipelineBuilder Use(Func middleware); - /// /// Constructs the final delegate that invokes each installed middleware component. - /// public WorkflowMiddlewareDelegate Build(); - - /// + /// 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..c1565a907 100644 --- a/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionMiddlewareExtensions.cs @@ -1,20 +1,37 @@ -using Elsa.Workflows.Contracts; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; 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..76850bbb9 100644 --- a/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionPipelinePipelineBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Pipelines/ActivityExecution/ActivityExecutionPipelinePipelineBuilder.cs @@ -1,22 +1,12 @@ -using Elsa.Workflows.Contracts; - namespace Elsa.Workflows.Pipelines.ActivityExecution; /// -public class ActivityExecutionPipelinePipelineBuilder : IActivityExecutionPipelineBuilder +public class ActivityExecutionPipelinePipelineBuilder(IServiceProvider serviceProvider) : IActivityExecutionPipelineBuilder { private readonly IList> _components = new List>(); - /// - /// Initializes a new instance of the class. - /// - public ActivityExecutionPipelinePipelineBuilder(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - } - /// - public IServiceProvider ServiceProvider { get; } + public IServiceProvider ServiceProvider { get; } = serviceProvider; /// public IActivityExecutionPipelineBuilder Use(Func middleware) @@ -25,6 +15,12 @@ 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..6215c720c 100644 --- a/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Pipelines/WorkflowExecution/WorkflowExecutionPipelineBuilder.cs @@ -1,5 +1,3 @@ -using Elsa.Workflows.Contracts; - namespace Elsa.Workflows.Pipelines.WorkflowExecution; /// @@ -54,6 +52,12 @@ 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/ActivityExecutionPipelineBuilderExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/ActivityExecutionPipelineBuilderExtensions.cs index e471f2815..6f20c873c 100644 --- a/src/modules/Elsa.Workflows.Runtime/Extensions/ActivityExecutionPipelineBuilderExtensions.cs +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/ActivityExecutionPipelineBuilderExtensions.cs @@ -1,3 +1,4 @@ +using Elsa.Workflows; using Elsa.Workflows.Contracts; using Elsa.Workflows.Pipelines.ActivityExecution; using Elsa.Workflows.Runtime.Middleware.Activities; 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..b647f0c7e --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/PipelineWorkflowsFeatureExtensions.cs @@ -0,0 +1,36 @@ +using Elsa.Extensions; +using Elsa.Workflows.Features; +using Elsa.Workflows.Middleware.Activities; + +// ReSharper disable once CheckNamespace + +namespace Elsa.Workflows.Runtime.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/WorkflowExecutionPipelineBuilderExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowExecutionPipelineBuilderExtensions.cs index 4d4d0128d..f65fcf45e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowExecutionPipelineBuilderExtensions.cs +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowExecutionPipelineBuilderExtensions.cs @@ -1,4 +1,4 @@ -using Elsa.Workflows.Contracts; +using Elsa.Workflows; using Elsa.Workflows.Middleware.Workflows; using Elsa.Workflows.Pipelines.WorkflowExecution; using Elsa.Workflows.Runtime.Middleware.Workflows; diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowsFeatureExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowsFeatureExtensions.cs deleted file mode 100644 index ca2eb46c3..000000000 --- a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowsFeatureExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Elsa.Extensions; -using Elsa.Workflows.Features; -using Elsa.Workflows.Middleware.Activities; -// ReSharper disable once CheckNamespace - -namespace Elsa.Workflows.Runtime.Extensions; - -/// -/// Adds an extension method to the that installs a default workflow runtime execution pipeline. -/// -public static class WorkflowsFeatureExtensions -{ - /// - /// Installs a default workflow runtime execution pipeline. - /// - public static WorkflowsFeature WithDefaultWorkflowExecutionPipeline(this WorkflowsFeature workflowsFeature) => - workflowsFeature.WithWorkflowExecutionPipeline(pipeline => - pipeline.UseDefaultPipeline()); - - /// - /// Installs an activity invoker that can run activities asynchronously in the background. - /// - public static WorkflowsFeature WithBackgroundActivityExecutionPipeline(this WorkflowsFeature workflowsFeature) => - workflowsFeature.WithActivityExecutionPipeline(pipeline => - pipeline - .UseExceptionHandling() - .UseExecutionLogging() - .UseNotifications() - .UseBackgroundActivityInvoker()); -} \ No newline at end of file diff --git a/src/modules/Elsa/Extensions/ServiceProviderExtensions.cs b/src/modules/Elsa/Extensions/ServiceProviderExtensions.cs index 734d90a3f..03ec09d5c 100644 --- a/src/modules/Elsa/Extensions/ServiceProviderExtensions.cs +++ b/src/modules/Elsa/Extensions/ServiceProviderExtensions.cs @@ -1,3 +1,4 @@ +using Elsa.Workflows; using Elsa.Workflows.Contracts; using Microsoft.Extensions.DependencyInjection; diff --git a/src/modules/Elsa/Features/ElsaFeature.cs b/src/modules/Elsa/Features/ElsaFeature.cs index 08350b6ec..e4fc95efa 100644 --- a/src/modules/Elsa/Features/ElsaFeature.cs +++ b/src/modules/Elsa/Features/ElsaFeature.cs @@ -37,7 +37,7 @@ public class ElsaFeature : FeatureBase Module .UseWorkflows(workflows => workflows .WithDefaultWorkflowExecutionPipeline() - .WithBackgroundActivityExecutionPipeline()) + .WithDefaultActivityExecutionPipeline()) .UseWorkflowManagement(management => { if (!DisableAutomaticActivityRegistration)