diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml
index 6fa5c5d9c..aa3b9cf2c 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:
diff --git a/Directory.Build.props b/Directory.Build.props
index 6dea5e839..720db3ec5 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -38,5 +38,6 @@
3.3.0-preview.470
+ 8.0.5
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9afe5d9ca..adee9e75e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -38,7 +38,8 @@
-
+
+
@@ -67,7 +68,6 @@
-
@@ -149,40 +149,34 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
+
-
-
-
-
-
+
\ No newline at end of file
diff --git a/Elsa.sln b/Elsa.sln
index 0570a8bdd..fae61d87b 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -117,6 +117,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{986E54
docker\docker-compose.yml = docker\docker-compose.yml
docker\docker-compose-datadog.yml = docker\docker-compose-datadog.yml
docker\ElsaServer-Datadog.Dockerfile = docker\ElsaServer-Datadog.Dockerfile
+ docker\otel-collector-config.yaml = docker\otel-collector-config.yaml
docker\init-db.sh = docker\init-db.sh
EndProjectSection
EndProject
@@ -470,6 +471,45 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Secrets.Scripting", "s
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.JavaScript.Libraries", "src\modules\Elsa.JavaScript.Libraries\Elsa.JavaScript.Libraries.csproj", "{5F80663C-B072-49DE-9A67-FD804BAD787E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.OpenTelemetry", "src\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj", "{25880971-403E-4872-93A1-D33089E07C91}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{FE1AFEC0-7C63-4EF8-8E24-D9703590A778}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k8s", "k8s", "{924BA0AE-46B1-40B5-992C-CFB4EB14EDB0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-server", "elsa-server", "{A2743EC6-7117-46E2-8AC1-8CDD52C8AAEF}"
+ ProjectSection(SolutionItems) = preProject
+ scripts\k8s\elsa-server\deployment.yaml = scripts\k8s\elsa-server\deployment.yaml
+ scripts\k8s\elsa-server\role-binding.yaml = scripts\k8s\elsa-server\role-binding.yaml
+ scripts\k8s\elsa-server\role.yaml = scripts\k8s\elsa-server\role.yaml
+ scripts\k8s\elsa-server\service-account.yml = scripts\k8s\elsa-server\service-account.yml
+ scripts\k8s\elsa-server\service.yaml = scripts\k8s\elsa-server\service.yaml
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-studio", "elsa-studio", "{A782913B-142F-4EA8-A2C7-82679CD2ABC1}"
+ ProjectSection(SolutionItems) = preProject
+ scripts\k8s\elsa-studio\deployment.yaml = scripts\k8s\elsa-studio\deployment.yaml
+ scripts\k8s\elsa-studio\service.yaml = scripts\k8s\elsa-studio\service.yaml
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plant-uml", "plant-uml", "{BFB87B50-3FE9-4AB1-8F18-96BE667C953D}"
+ ProjectSection(SolutionItems) = preProject
+ scripts\k8s\plant-uml\deployment.yaml = scripts\k8s\plant-uml\deployment.yaml
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postgres", "postgres", "{399C61BC-A51B-4B57-AA32-46BC24D4AE1C}"
+ ProjectSection(SolutionItems) = preProject
+ scripts\k8s\postgres\config-map.yaml = scripts\k8s\postgres\config-map.yaml
+ scripts\k8s\postgres\deployment.yaml = scripts\k8s\postgres\deployment.yaml
+ scripts\k8s\postgres\service.yml = scripts\k8s\postgres\service.yml
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "trace-lens", "trace-lens", "{2FDF67FD-2BD3-4F73-9A24-DA255641ED25}"
+ ProjectSection(SolutionItems) = preProject
+ scripts\k8s\trace-lens\deployment.yaml = scripts\k8s\trace-lens\deployment.yaml
+ scripts\k8s\trace-lens\service.yaml = scripts\k8s\trace-lens\service.yaml
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1070,6 +1110,10 @@ Global
{5F80663C-B072-49DE-9A67-FD804BAD787E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F80663C-B072-49DE-9A67-FD804BAD787E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F80663C-B072-49DE-9A67-FD804BAD787E}.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
@@ -1257,6 +1301,13 @@ Global
{29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1}
{6C606FEB-9A1F-4816-ABE4-22AFA8CEE771} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1}
{5F80663C-B072-49DE-9A67-FD804BAD787E} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76}
+ {25880971-403E-4872-93A1-D33089E07C91} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
+ {924BA0AE-46B1-40B5-992C-CFB4EB14EDB0} = {FE1AFEC0-7C63-4EF8-8E24-D9703590A778}
+ {A2743EC6-7117-46E2-8AC1-8CDD52C8AAEF} = {924BA0AE-46B1-40B5-992C-CFB4EB14EDB0}
+ {A782913B-142F-4EA8-A2C7-82679CD2ABC1} = {924BA0AE-46B1-40B5-992C-CFB4EB14EDB0}
+ {BFB87B50-3FE9-4AB1-8F18-96BE667C953D} = {924BA0AE-46B1-40B5-992C-CFB4EB14EDB0}
+ {399C61BC-A51B-4B57-AA32-46BC24D4AE1C} = {924BA0AE-46B1-40B5-992C-CFB4EB14EDB0}
+ {2FDF67FD-2BD3-4F73-9A24-DA255641ED25} = {924BA0AE-46B1-40B5-992C-CFB4EB14EDB0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
diff --git a/Elsa.sln.DotSettings b/Elsa.sln.DotSettings
index 4d587519c..08956874a 100644
--- a/Elsa.sln.DotSettings
+++ b/Elsa.sln.DotSettings
@@ -1,4 +1,4 @@
-
+
False
1000
1
@@ -16,6 +16,7 @@
True
True
True
+ True
True
True
True
diff --git a/design/custom/2x/elsa-logo.png b/design/custom/2x/elsa-logo.png
new file mode 100644
index 000000000..baa1319c0
Binary files /dev/null and b/design/custom/2x/elsa-logo.png differ
diff --git a/docker/ElsaServer-Datadog.Dockerfile b/docker/ElsaServer-Datadog.Dockerfile
index 207e69e5d..6491995e5 100644
--- a/docker/ElsaServer-Datadog.Dockerfile
+++ b/docker/ElsaServer-Datadog.Dockerfile
@@ -1,28 +1,23 @@
-# Version: 1
+# Version: 1
# Description: Dockerfile for building and running Elsa Server
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim AS build
WORKDIR /source
-# Determine the architecture and download the appropriate version of the tracer
-RUN ARCH=$(if [ "$(uname -m)" = "x86_64" ]; then echo "amd64"; elif [ "$(uname -m)" = "aarch64" ]; then echo "arm64"; else echo "amd64"; fi) \
- && TRACER_VERSION=$(curl -s https://api.github.com/repos/DataDog/dd-trace-dotnet/releases/latest | grep tag_name | cut -d '"' -f 4 | cut -c2-) \
- && curl -Lo /tmp/datadog-dotnet-apm.deb https://github.com/DataDog/dd-trace-dotnet/releases/download/v${TRACER_VERSION}/datadog-dotnet-apm_${TRACER_VERSION}_${ARCH}.deb
-
-# copy sources.
+# Copy sources.
COPY src/. ./src
COPY ./NuGet.Config ./
COPY *.props ./
-# restore packages.
-RUN dotnet restore "./src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj"
+# Restore packages.
+RUN dotnet restore "./src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj"
-# build and publish (UseAppHost=false creates platform independent binaries).
-WORKDIR /source/src/apps/Elsa.Server.Web
+# Build and publish (UseAppHost=false creates platform independent binaries).
+WORKDIR /source/src/bundles/Elsa.Server.Web
RUN dotnet build "Elsa.Server.Web.csproj" -c Release -o /app/build
RUN dotnet publish "Elsa.Server.Web.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore -f net8.0
-# move binaries into smaller base image.
+# Move binaries into smaller base image.
FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim AS base
WORKDIR /app
COPY --from=build /app/publish ./
@@ -38,21 +33,25 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
# Set PYTHONNET_PYDLL environment variable
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
-# Install the tracer
-RUN mkdir -p /opt/datadog \
- && mkdir -p /var/log/datadog \
- && dpkg -i /tmp/datadog-dotnet-apm.deb \
- && rm /tmp/datadog-dotnet-apm.deb
-
-# Enable the tracer
-ENV CORECLR_ENABLE_PROFILING=1
-ENV CORECLR_PROFILER={846F5F1C-F9AE-4B07-969E-05C26BC060D8}
-ENV CORECLR_PROFILER_PATH=/opt/datadog/Datadog.Trace.ClrProfiler.Native.so
-ENV DD_DOTNET_TRACER_HOME=/opt/datadog
-ENV DD_INTEGRATIONS=/opt/datadog/integrations.json
+# Install dependencies
+RUN apt-get update && apt-get install -y wget unzip curl
+
+# Set environment variables for OpenTelemetry Auto-Instrumentation
+ENV OTEL_DOTNET_AUTO_HOME=/otel
+ENV OTEL_LOG_LEVEL="debug"
+
+# Download and extract OpenTelemetry Auto-Instrumentation
+ARG OTEL_VERSION=1.7.0
+RUN mkdir /otel
+RUN curl -L -o /otel/otel-dotnet-install.sh https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/v${OTEL_VERSION}/otel-dotnet-auto-install.sh
+RUN chmod +x /otel/otel-dotnet-install.sh
+RUN /bin/bash /otel/otel-dotnet-install.sh
+
+# Provide necessary permissions for the script to execute
+RUN chmod +x /otel/instrument.sh
EXPOSE 8080/tcp
EXPOSE 443/tcp
-ENTRYPOINT ["dotnet", "Elsa.Server.Web.dll"]
+
+# Instrument the application and start it
+ENTRYPOINT ["/bin/bash", "-c", "source /otel/instrument.sh && dotnet Elsa.Server.Web.dll"]
\ No newline at end of file
diff --git a/docker/docker-compose-datadog.yml b/docker/docker-compose-datadog.yml
index d2f742b47..e90991c45 100644
--- a/docker/docker-compose-datadog.yml
+++ b/docker/docker-compose-datadog.yml
@@ -1,5 +1,3 @@
-version: '3.7'
-
services:
postgres:
image: postgres:latest
@@ -12,24 +10,13 @@ services:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
-
- cockroachdb:
- image: cockroachdb/cockroach:v22.1.0
- command: start-single-node --insecure
- ports:
- - "26257:26257" # CockroachDB SQL port
- - "8080:8080" # CockroachDB UI port
- volumes:
- - cockroachdb-data:/cockroach/cockroach-data
- environment:
- - COCKROACH_DATABASE=elsa
-
+
rabbitmq:
image: "rabbitmq:3-management"
ports:
- "15672:15672"
- "5672:5672"
-
+
redis:
image: redis:latest
ports:
@@ -44,7 +31,7 @@ services:
- postgres
- rabbitmq
- redis
- - datadog-agent
+ - otel-collector
environment:
DD_AGENT_HOST: datadog-agent
DD_ENV: development
@@ -52,6 +39,21 @@ services:
DD_TRACE_OTEL_ENABLED: true
DD_SERVICE: "Elsa Server"
DD_VERSION: "3.3.0"
+ # OpenTelemetry environment variables
+ OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4317" # Point to OpenTelemetry Collector
+ OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" # Use gRPC for OTLP
+ OTEL_TRACES_EXPORTER: "otlp"
+ OTEL_METRICS_EXPORTER: "otlp"
+ OTEL_LOGS_EXPORTER: "otpl"
+ OTEL_RESOURCE_ATTRIBUTES: "service.name=elsa-server-local,service.version=3.2.1-blueberry,deployment.environment=development"
+ OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES: "Elsa.Workflows"
+ OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED: "true"
+ OTEL_LOG_LEVEL: "debug"
+ OTEL_DOTNET_AUTO_RESOURCE_DETECTOR_ENABLED: "true"
+ OTEL_DOTNET_AUTO_LOGS_CONSOLE_EXPORTER_ENABLED: "true"
+ OTEL_DOTNET_AUTO_METRICS_CONSOLE_EXPORTER_ENABLED: "true"
+ OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED: "true"
+
ASPNETCORE_ENVIRONMENT: Development
PYTHONNET_PYDLL: /opt/homebrew/Cellar/python@3.11/3.11.6_1/Frameworks/Python.framework/Versions/3.11/bin/python3.11
PYTHONNET_RUNTIME: coreclr
@@ -61,7 +63,7 @@ services:
DISTRIBUTEDLOCKPROVIDER: "Postgres"
ports:
- "13000:8080"
-
+
elsa-studio:
pull_policy: always
build:
@@ -73,24 +75,41 @@ services:
ports:
- "14000:8080"
+ otel-collector:
+ image: otel/opentelemetry-collector-contrib:latest
+ volumes:
+ - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
+ command: [ "--config", "/etc/otel-collector-config.yaml", "--feature-gates", "-component.UseLocalHostAsDefaultHost" ]
+ environment:
+ DD_API_KEY: "secret api key"
+ DD_SITE: "datadoghq.eu"
+ ports:
+ - "13133:13133"
+ - "4317:4317"
+ - "4318:4318"
+
datadog-agent:
- image: datadog/agent:7
+ image: datadog/agent:latest
environment:
DD_API_KEY: ""
DD_SITE: "datadoghq.eu"
+ DD_HOSTNAME: "otel-collector"
DD_LOGS_ENABLED: "true"
+ DD_OTLP_CONFIG_LOGS_ENABLED: "true"
DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL: "true"
DD_APM_ENABLED: "true"
DD_APM_NON_LOCAL_TRAFFIC: "true"
+ DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_GRPC_ENDPOINT: 0.0.0.0:4317 # The Datadog Agent expects traces from OpenTelemetry Collector
+ DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_HTTP_ENDPOINT: 0.0.0.0:4318
+
+ # Service autodiscovery
DD_AC_INCLUDE: "name:postgres,name:rabbitmq,name:redis,name:elsa-server"
DD_AC_EXCLUDE: "name:datadog-agent"
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock
- - /proc/:/host/proc/:ro
- - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro
+
ports:
- "8126:8126"
+ - "14317:4317"
+ - "14318:4318"
volumes:
- postgres-data:
- cockroachdb-data:
+ postgres-data:
\ No newline at end of file
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 5cc838dac..51cb37e4e 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -1,6 +1,4 @@
-version: '3.7'
-
-services:
+services:
postgres:
image: postgres:latest
command: -c 'max_connections=2000'
diff --git a/docker/otel-collector-config.yaml b/docker/otel-collector-config.yaml
new file mode 100644
index 000000000..6cf8cefaf
--- /dev/null
+++ b/docker/otel-collector-config.yaml
@@ -0,0 +1,49 @@
+receivers:
+ otlp:
+ protocols:
+ grpc:
+ endpoint: 0.0.0.0:4317
+ http:
+ endpoint: 0.0.0.0:4318
+
+processors:
+ batch:
+ send_batch_max_size: 100
+ send_batch_size: 10 # Increased batch size for efficiency
+ timeout: 1s
+ tail_sampling:
+ decision_wait: 10s
+ num_traces: 10000 # Increased from 100 to handle more traces
+ expected_new_traces_per_sec: 100 # Increased from 10
+ decision_cache:
+ sampled_cache_size: 100000
+ policies: [
+ {
+ name: incidents-policy,
+ type: boolean_attribute,
+ boolean_attribute: { key: hasIncidents, value: true }
+ }
+ ]
+
+exporters:
+ debug:
+ verbosity: detailed
+ datadog:
+ api:
+ site: ${env:DD_SITE}
+ key: ${env:DD_API_KEY}
+
+service:
+ pipelines:
+ metrics:
+ receivers: [ otlp ]
+ processors: [ batch ]
+ exporters: [ datadog ]
+ traces:
+ receivers: [ otlp ]
+ processors: [ batch ] # Added tail_sampling to the main traces pipeline
+ exporters: [ debug, datadog ] # Directly exporting to debug and datadog
+ logs:
+ receivers: [ otlp ]
+ processors: [ batch ]
+ exporters: [ debug, datadog ]
diff --git a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj
index d4e953462..692a1a9c5 100644
--- a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj
+++ b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj
@@ -51,6 +51,7 @@
+
@@ -59,6 +60,10 @@
+
+
+
+
diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs
index bbd5582c9..1b77df743 100644
--- a/src/apps/Elsa.Server.Web/Program.cs
+++ b/src/apps/Elsa.Server.Web/Program.cs
@@ -373,6 +373,9 @@ services
elsa.UseRealTimeWorkflows();
}
+ if (useAzureServiceBus)
+ elsa.UseAzureServiceBus(asb => asb.AzureServiceBusOptions += options => configuration.GetSection("AzureServiceBus").Bind(options));
+
if (useMassTransit)
{
elsa.UseMassTransit(massTransit =>
diff --git a/src/apps/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj b/src/apps/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj
index 7a9f688cd..150e6d3cf 100644
--- a/src/apps/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj
+++ b/src/apps/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj
@@ -48,4 +48,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/apps/Elsa.ServerAndStudio.Web/appsettings.json b/src/apps/Elsa.ServerAndStudio.Web/appsettings.json
index 130e0f35d..843df197e 100644
--- a/src/apps/Elsa.ServerAndStudio.Web/appsettings.json
+++ b/src/apps/Elsa.ServerAndStudio.Web/appsettings.json
@@ -1,14 +1,10 @@
{
"Logging": {
"LogLevel": {
- "Default": "Information",
- "Elsa.Mediator": "Warning",
- "MassTransit": "Warning",
- "Microsoft.Extensions.Http": "Warning",
+ "Default": "Warning",
+ "Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
- "Microsoft.EntityFrameworkCore": "Warning",
- "Microsoft.AspNetCore": "Warning",
- "System.Net.Http": "Warning"
+ "OpenTelemetry": "Debug"
}
},
"AllowedHosts": "*",
@@ -115,5 +111,27 @@
}
]
}
+ },
+ "Scripting": {
+ "Python": {
+ "PythonDllPath": "",
+ "Scripts": [
+ "def greet(name): return f'Hello {name}!'",
+ "def say_hello_world(): return greet('World')"
+ ]
+ }
+ },
+ "AzureServiceBus": {
+ "ConnectionStringOrName": "AzureServiceBus",
+ "Queues": [
+ {
+ "name": "order-created"
+ },
+ {
+ "name": "order-completed"
+ }
+ ],
+ "Topics": [],
+ "Subscriptions": []
}
}
diff --git a/src/apps/Elsa.Studio.Web/Elsa.Studio.Web.csproj b/src/apps/Elsa.Studio.Web/Elsa.Studio.Web.csproj
index 3faed797c..ca1886c1f 100644
--- a/src/apps/Elsa.Studio.Web/Elsa.Studio.Web.csproj
+++ b/src/apps/Elsa.Studio.Web/Elsa.Studio.Web.csproj
@@ -16,6 +16,10 @@
+
+
+
+
diff --git a/src/clients/Elsa.Api.Client/Converters/TypeJsonConverter.cs b/src/clients/Elsa.Api.Client/Converters/TypeJsonConverter.cs
new file mode 100644
index 000000000..60602c7bc
--- /dev/null
+++ b/src/clients/Elsa.Api.Client/Converters/TypeJsonConverter.cs
@@ -0,0 +1,56 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Elsa.Api.Client.Extensions;
+using JetBrains.Annotations;
+
+namespace Elsa.Api.Client.Converters;
+
+///
+/// Converts objects to and from their assembly-qualified name.
+///
+[UsedImplicitly]
+public class TypeJsonConverter : JsonConverter
+{
+ ///
+ public override bool CanConvert(Type typeToConvert)
+ {
+ return typeToConvert == typeof(Type) || typeToConvert.FullName == "System.RuntimeType";
+ }
+
+ ///
+ public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var typeName = reader.GetString()!;
+
+ // Handle collection types.
+ if (typeName.EndsWith("[]"))
+ {
+ var elementTypeName = typeName[..^"[]".Length];
+ var elementType = Type.GetType(elementTypeName)!;
+ return typeof(List<>).MakeGenericType(elementType);
+ }
+
+ return Type.GetType(typeName);
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
+ {
+ // Handle collection types.
+ if (value is { IsGenericType: true, GenericTypeArguments.Length: 1 })
+ {
+ var elementType = value.GenericTypeArguments.First();
+ var typedEnumerable = typeof(IEnumerable<>).MakeGenericType(elementType);
+
+ if (typedEnumerable.IsAssignableFrom(value))
+ {
+ var elementTypeName = value.GetSimpleAssemblyQualifiedName();
+ JsonSerializer.Serialize(writer, $"{elementTypeName}[]", options);
+ return;
+ }
+ }
+
+ var typeName = value.GetSimpleAssemblyQualifiedName();
+ JsonSerializer.Serialize(writer, typeName, options);
+ }
+}
\ No newline at end of file
diff --git a/src/clients/Elsa.Api.Client/Elsa.Api.Client.csproj b/src/clients/Elsa.Api.Client/Elsa.Api.Client.csproj
index a740fd26b..bd58f9fc6 100644
--- a/src/clients/Elsa.Api.Client/Elsa.Api.Client.csproj
+++ b/src/clients/Elsa.Api.Client/Elsa.Api.Client.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs
index 96d4259ae..9f28a6070 100644
--- a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs
+++ b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs
@@ -121,7 +121,7 @@ public static class DependencyInjectionExtensions
/// An options object that can be used to configure the HTTP client builder.
public static IServiceCollection AddApi(this IServiceCollection services, Type apiType, ElsaClientBuilderOptions? httpClientBuilderOptions = default)
{
- var builder = services.AddRefitClient(apiType, _ => CreateRefitSettings(), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
+ var builder = services.AddRefitClient(apiType, sp => CreateRefitSettings(sp, httpClientBuilderOptions?.ConfigureJsonSerializerOptions), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
httpClientBuilderOptions?.ConfigureRetryPolicy?.Invoke(builder);
return services;
@@ -136,7 +136,7 @@ public static class DependencyInjectionExtensions
public static void AddApiWithoutRetryPolicy(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
{
var builder = services
- .AddRefitClient(_ => CreateRefitSettings(), typeof(T).Name)
+ .AddRefitClient(sp => CreateRefitSettings(sp), typeof(T).Name)
.ConfigureHttpClient(ConfigureElsaApiHttpClient);
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
}
@@ -153,7 +153,7 @@ public static class DependencyInjectionExtensions
/// Creates an API client for the specified API type.
public static T CreateApi(this IServiceProvider serviceProvider, HttpClient httpClient) where T : class
{
- return RestService.For(httpClient, CreateRefitSettings());
+ return RestService.For(httpClient, CreateRefitSettings(serviceProvider));
}
private static void ConfigureElsaApiHttpClient(IServiceProvider serviceProvider, HttpClient httpClient)
diff --git a/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs b/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs
index ec47e8710..8a87220a3 100644
--- a/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs
+++ b/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs
@@ -10,17 +10,12 @@ namespace Elsa.Api.Client;
///
public static class RefitSettingsHelper
{
- private static JsonSerializerOptions? _jsonSerializerOptions;
-
///
/// Creates a instance configured for Elsa.
///
- public static RefitSettings CreateRefitSettings()
+ public static RefitSettings CreateRefitSettings(IServiceProvider serviceProvider, Action? configureJsonSerializerOptions = null)
{
- var settings = new RefitSettings
- {
- ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions())
- };
+ var settings = new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions)) };
return settings;
}
@@ -28,11 +23,8 @@ public static class RefitSettingsHelper
///
/// Creates a instance configured for Elsa.
///
- public static JsonSerializerOptions CreateJsonSerializerOptions()
+ public static JsonSerializerOptions CreateJsonSerializerOptions(IServiceProvider serviceProvider, Action? configureJsonSerializerOptions = null)
{
- if (_jsonSerializerOptions != null)
- return _jsonSerializerOptions;
-
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
@@ -40,7 +32,10 @@ public static class RefitSettingsHelper
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new VersionOptionsJsonConverter());
+ options.Converters.Add(new TypeJsonConverter());
- return _jsonSerializerOptions = options;
+ configureJsonSerializerOptions?.Invoke(serviceProvider, options);
+
+ return options;
}
}
\ No newline at end of file
diff --git a/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs b/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs
index a7312eb60..52b0b1b77 100644
--- a/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs
+++ b/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using Elsa.Api.Client.HttpMessageHandlers;
using Microsoft.Extensions.DependencyInjection;
using Polly;
@@ -39,4 +40,9 @@ public class ElsaClientBuilderOptions
/// Gets or sets a delegate that can be used to configure the retry policy.
///
public Action? ConfigureRetryPolicy { get; set; } = builder => builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))));
+
+ ///
+ /// Gets or sets a delegate that can be used to configure the JSON serializer options.
+ ///
+ public Action? ConfigureJsonSerializerOptions { get; set; }
}
\ No newline at end of file
diff --git a/src/clients/Elsa.Api.Client/Resources/ActivityExecutions/Contracts/IActivityExecutionsApi.cs b/src/clients/Elsa.Api.Client/Resources/ActivityExecutions/Contracts/IActivityExecutionsApi.cs
index f7ae99343..2238c8162 100644
--- a/src/clients/Elsa.Api.Client/Resources/ActivityExecutions/Contracts/IActivityExecutionsApi.cs
+++ b/src/clients/Elsa.Api.Client/Resources/ActivityExecutions/Contracts/IActivityExecutionsApi.cs
@@ -27,4 +27,22 @@ public interface IActivityExecutionsApi
/// The response containing a list of activity executions.
[Get("/activity-executions/list")]
Task> ListAsync(ListActivityExecutionsRequest request, CancellationToken cancellationToken = default);
+
+ ///
+ /// Lists activity execution summaries for a given activity in a workflow instance.
+ ///
+ /// The request.
+ /// An optional cancellation token.
+ /// The response containing a list of activity execution summaries.
+ [Get("/activity-execution-summaries/list")]
+ Task> ListSummariesAsync(ListActivityExecutionsRequest request, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets a single activity execution by ID.
+ ///
+ /// The ID of the activity execution.
+ /// An optional cancellation token.
+ /// The activity execution.
+ [Get("/activity-executions/{id}")]
+ Task GetAsync(string id, CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/clients/Elsa.Api.Client/Resources/ActivityExecutions/Models/ActivityExecutionRecordSummary.cs b/src/clients/Elsa.Api.Client/Resources/ActivityExecutions/Models/ActivityExecutionRecordSummary.cs
new file mode 100644
index 000000000..a224c62de
--- /dev/null
+++ b/src/clients/Elsa.Api.Client/Resources/ActivityExecutions/Models/ActivityExecutionRecordSummary.cs
@@ -0,0 +1,82 @@
+using System.Linq.Expressions;
+using Elsa.Api.Client.Resources.WorkflowInstances.Models;
+using Elsa.Api.Client.Shared.Models;
+
+namespace Elsa.Api.Client.Resources.ActivityExecutions.Models;
+
+///
+/// Represents a summarized view of a single activity execution of an activity instance.
+///
+public class ActivityExecutionRecordSummary : Entity
+{
+ ///
+ /// Gets or sets the workflow instance ID.
+ ///
+ public string WorkflowInstanceId { get; set; } = default!;
+
+ ///
+ /// Gets or sets the activity ID.
+ ///
+ public string ActivityId { get; set; } = default!;
+
+ ///
+ /// Gets or sets the activity node ID.
+ ///
+ public string ActivityNodeId { get; set; } = default!;
+
+ ///
+ /// The type of the activity.
+ ///
+ public string ActivityType { get; set; } = default!;
+
+ ///
+ /// The version of the activity type.
+ ///
+ public int ActivityTypeVersion { get; set; }
+
+ ///
+ /// The name of the activity.
+ ///
+ public string? ActivityName { get; set; }
+
+ ///
+ /// Gets or sets the time at which the activity execution began.
+ ///
+ public DateTimeOffset StartedAt { get; set; }
+
+ ///
+ /// Gets or sets whether the activity has any bookmarks.
+ ///
+ public bool HasBookmarks { get; set; }
+
+ ///
+ /// Gets or sets the status of the activity.
+ ///
+ public ActivityStatus Status { get; set; }
+
+ ///
+ /// Gets or sets the time at which the activity execution completed.
+ ///
+ public DateTimeOffset? CompletedAt { get; set; }
+
+ ///
+ /// Returns a summary view of the specified .
+ ///
+ public static ActivityExecutionRecordSummary FromRecord(ActivityExecutionRecord record)
+ {
+ return new ActivityExecutionRecordSummary
+ {
+ Id = record.Id,
+ WorkflowInstanceId = record.WorkflowInstanceId,
+ ActivityId = record.ActivityId,
+ ActivityNodeId = record.ActivityNodeId,
+ ActivityType = record.ActivityType,
+ ActivityTypeVersion = record.ActivityTypeVersion,
+ ActivityName = record.ActivityName,
+ StartedAt = record.StartedAt,
+ HasBookmarks = record.HasBookmarks,
+ Status = record.Status,
+ CompletedAt = record.CompletedAt
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/clients/Elsa.Api.Client/Resources/StorageDrivers/Models/StorageDriverDescriptor.cs b/src/clients/Elsa.Api.Client/Resources/StorageDrivers/Models/StorageDriverDescriptor.cs
index e0bab6929..835893b38 100644
--- a/src/clients/Elsa.Api.Client/Resources/StorageDrivers/Models/StorageDriverDescriptor.cs
+++ b/src/clients/Elsa.Api.Client/Resources/StorageDrivers/Models/StorageDriverDescriptor.cs
@@ -5,4 +5,4 @@ namespace Elsa.Api.Client.Resources.StorageDrivers.Models;
///
/// The type name of the storage driver.
/// The display name of the storage driver.
-public record StorageDriverDescriptor(string TypeName, string DisplayName);
\ No newline at end of file
+public record StorageDriverDescriptor(string TypeName, string DisplayName, double Priority = 0, bool Deprecated = false);
\ No newline at end of file
diff --git a/src/common/Elsa.Api.Common/Models/PagedListResponse.cs b/src/common/Elsa.Api.Common/Models/PagedListResponse.cs
index 07bfebe9e..06cdb55e9 100644
--- a/src/common/Elsa.Api.Common/Models/PagedListResponse.cs
+++ b/src/common/Elsa.Api.Common/Models/PagedListResponse.cs
@@ -16,4 +16,6 @@ public record PagedListResponse: LinkedResource
public ICollection Items { get; set; } = default!;
public long TotalCount { get; set; }
+
+ public static PagedListResponse From(Page page) => new(page);
}
\ No newline at end of file
diff --git a/src/common/Elsa.Testing.Shared.Component/Extensions/HttpResponseMessageExtensions.cs b/src/common/Elsa.Testing.Shared.Component/Extensions/HttpResponseMessageExtensions.cs
index 41702cb24..d58fb70aa 100644
--- a/src/common/Elsa.Testing.Shared.Component/Extensions/HttpResponseMessageExtensions.cs
+++ b/src/common/Elsa.Testing.Shared.Component/Extensions/HttpResponseMessageExtensions.cs
@@ -5,10 +5,10 @@ namespace Elsa.Testing.Shared.Extensions;
public static class HttpResponseMessageExtensions
{
- public static async Task ReadAsJsonAsync(this HttpResponseMessage response, CancellationToken cancellationToken = default)
+ public static async Task ReadAsJsonAsync(this HttpResponseMessage response, IServiceProvider serviceProvider, CancellationToken cancellationToken = default)
{
var json = await response.Content.ReadAsStringAsync(cancellationToken);
- var options = RefitSettingsHelper.CreateJsonSerializerOptions();
+ var options = RefitSettingsHelper.CreateJsonSerializerOptions(serviceProvider);
return JsonSerializer.Deserialize(json, options)!;
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.AzureServiceBus/Features/AzureServiceBusFeature.cs b/src/modules/Elsa.AzureServiceBus/Features/AzureServiceBusFeature.cs
index ed4dbf8be..3dc47bc16 100644
--- a/src/modules/Elsa.AzureServiceBus/Features/AzureServiceBusFeature.cs
+++ b/src/modules/Elsa.AzureServiceBus/Features/AzureServiceBusFeature.cs
@@ -70,9 +70,8 @@ public class AzureServiceBusFeature : FeatureBase
.AddSingleton(ServiceBusAdministrationClientFactory)
.AddSingleton(ServiceBusClientFactory)
.AddSingleton()
- .AddSingleton()
.AddSingleton()
- ;
+ .AddScoped();
// Definition providers.
Services
diff --git a/src/modules/Elsa.AzureServiceBus/HostedServices/CreateQueuesTopicsAndSubscriptions.cs b/src/modules/Elsa.AzureServiceBus/HostedServices/CreateQueuesTopicsAndSubscriptions.cs
index b6285f044..3e488421e 100644
--- a/src/modules/Elsa.AzureServiceBus/HostedServices/CreateQueuesTopicsAndSubscriptions.cs
+++ b/src/modules/Elsa.AzureServiceBus/HostedServices/CreateQueuesTopicsAndSubscriptions.cs
@@ -1,4 +1,6 @@
using Elsa.AzureServiceBus.Contracts;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Elsa.AzureServiceBus.HostedServices;
@@ -6,16 +8,16 @@ namespace Elsa.AzureServiceBus.HostedServices;
///
/// A blocking hosted service that creates queues, topics and subscriptions.
///
-public class CreateQueuesTopicsAndSubscriptions : IHostedService
+[UsedImplicitly]
+public class CreateQueuesTopicsAndSubscriptions(IServiceScopeFactory scopeFactory) : IHostedService
{
- private readonly IServiceBusInitializer _serviceBusInitializer;
- ///
- /// Constructor.
- ///
- public CreateQueuesTopicsAndSubscriptions(IServiceBusInitializer serviceBusInitializer) => _serviceBusInitializer = serviceBusInitializer;
-
///
- public Task StartAsync(CancellationToken cancellationToken) => _serviceBusInitializer.InitializeAsync(cancellationToken);
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ await using var scope = scopeFactory.CreateAsyncScope();
+ var initializer = scope.ServiceProvider.GetRequiredService();
+ await initializer.InitializeAsync(cancellationToken);
+ }
///
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
diff --git a/src/modules/Elsa.AzureServiceBus/HostedServices/StartWorkers.cs b/src/modules/Elsa.AzureServiceBus/HostedServices/StartWorkers.cs
index 59d0cc72d..3c6526053 100644
--- a/src/modules/Elsa.AzureServiceBus/HostedServices/StartWorkers.cs
+++ b/src/modules/Elsa.AzureServiceBus/HostedServices/StartWorkers.cs
@@ -46,6 +46,6 @@ public class StartWorkers(IServiceScopeFactory scopeFactory) : IHostedService
private async Task EnsureWorkersAsync(IWorkerManager workerManager, IEnumerable stimuli, CancellationToken cancellationToken)
{
- foreach (var payload in stimuli) await workerManager.StartWorkerAsync(payload.QueueOrTopic, payload.Subscription, cancellationToken);
+ foreach (var stimulus in stimuli) await workerManager.StartWorkerAsync(stimulus.QueueOrTopic, stimulus.Subscription, cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs
index ace176adf..c2012d190 100644
--- a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs
+++ b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs
@@ -101,7 +101,7 @@ internal class DapperActivityExecutionRecordStore(Store Map(ActivityExecutionRecord source, CancellationToken cancellationToken)
+ private ActivityExecutionRecordRecord Map(ActivityExecutionRecord source, CancellationToken cancellationToken)
{
return new ActivityExecutionRecordRecord
{
@@ -125,7 +125,7 @@ internal class DapperActivityExecutionRecordStore(Store MapAsync(ActivityExecutionRecordRecord source, CancellationToken cancellationToken)
+ private ActivityExecutionRecord Map(ActivityExecutionRecordRecord source, CancellationToken cancellationToken)
{
return new ActivityExecutionRecord
{
diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/Elsa.EntityFrameworkCore.Common.csproj b/src/modules/Elsa.EntityFrameworkCore.Common/Elsa.EntityFrameworkCore.Common.csproj
index 3e6b0e3dc..7c23bc98b 100644
--- a/src/modules/Elsa.EntityFrameworkCore.Common/Elsa.EntityFrameworkCore.Common.csproj
+++ b/src/modules/Elsa.EntityFrameworkCore.Common/Elsa.EntityFrameworkCore.Common.csproj
@@ -1,4 +1,4 @@
-
+
@@ -20,7 +20,7 @@
-
+
diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Elsa.EntityFrameworkCore.MySql.csproj b/src/modules/Elsa.EntityFrameworkCore.MySql/Elsa.EntityFrameworkCore.MySql.csproj
index c00d3b8d7..7945447f3 100644
--- a/src/modules/Elsa.EntityFrameworkCore.MySql/Elsa.EntityFrameworkCore.MySql.csproj
+++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Elsa.EntityFrameworkCore.MySql.csproj
@@ -12,6 +12,10 @@
+
+
+
+
diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Elsa.EntityFrameworkCore.PostgreSql.csproj b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Elsa.EntityFrameworkCore.PostgreSql.csproj
index e0f1c22ec..f3216e047 100644
--- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Elsa.EntityFrameworkCore.PostgreSql.csproj
+++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Elsa.EntityFrameworkCore.PostgreSql.csproj
@@ -12,6 +12,10 @@
+
+
+
+
diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Elsa.EntityFrameworkCore.SqlServer.csproj b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Elsa.EntityFrameworkCore.SqlServer.csproj
index 40e587431..b2b5b5804 100644
--- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Elsa.EntityFrameworkCore.SqlServer.csproj
+++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Elsa.EntityFrameworkCore.SqlServer.csproj
@@ -18,9 +18,10 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Elsa.EntityFrameworkCore.Sqlite.csproj b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Elsa.EntityFrameworkCore.Sqlite.csproj
index 7b7edfa04..aedd72175 100644
--- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Elsa.EntityFrameworkCore.Sqlite.csproj
+++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Elsa.EntityFrameworkCore.Sqlite.csproj
@@ -13,6 +13,10 @@
+
+
+
+
diff --git a/src/modules/Elsa.EntityFrameworkCore/Elsa.EntityFrameworkCore.csproj b/src/modules/Elsa.EntityFrameworkCore/Elsa.EntityFrameworkCore.csproj
index 1d6ce3fff..8bd6e0c7c 100644
--- a/src/modules/Elsa.EntityFrameworkCore/Elsa.EntityFrameworkCore.csproj
+++ b/src/modules/Elsa.EntityFrameworkCore/Elsa.EntityFrameworkCore.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/ActivityExecutionLogStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/ActivityExecutionLogStore.cs
index 9e08035fe..c6f1c1b60 100644
--- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/ActivityExecutionLogStore.cs
+++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/ActivityExecutionLogStore.cs
@@ -84,12 +84,12 @@ public class EFCoreActivityExecutionStore(
{
entity = entity.SanitizeLogMessage();
var compressionAlgorithm = options.Value.CompressionAlgorithm ?? nameof(None);
- var serializedActivityState = entity.ActivityState != null ? await safeSerializer.SerializeAsync(entity.ActivityState, cancellationToken) : null;
+ var serializedActivityState = entity.ActivityState != null ? safeSerializer.Serialize(entity.ActivityState, cancellationToken) : null;
var compressedSerializedActivityState = serializedActivityState != null ? await compressionCodecResolver.Resolve(compressionAlgorithm).CompressAsync(serializedActivityState, cancellationToken) : null;
dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = compressedSerializedActivityState;
dbContext.Entry(entity).Property("SerializedActivityStateCompressionAlgorithm").CurrentValue = compressionAlgorithm;
- dbContext.Entry(entity).Property("SerializedOutputs").CurrentValue = entity.Outputs?.Any() == true ? await safeSerializer.SerializeAsync(entity.Outputs, cancellationToken) : null;
+ dbContext.Entry(entity).Property("SerializedOutputs").CurrentValue = entity.Outputs?.Any() == true ? safeSerializer.Serialize(entity.Outputs, cancellationToken) : null;
dbContext.Entry(entity).Property("SerializedProperties").CurrentValue = entity.Properties.Any() ? payloadSerializer.Serialize(entity.Properties) : null;
dbContext.Entry(entity).Property("SerializedException").CurrentValue = entity.Exception != null ? payloadSerializer.Serialize(entity.Exception) : null;
dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload?.Any() == true ? payloadSerializer.Serialize(entity.Payload) : null;
@@ -103,7 +103,7 @@ public class EFCoreActivityExecutionStore(
entity.ActivityState = await DeserializeActivityState(dbContext, entity, cancellationToken);
entity.Outputs = Deserialize>(dbContext, entity, "SerializedOutputs");
- entity.Properties = Deserialize?>(dbContext, entity, "SerializedProperties") ?? new Dictionary();
+ entity.Properties = DeserializePayload?>(dbContext, entity, "SerializedProperties") ?? new Dictionary();
entity.Exception = DeserializePayload(dbContext, entity, "SerializedException");
entity.Payload = DeserializePayload>(dbContext, entity, "SerializedPayload");
}
diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowExecutionLogStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowExecutionLogStore.cs
index b8987247d..31c97306a 100644
--- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowExecutionLogStore.cs
+++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowExecutionLogStore.cs
@@ -75,8 +75,8 @@ public class EFCoreWorkflowExecutionLogStore(EntityStore
- /// Returns true if the specified type is date-like type, false otherwise.
+ /// Returns true if the specified type is a date-like type, false otherwise.
///
private static bool IsDateType(Type type)
{
diff --git a/src/modules/Elsa.FileStorage/Elsa.FileStorage.csproj b/src/modules/Elsa.FileStorage/Elsa.FileStorage.csproj
index 60d30ee8d..432926232 100644
--- a/src/modules/Elsa.FileStorage/Elsa.FileStorage.csproj
+++ b/src/modules/Elsa.FileStorage/Elsa.FileStorage.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/src/modules/Elsa.Http/Elsa.Http.csproj b/src/modules/Elsa.Http/Elsa.Http.csproj
index cd4a44274..b0cf13616 100644
--- a/src/modules/Elsa.Http/Elsa.Http.csproj
+++ b/src/modules/Elsa.Http/Elsa.Http.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/modules/Elsa.MassTransit.AzureServiceBus/Features/AzureServiceBusFeature.cs b/src/modules/Elsa.MassTransit.AzureServiceBus/Features/AzureServiceBusFeature.cs
index 0fc93e992..3ba3cc824 100644
--- a/src/modules/Elsa.MassTransit.AzureServiceBus/Features/AzureServiceBusFeature.cs
+++ b/src/modules/Elsa.MassTransit.AzureServiceBus/Features/AzureServiceBusFeature.cs
@@ -1,4 +1,5 @@
using Azure.Messaging.ServiceBus.Administration;
+using Elsa.Common.Contracts;
using Elsa.Extensions;
using Elsa.Features.Abstractions;
using Elsa.Features.Attributes;
@@ -121,6 +122,13 @@ public class AzureServiceBusFeature : FeatureBase
}
configurator.ConfigureEndpoints(context, new KebabCaseEndpointNameFormatter("Elsa", false));
+
+ configurator.ConfigureJsonSerializerOptions(serializerOptions =>
+ {
+ var serializer = context.GetRequiredService();
+ serializer.ApplyOptions(serializerOptions);
+ return serializerOptions;
+ });
});
};
});
diff --git a/src/modules/Elsa.MassTransit.RabbitMq/Features/RabbitMqServiceBusFeature.cs b/src/modules/Elsa.MassTransit.RabbitMq/Features/RabbitMqServiceBusFeature.cs
index 064d0a948..3359fffb0 100644
--- a/src/modules/Elsa.MassTransit.RabbitMq/Features/RabbitMqServiceBusFeature.cs
+++ b/src/modules/Elsa.MassTransit.RabbitMq/Features/RabbitMqServiceBusFeature.cs
@@ -1,3 +1,4 @@
+using Elsa.Common.Contracts;
using Elsa.Extensions;
using Elsa.Features.Abstractions;
using Elsa.Features.Attributes;
@@ -87,6 +88,13 @@ public class RabbitMqServiceBusFeature : FeatureBase
}
configurator.ConfigureEndpoints(context, new KebabCaseEndpointNameFormatter("Elsa", false));
+
+ configurator.ConfigureJsonSerializerOptions(serializerOptions =>
+ {
+ var serializer = context.GetRequiredService();
+ serializer.ApplyOptions(serializerOptions);
+ return serializerOptions;
+ });
});
};
});
diff --git a/src/modules/Elsa.MassTransit/Consumers/DispatchWorkflowRequestConsumer.cs b/src/modules/Elsa.MassTransit/Consumers/DispatchWorkflowRequestConsumer.cs
index bc7f233cb..1460939ae 100644
--- a/src/modules/Elsa.MassTransit/Consumers/DispatchWorkflowRequestConsumer.cs
+++ b/src/modules/Elsa.MassTransit/Consumers/DispatchWorkflowRequestConsumer.cs
@@ -119,4 +119,12 @@ public class DispatchWorkflowRequestConsumer(IWorkflowDefinitionService workflow
var workflowClient = await workflowRuntime.CreateClientAsync(message.InstanceId, cancellationToken);
await workflowClient.RunInstanceAsync(request, cancellationToken);
}
+
+ private IDictionary? DeserializeInput(string? json)
+ {
+ if (string.IsNullOrWhiteSpace(json))
+ return null;
+
+ return jsonSerializer.Deserialize>(json);
+ }
}
\ No newline at end of file
diff --git a/src/modules/Elsa.MassTransit/Messages/DispatchResumeWorkflows.cs b/src/modules/Elsa.MassTransit/Messages/DispatchResumeWorkflows.cs
index 5496500ea..ec9f59969 100644
--- a/src/modules/Elsa.MassTransit/Messages/DispatchResumeWorkflows.cs
+++ b/src/modules/Elsa.MassTransit/Messages/DispatchResumeWorkflows.cs
@@ -14,6 +14,10 @@ public class DispatchResumeWorkflows(string activityTypeName, object stimulus)
public string? CorrelationId { get; set; }
public string? WorkflowInstanceId { get; set; }
public string? ActivityInstanceId { get; set; }
+
+ [Obsolete("This property is no longer used and will be removed in a future version. Use the SerializedInput property instead.")]
public IDictionary? Input { get; set; }
+
+ public string? SerializedInput { get; set; }
public IDictionary? Properties { get; set; }
}
\ No newline at end of file
diff --git a/src/modules/Elsa.MassTransit/Messages/DispatchTriggerWorkflowsRequest.cs b/src/modules/Elsa.MassTransit/Messages/DispatchTriggerWorkflowsRequest.cs
index 86838495a..adbfd6d88 100644
--- a/src/modules/Elsa.MassTransit/Messages/DispatchTriggerWorkflowsRequest.cs
+++ b/src/modules/Elsa.MassTransit/Messages/DispatchTriggerWorkflowsRequest.cs
@@ -14,6 +14,10 @@ public class DispatchTriggerWorkflows(string activityTypeName, object stimulus)
public string? CorrelationId { get; set; }
public string? WorkflowInstanceId { get; set; }
public string? ActivityInstanceId { get; set; }
+
+ [Obsolete("This property is no longer used and will be removed in a future version. Use the SerializedInput property instead.")]
public IDictionary? Input { get; set; }
+
+ public string? SerializedInput { get; set; }
public IDictionary? Properties { get; set; }
}
\ No newline at end of file
diff --git a/src/modules/Elsa.MassTransit/Messages/DispatchWorkflowDefinition.cs b/src/modules/Elsa.MassTransit/Messages/DispatchWorkflowDefinition.cs
index bd76648ec..7ae22b69c 100644
--- a/src/modules/Elsa.MassTransit/Messages/DispatchWorkflowDefinition.cs
+++ b/src/modules/Elsa.MassTransit/Messages/DispatchWorkflowDefinition.cs
@@ -32,8 +32,14 @@ public record DispatchWorkflowDefinition
/// The ID of the parent workflow instance.
public string? ParentWorkflowInstanceId { get; init; }
+ /// Deprecated. Use the property instead.
+ [Obsolete("This property is no longer used and will be removed in a future version. Use the SerializedInput property instead.")]
+ public IDictionary? Input { get; set; }
+
+ ///
/// Any input to pass to the workflow.
- public IDictionary? Input { get; init; }
+ ///
+ public string? SerializedInput { get; set; }
/// Any properties to attach to the workflow.
public IDictionary? Properties { get; init; }
diff --git a/src/modules/Elsa.MassTransit/Messages/DispatchWorkflowInstance.cs b/src/modules/Elsa.MassTransit/Messages/DispatchWorkflowInstance.cs
index 16ee63bb7..4719083d2 100644
--- a/src/modules/Elsa.MassTransit/Messages/DispatchWorkflowInstance.cs
+++ b/src/modules/Elsa.MassTransit/Messages/DispatchWorkflowInstance.cs
@@ -8,6 +8,8 @@ public class DispatchWorkflowInstance(string instanceId)
public string? BookmarkId { get; set; }
public ActivityHandle? ActivityHandle { get; set; }
public IDictionary? Input { get; set; }
+
+ public string? SerializedInput { get; set; }
public IDictionary? Properties { get; set; }
public string? CorrelationId { get; set; }
}
\ No newline at end of file
diff --git a/src/modules/Elsa.MassTransit/Services/MassTransitWorkflowDispatcher.cs b/src/modules/Elsa.MassTransit/Services/MassTransitWorkflowDispatcher.cs
index 5f806672c..04a27055a 100644
--- a/src/modules/Elsa.MassTransit/Services/MassTransitWorkflowDispatcher.cs
+++ b/src/modules/Elsa.MassTransit/Services/MassTransitWorkflowDispatcher.cs
@@ -25,6 +25,7 @@ public class MassTransitWorkflowDispatcher(
IStimulusHasher stimulusHasher,
ITriggerStore triggerStore,
IBookmarkStore bookmarkStore,
+ IPayloadSerializer jsonSerializer,
ILogger logger)
: IWorkflowDispatcher
{
@@ -53,6 +54,7 @@ public class MassTransitWorkflowDispatcher(
public async Task DispatchAsync(DispatchWorkflowInstanceRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default)
{
var sendEndpoint = await GetSendEndpointAsync(options);
+ var serializedInput = SerializeInput(request.Input);
await sendEndpoint.Send(new DispatchWorkflowInstance(request.InstanceId)
{
@@ -173,4 +175,9 @@ public class MassTransitWorkflowDispatcher(
var sendEndpoint = await bus.GetSendEndpoint(new Uri($"queue:{endpointName}"));
return sendEndpoint;
}
+
+ private string? SerializeInput(object? input)
+ {
+ return input != null ? jsonSerializer.Serialize(input) : null;
+ }
}
\ No newline at end of file
diff --git a/src/modules/Elsa.MongoDb/Elsa.MongoDb.csproj b/src/modules/Elsa.MongoDb/Elsa.MongoDb.csproj
index 91fc3bdff..2a84690bf 100644
--- a/src/modules/Elsa.MongoDb/Elsa.MongoDb.csproj
+++ b/src/modules/Elsa.MongoDb/Elsa.MongoDb.csproj
@@ -13,6 +13,11 @@
+
+
+
+
+
diff --git a/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj b/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj
index 8660d883e..dc06e309b 100644
--- a/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj
+++ b/src/modules/Elsa.OpenTelemetry/Elsa.OpenTelemetry.csproj
@@ -11,8 +11,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs
index 4c8e1e1aa..043a11092 100644
--- a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs
+++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingActivityExecutionMiddleware.cs
@@ -1,36 +1,82 @@
using System.Diagnostics;
+using Elsa.Common.Contracts;
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 Newtonsoft.Json;
using Activity = System.Diagnostics.Activity;
using ActivityKind = System.Diagnostics.ActivityKind;
namespace Elsa.OpenTelemetry.Middleware;
+///
[UsedImplicitly]
-public class OpenTelemetryTracingActivityExecutionMiddleware(ActivityMiddlewareDelegate next) : IActivityExecutionMiddleware
+public class OpenTelemetryTracingActivityExecutionMiddleware(ActivityMiddlewareDelegate next, ISystemClock systemClock) : 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"));
+ using var span = ElsaOpenTelemetry.ActivitySource.StartActivity($"ActivityExecution", ActivityKind.Internal, Activity.Current?.Context ?? default);
+
+ if (span == null)
+ {
+ await next(context);
+ return;
+ }
+
+ span.SetTag("activity.nodeId", activity.NodeId);
+ span.SetTag("activity.type", activity.Type);
+ span.SetTag("activity.name", activity.Name);
+ span.SetTag("activityInstance.id", context.Id);
+ span.SetTag("activityExecution.startTimeUtc", span.StartTimeUtc);
+
+ span.AddEvent(new ActivityEvent("Executing", tags: CreateStatusTags(context)));
+
await next(context);
- span?.AddEvent(new ActivityEvent("Executed"));
- span?.AddTag("activityInstance.newStatus", context.Status.ToString());
+
+ if (context.Status == ActivityStatus.Faulted)
+ {
+ span.AddEvent(new ActivityEvent("Faulted", tags: CreateStatusTags(context)));
+ span.SetStatus(ActivityStatusCode.Error);
+ span.SetTag("error", true);
+ span.SetTag("activityInstance.hasIncidents", true);
+
+ var errorMessage = string.IsNullOrWhiteSpace(context.Exception?.Message) ? "Unknown error" : context.Exception.Message;
+ span.SetTag("error.message", errorMessage);
+
+ if (!string.IsNullOrEmpty(context.Exception?.StackTrace))
+ span.SetTag("error.stackTrace", context.Exception.StackTrace);
+ }
+ else
+ {
+ span.AddEvent(new ActivityEvent("Executed", tags: CreateStatusTags(context)));
+ span.SetStatus(ActivityStatusCode.Ok);
+ }
+
+ var now = systemClock.UtcNow;
+ span.SetTag("activityExecution.endTimeUtc", now);
+ span.SetTag("activityExecution.durationMs", (now - span.StartTimeUtc).TotalMilliseconds);
+ }
+
+ private ActivityTagsCollection CreateStatusTags(ActivityExecutionContext context)
+ {
+ return new ActivityTagsCollection(new Dictionary
+ {
+ ["activityInstance.status"] = 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);
-}
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs
index b2a599765..f5d20b7c5 100644
--- a/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs
+++ b/src/modules/Elsa.OpenTelemetry/Middleware/OpenTelemetryTracingWorkflowExecutionMiddleware.cs
@@ -1,42 +1,103 @@
using System.Diagnostics;
+using System.Text.Json;
+using Elsa.Common.Contracts;
+using Elsa.Expressions.Services;
+using Elsa.Extensions;
using Elsa.OpenTelemetry.Helpers;
using Elsa.Workflows;
+using Elsa.Workflows.Contracts;
using Elsa.Workflows.Pipelines.WorkflowExecution;
+using Elsa.Workflows.Serialization.Converters;
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 class OpenTelemetryTracingWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, ISystemClock systemClock) : WorkflowExecutionMiddleware(next)
{
+ private readonly JsonSerializerOptions? _incidentSerializerOptions = new JsonSerializerOptions().WithConverters(new TypeJsonConverter(WellKnownTypeRegistry.CreateDefault()));
+
+ ///
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);
+ using var span = ElsaOpenTelemetry.ActivitySource.StartActivity("WorkflowExecution", ActivityKind.Internal, Activity.Current?.Context ?? default);
+
+ if (span == null) // No listener is registered.
+ {
+ await Next(context);
+ return;
+ }
+
+ span.SetTag("workflowInstance.id", workflowInstanceId);
+ span.SetTag("workflowDefinition.definitionId", workflow.Identity.DefinitionId);
+ span.SetTag("workflowDefinition.version", workflow.Identity.Version);
+ span.SetTag("workflowDefinition.name", workflow.WorkflowMetadata.Name);
+ span.SetTag("workflowExecution.startTimeUtc", span.StartTimeUtc);
- if(!string.IsNullOrWhiteSpace(context.CorrelationId))
- activity?.AddTag("correlationId", context.CorrelationId);
+ if(context.TriggerActivityId != null)
+ {
+ var activity = context.FindActivityById(context.TriggerActivityId) ?? throw new Exception($"Trigger activity with ID {context.TriggerActivityId} not found. This should not happen.");
+ span.SetTag("workflowExecution.trigger.activityId", activity.Id);
+ span.SetTag("workflowExecution.trigger.activityName", activity.Name);
+ span.SetTag("workflowExecution.trigger.activityType", activity.Type);
+ }
- 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"));
+ span.AddEvent(new ActivityEvent("Executing", tags: CreateStatusTags(context)));
await Next(context);
- activity?.AddEvent(new ActivityEvent("Executed"));
- activity?.AddTag("workflowInstance.newStatus", context.Status.ToString());
- activity?.AddTag("workflowInstance.newSubStatus", context.SubStatus.ToString());
+
+ if (context.SubStatus == WorkflowSubStatus.Faulted)
+ {
+ span.AddEvent(new ActivityEvent("Faulted", tags: CreateStatusTags(context)));
+ span.SetStatus(ActivityStatusCode.Error);
+ span.SetTag("error", true);
+ }
+ else
+ {
+ span.AddEvent(new ActivityEvent("Executed", tags: CreateStatusTags(context)));
+ span.SetStatus(ActivityStatusCode.Ok);
+ }
+
+ if(context.Incidents.Any())
+ {
+ span.SetStatus(ActivityStatusCode.Error);
+ span.SetTag("workflowInstance.hasIncidents", true);
+ span.SetTag("error", true);
+
+ if (context.Incidents.Count > 0)
+ span.SetTag("error.message", JsonSerializer.Serialize(context.Incidents, _incidentSerializerOptions));
+ }
+
+ if (!string.IsNullOrWhiteSpace(context.CorrelationId))
+ span.SetTag("workflowInstance.correlationId", context.CorrelationId);
+
+ var now = systemClock.UtcNow;
+ span.SetTag("workflowExecution.endTimeUtc", now);
+ span.SetTag("workflowExecution.durationMs", (now - span.StartTimeUtc).TotalMilliseconds);
+ }
+
+ private ActivityTagsCollection CreateStatusTags(WorkflowExecutionContext context)
+ {
+ return new ActivityTagsCollection(new Dictionary
+ {
+ ["workflowInstance.status"] = context.Status.ToString(),
+ ["workflowInstance.subStatus"] = 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);
-}
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/Elsa.Quartz.EntityFrameworkCore.MySql.csproj b/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/Elsa.Quartz.EntityFrameworkCore.MySql.csproj
index d0ac5a8fa..145a64880 100644
--- a/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/Elsa.Quartz.EntityFrameworkCore.MySql.csproj
+++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/Elsa.Quartz.EntityFrameworkCore.MySql.csproj
@@ -14,6 +14,10 @@
+
+
+
+
diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.PostgreSql/Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj b/src/modules/Elsa.Quartz.EntityFrameworkCore.PostgreSql/Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj
index 43788dd8e..8f6d2c9c6 100644
--- a/src/modules/Elsa.Quartz.EntityFrameworkCore.PostgreSql/Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj
+++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.PostgreSql/Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj
@@ -15,6 +15,10 @@
+
+
+
+
diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.SqlServer/Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj b/src/modules/Elsa.Quartz.EntityFrameworkCore.SqlServer/Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj
index f150fdcdb..c6ede3ade 100644
--- a/src/modules/Elsa.Quartz.EntityFrameworkCore.SqlServer/Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj
+++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.SqlServer/Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj
@@ -21,8 +21,9 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.Sqlite/Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj b/src/modules/Elsa.Quartz.EntityFrameworkCore.Sqlite/Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj
index 63abad7b2..1a5ad7e07 100644
--- a/src/modules/Elsa.Quartz.EntityFrameworkCore.Sqlite/Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj
+++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.Sqlite/Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj
@@ -13,6 +13,11 @@
+
+
+
+
+
diff --git a/src/modules/Elsa.Telnyx/Elsa.Telnyx.csproj b/src/modules/Elsa.Telnyx/Elsa.Telnyx.csproj
index 12026827f..a3c269bb2 100644
--- a/src/modules/Elsa.Telnyx/Elsa.Telnyx.csproj
+++ b/src/modules/Elsa.Telnyx/Elsa.Telnyx.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/src/modules/Elsa.WorkflowProviders.BlobStorage/Elsa.WorkflowProviders.BlobStorage.csproj b/src/modules/Elsa.WorkflowProviders.BlobStorage/Elsa.WorkflowProviders.BlobStorage.csproj
index 6e6ed94eb..ab9102873 100644
--- a/src/modules/Elsa.WorkflowProviders.BlobStorage/Elsa.WorkflowProviders.BlobStorage.csproj
+++ b/src/modules/Elsa.WorkflowProviders.BlobStorage/Elsa.WorkflowProviders.BlobStorage.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj b/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj
index c216dc49d..c41a9e2d3 100644
--- a/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj
+++ b/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj
@@ -15,4 +15,8 @@
+
+
+
+
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutionSummaries/ListSummaries/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutionSummaries/ListSummaries/Endpoint.cs
new file mode 100644
index 000000000..55638298e
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutionSummaries/ListSummaries/Endpoint.cs
@@ -0,0 +1,38 @@
+using Elsa.Abstractions;
+using Elsa.Common.Entities;
+using Elsa.Models;
+using Elsa.Workflows.Runtime.Contracts;
+using Elsa.Workflows.Runtime.Entities;
+using Elsa.Workflows.Runtime.Filters;
+using Elsa.Workflows.Runtime.OrderDefinitions;
+using JetBrains.Annotations;
+
+namespace Elsa.Workflows.Api.Endpoints.ActivityExecutionSummaries.ListSummaries;
+
+///
+/// Lists a summary view of the executions for a given activity.
+///
+[PublicAPI]
+internal class Endpoint(IActivityExecutionStore store) : ElsaEndpoint>
+{
+ ///
+ public override void Configure()
+ {
+ Get("/activity-execution-summaries/list");
+ ConfigurePermissions("read:activity-execution");
+ }
+
+ ///
+ public override async Task> ExecuteAsync(Request request, CancellationToken cancellationToken)
+ {
+ var filter = new ActivityExecutionRecordFilter
+ {
+ WorkflowInstanceId = request.WorkflowInstanceId,
+ ActivityNodeId = request.ActivityNodeId,
+ Completed = request.Completed
+ };
+ var order = new ActivityExecutionRecordOrder(x => x.StartedAt, OrderDirection.Ascending);
+ var records = (await store.FindManySummariesAsync(filter, order, cancellationToken)).ToList();
+ return new ListResponse(records);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutionSummaries/ListSummaries/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutionSummaries/ListSummaries/Models.cs
new file mode 100644
index 000000000..b90456857
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutionSummaries/ListSummaries/Models.cs
@@ -0,0 +1,22 @@
+namespace Elsa.Workflows.Api.Endpoints.ActivityExecutionSummaries.ListSummaries;
+
+///
+/// A request for a list of activity execution log record summaries for a given activity in a workflow instance.
+///
+internal class Request
+{
+ ///
+ /// The ID of the workflow instance to get the execution log for.
+ ///
+ public string WorkflowInstanceId { get; set; } = default!;
+
+ ///
+ /// The node ID of the activity to get the execution record for.
+ ///
+ public string ActivityNodeId { get; set; } = default!;
+
+ ///
+ /// Whether to include completed activity execution records. If not specified, all activity execution records will be included.
+ ///
+ public bool? Completed { get; set; }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutions/Get/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutions/Get/Endpoint.cs
new file mode 100644
index 000000000..8828ebb8b
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/ActivityExecutions/Get/Endpoint.cs
@@ -0,0 +1,41 @@
+using Elsa.Abstractions;
+using Elsa.Workflows.Runtime.Contracts;
+using Elsa.Workflows.Runtime.Entities;
+using Elsa.Workflows.Runtime.Filters;
+using JetBrains.Annotations;
+
+namespace Elsa.Workflows.Api.Endpoints.ActivityExecutions.Get;
+
+///
+/// Gets an individual execution for a given activity.
+///
+[PublicAPI]
+internal class Endpoint(IActivityExecutionStore store) : ElsaEndpointWithoutRequest
+{
+ ///
+ public override void Configure()
+ {
+ Get("/activity-executions/{id}");
+ ConfigurePermissions("read:activity-execution");
+ }
+
+ ///
+ public override async Task HandleAsync(CancellationToken cancellationToken)
+ {
+ var id = Route("id");
+ var filter = new ActivityExecutionRecordFilter
+ {
+ Id = id
+ };
+
+ var record = await store.FindAsync(filter, cancellationToken);
+
+ if (record == null)
+ {
+ await SendNotFoundAsync(cancellationToken);
+ return;
+ }
+
+ await SendOkAsync(record, cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Endpoint.cs
index a38030ea5..207501e21 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Endpoint.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Endpoint.cs
@@ -31,7 +31,7 @@ public class List : ElsaEndpointWithoutRequest
public override Task ExecuteAsync(CancellationToken ct)
{
var drivers = _registry.List();
- var descriptors = drivers.Select(FromDriver).ToList();
+ var descriptors = drivers.Select(FromDriver).OrderByDescending(x => x.Priority).ToList();
var response = new Response(descriptors);
return Task.FromResult(response);
@@ -40,7 +40,9 @@ public class List : ElsaEndpointWithoutRequest
private static StorageDriverDescriptor FromDriver(IStorageDriver driver)
{
var type = driver.GetType();
+ var deprecated = type.GetCustomAttribute() != null;
var displayName = type.GetCustomAttribute()?.Name ?? type.GetCustomAttribute()?.DisplayName ?? type.Name.Replace("StorageDriver", "");
- return new StorageDriverDescriptor(type.GetSimpleAssemblyQualifiedName(), displayName);
+ var priority = driver.Priority;
+ return new StorageDriverDescriptor(type.GetSimpleAssemblyQualifiedName(), displayName, priority, deprecated);
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Models.cs
index 127a1ce68..c52464164 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Models.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/StorageDrivers/List/Models.cs
@@ -5,4 +5,4 @@ public class Response(ICollection items)
public ICollection Items { get; set; } = items;
}
-public record StorageDriverDescriptor(string TypeName, string DisplayName);
\ No newline at end of file
+public record StorageDriverDescriptor(string TypeName, string DisplayName, double Priority = 0, bool Deprecated = false);
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs
index 8d86392ad..ccfd79067 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs
@@ -123,8 +123,8 @@ internal class Export : ElsaEndpointWithMapper
var bookmarks = request.IncludeBookmarks ? await LoadBookmarksAsync(workflowState.Id, cancellationToken) : null;
var workflowStateElement = _workflowStateSerializer.SerializeToElement(workflowState);
var bookmarksElement = bookmarks != null ? SerializeBookmarks(bookmarks) : default(JsonElement?);
- var executionLogRecordsElement = executionLogRecords != null ? await _safeSerializer.SerializeToElementAsync(executionLogRecords, cancellationToken) : default(JsonElement?);
- var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? await _safeSerializer.SerializeToElementAsync(activityExecutionLogRecords, cancellationToken) : default(JsonElement?);
+ var executionLogRecordsElement = executionLogRecords != null ? _safeSerializer.SerializeToElement(executionLogRecords, cancellationToken) : default(JsonElement?);
+ var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? _safeSerializer.SerializeToElement(activityExecutionLogRecords, cancellationToken) : default(JsonElement?);
var model = new ExportedWorkflowState(workflowStateElement, bookmarksElement, activityExecutionLogRecordsElement, executionLogRecordsElement);
return model;
}
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Import/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Import/Endpoint.cs
index db551b630..83af6f479 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Import/Endpoint.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Import/Endpoint.cs
@@ -148,13 +148,13 @@ internal class Import : ElsaEndpointWithoutRequest
if (model.ActivityExecutionRecords != null)
{
- var activityExecutionRecords = await _safeSerializer.DeserializeAsync>(model.ActivityExecutionRecords.Value, cancellationToken);
+ var activityExecutionRecords = _safeSerializer.Deserialize>(model.ActivityExecutionRecords.Value, cancellationToken);
await _activityExecutionStore.SaveManyAsync(activityExecutionRecords, cancellationToken);
}
if (model.WorkflowExecutionLogRecords != null)
{
- var workflowExecutionLogRecords = await _safeSerializer.DeserializeAsync>(model.WorkflowExecutionLogRecords.Value, cancellationToken);
+ var workflowExecutionLogRecords = _safeSerializer.Deserialize>(model.WorkflowExecutionLogRecords.Value, cancellationToken);
await _workflowExecutionLogStore.SaveManyAsync(workflowExecutionLogRecords, cancellationToken);
}
}
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/GetLastEntry/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/GetLastEntry/Endpoint.cs
index 64dd32871..0c7378351 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/GetLastEntry/Endpoint.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/GetLastEntry/Endpoint.cs
@@ -12,16 +12,8 @@ namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Journal.GetLastEntry;
/// Return the last log entry for the specified workflow instance and activity ID.
///
[PublicAPI]
-public class Get : ElsaEndpoint
+public class Get(IWorkflowExecutionLogStore store) : ElsaEndpoint
{
- private readonly IWorkflowExecutionLogStore _store;
-
- ///
- public Get(IWorkflowExecutionLogStore store)
- {
- _store = store;
- }
-
///
public override void Configure()
{
@@ -36,7 +28,7 @@ public class Get : ElsaEndpoint
{
WorkflowInstanceId = request.WorkflowInstanceId,
ActivityId = request.ActivityId,
- EventNames = new[] { "Started", "Completed", "Faulted" }
+ EventNames = ["Started", "Completed", "Faulted"]
};
var sort = new WorkflowExecutionLogRecordOrder(
@@ -44,7 +36,7 @@ public class Get : ElsaEndpoint
OrderDirection.Descending
);
- var entry = await _store.FindAsync(filter, sort, cancellationToken);
+ var entry = await store.FindAsync(filter, sort, cancellationToken);
if (entry == null)
{
diff --git a/src/modules/Elsa.Workflows.Core/Activities/Composite.cs b/src/modules/Elsa.Workflows.Core/Activities/Composite.cs
index f702057a8..6661adb95 100644
--- a/src/modules/Elsa.Workflows.Core/Activities/Composite.cs
+++ b/src/modules/Elsa.Workflows.Core/Activities/Composite.cs
@@ -17,7 +17,7 @@ namespace Elsa.Workflows.Activities;
/// Represents a composite activity that has a single activity. Like a workflow, but without workflow-level properties.
///
[PublicAPI]
-public abstract class Composite : Activity, IVariableContainer
+public abstract class Composite : Activity, IVariableContainer, IComposite
{
///
protected Composite(string? source = default, int? line = default) : base(source, line)
@@ -172,6 +172,10 @@ public abstract class Composite : Activity, IVariableContainer
/// Creates a new activity.
///
protected static SetVariable SetVariable(Variable variable, Variable value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) => new(variable, value, source, line);
+
+ public virtual void Setup()
+ {
+ }
}
///
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IComposite.cs b/src/modules/Elsa.Workflows.Core/Contracts/IComposite.cs
new file mode 100644
index 000000000..6953b59ef
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Core/Contracts/IComposite.cs
@@ -0,0 +1,6 @@
+namespace Elsa.Workflows.Contracts;
+
+public interface IComposite
+{
+ void Setup();
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IPayloadSerializer.cs b/src/modules/Elsa.Workflows.Core/Contracts/IPayloadSerializer.cs
index 5768049ea..762765bf4 100644
--- a/src/modules/Elsa.Workflows.Core/Contracts/IPayloadSerializer.cs
+++ b/src/modules/Elsa.Workflows.Core/Contracts/IPayloadSerializer.cs
@@ -48,4 +48,9 @@ public interface IPayloadSerializer
/// The serialized state.
/// The deserialized state.
T Deserialize(JsonElement serializedData);
+
+ ///
+ /// Gets the JSON serializer options.
+ ///
+ JsonSerializerOptions GetOptions();
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/ISafeSerializer.cs b/src/modules/Elsa.Workflows.Core/Contracts/ISafeSerializer.cs
index e241694d3..1bf90ac24 100644
--- a/src/modules/Elsa.Workflows.Core/Contracts/ISafeSerializer.cs
+++ b/src/modules/Elsa.Workflows.Core/Contracts/ISafeSerializer.cs
@@ -11,24 +11,57 @@ public interface ISafeSerializer
///
/// Serializes the specified state.
///
+ [Obsolete("Use the non-async Serialize instead.")]
[RequiresUnreferencedCode("The type T may be trimmed.")]
ValueTask SerializeAsync(object? value, CancellationToken cancellationToken = default);
///
/// Serializes the specified state to a object.
///
+ [Obsolete("Use the non-async SerializeToElement instead.")]
[RequiresUnreferencedCode("The type T may be trimmed.")]
ValueTask SerializeToElementAsync(object? value, CancellationToken cancellationToken = default);
///
/// Deserializes the specified state.
///
+ [Obsolete("Use the non-async Deserialize instead.")]
[RequiresUnreferencedCode("The type T may be trimmed.")]
ValueTask DeserializeAsync(string json, CancellationToken cancellationToken = default);
///
/// Deserializes the specified state.
///
+ [Obsolete("Use the non-async Deserialize instead.")]
[RequiresUnreferencedCode("The type T may be trimmed.")]
ValueTask DeserializeAsync(JsonElement element, CancellationToken cancellationToken = default);
+
+ ///
+ /// Serializes the specified state.
+ ///
+ [RequiresUnreferencedCode("The type T may be trimmed.")]
+ string Serialize(object? value, CancellationToken cancellationToken = default);
+
+ ///
+ /// Serializes the specified state to a object.
+ ///
+ [RequiresUnreferencedCode("The type T may be trimmed.")]
+ JsonElement SerializeToElement(object? value, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deserializes the specified state.
+ ///
+ [RequiresUnreferencedCode("The type T may be trimmed.")]
+ T Deserialize(string json, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deserializes the specified state.
+ ///
+ [RequiresUnreferencedCode("The type T may be trimmed.")]
+ T Deserialize(JsonElement element, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets the JSON serializer options.
+ ///
+ JsonSerializerOptions GetOptions();
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IStorageDriver.cs b/src/modules/Elsa.Workflows.Core/Contracts/IStorageDriver.cs
index d17ab4735..50ca0eec2 100644
--- a/src/modules/Elsa.Workflows.Core/Contracts/IStorageDriver.cs
+++ b/src/modules/Elsa.Workflows.Core/Contracts/IStorageDriver.cs
@@ -5,6 +5,11 @@ namespace Elsa.Workflows.Contracts;
///
public interface IStorageDriver
{
+ ///
+ /// The priority of the storage driver. Drivers with higher priority are used before drivers with lower priority.
+ ///
+ double Priority { get; }
+
///
/// Writes a value to the storage driver.
///
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowStateSerializer.cs b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowStateSerializer.cs
index 4a4fd0a58..b55fd8150 100644
--- a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowStateSerializer.cs
+++ b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowStateSerializer.cs
@@ -13,27 +13,70 @@ public interface IWorkflowStateSerializer
///
/// The workflow state to serialize.
/// The serialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
+ [Obsolete("Use the non-async version Serialize instead.")]
+ Task SerializeAsync(WorkflowState workflowState, CancellationToken cancellationToken = default);
+
+ ///
+ /// Serializes the specified workflow state.
+ ///
+ /// The workflow state to serialize.
+ /// The serialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
string Serialize(WorkflowState workflowState);
///
/// Serializes the specified workflow state.
///
/// The workflow state to serialize.
+ /// The cancellation token.
/// The serialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
+ [Obsolete("Use the non-async version SerializeToUtfBytes instead.")]
+ Task SerializeToUtfBytesAsync(WorkflowState workflowState, CancellationToken cancellationToken = default);
+
+ ///
+ /// Serializes the specified workflow state.
+ ///
+ /// The workflow state to serialize.
+ /// The serialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
byte[] SerializeToUtfBytes(WorkflowState workflowState);
///
/// Serializes the specified workflow state.
///
/// The workflow state to serialize.
+ /// The cancellation token.
/// The serialized workflow state.
- JsonElement SerializeToElement(WorkflowState workflowState);
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
+ [Obsolete("Use the non-async version SerializeToElement instead.")]
+ Task SerializeToElementAsync(WorkflowState workflowState, CancellationToken cancellationToken = default);
///
/// Serializes the specified workflow state.
///
/// The workflow state to serialize.
/// The serialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
+ JsonElement SerializeToElement(WorkflowState workflowState);
+
+ ///
+ /// Serializes the specified workflow state.
+ ///
+ /// The workflow state to serialize.
+ /// The cancellation token.
+ /// The serialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
+ [Obsolete("Use the non-async version Serialize instead.")]
+ Task SerializeAsync(object workflowState, CancellationToken cancellationToken = default);
+
+ ///
+ /// Serializes the specified workflow state.
+ ///
+ /// The workflow state to serialize.
+ /// The serialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
string Serialize(object workflowState);
///
@@ -41,19 +84,52 @@ public interface IWorkflowStateSerializer
///
/// The serialized state.
/// The deserialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
+ [Obsolete("Use the non-async version Deserialize instead.")]
+ Task DeserializeAsync(string serializedState, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deserializes the specified serialized state.
+ ///
+ /// The serialized state.
+ /// The deserialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
WorkflowState Deserialize(string serializedState);
///
/// Deserializes the specified serialized state.
///
/// The serialized state.
+ /// The cancellation token.
/// The deserialized workflow state.
- WorkflowState Deserialize(JsonElement serializedState);
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
+ [Obsolete("Use the non-async version Deserialize instead.")]
+ Task DeserializeAsync(JsonElement serializedState, CancellationToken cancellationToken = default);
///
/// Deserializes the specified serialized state.
///
/// The serialized state.
/// The deserialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
+ WorkflowState Deserialize(JsonElement serializedState);
+
+ ///
+ /// Deserializes the specified serialized state.
+ ///
+ /// The serialized state.
+ /// The cancellation token.
+ /// The deserialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
+ [Obsolete("Use the non-async version Deserialize instead.")]
+ Task DeserializeAsync(string serializedState, CancellationToken cancellationToken = default);
+
+ ///
+ /// Deserializes the specified serialized state.
+ ///
+ /// The serialized state.
+ /// The cancellation token.
+ /// The deserialized workflow state.
+ [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
T Deserialize(string serializedState);
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs
index a075530c0..c66e759bf 100644
--- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs
+++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs
@@ -108,6 +108,47 @@ public static partial class ActivityExecutionContextExtensions
///
public static IDictionary GetVariableValues(this ActivityExecutionContext activityExecutionContext) => activityExecutionContext.ExpressionExecutionContext.ReadAndFlattenMemoryBlocks();
+ ///
+ /// Evaluates each input property of the activity.
+ ///
+ public static async Task EvaluateInputPropertiesAsync(this ActivityExecutionContext context)
+ {
+ var activityDescriptor = context.ActivityDescriptor;
+ var inputDescriptors = activityDescriptor.Inputs.Where(x => x.AutoEvaluate).ToList();
+
+ // Evaluate inputs.
+ foreach (var inputDescriptor in inputDescriptors)
+ await EvaluateInputPropertyAsync(context, activityDescriptor, inputDescriptor);
+
+ context.SetHasEvaluatedProperties();
+ }
+
+ ///
+ /// Evaluates the specified input property of the activity.
+ ///
+ public static async Task EvaluateInputPropertyAsync(this ActivityExecutionContext context, Expression>> propertyExpression)
+ {
+ var inputName = propertyExpression.GetProperty()!.Name;
+ var input = await EvaluateInputPropertyAsync(context, inputName);
+ return input.ConvertTo();
+ }
+
+ ///
+ /// Evaluates a specific input property of the activity.
+ ///
+ public static async Task