Merge branch 'v3.0.4'

This commit is contained in:
Sipke Schoorstra 2024-01-24 11:07:56 +01:00
commit d0a72deadf
11 changed files with 118 additions and 99 deletions

View file

@ -10,7 +10,6 @@ using Elsa.EntityFrameworkCore.Modules.Identity;
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.Extensions;
using Elsa.Http.Handlers;
using Elsa.Http.Options;
using Elsa.MongoDb.Extensions;
using Elsa.MongoDb.Modules.Identity;

View file

@ -17,7 +17,7 @@
"Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;",
"MongoDb": "mongodb://localhost:27017/elsa-workflows",
"AzureServiceBus": "",
"RabbitMq": "amqp://guest:guest@localhost:5672/elsa"
"RabbitMq": "amqp://guest:guest@localhost:5672"
},
"Smtp": {
"Host": "localhost",

View file

@ -18,6 +18,7 @@
<ProjectReference Include="..\..\modules\Elsa.Identity\Elsa.Identity.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Python\Elsa.Python.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Quartz\Elsa.Quartz.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.Webhooks\Elsa.Webhooks.csproj"/>

View file

@ -6,6 +6,8 @@ using Elsa.Extensions;
using Elsa.Webhooks.Extensions;
using Microsoft.AspNetCore.Mvc;
const bool useMassTransit = true;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseStaticWebAssets();
var services = builder.Services;
@ -16,32 +18,39 @@ var identityTokenSection = identitySection.GetSection("Tokens");
// Add Elsa services.
services
.AddElsa(elsa => elsa
.UseSasTokens()
.UseIdentity(identity =>
.AddElsa(elsa =>
{
elsa
.UseSasTokens()
.UseIdentity(identity =>
{
identity.IdentityOptions = options => identitySection.Bind(options);
identity.TokenOptions = options => identityTokenSection.Bind(options);
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
})
.UseDefaultAuthentication()
.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
.UseScheduling()
.UseJavaScript(options => options.AllowClrAccess = true)
.UseLiquid()
.UseCSharp()
.UsePython()
.UseHttp(http => http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options))
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options))
.UseWebhooks(webhooks => webhooks.WebhookOptions = options => builder.Configuration.GetSection("Webhooks").Bind(options))
.UseWorkflowsApi()
.UseRealTimeWorkflows()
.AddActivitiesFrom<Program>()
.AddWorkflowsFrom<Program>();
if (useMassTransit)
{
identity.IdentityOptions = options => identitySection.Bind(options);
identity.TokenOptions = options => identityTokenSection.Bind(options);
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
})
.UseDefaultAuthentication()
.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
.UseScheduling()
.UseJavaScript(options => options.AllowClrAccess = true)
.UseLiquid()
.UseCSharp()
.UsePython()
.UseHttp(http => http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options))
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options))
.UseWebhooks(webhooks => webhooks.WebhookOptions = options => builder.Configuration.GetSection("Webhooks").Bind(options))
.UseWorkflowsApi()
.UseRealTimeWorkflows()
.AddActivitiesFrom<Program>()
.AddWorkflowsFrom<Program>()
);
elsa.UseMassTransit();
}
});
services.AddHealthChecks();

View file

