diff --git a/.github/workflows/elsa-server-and-studio.yml b/.github/workflows/elsa-server-and-studio.yml
index 48eade767..cfe0e4a82 100644
--- a/.github/workflows/elsa-server-and-studio.yml
+++ b/.github/workflows/elsa-server-and-studio.yml
@@ -3,7 +3,7 @@ on:
workflow_dispatch:
push:
branches:
- - main
+ - develop/3.6.0
jobs:
push_to_registry:
diff --git a/.github/workflows/elsa-server.yml b/.github/workflows/elsa-server.yml
index 306543f28..3816ae32b 100644
--- a/.github/workflows/elsa-server.yml
+++ b/.github/workflows/elsa-server.yml
@@ -3,7 +3,7 @@ on:
workflow_dispatch:
push:
branches:
- - main
+ - develop/3.6.0
jobs:
push_to_registry:
diff --git a/.github/workflows/elsa-studio.yml b/.github/workflows/elsa-studio.yml
index 2dd39bce8..4ae49a986 100644
--- a/.github/workflows/elsa-studio.yml
+++ b/.github/workflows/elsa-studio.yml
@@ -3,7 +3,7 @@ on:
workflow_dispatch:
push:
branches:
- - main
+ - develop/3.6.0
jobs:
push_to_registry:
diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml
index 853e39fd2..56b389005 100644
--- a/.github/workflows/packages.yml
+++ b/.github/workflows/packages.yml
@@ -48,7 +48,7 @@ jobs:
run: |
if [[ "${{ github.ref }}" == refs/tags/* && "${{ github.event_name }}" == "release" && ("${{ github.event.action }}" == "published" || "${{ github.event.action }}" == "prereleased")]]; then
git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
- git branch --remote --contains | grep origin/main
+ git branch --remote --contains | grep origin/develop/3.6.0
else
git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
git branch --remote --contains | grep origin/${BRANCH_NAME}
@@ -60,7 +60,7 @@ jobs:
TAG_NAME=${TAG_NAME#refs/tags/} # remove the refs/tags/ prefix
echo "VERSION=${TAG_NAME}" >> $GITHUB_ENV
else
- echo "VERSION=${{env.base_version}}-${PACKAGE_PREFIX}.${{github.run_number}}" >> $GITHUB_ENV
+ echo "VERSION=${{env.base_version}}-preview.${{github.run_number}}" >> $GITHUB_ENV
fi
# - name: Set up JDK 17
# uses: actions/setup-java@v2
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2c0f1d979..37e306f02 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -9,7 +9,7 @@
9.7.0
-
+
@@ -50,11 +50,11 @@
-
-
+
+
-
+
@@ -106,7 +106,7 @@
-
+
@@ -133,7 +133,7 @@
-
+
@@ -152,6 +152,10 @@
+
+
+
+
@@ -171,7 +175,7 @@
-
+
\ No newline at end of file
diff --git a/Elsa.sln b/Elsa.sln
index 642fbb80c..92910e32c 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -64,6 +64,9 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit", "unit", "{18453B51-25EB-4317-A4B3-B10518252E92}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration", "integration", "{1B8D5897-902E-4632-8698-E89CAF3DDF54}"
+ ProjectSection(SolutionItems) = preProject
+ test\integration\Elsa.Logging.Core.LoggerSinkTests.cs = test\integration\Elsa.Logging.Core.LoggerSinkTests.cs
+ EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "component", "component", "{08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C}"
ProjectSection(SolutionItems) = preProject
@@ -247,6 +250,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Expressions.JavaScript
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Expressions.JavaScript.Libraries", "src\modules\Elsa.Expressions.JavaScript.Libraries\Elsa.Expressions.JavaScript.Libraries.csproj", "{08B69CC4-B5F0-44E8-FA7A-6BF6F00CA40A}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "diagnostics", "diagnostics", "{1FB2FE77-5D7F-48D5-8FFE-530D21AFBA7D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Logging", "src\modules\Elsa.Logging\Elsa.Logging.csproj", "{68A0BC44-8A3E-4C45-8AFF-0662B81D2739}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Logging.Console", "src\modules\Elsa.Logging.Console\Elsa.Logging.Console.csproj", "{2CE3BD1E-0966-47DF-B870-6A4EB7EA0188}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Logging.Serilog", "src\modules\Elsa.Logging.Serilog\Elsa.Logging.Serilog.csproj", "{3E6DFD22-5F71-4A4E-A792-B010ADDCFBA6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Logging.Core", "src\modules\Elsa.Logging.Core\Elsa.Logging.Core.csproj", "{48A85A19-B654-4570-B332-653BC0B6A846}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Logging.Core.IntegrationTests", "test\integration\Elsa.Logging.Core.IntegrationTests\Elsa.Logging.Core.IntegrationTests.csproj", "{A5C87AAF-E607-4DA7-B2E1-08FEAA41B293}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Logging.Core.UnitTests", "test\unit\Elsa.Logging.Core.UnitTests\Elsa.Logging.Core.UnitTests.csproj", "{4229B9B3-60D3-4CFE-B147-B3865212C6C8}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Expressions.Liquid", "src\modules\Elsa.Expressions.Liquid\Elsa.Expressions.Liquid.csproj", "{6AF53651-99F0-1DE0-D37B-4FF6B0348DBB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Expressions.Python", "src\modules\Elsa.Expressions.Python\Elsa.Expressions.Python.csproj", "{9D8FB664-88B4-10BE-58A2-D9A1644AD2E4}"
@@ -461,6 +478,42 @@ Global
{E7137FB0-1988-4562-AD8D-D0D9D2EE85F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7137FB0-1988-4562-AD8D-D0D9D2EE85F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7137FB0-1988-4562-AD8D-D0D9D2EE85F6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB24F9FE-D7BD-4FCC-907E-AE400288C2A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB24F9FE-D7BD-4FCC-907E-AE400288C2A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB24F9FE-D7BD-4FCC-907E-AE400288C2A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB24F9FE-D7BD-4FCC-907E-AE400288C2A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9CA02818-F7EB-4A0B-B27B-BC74ACD499C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9CA02818-F7EB-4A0B-B27B-BC74ACD499C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9CA02818-F7EB-4A0B-B27B-BC74ACD499C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9CA02818-F7EB-4A0B-B27B-BC74ACD499C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C583AF05-D517-4B7F-8955-6B61500ED3D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C583AF05-D517-4B7F-8955-6B61500ED3D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C583AF05-D517-4B7F-8955-6B61500ED3D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C583AF05-D517-4B7F-8955-6B61500ED3D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {68A0BC44-8A3E-4C45-8AFF-0662B81D2739}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {68A0BC44-8A3E-4C45-8AFF-0662B81D2739}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {68A0BC44-8A3E-4C45-8AFF-0662B81D2739}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {68A0BC44-8A3E-4C45-8AFF-0662B81D2739}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2CE3BD1E-0966-47DF-B870-6A4EB7EA0188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2CE3BD1E-0966-47DF-B870-6A4EB7EA0188}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2CE3BD1E-0966-47DF-B870-6A4EB7EA0188}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2CE3BD1E-0966-47DF-B870-6A4EB7EA0188}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3E6DFD22-5F71-4A4E-A792-B010ADDCFBA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3E6DFD22-5F71-4A4E-A792-B010ADDCFBA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3E6DFD22-5F71-4A4E-A792-B010ADDCFBA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3E6DFD22-5F71-4A4E-A792-B010ADDCFBA6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {48A85A19-B654-4570-B332-653BC0B6A846}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {48A85A19-B654-4570-B332-653BC0B6A846}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {48A85A19-B654-4570-B332-653BC0B6A846}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {48A85A19-B654-4570-B332-653BC0B6A846}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5C87AAF-E607-4DA7-B2E1-08FEAA41B293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5C87AAF-E607-4DA7-B2E1-08FEAA41B293}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5C87AAF-E607-4DA7-B2E1-08FEAA41B293}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A5C87AAF-E607-4DA7-B2E1-08FEAA41B293}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4229B9B3-60D3-4CFE-B147-B3865212C6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4229B9B3-60D3-4CFE-B147-B3865212C6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4229B9B3-60D3-4CFE-B147-B3865212C6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4229B9B3-60D3-4CFE-B147-B3865212C6C8}.Release|Any CPU.Build.0 = Release|Any CPU
{6F14B066-DF7B-2409-59D8-CCCA90A4974C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F14B066-DF7B-2409-59D8-CCCA90A4974C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F14B066-DF7B-2409-59D8-CCCA90A4974C}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -565,6 +618,8 @@ 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}
+ {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
+ {47FBCB04-0C2D-453C-BE2F-7052CAC22524} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB}
{B32DB9B2-AD6C-48A5-8682-4373CB045185} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E}
{454652D5-E1BB-4D4B-9B21-9CEFC900C4FB} = {B32DB9B2-AD6C-48A5-8682-4373CB045185}
{31089E79-694B-4F45-97AF-86D34A7B231E} = {B32DB9B2-AD6C-48A5-8682-4373CB045185}
@@ -578,6 +633,21 @@ Global
{66E2E2CF-967F-4564-89E8-F46FA973C99B} = {986E5482-0482-448C-B9E4-EC67A9474B85}
{0A04B1FD-06C0-4271-A910-A08C263DBC44} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6}
{9B80A705-2E31-4012-964A-83963DCDB384} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6}
+ {CD7DC0D1-FFDC-417A-89BE-7F32408F583E} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
+ {70593549-8B26-4D63-9857-6BA8BB3E31DB} = {CD7DC0D1-FFDC-417A-89BE-7F32408F583E}
+ {E7137FB0-1988-4562-AD8D-D0D9D2EE85F6} = {CD7DC0D1-FFDC-417A-89BE-7F32408F583E}
+ {7FD1FD1E-5778-4065-AAA5-1F878129EF77} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
+ {EB24F9FE-D7BD-4FCC-907E-AE400288C2A5} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77}
+ {9CA02818-F7EB-4A0B-B27B-BC74ACD499C9} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77}
+ {C583AF05-D517-4B7F-8955-6B61500ED3D8} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77}
+ {1FB2FE77-5D7F-48D5-8FFE-530D21AFBA7D} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
+ {68A0BC44-8A3E-4C45-8AFF-0662B81D2739} = {1FB2FE77-5D7F-48D5-8FFE-530D21AFBA7D}
+ {2CDF3E1C-267D-4198-B1C7-7E1F548FC120} = {1FB2FE77-5D7F-48D5-8FFE-530D21AFBA7D}
+ {2CE3BD1E-0966-47DF-B870-6A4EB7EA0188} = {1FB2FE77-5D7F-48D5-8FFE-530D21AFBA7D}
+ {3E6DFD22-5F71-4A4E-A792-B010ADDCFBA6} = {1FB2FE77-5D7F-48D5-8FFE-530D21AFBA7D}
+ {48A85A19-B654-4570-B332-653BC0B6A846} = {1FB2FE77-5D7F-48D5-8FFE-530D21AFBA7D}
+ {A5C87AAF-E607-4DA7-B2E1-08FEAA41B293} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
+ {4229B9B3-60D3-4CFE-B147-B3865212C6C8} = {18453B51-25EB-4317-A4B3-B10518252E92}
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{6F14B066-DF7B-2409-59D8-CCCA90A4974C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{C55015F0-E9DF-4BF9-8131-D7539E938220} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 8acb41bf3..debf66283 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -45,7 +45,6 @@
- "5500:5500"
volumes:
- ./data/oracle-data:/opt/oracle/oradata
- - ./setup/oracle-setup:/opt/oracle/scripts/setup
mongodb:
image: mongo:latest
@@ -158,7 +157,6 @@
volumes:
sqlserver_data:
postgres-data:
- oracle-data-free1:
mysql_data2:
cockroachdb-data:
mongodb_data:
diff --git a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj
index 4cec33381..d5c6500ab 100644
--- a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj
+++ b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj
@@ -1,4 +1,4 @@
-
+
diff --git a/src/common/Elsa.Testing.Shared.Integration/RunActivityExtensions.cs b/src/common/Elsa.Testing.Shared.Integration/RunActivityExtensions.cs
index 52bc601bb..4efb894ef 100644
--- a/src/common/Elsa.Testing.Shared.Integration/RunActivityExtensions.cs
+++ b/src/common/Elsa.Testing.Shared.Integration/RunActivityExtensions.cs
@@ -1,14 +1,6 @@
-using Elsa.Common.Models;
using Elsa.Workflows;
-using Elsa.Workflows.Management;
-using Elsa.Workflows.Management.Entities;
-using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Models;
using Elsa.Workflows.Options;
-using Elsa.Workflows.Runtime;
-using Elsa.Workflows.Runtime.Filters;
-using Elsa.Workflows.Runtime.Messages;
-using Elsa.Workflows.State;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
@@ -45,6 +37,7 @@ public static class RunActivityExtensions
/// The result of running the activity.
public static async Task RunActivityAsync(this IServiceProvider services, IActivity activity, RunWorkflowOptions options, CancellationToken cancellationToken = default)
{
+ await services.PopulateRegistriesAsync();
var workflowRunner = services.GetRequiredService();
var result = await workflowRunner.RunAsync(activity, options, cancellationToken);
return result;
diff --git a/src/modules/Elsa.Common/Converters/BooleanConverter.cs b/src/modules/Elsa.Common/Converters/BooleanConverter.cs
new file mode 100644
index 000000000..253c65f64
--- /dev/null
+++ b/src/modules/Elsa.Common/Converters/BooleanConverter.cs
@@ -0,0 +1,29 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Elsa.Common.Converters;
+
+public class BooleanConverter : JsonConverter
+{
+ public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.True:
+ return true;
+ case JsonTokenType.False:
+ return false;
+ case JsonTokenType.String:
+ var value = reader.GetString();
+ if (bool.TryParse(value, out var b))
+ return b;
+ break;
+ }
+ throw new JsonException($"Cannot convert {reader.TokenType} to bool");
+ }
+
+ public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
+ {
+ writer.WriteBooleanValue(value);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Common/Converters/NullableBooleanConverter.cs b/src/modules/Elsa.Common/Converters/NullableBooleanConverter.cs
new file mode 100644
index 000000000..7aed5ffdc
--- /dev/null
+++ b/src/modules/Elsa.Common/Converters/NullableBooleanConverter.cs
@@ -0,0 +1,34 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Elsa.Common.Converters;
+
+public class NullableBooleanConverter : JsonConverter
+{
+ public override bool? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.True:
+ return true;
+ case JsonTokenType.False:
+ return false;
+ case JsonTokenType.String:
+ var value = reader.GetString();
+ if (bool.TryParse(value, out var b))
+ return b;
+ break;
+ case JsonTokenType.Null:
+ return null;
+ }
+ throw new JsonException($"Cannot convert {reader.TokenType} to bool?");
+ }
+
+ public override void Write(Utf8JsonWriter writer, bool? value, JsonSerializerOptions options)
+ {
+ if (value.HasValue)
+ writer.WriteBooleanValue(value.Value);
+ else
+ writer.WriteNullValue();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Activities/SendHttpRequestBase.cs b/src/modules/Elsa.Http/Activities/SendHttpRequestBase.cs
index 898c76365..34e90abc7 100644
--- a/src/modules/Elsa.Http/Activities/SendHttpRequestBase.cs
+++ b/src/modules/Elsa.Http/Activities/SendHttpRequestBase.cs
@@ -216,6 +216,7 @@ public abstract class SendHttpRequestBase(string? source = null, int? line = nul
async Task SendRequestAsyncCore(CancellationToken ct = default)
{
var request = PrepareRequest(context);
+
return await httpClient.SendAsync(request, ct);
}
}
diff --git a/src/modules/Elsa.Resilience.Core/Services/ResilientActivityInvoker.cs b/src/modules/Elsa.Resilience.Core/Services/ResilientActivityInvoker.cs
index 8bf2c250f..bc86d879e 100644
--- a/src/modules/Elsa.Resilience.Core/Services/ResilientActivityInvoker.cs
+++ b/src/modules/Elsa.Resilience.Core/Services/ResilientActivityInvoker.cs
@@ -1,5 +1,6 @@
using System.Text.Json;
using Elsa.Expressions.Helpers;
+using Elsa.Extensions;
using Elsa.Resilience.Entities;
using Elsa.Resilience.Extensions;
using Elsa.Resilience.Models;
@@ -12,55 +13,56 @@ using Polly.Telemetry;
namespace Elsa.Resilience;
public class ResilientActivityInvoker(
- IResilienceStrategyConfigEvaluator resilienceStrategyConfigEvaluator,
- IRetryAttemptRecorder retryAttemptRecorder,
- IIdentityGenerator identityGenerator,
+ IResilienceStrategyConfigEvaluator resilienceStrategyConfigEvaluator,
+ IRetryAttemptRecorder retryAttemptRecorder,
+ IIdentityGenerator identityGenerator,
ResilienceStrategySerializer resilienceStrategySerializer) : IResilientActivityInvoker
{
private const string ResilienceStrategyIdPropKey = "resilienceStrategy";
+ private const string RetryAttemptsCountKey = "RetryAttemptsCount";
public async Task InvokeAsync(IResilientActivity activity, ActivityExecutionContext context, Func> action, CancellationToken cancellationToken = default)
{
// Get the resilience strategy.
var strategyConfig = GetStrategyConfig(activity);
var resilienceStrategy = await resilienceStrategyConfigEvaluator.EvaluateAsync(strategyConfig, context.ExpressionExecutionContext, cancellationToken);
-
+
// If no resilience strategy is configured, execute the action as-is.
if (resilienceStrategy == null)
return await action();
-
+
// Record the applied strategy as part of the activity execution context for diagnostics.
var resilienceStrategyModel = JsonSerializer.SerializeToNode(resilienceStrategy, resilienceStrategySerializer.SerializerOptions)!;
context.SetResilienceStrategy(resilienceStrategyModel);
-
+
// Create a resilience pipeline builder.
var builder = CreateResiliencePipelineBuilder();
var retries = new List();
context.TransientProperties[RetryAttempt.RetriesKey] = retries;
-
+
// Create a resilience context.
var resilienceContext = ResilienceContextPool.Shared.Get(cancellationToken);
resilienceContext.Properties.Set(new(nameof(ActivityExecutionContext)), context);
-
+
try
{
// Configure the resilience pipeline.
await resilienceStrategy.ConfigurePipeline(builder, resilienceContext);
var pipeline = builder.Build();
-
+
// Execute the action within the resilience pipeline.
var result = await pipeline.ExecuteAsync(async _ => await action(), resilienceContext);
-
+
// Record the retry attempts.
await RecordRetryAttempts(activity, context, retries, cancellationToken);
-
+
return result;
}
finally
{
ResilienceContextPool.Shared.Return(resilienceContext);
}
-
+
}
private async Task RecordRetryAttempts(IResilientActivity activity, ActivityExecutionContext context, ICollection attempts, CancellationToken cancellationToken = default)
@@ -70,9 +72,11 @@ public class ResilientActivityInvoker(
var records = Map(context, activity, attempts);
var recordContext = new RecordRetryAttemptsContext(context, records, cancellationToken);
await retryAttemptRecorder.RecordAsync(recordContext);
-
+
// Propagate a flag that retries have occurred. This information can then be used to show the retry attempts in the workflow designer.
context.SetRetriesAttemptedFlag();
+
+ context.SetExtensionsMetadata(RetryAttemptsCountKey, attempts.Count);
}
}
@@ -89,7 +93,7 @@ public class ResilientActivityInvoker(
? null
: value.ConvertTo();
}
-
+
private ICollection Map(ActivityExecutionContext activityExecutionContext, IResilientActivity resilientActivity, ICollection attempts)
{
return attempts.Select(x => Map(activityExecutionContext, resilientActivity, x)).ToList();
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/GetEndpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/GetEndpoint.cs
index 8f0f8e148..4bd54724a 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/GetEndpoint.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/GetEndpoint.cs
@@ -1,3 +1,4 @@
+using Elsa.Abstractions;
using Elsa.Workflows.Management;
using Elsa.Workflows.Runtime;
using JetBrains.Annotations;
@@ -12,13 +13,27 @@ internal class GetEndpoint(
IWorkflowDefinitionService workflowDefinitionService,
IWorkflowRuntime workflowRuntime,
IWorkflowStarter workflowStarter,
- IApiSerializer apiSerializer)
- : EndpointBase(workflowDefinitionService, workflowRuntime, workflowStarter, apiSerializer)
+ IApiSerializer apiSerializer)
+ : ElsaEndpoint
{
///
public override void Configure()
{
- base.Configure();
+ Routes("/workflow-definitions/{definitionId}/execute");
+ ConfigurePermissions("exec:workflow-definitions");
Verbs(FastEndpoints.Http.GET);
}
+
+ ///
+ public override async Task HandleAsync(GetRequest request, CancellationToken cancellationToken)
+ {
+ await WorkflowExecutionHelper.ExecuteWorkflowAsync(
+ request,
+ workflowDefinitionService,
+ workflowRuntime,
+ workflowStarter,
+ apiSerializer,
+ HttpContext,
+ cancellationToken);
+ }
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Models.cs
index 468ded31c..2ec0598cd 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Models.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Models.cs
@@ -1,5 +1,4 @@
using System.Dynamic;
-using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Common.Models;
using Elsa.Expressions.Helpers;
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/PostEndpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/PostEndpoint.cs
index 2727e32a0..24d6168e3 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/PostEndpoint.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/PostEndpoint.cs
@@ -1,3 +1,5 @@
+using System.Text.Json;
+using Elsa.Abstractions;
using Elsa.Workflows.Management;
using Elsa.Workflows.Runtime;
using JetBrains.Annotations;
@@ -13,12 +15,59 @@ internal class PostEndpoint(
IWorkflowRuntime workflowRuntime,
IWorkflowStarter workflowStarter,
IApiSerializer apiSerializer)
- : EndpointBase(workflowDefinitionService, workflowRuntime, workflowStarter, apiSerializer)
+ : ElsaEndpointWithoutRequest
{
///
public override void Configure()
{
- base.Configure();
+ Routes("/workflow-definitions/{definitionId}/execute");
+ ConfigurePermissions("exec:workflow-definitions");
Verbs(FastEndpoints.Http.POST);
}
+
+ ///
+ public override async Task HandleAsync(CancellationToken cancellationToken)
+ {
+ PostRequest? request = null;
+
+ if (HttpContext.Request.ContentLength > 0 && (HttpContext.Request.ContentType?.Contains("application/json") ?? true))
+ {
+ try
+ {
+ request = await JsonSerializer.DeserializeAsync(HttpContext.Request.Body,
+ new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true
+ }, cancellationToken: cancellationToken);
+ }
+ catch
+ {
+ AddError("Invalid request body.");
+ }
+ }
+
+ request ??= new();
+
+ var definitionId = Route("definitionId");
+
+ if (string.IsNullOrWhiteSpace(definitionId))
+ AddError("Missing workflow definition ID.");
+ else
+ request.DefinitionId = definitionId;
+
+ if (ValidationFailed)
+ {
+ await Send.ErrorsAsync(cancellation: cancellationToken);
+ return;
+ }
+
+ await WorkflowExecutionHelper.ExecuteWorkflowAsync(
+ request,
+ workflowDefinitionService,
+ workflowRuntime,
+ workflowStarter,
+ apiSerializer,
+ HttpContext,
+ cancellationToken);
+ }
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/WorkflowExecutionHelper.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/WorkflowExecutionHelper.cs
new file mode 100644
index 000000000..359c1cb13
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/WorkflowExecutionHelper.cs
@@ -0,0 +1,91 @@
+using System.Net.Mime;
+using Elsa.Common.Models;
+using Elsa.Workflows.Management;
+using Elsa.Workflows.Runtime;
+using Elsa.Workflows.State;
+using FastEndpoints;
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Execute;
+
+public static class WorkflowExecutionHelper
+{
+ public static async Task ExecuteWorkflowAsync(
+ IExecutionRequest request,
+ IWorkflowDefinitionService workflowDefinitionService,
+ IWorkflowRuntime workflowRuntime,
+ IWorkflowStarter workflowStarter,
+ IApiSerializer apiSerializer,
+ HttpContext httpContext,
+ CancellationToken cancellationToken)
+ {
+ var definitionId = request.DefinitionId;
+ var versionOptions = request.VersionOptions ?? VersionOptions.Published;
+ var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(definitionId, versionOptions, cancellationToken);
+
+ if (workflowGraph == null)
+ {
+ await httpContext.Response.SendNotFoundAsync(cancellation: cancellationToken);
+ return;
+ }
+
+ var startRequest = new StartWorkflowRequest
+ {
+ Workflow = workflowGraph.Workflow,
+ CorrelationId = request.CorrelationId,
+ Name = request.Name,
+ Input = request.GetInputAsDictionary(),
+ Variables = request.GetVariablesAsDictionary(),
+ TriggerActivityId = request.TriggerActivityId,
+ ActivityHandle = request.ActivityHandle
+ };
+
+ var startResponse = await workflowStarter.StartWorkflowAsync(startRequest, cancellationToken);
+
+ if(!httpContext.Response.HasStarted)
+ httpContext.Response.Headers.Append("x-elsa-workflow-cannot-start", startResponse.CannotStart.ToString());
+
+ if (startResponse.CannotStart)
+ {
+ httpContext.Response.StatusCode = StatusCodes.Status200OK;
+ await httpContext.Response.SendOkAsync(cancellationToken);
+ return;
+ }
+
+ var instanceId = startResponse.WorkflowInstanceId!;
+
+ if(!httpContext.Response.HasStarted)
+ httpContext.Response.Headers.Append("x-elsa-workflow-instance-id", instanceId);
+
+ var workflowClient = await workflowRuntime.CreateClientAsync(instanceId, cancellationToken);
+
+ if (startResponse.SubStatus == WorkflowSubStatus.Faulted)
+ {
+ var workflowState = await workflowClient.ExportStateAsync(cancellationToken);
+ await HandleFaultAsync(workflowState, apiSerializer, httpContext, cancellationToken);
+ }
+ else
+ {
+ if (!httpContext.Response.HasStarted)
+ {
+ httpContext.Response.Headers.Append("x-elsa-response", "true");
+ if (httpContext.Response.StatusCode == StatusCodes.Status200OK)
+ {
+ var workflowState = await workflowClient.ExportStateAsync(cancellationToken);
+ var response = apiSerializer.Serialize(new Response(workflowState));
+ httpContext.Response.ContentType = MediaTypeNames.Application.Json;
+ await httpContext.Response.WriteAsync(response, cancellationToken);
+ }
+ }
+ }
+ }
+
+ private static async Task HandleFaultAsync(WorkflowState workflowState, IApiSerializer apiSerializer, HttpContext httpContext, CancellationToken cancellationToken)
+ {
+ var faultedResponse = apiSerializer.Serialize(new Response(workflowState));
+ httpContext.Response.ContentType = MediaTypeNames.Application.Json;
+ httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
+ await httpContext.Response.WriteAsync(faultedResponse, cancellationToken);
+ }
+}
+
diff --git a/src/modules/Elsa.Workflows.Core/Attributes/InputAttribute.cs b/src/modules/Elsa.Workflows.Core/Attributes/InputAttribute.cs
index 98335c6b0..982f409d0 100644
--- a/src/modules/Elsa.Workflows.Core/Attributes/InputAttribute.cs
+++ b/src/modules/Elsa.Workflows.Core/Attributes/InputAttribute.cs
@@ -78,6 +78,12 @@ public class InputAttribute : Attribute
///
public bool AutoEvaluate { get; set; } = true;
+ ///
+ /// Specifies the type of a custom evaluator to use for evaluating the input property value.
+ /// The evaluator type determines how the value for the property is resolved at runtime.
+ ///
+ public Type? EvaluatorType { get; set; }
+
///
/// A value indicating whether this input can be serialized as part of the workflow instance,
///
diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityInputEvaluatorContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityInputEvaluatorContext.cs
new file mode 100644
index 000000000..99e1a4572
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityInputEvaluatorContext.cs
@@ -0,0 +1,12 @@
+using Elsa.Expressions.Contracts;
+using Elsa.Expressions.Models;
+using Elsa.Workflows.Models;
+
+namespace Elsa.Workflows;
+
+public record ActivityInputEvaluatorContext(
+ ActivityExecutionContext ActivityExecutionContext,
+ ExpressionExecutionContext ExpressionExecutionContext,
+ InputDescriptor InputDescriptor,
+ Input Input,
+ IExpressionEvaluator ExpressionEvaluator);
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs
index 262d327c1..fa1f67c30 100644
--- a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs
+++ b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs
@@ -636,7 +636,7 @@ public partial class WorkflowExecutionContext : IExecutionContext
{
// Filter out completed activity execution contexts, except for the root Workflow activity context, which stores workflow-level variables.
// This will currently break scripts accessing activity output directly, but there's a workaround for that via variable capturing.
- // We may ultimately restore direct output access, but in a different way.
+ // We may ultimately restore direct output access, but differently.
return ActivityExecutionContexts.Where(x => !x.IsCompleted || x.ParentActivityExecutionContext == null);
}
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityInputEvaluator.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityInputEvaluator.cs
new file mode 100644
index 000000000..ec94417a4
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Core/Contracts/IActivityInputEvaluator.cs
@@ -0,0 +1,6 @@
+namespace Elsa.Workflows;
+
+public interface IActivityInputEvaluator
+{
+ Task
diff --git a/test/component/Elsa.Workflows.ComponentTests/Helpers/Fixtures/WorkflowServer.cs b/test/component/Elsa.Workflows.ComponentTests/Helpers/Fixtures/WorkflowServer.cs
index eb9c4ffad..62359f7ca 100644
--- a/test/component/Elsa.Workflows.ComponentTests/Helpers/Fixtures/WorkflowServer.cs
+++ b/test/component/Elsa.Workflows.ComponentTests/Helpers/Fixtures/WorkflowServer.cs
@@ -30,6 +30,14 @@ public class WorkflowServer(Infrastructure infrastructure, string url) : WebAppl
return RestService.For(client, CreateRefitSettings(Services));
}
+ public HttpClient CreateHttpClient()
+ {
+ var client = CreateClient();
+ client.BaseAddress = new(client.BaseAddress!, "/elsa/api/");
+ client.Timeout = TimeSpan.FromMinutes(1);
+ return client;
+ }
+
public HttpClient CreateHttpWorkflowClient()
{
var client = CreateClient();
diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Endpoints/WorkflowDefinitions/Execute/GetTests.cs b/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Endpoints/WorkflowDefinitions/Execute/GetTests.cs
new file mode 100644
index 000000000..f3cfc0f31
--- /dev/null
+++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Endpoints/WorkflowDefinitions/Execute/GetTests.cs
@@ -0,0 +1,44 @@
+using System.Net;
+using Elsa.Testing.Shared.Extensions;
+using Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Execute;
+using Elsa.Workflows.ComponentTests.Abstractions;
+using Elsa.Workflows.ComponentTests.Fixtures;
+
+namespace Elsa.Workflows.ComponentTests.Scenarios.RestApis.Endpoints.WorkflowDefinitions.Execute;
+
+public class GetTests(App app) : AppComponentTest(app)
+{
+ private const string DefinitionId = "3790068018ac4f02";
+ private const string Url = "workflow-definitions/{0}/execute";
+
+ [Fact]
+ public async Task Get_WithCorrelationId_ShouldReturnOk()
+ {
+ var client = WorkflowServer.CreateHttpClient();
+ var url = string.Format(Url, DefinitionId) + "?correlationId=" + Guid.NewGuid();
+ using var response = await client.GetAsync(url);
+ var model = await response.ReadAsJsonAsync(WorkflowServer.Services);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(WorkflowSubStatus.Finished, model.WorkflowState.SubStatus);
+ }
+
+ [Fact]
+ public async Task Get_WithoutCorrelationId_ShouldReturnOk()
+ {
+ var client = WorkflowServer.CreateHttpClient();
+ var url = string.Format(Url, DefinitionId);
+ using var response = await client.GetAsync(url);
+ var model = await response.ReadAsJsonAsync(WorkflowServer.Services);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(WorkflowSubStatus.Finished, model.WorkflowState.SubStatus);
+ }
+
+ [Fact]
+ public async Task Get_MissingDefinitionId_ShouldReturnNotFoundError()
+ {
+ var client = WorkflowServer.CreateHttpClient();
+ var url = "/workflow-definitions//execute";
+ using var response = await client.GetAsync(url);
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+}
diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Endpoints/WorkflowDefinitions/Execute/PostTests.cs b/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Endpoints/WorkflowDefinitions/Execute/PostTests.cs
new file mode 100644
index 000000000..8e27585fb
--- /dev/null
+++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Endpoints/WorkflowDefinitions/Execute/PostTests.cs
@@ -0,0 +1,63 @@
+using System.Net;
+using System.Text;
+using System.Text.Json;
+using Elsa.Testing.Shared.Extensions;
+using Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Execute;
+using Elsa.Workflows.ComponentTests.Abstractions;
+using Elsa.Workflows.ComponentTests.Fixtures;
+
+namespace Elsa.Workflows.ComponentTests.Scenarios.RestApis.Endpoints.WorkflowDefinitions.Execute;
+
+public class PostTests(App app) : AppComponentTest(app)
+{
+ private const string DefinitionId = "3790068018ac4f02";
+ private const string Url = "workflow-definitions/{0}/execute";
+
+ [Fact]
+ public async Task Post_WithValidJsonBody_ShouldReturnOk()
+ {
+ var client = WorkflowServer.CreateHttpClient();
+ var requestBody = JsonSerializer.Serialize(new PostRequest
+ {
+ CorrelationId = Guid.NewGuid().ToString()
+ });
+ var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
+ using var response = await client.PostAsync(string.Format(Url, DefinitionId), content);
+ var model = await response.ReadAsJsonAsync(WorkflowServer.Services);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(WorkflowSubStatus.Finished, model.WorkflowState.SubStatus);
+ }
+
+ [Fact]
+ public async Task Post_WithoutBodyAndWithoutContentType_ShouldReturnOk()
+ {
+ var client = WorkflowServer.CreateHttpClient();
+ var request = new HttpRequestMessage(HttpMethod.Post, string.Format(Url, DefinitionId));
+ // No content, no content-type
+ using var response = await client.SendAsync(request);
+ var model = await response.ReadAsJsonAsync(WorkflowServer.Services);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(WorkflowSubStatus.Finished, model.WorkflowState.SubStatus);
+ }
+
+ [Fact]
+ public async Task Post_WithoutBodyButWithContentType_ShouldReturnOk()
+ {
+ var client = WorkflowServer.CreateHttpClient();
+ var request = new HttpRequestMessage(HttpMethod.Post, string.Format(Url, DefinitionId));
+ request.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json");
+ using var response = await client.SendAsync(request);
+ var model = await response.ReadAsJsonAsync(WorkflowServer.Services);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(WorkflowSubStatus.Finished, model.WorkflowState.SubStatus);
+ }
+
+ [Fact]
+ public async Task Post_MissingDefinitionId_ShouldReturnNotFoundError()
+ {
+ var client = WorkflowServer.CreateHttpClient();
+ var request = new HttpRequestMessage(HttpMethod.Post, "/workflow-definitions//execute");
+ using var response = await client.SendAsync(request);
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+}
\ No newline at end of file
diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Workflows/hello-world.json b/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Workflows/hello-world.json
new file mode 100644
index 000000000..307ecb8b6
--- /dev/null
+++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/RestApis/Workflows/hello-world.json
@@ -0,0 +1,42 @@
+{
+ "id": "2790068018ac4f01",
+ "definitionId": "3790068018ac4f02",
+ "name": "Hello World",
+ "isLatest": true,
+ "isPublished": true,
+ "root": {
+ "type": "Elsa.Flowchart",
+ "version": 1,
+ "id": "969b0703a9379c3b",
+ "nodeId": "Workflow1:969b0703a9379c3b",
+ "activities": [
+ {
+ "text": {
+ "typeName": "String",
+ "expression": {
+ "type": "Literal",
+ "value": "Hello World!"
+ }
+ },
+ "id": "b039045bb7443e57",
+ "nodeId": "Workflow1:969b0703a9379c3b:b039045bb7443e57",
+ "name": "WriteLine1",
+ "type": "Elsa.WriteLine",
+ "version": 1,
+ "metadata": {
+ "designer": {
+ "position": {
+ "x": -231.796875,
+ "y": 269
+ },
+ "size": {
+ "width": 139.296875,
+ "height": 50
+ }
+ }
+ }
+ }
+ ],
+ "connections": []
+ }
+}
\ No newline at end of file