From 36791a5d2519ecc4054546c57fa6b60755b68742 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Fri, 29 Dec 2023 10:51:13 +0300 Subject: [PATCH 1/9] Fix Httpendpoint Path --- src/bundles/Elsa.ServerAndStudio.Web/appsettings.json | 1 + src/modules/Elsa.Http/UIHints/HttpEndpointPathUIHandler.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bundles/Elsa.ServerAndStudio.Web/appsettings.json b/src/bundles/Elsa.ServerAndStudio.Web/appsettings.json index a8caf8fe0..6a427b4da 100644 --- a/src/bundles/Elsa.ServerAndStudio.Web/appsettings.json +++ b/src/bundles/Elsa.ServerAndStudio.Web/appsettings.json @@ -66,6 +66,7 @@ "DefaultSender": "noreply@crmservices.com" }, "Http": { + "BaseUrl": "https://localhost:5001", "BasePath": "/api/workflows" }, "Webhooks": { diff --git a/src/modules/Elsa.Http/UIHints/HttpEndpointPathUIHandler.cs b/src/modules/Elsa.Http/UIHints/HttpEndpointPathUIHandler.cs index e81a9d840..0258340be 100644 --- a/src/modules/Elsa.Http/UIHints/HttpEndpointPathUIHandler.cs +++ b/src/modules/Elsa.Http/UIHints/HttpEndpointPathUIHandler.cs @@ -16,8 +16,8 @@ public class HttpEndpointPathUIHandler(IOptions options) : public ValueTask> GetUIPropertiesAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken = default) { var baseUrl = options.Value.BaseUrl; - var apiRoutePrefix = options.Value.ApiRoutePrefix; - var completeBaseUrl = new Uri(baseUrl, apiRoutePrefix); + var basePath = options.Value.BasePath; + var completeBaseUrl = new Uri(baseUrl, basePath); return new(new Dictionary { From dc223bc27393bc814b35dcde02ca7e6d75220532 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 29 Dec 2023 09:17:22 +0100 Subject: [PATCH 2/9] Refactor parameter in AddElsaClient method The parameter "configureOptions" in the AddElsaClient method has been renamed and refactored to allow direct HTTP client configuration. This change enables the actions for configuring the HTTP client to be passed directly, providing greater flexibility for customization. --- .../Extensions/DependencyInjectionExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs index a16717ead..86168c4ab 100644 --- a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs @@ -38,9 +38,9 @@ public static class DependencyInjectionExtensions /// The service collection. /// The base address of the Elsa API. /// The API key to use for authentication. - /// An optional delegate that can be used to configure the client options. + /// An optional delegate that can be used to configure the HTTP client. /// An optional delegate that can be used to configure the client builder options. - public static IServiceCollection AddElsaClient(this IServiceCollection services, Uri baseAddress, string apiKey, Action? configureOptions = default, Action? configureBuilderOptions = default) + public static IServiceCollection AddElsaClient(this IServiceCollection services, Uri baseAddress, string apiKey, Action? configureHttpClient = default, Action? configureBuilderOptions = default) { services.AddScoped(); return services.AddElsaClient( @@ -48,7 +48,7 @@ public static class DependencyInjectionExtensions { options.BaseAddress = baseAddress; options.ApiKey = apiKey; - configureOptions?.Invoke(options); + options.ConfigureHttpClient = configureHttpClient; }, configureBuilderOptions: options => { From 1d21cb3733f6953d0f14cb643827e71064d6d586 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 29 Dec 2023 09:30:51 +0100 Subject: [PATCH 3/9] Add extension method to get workflow instance ID This adds a new extension method in HttpResponseMessageExtensions class which retrieves the workflow instance ID from the response. It also includes a HeaderNames class to maintain the consistency of header names used by the Elsa API. --- .../Elsa.Api.Client/Extensions/HeaderExtensions.cs | 14 ++++++++++++++ src/clients/Elsa.Api.Client/Shared/HeaderNames.cs | 12 ++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/clients/Elsa.Api.Client/Extensions/HeaderExtensions.cs create mode 100644 src/clients/Elsa.Api.Client/Shared/HeaderNames.cs diff --git a/src/clients/Elsa.Api.Client/Extensions/HeaderExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/HeaderExtensions.cs new file mode 100644 index 000000000..e23bafce7 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Extensions/HeaderExtensions.cs @@ -0,0 +1,14 @@ +using Elsa.Api.Client.Shared; + +namespace Elsa.Api.Client.Extensions; + +/// +/// Contains extension methods for the class. +/// +public static class HttpResponseMessageExtensions +{ + /// + /// Gets the workflow instance ID from the response. + /// + public static string? GetWorkflowInstanceId(this HttpResponseMessage response) => response.Headers.TryGetValues(HeaderNames.WorkflowInstanceId, out var values) ? values.FirstOrDefault() : default; +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Shared/HeaderNames.cs b/src/clients/Elsa.Api.Client/Shared/HeaderNames.cs new file mode 100644 index 000000000..a3c26753c --- /dev/null +++ b/src/clients/Elsa.Api.Client/Shared/HeaderNames.cs @@ -0,0 +1,12 @@ +namespace Elsa.Api.Client.Shared; + +/// +/// Contains header names used by the Elsa API. +/// +public static class HeaderNames +{ + /// + /// The name of the header that contains the workflow instance ID. + /// + public const string WorkflowInstanceId = "x-elsa-workflow-instance-id"; +} \ No newline at end of file From 704d4cd8348e2180cfdeff1e79a3913d2cb1bd5a Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 29 Dec 2023 11:34:58 +0100 Subject: [PATCH 4/9] Remove ElsaClient and related interfaces Files ElsaClient.cs, IElsaClient.cs and IElsaClientFactory.cs have been deleted from the Elsa.Api.Client project. The commit also includes an update in the DependencyInjectionExtensions.cs file where IElsaClient service registration is removed, reflecting these changes. --- .../Elsa.Api.Client/Contracts/IElsaClient.cs | 37 ---------------- .../Contracts/IElsaClientFactory.cs | 14 ------ .../DependencyInjectionExtensions.cs | 3 -- .../Elsa.Api.Client/Services/ElsaClient.cs | 43 ------------------- 4 files changed, 97 deletions(-) delete mode 100644 src/clients/Elsa.Api.Client/Contracts/IElsaClient.cs delete mode 100644 src/clients/Elsa.Api.Client/Contracts/IElsaClientFactory.cs delete mode 100644 src/clients/Elsa.Api.Client/Services/ElsaClient.cs diff --git a/src/clients/Elsa.Api.Client/Contracts/IElsaClient.cs b/src/clients/Elsa.Api.Client/Contracts/IElsaClient.cs deleted file mode 100644 index 1530f9190..000000000 --- a/src/clients/Elsa.Api.Client/Contracts/IElsaClient.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts; -using Elsa.Api.Client.Resources.ActivityDescriptors.Contracts; -using Elsa.Api.Client.Resources.Scripting.Contracts; -using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts; -using Elsa.Api.Client.Resources.WorkflowInstances.Contracts; - -namespace Elsa.Api.Client.Contracts; - -/// -/// Represents a client for the Elsa API. Each API is exposed as a property. -/// -public interface IElsaClient -{ - /// - /// Gets the workflow definitions API. - /// - IWorkflowDefinitionsApi WorkflowDefinitions { get; } - - /// - /// Gets the activity descriptors API. - /// - IActivityDescriptorsApi ActivityDescriptors { get; } - - /// - /// Get the activity descriptor Options API - /// - IActivityDescriptorOptionsApi ActivityDescriptorOptions { get; } - /// - /// Gets the workflow instances API. - /// - IWorkflowInstancesApi WorkflowInstances { get; } - - /// - /// Gets the javascript API. - /// - IJavaScriptApi JavaScript { get; } -} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Contracts/IElsaClientFactory.cs b/src/clients/Elsa.Api.Client/Contracts/IElsaClientFactory.cs deleted file mode 100644 index cf109850e..000000000 --- a/src/clients/Elsa.Api.Client/Contracts/IElsaClientFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Elsa.Api.Client.Options; - -namespace Elsa.Api.Client.Contracts; - -/// -/// A factory for creating instances. -/// -public interface IElsaClientFactory -{ - /// - /// Creates a new instance. - /// - IElsaClient CreateClient(Action configureOptions, Action? configureHttpClientBuilder = default); -} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs index 86168c4ab..ff389d9fc 100644 --- a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs @@ -1,6 +1,5 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Elsa.Api.Client.Contracts; using Elsa.Api.Client.Converters; using Elsa.Api.Client.HttpMessageHandlers; using Elsa.Api.Client.Options; @@ -17,7 +16,6 @@ using Elsa.Api.Client.Resources.WorkflowActivationStrategies.Contracts; using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts; using Elsa.Api.Client.Resources.WorkflowExecutionContexts.Contracts; using Elsa.Api.Client.Resources.WorkflowInstances.Contracts; -using Elsa.Api.Client.Services; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -66,7 +64,6 @@ public static class DependencyInjectionExtensions configureBuilderOptions?.Invoke(builderOptions); services.Configure(configureOptions ?? (_ => { })); - services.AddScoped(); services.AddApi(builderOptions); services.AddApi(builderOptions); services.AddApi(builderOptions); diff --git a/src/clients/Elsa.Api.Client/Services/ElsaClient.cs b/src/clients/Elsa.Api.Client/Services/ElsaClient.cs deleted file mode 100644 index 3a0008512..000000000 --- a/src/clients/Elsa.Api.Client/Services/ElsaClient.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Elsa.Api.Client.Contracts; -using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts; -using Elsa.Api.Client.Resources.ActivityDescriptors.Contracts; -using Elsa.Api.Client.Resources.Scripting.Contracts; -using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts; -using Elsa.Api.Client.Resources.WorkflowInstances.Contracts; - -namespace Elsa.Api.Client.Services; - -/// -public class ElsaClient : IElsaClient -{ - /// - /// Initializes a new instance of the class. - /// - public ElsaClient(IWorkflowDefinitionsApi workflowDefinitions - , IWorkflowInstancesApi workflowInstances - , IActivityDescriptorsApi activityDescriptors - , IActivityDescriptorOptionsApi activityDescriptorOptions - , IJavaScriptApi javaScript) - { - WorkflowDefinitions = workflowDefinitions; - WorkflowInstances = workflowInstances; - ActivityDescriptors = activityDescriptors; - ActivityDescriptorOptions = activityDescriptorOptions; - JavaScript = javaScript; - } - - /// - public IWorkflowDefinitionsApi WorkflowDefinitions { get; } - - /// - public IActivityDescriptorsApi ActivityDescriptors { get; } - - /// - public IActivityDescriptorOptionsApi ActivityDescriptorOptions { get; } - - /// - public IWorkflowInstancesApi WorkflowInstances { get; } - - /// - public IJavaScriptApi JavaScript { get; } -} \ No newline at end of file From 3acd7f7c1e13b174b6a53f52e406793d3ff76851 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 29 Dec 2023 11:35:15 +0100 Subject: [PATCH 5/9] Add condition to return json as string A condition has been added to check if the returnType is of type 'string'. If so, the json is returned as string directly without converting. This is particularly useful to avoid unnecessary conversion when the returnType is already a string. --- src/modules/Elsa.Http/Parsers/JsonHttpContentParser.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/Elsa.Http/Parsers/JsonHttpContentParser.cs b/src/modules/Elsa.Http/Parsers/JsonHttpContentParser.cs index ad2906c49..4f2ee9bba 100644 --- a/src/modules/Elsa.Http/Parsers/JsonHttpContentParser.cs +++ b/src/modules/Elsa.Http/Parsers/JsonHttpContentParser.cs @@ -30,6 +30,9 @@ public class JsonHttpContentParser : IHttpContentParser using var reader = new StreamReader(content, leaveOpen: true); var json = await reader.ReadToEndAsync(); + + if(returnType == typeof(string)) + return json; if (returnType == null || returnType.IsPrimitive) return json.ConvertTo(returnType ?? typeof(string))!; From cb4dfea8d3de62e8cccd2aebf3557997f6f813c9 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Fri, 29 Dec 2023 15:37:28 +0300 Subject: [PATCH 6/9] Disable PythonEngine Shutdown to prevent deadlock --- .../HostedServices/PythonGlobalInterpreterManager.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/modules/Elsa.Python/HostedServices/PythonGlobalInterpreterManager.cs b/src/modules/Elsa.Python/HostedServices/PythonGlobalInterpreterManager.cs index 4b75d927f..084d1ebe7 100644 --- a/src/modules/Elsa.Python/HostedServices/PythonGlobalInterpreterManager.cs +++ b/src/modules/Elsa.Python/HostedServices/PythonGlobalInterpreterManager.cs @@ -44,13 +44,5 @@ public class PythonGlobalInterpreterManager : IHostedService } /// - public Task StopAsync(CancellationToken cancellationToken) - { - if(_mainThreadState != IntPtr.Zero) - { - PythonEngine.EndAllowThreads(_mainThreadState); - PythonEngine.Shutdown(); - } - return Task.CompletedTask; - } + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } \ No newline at end of file From 17452f740c3129e0681c529fd66201474c12629e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 29 Dec 2023 18:51:31 +0100 Subject: [PATCH 7/9] Improve variable resolution in workflow activity Added a reference to Variable.Id in the SetVariable activity within the Elsa.Workflows.Core module to ensure the correct variable scope is utilized. This change enhances the precision of the variable resolution process by ensuring the correct variable is identified and updated. --- src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs b/src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs index ad4dc9ab9..8265840d3 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs @@ -5,6 +5,7 @@ using Elsa.Extensions; using Elsa.Workflows.Attributes; using Elsa.Workflows.Memory; using Elsa.Workflows.Models; +using Elsa.Workflows.UIHints; using JetBrains.Annotations; namespace Elsa.Workflows.Activities; @@ -100,7 +101,10 @@ public class SetVariable : CodeActivity /// protected override void Execute(ActivityExecutionContext context) { + // Always refer to the variable by ID to ensure that the variable is resolved from the correct scope. + var variableId = Variable.Id; + var variable = context.ExpressionExecutionContext.EnumerateVariablesInScope().FirstOrDefault(x => x.Id == variableId); var value = context.Get(Value); - context.SetVariable(Variable.Name, value); + variable?.Set(context, value); } } \ No newline at end of file From d9781510f2b2274e97c62684f45ef750f7277f5e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 29 Dec 2023 19:23:11 +0100 Subject: [PATCH 8/9] Refactor Elsa API client to use API key authentication The Elsa API client configuration has been refactored to use API key for authentication instead of HTTP handlers. Besides, the configuration method name was changed to 'AddElsaApiKeyClient' and its parameters were simplified for ease of use. Object 'ElsaClientBuilderOptions' has also been extended with additional properties for further customization. --- .../DependencyInjectionExtensions.cs | 49 +++++++++---------- .../Options/ElsaClientBuilderOptions.cs | 26 ++++++++-- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs index ff389d9fc..416962a0b 100644 --- a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs @@ -1,7 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using Elsa.Api.Client.Converters; -using Elsa.Api.Client.HttpMessageHandlers; using Elsa.Api.Client.Options; using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts; using Elsa.Api.Client.Resources.ActivityDescriptors.Contracts; @@ -31,39 +30,39 @@ namespace Elsa.Api.Client.Extensions; public static class DependencyInjectionExtensions { /// - /// Adds the Elsa client to the service collection. + /// Adds the Elsa API client configured to use an API key to the service collection. /// - /// The service collection. - /// The base address of the Elsa API. - /// The API key to use for authentication. - /// An optional delegate that can be used to configure the HTTP client. - /// An optional delegate that can be used to configure the client builder options. - public static IServiceCollection AddElsaClient(this IServiceCollection services, Uri baseAddress, string apiKey, Action? configureHttpClient = default, Action? configureBuilderOptions = default) + public static IServiceCollection AddElsaApiKeyClient(this IServiceCollection services, Action configureOptions) { - services.AddScoped(); - return services.AddElsaClient( - options => - { - options.BaseAddress = baseAddress; - options.ApiKey = apiKey; - options.ConfigureHttpClient = configureHttpClient; - }, - configureBuilderOptions: options => - { - options.ConfigureHttpClientBuilder = builder => builder.AddHttpMessageHandler(); - configureBuilderOptions?.Invoke(options); - }); + var options = new ElsaClientOptions(); + configureOptions(options); + + return services.AddElsaClient(client => + { + client.BaseAddress = options.BaseAddress; + client.ApiKey = options.ApiKey; + client.ConfigureHttpClient = options.ConfigureHttpClient; + }); } - + /// /// Adds the Elsa client to the service collection. /// - public static IServiceCollection AddElsaClient(this IServiceCollection services, Action? configureOptions = default, Action? configureBuilderOptions = default) + public static IServiceCollection AddElsaClient(this IServiceCollection services, Action configureClient) { var builderOptions = new ElsaClientBuilderOptions(); - configureBuilderOptions?.Invoke(builderOptions); + configureClient.Invoke(builderOptions); + + builderOptions.ConfigureHttpClientBuilder += builder => builder.AddHttpMessageHandler(sp => (DelegatingHandler)sp.GetRequiredService(builderOptions.AuthenticationHandler)); - services.Configure(configureOptions ?? (_ => { })); + services.AddScoped(builderOptions.AuthenticationHandler); + + services.Configure(options => + { + options.BaseAddress = builderOptions.BaseAddress; + options.ConfigureHttpClient = builderOptions.ConfigureHttpClient; + options.ApiKey = builderOptions.ApiKey; + }); services.AddApi(builderOptions); services.AddApi(builderOptions); services.AddApi(builderOptions); diff --git a/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs b/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs index ab406a218..998b6d499 100644 --- a/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs +++ b/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs @@ -1,3 +1,4 @@ +using Elsa.Api.Client.HttpMessageHandlers; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Api.Client.Options; @@ -7,10 +8,29 @@ namespace Elsa.Api.Client.Options; /// public class ElsaClientBuilderOptions { + /// + /// Gets or sets the base address of the Elsa server. + /// + public Uri BaseAddress { get; set; } = default!; + + /// + /// Gets or sets the API key function to use when authenticating with the Elsa server. + /// + public string? ApiKey { get; set; } + + /// + /// A type that can be used to authenticate with the Elsa server. + /// Defaults to . + /// + public Type AuthenticationHandler { get; set; } = typeof(ApiKeyHttpMessageHandler); + + /// + /// Gets or sets a delegate that can be used to configure the HTTP client. + /// + public Action? ConfigureHttpClient { get; set; } + /// /// Gets or sets a delegate that can be used to configure the HTTP client builder. /// - public Action? ConfigureHttpClientBuilder { get; set; } - - + public Action ConfigureHttpClientBuilder { get; set; } = _ => { }; } \ No newline at end of file From c33801de1f426bb737a330b2ab5990f5cd7db8a1 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 29 Dec 2023 19:37:31 +0100 Subject: [PATCH 9/9] Add workflow result variable to context scope In the ExpressionExecutionContextExtensions module, an additional check and yield operation has been implemented. This allows the inclusion of the workflow result variable, if it exists, to the current context scope. --- .../Extensions/ExpressionExecutionContextExtensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index 05d5d1d55..cb9dd853c 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -314,6 +314,12 @@ public static class ExpressionExecutionContextExtensions currentScope = currentScope.ParentContext; } + + if (context.TryGetWorkflowExecutionContext(out var workflowExecutionContext)) + { + if (workflowExecutionContext.Workflow.ResultVariable != null) + yield return workflowExecutionContext.Workflow.ResultVariable; + } } ///