Merge branch 'main' into custom-uiprovider

This commit is contained in:
Sipke Schoorstra 2023-12-29 19:44:18 +01:00
commit cfb3d032ea
13 changed files with 91 additions and 137 deletions

View file

@ -66,6 +66,7 @@
"DefaultSender": "noreply@crmservices.com"
},
"Http": {
"BaseUrl": "https://localhost:5001",
"BasePath": "/api/workflows"
},
"Webhooks": {

View file

@ -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;
/// <summary>
/// Represents a client for the Elsa API. Each API is exposed as a property.
/// </summary>
public interface IElsaClient
{
/// <summary>
/// Gets the workflow definitions API.
/// </summary>
IWorkflowDefinitionsApi WorkflowDefinitions { get; }
/// <summary>
/// Gets the activity descriptors API.
/// </summary>
IActivityDescriptorsApi ActivityDescriptors { get; }
/// <summary>
/// Get the activity descriptor Options API
/// </summary>
IActivityDescriptorOptionsApi ActivityDescriptorOptions { get; }
/// <summary>
/// Gets the workflow instances API.
/// </summary>
IWorkflowInstancesApi WorkflowInstances { get; }
/// <summary>
/// Gets the javascript API.
/// </summary>
IJavaScriptApi JavaScript { get; }
}

View file

@ -1,14 +0,0 @@
using Elsa.Api.Client.Options;
namespace Elsa.Api.Client.Contracts;
/// <summary>
/// A factory for creating <see cref="IElsaClient"/> instances.
/// </summary>
public interface IElsaClientFactory
{
/// <summary>
/// Creates a new <see cref="IElsaClient"/> instance.
/// </summary>
IElsaClient CreateClient(Action<ElsaClientOptions> configureOptions, Action<ElsaClientBuilderOptions>? configureHttpClientBuilder = default);
}

View file

@ -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
{
/// <summary>
/// Adds the Elsa client to the service collection.
/// Adds the Elsa API client configured to use an API key to the service collection.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="baseAddress">The base address of the Elsa API.</param>
/// <param name="apiKey">The API key to use for authentication.</param>
/// <param name="configureOptions">An optional delegate that can be used to configure the client options.</param>
/// <param name="configureBuilderOptions">An optional delegate that can be used to configure the client builder options.</param>
public static IServiceCollection AddElsaClient(this IServiceCollection services, Uri baseAddress, string apiKey, Action<ElsaClientOptions>? configureOptions = default, Action<ElsaClientBuilderOptions>? configureBuilderOptions = default)
public static IServiceCollection AddElsaApiKeyClient(this IServiceCollection services, Action<ElsaClientOptions> configureOptions)
{
services.AddScoped<ApiKeyHttpMessageHandler>();
return services.AddElsaClient(
options =>
{
options.BaseAddress = baseAddress;
options.ApiKey = apiKey;
configureOptions?.Invoke(options);
},
configureBuilderOptions: options =>
{
options.ConfigureHttpClientBuilder = builder => builder.AddHttpMessageHandler<ApiKeyHttpMessageHandler>();
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;
});
}
/// <summary>
/// Adds the Elsa client to the service collection.
/// </summary>
public static IServiceCollection AddElsaClient(this IServiceCollection services, Action<ElsaClientOptions>? configureOptions = default, Action<ElsaClientBuilderOptions>? configureBuilderOptions = default)
public static IServiceCollection AddElsaClient(this IServiceCollection services, Action<ElsaClientBuilderOptions> 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<IElsaClient, ElsaClient>();
services.AddScoped(builderOptions.AuthenticationHandler);
services.Configure<ElsaClientOptions>(options =>
{
options.BaseAddress = builderOptions.BaseAddress;
options.ConfigureHttpClient = builderOptions.ConfigureHttpClient;
options.ApiKey = builderOptions.ApiKey;
});
services.AddApi<IWorkflowDefinitionsApi>(builderOptions);
services.AddApi<IWorkflowInstancesApi>(builderOptions);
services.AddApi<IActivityDescriptorsApi>(builderOptions);

View file

@ -0,0 +1,14 @@
using Elsa.Api.Client.Shared;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Contains extension methods for the <see cref="HttpResponseMessage"/> class.
/// </summary>
public static class HttpResponseMessageExtensions
{
/// <summary>
/// Gets the workflow instance ID from the response.
/// </summary>
public static string? GetWorkflowInstanceId(this HttpResponseMessage response) => response.Headers.TryGetValues(HeaderNames.WorkflowInstanceId, out var values) ? values.FirstOrDefault() : default;
}

View file

@ -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;
/// </summary>
public class ElsaClientBuilderOptions
{
/// <summary>
/// Gets or sets the base address of the Elsa server.
/// </summary>
public Uri BaseAddress { get; set; } = default!;
/// <summary>
/// Gets or sets the API key function to use when authenticating with the Elsa server.
/// </summary>
public string? ApiKey { get; set; }
/// <summary>
/// A <see cref="DelegatingHandler"/> type that can be used to authenticate with the Elsa server.
/// Defaults to <see cref="ApiKeyHttpMessageHandler"/>.
/// </summary>
public Type AuthenticationHandler { get; set; } = typeof(ApiKeyHttpMessageHandler);
/// <summary>
/// Gets or sets a delegate that can be used to configure the HTTP client.
/// </summary>
public Action<IServiceProvider, HttpClient>? ConfigureHttpClient { get; set; }
/// <summary>
/// Gets or sets a delegate that can be used to configure the HTTP client builder.
/// </summary>
public Action<IHttpClientBuilder>? ConfigureHttpClientBuilder { get; set; }
public Action<IHttpClientBuilder> ConfigureHttpClientBuilder { get; set; } = _ => { };
}

View file

@ -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;
/// <inheritdoc />
public class ElsaClient : IElsaClient
{
/// <summary>
/// Initializes a new instance of the <see cref="ElsaClient"/> class.
/// </summary>
public ElsaClient(IWorkflowDefinitionsApi workflowDefinitions
, IWorkflowInstancesApi workflowInstances
, IActivityDescriptorsApi activityDescriptors
, IActivityDescriptorOptionsApi activityDescriptorOptions
, IJavaScriptApi javaScript)
{
WorkflowDefinitions = workflowDefinitions;
WorkflowInstances = workflowInstances;
ActivityDescriptors = activityDescriptors;
ActivityDescriptorOptions = activityDescriptorOptions;
JavaScript = javaScript;
}
/// <inheritdoc />
public IWorkflowDefinitionsApi WorkflowDefinitions { get; }
/// <inheritdoc />
public IActivityDescriptorsApi ActivityDescriptors { get; }
/// <inheritdoc />
public IActivityDescriptorOptionsApi ActivityDescriptorOptions { get; }
/// <inheritdoc />
public IWorkflowInstancesApi WorkflowInstances { get; }
/// <inheritdoc />
public IJavaScriptApi JavaScript { get; }
}

View file

@ -0,0 +1,12 @@
namespace Elsa.Api.Client.Shared;
/// <summary>
/// Contains header names used by the Elsa API.
/// </summary>
public static class HeaderNames
{
/// <summary>
/// The name of the header that contains the workflow instance ID.
/// </summary>
public const string WorkflowInstanceId = "x-elsa-workflow-instance-id";
}

View file

@ -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))!;

View file

@ -16,8 +16,8 @@ public class HttpEndpointPathUIHandler(IOptions<HttpActivityOptions> options) :
public ValueTask<IDictionary<string, object>> 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<string, object>
{

View file

@ -44,13 +44,5 @@ public class PythonGlobalInterpreterManager : IHostedService
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken)
{
if(_mainThreadState != IntPtr.Zero)
{
PythonEngine.EndAllowThreads(_mainThreadState);
PythonEngine.Shutdown();
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

View file

@ -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
/// <inheritdoc />
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);
}
}

View file

@ -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;
}
}
/// <summary>