@ -13,7 +13,8 @@
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;"
"Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;",
"RabbitMq": "rabbitmq://guest:guest@localhost:5672"
},
"Hosting": {
"BasePath": ""

View file

@ -6,6 +6,7 @@ using Elsa.MassTransit.Implementations;
using Elsa.Workflows;
using Elsa.Workflows.Attributes;
using Elsa.Workflows.Models;
using Elsa.Workflows.UIHints;
using MassTransit;
namespace Elsa.MassTransit.Activities;
@ -29,7 +30,10 @@ public class PublishMessage : CodeActivity
/// <summary>
/// The message to send. Must be a concrete implementation of the configured <see cref="MessageType"/>.
/// </summary>
[Input(Description = "The message to send. Must be a concrete implementation of the configured message type.")]
[Input(
Description = "The message to send. Must be a concrete implementation of the configured message type.",
UIHint = InputUIHints.MultiLine
)]
public Input<object> Message { get; set; } = default!;
/// <inheritdoc />

View file

@ -6,8 +6,8 @@ using Elsa.MassTransit.Options;
using Elsa.Workflows;
using Elsa.Workflows.Attributes;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.UIHints;
using Elsa.Workflows.Helpers;
using Elsa.Workflows.Management;
using Elsa.Workflows.Models;
using Humanizer;
using Microsoft.Extensions.Options;
@ -17,40 +17,31 @@ namespace Elsa.MassTransit.Implementations;
/// <summary>
/// Provides activities to the system from the configured MassTransit message types.
/// </summary>
public class MassTransitActivityTypeProvider : IActivityProvider
public class MassTransitActivityTypeProvider(IActivityFactory activityFactory, IOptions<MassTransitActivityOptions> options, IActivityDescriber activityDescriber) : IActivityProvider
{
private readonly IActivityFactory _activityFactory;
private readonly MassTransitActivityOptions _options;
/// <summary>
/// Constructor.
/// </summary>
public MassTransitActivityTypeProvider(IActivityFactory activityFactory, IOptions<MassTransitActivityOptions> options)
{
_activityFactory = activityFactory;
_options = options.Value;
}
/// <inheritdoc />
public ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
public async ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
{
var messageTypes = _options.MessageTypes;
var descriptors = CreateDescriptors(messageTypes).ToList();
return new(descriptors);
var messageTypes = options.Value.MessageTypes;
var descriptors = await CreateDescriptorsAsync(messageTypes, cancellationToken);
return descriptors.ToList();
}
private IEnumerable<ActivityDescriptor> CreateDescriptors(IEnumerable<Type> messageTypes)
private async Task<IEnumerable<ActivityDescriptor>> CreateDescriptorsAsync(IEnumerable<Type> messageTypes, CancellationToken cancellationToken = default)
{
var descriptors = new List<ActivityDescriptor>();
foreach (var messageType in messageTypes)
{
yield return CreateMessageReceivedDescriptor(messageType);
descriptors.Add(await CreateMessageReceivedDescriptor(messageType, cancellationToken));
if(messageType.IsClass)
yield return CreatePublishMessageDescriptor(messageType);
descriptors.Add(await CreatePublishMessageDescriptor(messageType, cancellationToken));
}
return descriptors;
}
private ActivityDescriptor CreateMessageReceivedDescriptor(Type messageType)
private async Task<ActivityDescriptor> CreateMessageReceivedDescriptor(Type messageType, CancellationToken cancellationToken = default)
{
var activityAttr = messageType.GetCustomAttribute<ActivityAttribute>();
var typeName = activityAttr?.Type ?? messageType.Name;
@ -61,6 +52,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider
var category = categoryAttr?.Category ?? activityAttr?.Category ?? "MassTransit";
var descriptionAttr = messageType.GetCustomAttribute<DescriptionAttribute>();
var description = descriptionAttr?.Description ?? activityAttr?.Description;
var outputDescriptor = await activityDescriber.DescribeOutputProperty<MessageReceived, object>(x => x.Result!, cancellationToken);
var openOutputType = typeof(Output<>);
var outputType = openOutputType.MakeGenericType(messageType);
outputDescriptor.Type = outputType;
return new()
{
@ -73,17 +69,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider
IsBrowsable = true,
Outputs =
{
new OutputDescriptor
{
Description = "The received message",
DisplayName = "Received Message",
Name = nameof(MessageReceived.Result),
Type = typeof(object)
}
outputDescriptor
},
Constructor = context =>
{
var activity = _activityFactory.Create<MessageReceived>(context);
var activity = activityFactory.Create<MessageReceived>(context);
activity.Type = fullTypeName;
activity.MessageType = messageType;
return activity;
@ -91,7 +81,7 @@ public class MassTransitActivityTypeProvider : IActivityProvider
};
}
private ActivityDescriptor CreatePublishMessageDescriptor(Type messageType)
private async Task<ActivityDescriptor> CreatePublishMessageDescriptor(Type messageType, CancellationToken cancellationToken = default)
{
var activityAttr = messageType.GetCustomAttribute<ActivityAttribute>();
var typeName = activityAttr?.Type ?? messageType.Name;
@ -104,6 +94,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider
var descriptionAttr = messageType.GetCustomAttribute<DescriptionAttribute>();
var description = descriptionAttr?.Description ?? activityAttr?.Description;
var messageInputDescriptor = await activityDescriber.DescribeInputPropertyAsync<PublishMessage, object>(x => x.Message, cancellationToken: cancellationToken);
var openInputType = typeof(Input<>);
var inputType = openInputType.MakeGenericType(messageType);
messageInputDescriptor.Type = inputType;
return new()
{
TypeName = fullTypeName,
@ -115,18 +110,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider
IsBrowsable = true,
Inputs =
{
new InputDescriptor
{
Description = "The message to publish.",
UIHint = InputUIHints.MultiLine,
DisplayName = "Message",
Type = typeof(Input<object>),
Name = nameof(PublishMessage.Message)
}
messageInputDescriptor
},
Constructor = context =>
{
var activity = _activityFactory.Create<PublishMessage>(context);
var activity = activityFactory.Create<PublishMessage>(context);
activity.Type = fullTypeName;
activity.MessageType = messageType;
return activity;

View file

@ -292,7 +292,12 @@ public static class ExpressionExecutionContextExtensions
select v;
var variable = q.FirstOrDefault();
variable?.Set(context, value);
if(variable != null)
variable.Set(context, value);
if (variable == null)
CreateVariable(context, variableName, value);
}
/// <summary>

View file

@ -36,6 +36,31 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
return state;
}
/// <inheritdoc />
public WorkflowExecutionContext Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state)
{
workflowExecutionContext.Id = state.Id;
workflowExecutionContext.CorrelationId = state.CorrelationId;
workflowExecutionContext.SubStatus = state.SubStatus;
workflowExecutionContext.Bookmarks = state.Bookmarks;
workflowExecutionContext.Output = state.Output;
workflowExecutionContext.ExecutionLogSequence = state.ExecutionLogSequence;
workflowExecutionContext.CreatedAt = state.CreatedAt;
ApplyInput(state, workflowExecutionContext);
ApplyProperties(state, workflowExecutionContext);
ApplyActivityExecutionContexts(state, workflowExecutionContext);
ApplyCompletionCallbacks(state, workflowExecutionContext);
ApplyScheduledActivities(state, workflowExecutionContext);
return workflowExecutionContext;
}
private void ApplyInput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
{
// Only add input from state if the input doesn't already exist on the workflow execution context.
foreach (var inputItem in state.Input)
if (!workflowExecutionContext.Input.ContainsKey(inputItem.Key)) workflowExecutionContext.Input.Add(inputItem.Key, inputItem.Value);
}
private IDictionary<string, object> GetPersistableInput(WorkflowExecutionContext workflowExecutionContext)
{
// TODO: This is a temporary solution. We need to find a better way to handle this.
@ -52,24 +77,6 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
return filteredInput;
}
/// <inheritdoc />
public WorkflowExecutionContext Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state)
{
// Do not map input. We don't want to overwrite the input that was passed to the workflow.
workflowExecutionContext.Id = state.Id;
workflowExecutionContext.CorrelationId = state.CorrelationId;
workflowExecutionContext.SubStatus = state.SubStatus;
workflowExecutionContext.Bookmarks = state.Bookmarks;
workflowExecutionContext.Output = state.Output;
workflowExecutionContext.ExecutionLogSequence = state.ExecutionLogSequence;
workflowExecutionContext.CreatedAt = state.CreatedAt;
ApplyProperties(state, workflowExecutionContext);
ApplyActivityExecutionContexts(state, workflowExecutionContext);
ApplyCompletionCallbacks(state, workflowExecutionContext);
ApplyScheduledActivities(state, workflowExecutionContext);
return workflowExecutionContext;
}
private void ExtractProperties(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
{
state.Properties = workflowExecutionContext.Properties;

View file

@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\bundles\Elsa\Elsa.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.CSharp\Elsa.CSharp.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.Http\Elsa.Http.csproj" />
<ProjectReference Include="..\..\..\modules\Elsa.Identity\Elsa.Identity.csproj" />

View file

@ -28,16 +28,20 @@ builder.Services.AddElsa(elsa =>
// Add services for HTTP activities and workflow middleware.
elsa.UseHttp();
// Use JavaScript.
elsa.UseJavaScript(javaScript =>
// Use C#.
elsa.UseCSharp(options =>
{
javaScript.AllowClrAccess = true;
javaScript.ConfigureEngine(options =>
{
options.RegisterType<OrderCompleted>();
options.RegisterType<OrderCreated>();
});
options.Assemblies.Add(typeof(OrderCreated).Assembly);
options.Namespaces.Add(typeof(OrderCreated).Namespace!);
});
// Use JavaScript.
elsa.UseJavaScript(options =>
{
options.AllowClrAccess = true;
options.RegisterType<OrderCreated>();
options.RegisterType<OrderCompleted>();
});
// Use C#.
@ -56,7 +60,7 @@ builder.Services.AddElsa(elsa =>
identity.UseAdminUserProvider();
identity.TokenOptions = options =>
{
options.SigningKey = "secret-token-signing-key";
options.SigningKey = "super-secret-and-securely-stored-token-signing-key";
options.AccessTokenLifetime = TimeSpan.FromDays(1);
};
});
@ -67,7 +71,7 @@ builder.Services.AddElsa(elsa =>
// Configure MassTransit.
elsa.UseMassTransit(massTransit =>
{
massTransit.UseRabbitMq(rabbitMqConnectionString);
//massTransit.UseRabbitMq(rabbitMqConnectionString);
massTransit.AddMessageType<OrderCompleted>();
massTransit.AddMessageType<OrderCreated>();
});