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/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 a16717ead..416962a0b 100644 --- a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs @@ -1,8 +1,6 @@ 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; using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts; using Elsa.Api.Client.Resources.ActivityDescriptors.Contracts; @@ -17,7 +15,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; @@ -33,40 +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 client options. - /// 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 AddElsaApiKeyClient(this IServiceCollection services, Action configureOptions) { - services.AddScoped(); - return services.AddElsaClient( - options => - { - options.BaseAddress = baseAddress; - options.ApiKey = apiKey; - configureOptions?.Invoke(options); - }, - 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(); + 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/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/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 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 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 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))!; 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 { 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 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 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; + } } ///