Merge remote-tracking branch 'origin/patch/3.2.x'

This commit is contained in:
Sipke Schoorstra 2024-07-11 10:28:24 +02:00
commit 88f657e940
45 changed files with 322 additions and 151 deletions

View file

@ -3,7 +3,7 @@ on:
workflow_dispatch:
push:
branches:
- main
- patch/3.2.x
jobs:
push_to_registry:
@ -29,7 +29,7 @@ jobs:
with:
# list of Docker images to use as base name for tags
images: |
elsaworkflows/elsa-server-and-studio-v3
elsaworkflows/elsa-server-and-studio-v3-2-0-rc3
flavor: |
latest=true
# generate Docker tags based on the following events/attributes

View file

@ -3,7 +3,7 @@ on:
workflow_dispatch:
push:
branches:
- main
- patch/3.2.x
jobs:
push_to_registry:
@ -29,7 +29,7 @@ jobs:
with:
# list of Docker images to use as base name for tags
images: |
elsaworkflows/elsa-server-v3
elsaworkflows/elsa-server-v3-2-0-rc3
flavor: |
latest=true
# generate Docker tags based on the following events/attributes

View file

@ -3,7 +3,7 @@ on:
workflow_dispatch:
push:
branches:
- main
- patch/3.2.x
jobs:
push_to_registry:
@ -29,7 +29,7 @@ jobs:
with:
# list of Docker images to use as base name for tags
images: |
elsaworkflows/elsa-studio-v3
elsaworkflows/elsa-studio-v3-2-0-rc3
flavor: |
latest=true
# generate Docker tags based on the following events/attributes

View file

@ -1,34 +1,35 @@
<Project>
<PropertyGroup>
<Authors>Elsa Workflows Community</Authors>
<Copyright>2023</Copyright>
<PropertyGroup>
<Authors>Elsa Workflows Community</Authors>
<Copyright>2023</Copyright>
<PackageProjectUrl>https://github.com/elsa-workflows/elsa-core</PackageProjectUrl>
<RepositoryUrl>https://github.com/elsa-workflows/elsa-core</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/elsa-workflows/elsa-core</PackageProjectUrl>
<RepositoryUrl>https://github.com/elsa-workflows/elsa-core</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>icon.png</PackageIcon>
<!-- For NuGet browsers that currently don't support <PackageIcon> yet -->
<PackageIconUrl>https://v3.elsaworkflows.io/nuget-icon.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AnalysisModeDocumentation>Default</AnalysisModeDocumentation>
<AnalysisLevelDocumentation>latest</AnalysisLevelDocumentation>
<!-- For NuGet browsers that currently don't support <PackageIcon> yet -->
<PackageIconUrl>https://v3.elsaworkflows.io/nuget-icon.png</PackageIconUrl>
<!-- https://github.com/dotnet/sourcelink -->
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
</PropertyGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);CS0162;CS1591</NoWarn>
</PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AnalysisModeDocumentation>Default</AnalysisModeDocumentation>
<AnalysisLevelDocumentation>latest</AnalysisLevelDocumentation>
<!-- https://github.com/dotnet/sourcelink -->
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
</PropertyGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);CS0162;CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<ElsaStudioVersion>3.2.0-rc3.417</ElsaStudioVersion>
</PropertyGroup>
</Project>

View file

@ -23,9 +23,9 @@
<PackageVersion Include="DistributedLock.Postgres" Version="1.1.0" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3" />
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="8.14.0" />
<PackageVersion Include="Elsa.Studio" Version="3.2.0-rc2.369" />
<PackageVersion Include="Elsa.Studio.Core.BlazorWasm" Version="3.2.0-rc2.369" />
<PackageVersion Include="Elsa.Studio.Login.BlazorWasm" Version="3.2.0-rc2.369" />
<PackageVersion Include="Elsa.Studio" Version="$(ElsaStudioVersion)" />
<PackageVersion Include="Elsa.Studio.Core.BlazorWasm" Version="$(ElsaStudioVersion)" />
<PackageVersion Include="Elsa.Studio.Login.BlazorWasm" Version="$(ElsaStudioVersion)" />
<PackageVersion Include="FastEndpoints" Version="5.26.0" />
<PackageVersion Include="FastEndpoints.Security" Version="5.26.0" />
<PackageVersion Include="FastEndpoints.Swagger" Version="5.26.0" />

View file

@ -10,20 +10,20 @@
<ProjectReference Include="..\..\bundles\Elsa\Elsa.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed.MassTransit\Elsa.Caching.Distributed.MassTransit.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed\Elsa.Caching.Distributed.csproj" />
<ProjectReference Include="..\..\modules\Elsa.CSharp\Elsa.CSharp.csproj" />
<ProjectReference Include="..\..\modules\Elsa.CSharp\Elsa.CSharp.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.Email\Elsa.Email.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.MySql\Elsa.EntityFrameworkCore.MySql.csproj" />
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.MySql\Elsa.EntityFrameworkCore.MySql.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.Hangfire\Elsa.Hangfire.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.Http\Elsa.Http.csproj"/>
<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.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj" />
<ProjectReference Include="..\..\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj" />
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj" />
<ProjectReference Include="..\..\modules\Elsa.ProtoActor\Elsa.ProtoActor.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Python\Elsa.Python.csproj" />
<ProjectReference Include="..\..\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj"/>
<ProjectReference Include="..\..\modules\Elsa.ProtoActor\Elsa.ProtoActor.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"/>
<ProjectReference Include="..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj"/>
@ -31,10 +31,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" />
<PackageReference Include="Proto.Persistence.Sqlite" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server"/>
<PackageReference Include="Proto.Persistence.Sqlite"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Elsa.Studio"/>
<PackageReference Include="Elsa.Studio.Login.BlazorWasm"/>

View file

@ -1,8 +1,6 @@
namespace Elsa.ServerAndStudio.Web.Enums;
/// <summary>
/// Represents the transport options for distributed caching.
/// </summary>
public enum DistributedCachingTransport
{
None,

View file

@ -1,8 +1,6 @@
namespace Elsa.ServerAndStudio.Web.Enums;
/// <summary>
/// Represents the type of messaging broker used in MassTransit.
/// </summary>
public enum MassTransitBroker
{
Memory,

View file

@ -13,8 +13,4 @@
<PackageReference Include="Microsoft.Extensions.Http" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="wwwroot\index.html" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,41 @@
using System.Text.Json;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for the PropertyBag class.
/// </summary>
public static class PropertyBagExtensions
{
/// <summary>
/// Tries to retrieve a value from the PropertyBag based on the provided key.
/// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function.
/// </summary>
/// <typeparam name="T">The type of the value to retrieve.</typeparam>
/// <param name="propertyBag">The PropertyBag to retrieve the value from.</param>
/// <param name="key">The key of the value to retrieve.</param>
/// <param name="defaultValue">A function that returns the default value to be returned if the key does not exist in the PropertyBag.</param>
/// <returns>The value associated with the key, or the default value if the key does not exist.</returns>
public static T TryGetValueOrDefault<T>(this PropertyBag propertyBag, string key, Func<T> defaultValue)
{
if (!propertyBag.TryGetValue(key, out var value))
return defaultValue();
var json = (string)value;
return JsonSerializer.Deserialize<T>(json);
}
/// <summary>
/// Sets a value in the PropertyBag based on the provided key.
/// The value is serialized using JSON.
/// </summary>
/// <param name="propertyBag">The PropertyBag to set the value in.</param>
/// <param name="key">The key to associate with the value.</param>
/// <param name="value">The value to store in the PropertyBag.</param>
public static void SetValue(this PropertyBag propertyBag, string key, object value)
{
var json = JsonSerializer.Serialize(value);
propertyBag[key] = json;
}
}

View file

@ -209,6 +209,7 @@ public interface IWorkflowDefinitionsApi
/// <param name="definitionId">The definition ID of the workflow definition to revert.</param>
/// <param name="version">The version to revert to.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>A summary view of the newly created workflow definition version.</returns>
[Post("/workflow-definitions/{definitionId}/revert/{version}")]
Task RevertVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default);
Task<WorkflowDefinitionSummary> RevertVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default);
}

View file

@ -58,6 +58,11 @@ public class WorkflowDefinition : LinkedEntity
/// </summary>
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
/// <summary>
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
/// </summary>
public PropertyBag PropertyBag { get; set; } = new();
/// <summary>
/// The name of the workflow provider that created this workflow, if any.
/// </summary>

View file

@ -55,6 +55,11 @@ public class WorkflowDefinitionModel : LinkedEntity
/// </summary>
public IDictionary<string, object>? CustomProperties { get; set; }
/// <summary>
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
/// </summary>
public PropertyBag PropertyBag { get; set; } = new();
/// <summary>
/// Gets or sets whether the workflow definition is read-only.
/// </summary>

View file

@ -1,22 +1,17 @@
namespace Elsa.Api.Client.Resources.WorkflowInstances.Requests;
/// <summary>
/// Represents a request to list journal records.
/// </summary>
public class JournalFilter
{
/// <summary>
/// Gets or sets the activity IDs to filter by.
/// </summary>
public ICollection<string>? ActivityIds { get; set; }
/// <summary>
/// Gets or sets the activity node IDs to filter by.
public ICollection<string>? ActivityNodeIds { get; set; }
/// Gets or sets the activity types to filter out.
/// </summary>
public ICollection<string>? ExcludedActivityTypes { get; set; }
/// <summary>
/// Gets or sets the event types to filter by.
/// </summary>
public ICollection<string>? EventNames { get; set; }
}

View file

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
namespace Elsa.Api.Client.Shared.Models;
/// A dictionary of values that is skipped by polymorphic serialization.
public class PropertyBag : Dictionary<string, object>
{
/// <inheritdoc />
[JsonConstructor]
public PropertyBag() : base(StringComparer.OrdinalIgnoreCase)
{
}
/// <inheritdoc />
public PropertyBag(IDictionary<string, object> dictionary) : this()
{
foreach (var kvp in dictionary)
Add(kvp.Key, kvp.Value);
}
}

View file

@ -21,4 +21,5 @@ public class WrappedInput
/// Gets or sets the memory reference of this input.
/// </summary>
public MemoryReference MemoryReference { get; set; } = default!;
}
}

View file

@ -0,0 +1,67 @@
using System.Text.Json;
using Elsa.Common.Models;
// ReSharper disable once CheckNamespace
namespace Elsa.Extensions;
/// <summary>
/// Provides extension methods for the PropertyBag class.
/// </summary>
public static class PropertyBagExtensions
{
/// <summary>
/// Tries to retrieve a value from the PropertyBag based on the provided key.
/// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function.
/// </summary>
/// <typeparam name="T">The type of the value to retrieve.</typeparam>
/// <param name="propertyBag">The PropertyBag to retrieve the value from.</param>
/// <param name="key">The key of the value to retrieve.</param>
/// <param name="defaultValue">A function that returns the default value to be returned if the key does not exist in the PropertyBag.</param>
/// <param name="options">Optional JSON serializer options.</param>
/// <returns>The value associated with the key, or the default value if the key does not exist.</returns>
public static T TryGetValueOrDefault<T>(this PropertyBag propertyBag, string key, Func<T> defaultValue, JsonSerializerOptions? options = null)
{
if (!propertyBag.TryGetValue(key, out var value))
return defaultValue();
var json = (string)value;
return JsonSerializer.Deserialize<T>(json, options);
}
/// <summary>
/// Tries to retrieve a value from the PropertyBag based on the provided key.
/// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function.
/// </summary>
/// <typeparam name="T">The type of the value to retrieve.</typeparam>
/// <param name="propertyBag">The PropertyBag to retrieve the value from.</param>
/// <param name="key">The key of the value to retrieve.</param>
/// <param name="value">The deserialized value.</param>
/// <param name="options">Optional JSON serializer options.</param>
/// <returns>True if the value exists, false otherwise.</returns>
public static bool TryGetValue<T>(this PropertyBag propertyBag, string key, out T value, JsonSerializerOptions? options = null)
{
if (!propertyBag.TryGetValue(key, out var v))
{
value = default!;
return false;
}
var json = (string)v;
value = JsonSerializer.Deserialize<T>(json, options);
return true;
}
/// <summary>
/// Sets a value in the PropertyBag based on the provided key.
/// The value is serialized using JSON.
/// </summary>
/// <param name="propertyBag">The PropertyBag to set the value in.</param>
/// <param name="key">The key to associate with the value.</param>
/// <param name="value">The value to store in the PropertyBag.</param>
/// /// <param name="options">Optional JSON serializer options.</param>
public static void SetValue(this PropertyBag propertyBag, string key, object value, JsonSerializerOptions? options = null)
{
var json = JsonSerializer.Serialize(value);
propertyBag[key] = json;
}
}

View file

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
namespace Elsa.Common.Models;
/// A dictionary of values that is skipped by polymorphic serialization.
public class PropertyBag : Dictionary<string, object>
{
/// <inheritdoc />
[JsonConstructor]
public PropertyBag() : base(StringComparer.OrdinalIgnoreCase)
{
}
/// <inheritdoc />
public PropertyBag(IDictionary<string, object> dictionary) : this()
{
foreach (var kvp in dictionary)
Add(kvp.Key, kvp.Value);
}
}

View file

@ -115,4 +115,4 @@ public struct VersionOptions
/// Returns a simple string representation of this <see cref="VersionOptions"/>.
/// </summary>
public override string ToString() => AllVersions ? "AllVersions" : IsDraft ? "Draft" : IsLatest ? "Latest" : IsPublished ? "Published" : IsLatestOrPublished ? "LatestOrPublished" : IsLatestAndPublished ? "LatestAndPublished" : Version.ToString(CultureInfo.InvariantCulture);
}
}

View file

@ -26,7 +26,6 @@ public class RunMigrationsHostedService<TDbContext> : IHostedService where TDbCo
var dbContextFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<TDbContext>>();
await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
await dbContext.Database.MigrateAsync(cancellationToken);
await dbContext.DisposeAsync();
}
/// <inheritdoc />

View file

@ -17,6 +17,7 @@ internal class Configurations : IEntityTypeConfiguration<WorkflowDefinition>, IE
builder.Ignore(x => x.Outputs);
builder.Ignore(x => x.Outcomes);
builder.Ignore(x => x.CustomProperties);
builder.Ignore(x => x.PropertyBag);
builder.Ignore(x => x.Options);
builder.Property<string>("Data");
builder.Property<bool?>("UsableAsActivity");

View file

@ -177,8 +177,8 @@ public class EFCoreWorkflowDefinitionStore(EntityStore<ManagementElsaDbContext,
private ValueTask OnSaveAsync(ManagementElsaDbContext managementElsaDbContext, WorkflowDefinition entity, CancellationToken cancellationToken)
{
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties);
var json = payloadSerializer.Serialize(data);
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties, entity.PropertyBag);
var json = _payloadSerializer.Serialize(data);
managementElsaDbContext.Entry(entity).Property("Data").CurrentValue = json;
managementElsaDbContext.Entry(entity).Property("UsableAsActivity").CurrentValue = data.Options.UsableAsActivity;
@ -190,7 +190,7 @@ public class EFCoreWorkflowDefinitionStore(EntityStore<ManagementElsaDbContext,
if (entity == null)
return ValueTask.CompletedTask;
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties);
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties, entity.PropertyBag);
var json = (string?)managementElsaDbContext.Entry(entity).Property("Data").CurrentValue;
if (!string.IsNullOrWhiteSpace(json))
@ -202,6 +202,7 @@ public class EFCoreWorkflowDefinitionStore(EntityStore<ManagementElsaDbContext,
entity.Outputs = data.Outputs;
entity.Outcomes = data.Outcomes;
entity.CustomProperties = data.CustomProperties;
entity.PropertyBag = data.PropertyBag;
return ValueTask.CompletedTask;
}
@ -254,7 +255,8 @@ public class EFCoreWorkflowDefinitionStore(EntityStore<ManagementElsaDbContext,
ICollection<InputDefinition> inputs,
ICollection<OutputDefinition> outputs,
ICollection<string> outcomes,
IDictionary<string, object> customProperties
IDictionary<string, object> customProperties,
PropertyBag propertyBag
)
{
Options = options;
@ -263,6 +265,7 @@ public class EFCoreWorkflowDefinitionStore(EntityStore<ManagementElsaDbContext,
Outputs = outputs;
Outcomes = outcomes;
CustomProperties = customProperties;
PropertyBag = propertyBag;
}
public WorkflowOptions Options { get; set; } = new();
@ -270,6 +273,10 @@ public class EFCoreWorkflowDefinitionStore(EntityStore<ManagementElsaDbContext,
public ICollection<InputDefinition> Inputs { get; set; } = new List<InputDefinition>();
public ICollection<OutputDefinition> Outputs { get; set; } = new List<OutputDefinition>();
public ICollection<string> Outcomes { get; set; } = new List<string>();
[Obsolete("Use PropertyBag instead")]
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
public PropertyBag PropertyBag { get; set; } = new();
}
}

View file

@ -24,16 +24,17 @@ public class ExpressionOptions
this.AddTypeAlias<bool>("Boolean");
this.AddTypeAlias<decimal>("Decimal");
this.AddTypeAlias<double>("Double");
this.AddTypeAlias<byte[]>("ByteArray");
this.AddTypeAlias<Guid>("Guid");
this.AddTypeAlias<DateTime>("DateTime");
this.AddTypeAlias<DateTimeOffset>("DateTimeOffset");
this.AddTypeAlias<TimeSpan>("TimeSpan");
this.AddTypeAlias<ExpandoObject>("ExpandoObject");
this.AddTypeAlias<Stream>("Stream");
this.AddTypeAlias<ExpandoObject>("JSON");
this.AddTypeAlias<Dictionary<string, string>>("StringDictionary");
this.AddTypeAlias<IDictionary<string, string>>("StringDictionary");
this.AddTypeAlias<IDictionary<string, object>>("ObjectDictionary");
this.AddTypeAlias<Dictionary<string, string>>("StringMap");
this.AddTypeAlias<Dictionary<string, object>>("ObjectMap");
this.AddTypeAlias<Dictionary<string, object>>("ObjectDictionary");
}
/// Gets the type alias dictionary.

View file

@ -222,6 +222,11 @@ public class HttpFeature : FeatureBase
Services.Configure<ExpressionOptions>(options =>
{
options.AddTypeAlias<HttpRequest>("HttpRequest");
options.AddTypeAlias<HttpResponse>("HttpResponse");
options.AddTypeAlias<HttpResponseMessage>("HttpResponseMessage");
options.AddTypeAlias<HttpHeaders>("HttpHeaders");
options.AddTypeAlias<RouteData>("RouteData");
options.AddTypeAlias<IFormFile>("FormFile");
options.AddTypeAlias<IFormFile[]>("FormFile[]");
options.AddTypeAlias<HttpFile>("HttpFile");

View file

@ -4,16 +4,16 @@ using Elsa.Workflows.Management.Entities;
// ReSharper disable once CheckNamespace
namespace Elsa.Extensions;
/// <summary>
/// Adds extension methods to <see cref="WorkflowDefinition"/>.
/// </summary>
public static class WorkflowDefinitionExtensions
public static class WorkflowContextWorkflowDefinitionExtensions
{
/// <summary>
/// Gets the workflow context provider types that are installed on the workflow definition.
/// </summary>
/// <param name="workflowDefinition">The workflow definition to get the provider types from.</param>
/// <returns>The workflow context provider types.</returns>
public static IEnumerable<Type> GetWorkflowContextProviderTypes(this WorkflowDefinition workflowDefinition) =>
workflowDefinition.CustomProperties.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List<Type>());
public static IEnumerable<Type> GetWorkflowContextProviderTypes(this WorkflowDefinition workflowDefinition)
{
return workflowDefinition.PropertyBag.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List<Type>());
}
}

View file

@ -1,36 +1,38 @@
using System.Text.Json;
using Elsa.Expressions.Contracts;
using Elsa.Extensions;
using Elsa.WorkflowContexts.Contracts;
using Elsa.Workflows;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Pipelines.ActivityExecution;
using Elsa.Workflows.Serialization.Converters;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.WorkflowContexts.Middleware;
/// <summary>
/// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers.
/// </summary>
public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMiddleware
[UsedImplicitly]
public class WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory, IWellKnownTypeRegistry wellKnownTypeRegistry) : IActivityExecutionMiddleware
{
private readonly ActivityMiddlewareDelegate _next;
private readonly IServiceScopeFactory _serviceScopeFactory;
/// <summary>
/// Constructor.
/// </summary>
public WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory)
private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
{
_next = next;
_serviceScopeFactory = serviceScopeFactory;
}
PropertyNameCaseInsensitive = true
}.WithConverters(new TypeJsonConverter(wellKnownTypeRegistry));
/// <inheritdoc />
public async ValueTask InvokeAsync(ActivityExecutionContext context)
{
// Check if the workflow contains any workflow context providers.
if (!context.WorkflowExecutionContext.Workflow.CustomProperties.TryGetValue<ICollection<Type>>(Constants.WorkflowContextProviderTypesKey, out var providerTypes))
if (!context.WorkflowExecutionContext.Workflow.PropertyBag.TryGetValue<ICollection<Type>>(Constants.WorkflowContextProviderTypesKey, out var providerTypes, _jsonSerializerOptions))
{
await _next(context);
await next(context);
return;
}
if (providerTypes.Count == 0)
{
await next(context);
return;
}
@ -40,12 +42,12 @@ public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMidd
// Is the activity configured to load the context?
foreach (var providerType in providerTypes)
{
// Is the activity configured to load the context or is this a background execution?
// Is the activity configured to load the context, or is this a background execution?
var load = isBackgroundExecution || context.Activity.GetActivityWorkflowContextSettings(providerType).Load;
if (!load) continue;
// Load the context.
using var scope = _serviceScopeFactory.CreateScope();
using var scope = serviceScopeFactory.CreateScope();
var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType);
var value = await provider.LoadAsync(context.WorkflowExecutionContext);
@ -54,7 +56,7 @@ public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMidd
}
// Invoke the next middleware.
await _next(context);
await next(context);
// Invoke each workflow context provider to persists the context.
foreach (var providerType in providerTypes)
@ -64,7 +66,7 @@ public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMidd
if (!save) continue;
// Get the loaded value from the workflow execution context.
using var scope = _serviceScopeFactory.CreateScope();
using var scope = serviceScopeFactory.CreateScope();
var value = context.WorkflowExecutionContext.GetWorkflowContext(providerType);
// Save the context.

View file

@ -2,6 +2,7 @@ using Elsa.Abstractions;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.GetById;
@ -12,6 +13,7 @@ internal class GetById(IWorkflowDefinitionStore store, IWorkflowDefinitionLinker
{
Get("/workflow-definitions/by-id/{id}");
ConfigurePermissions("read:workflow-definitions");
Options(x => x.WithName("GetWorkflowDefinitionById"));
}
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)

View file

@ -4,6 +4,7 @@ using Elsa.Workflows.Api.Constants;
using Elsa.Workflows.Api.Requirements;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Management.Models;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
@ -46,7 +47,12 @@ internal class RevertVersion(IWorkflowDefinitionManager workflowDefinitionManage
return;
}
await workflowDefinitionManager.RevertVersionAsync(definitionId, version, cancellationToken);
await SendOkAsync(cancellationToken);
var newDefinition = await workflowDefinitionManager.RevertVersionAsync(definitionId, version, cancellationToken);
var newDefinitionSummary = WorkflowDefinitionSummary.FromDefinition(newDefinition);
await SendCreatedAtAsync("GetWorkflowDefinitionById", new
{
id = newDefinition.Id
}, newDefinitionSummary, cancellation: cancellationToken);
}
}

View file

@ -39,6 +39,7 @@ internal class Get : ElsaEndpoint<Request, Response>
{
WorkflowInstanceId = request.WorkflowInstanceId,
ActivityIds = request.Filter?.ActivityIds,
ActivityNodeIds = request.Filter?.ActivityNodeIds,
ExcludeActivityTypes = request.Filter?.ExcludedActivityTypes,
EventNames = request.Filter?.EventNames,
};

View file

@ -5,45 +5,32 @@ using FastEndpoints;
namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Journal.FilteredList;
/// <summary>
/// Represents a request for a page of workflow execution log records.
/// </summary>
internal class Request
{
/// <summary>
/// The ID of the workflow instance to get the execution log for.
/// </summary>
[BindFrom("id")] public string WorkflowInstanceId { get; set; } = default!;
/// <summary>
/// The filter to apply.
/// </summary>
public JournalFilter? Filter { get; set; }
/// <summary>
/// The zero-based page number to get.
/// </summary>
public int? Page { get; set; }
/// <summary>
/// The size of the page to get.
/// </summary>
public int? PageSize { get; set; }
/// <summary>
/// The number of records to skip.
/// </summary>
public int? Skip { get; set; }
/// <summary>
/// The number of records to take.
/// </summary>
public int? Take { get; set; }
}
internal class JournalFilter
{
public ICollection<string>? ActivityIds { get; set; }
public ICollection<string>? ActivityNodeIds { get; set; }
public ICollection<string>? ExcludedActivityTypes { get; set; }
public ICollection<string>? EventNames { get; set; }
}

View file

@ -32,6 +32,7 @@ public class StaticWorkflowDefinitionLinker(
Outputs = workflowDefinitionModel.Outputs,
Outcomes = workflowDefinitionModel.Outcomes,
CustomProperties = workflowDefinitionModel.CustomProperties,
PropertyBag = workflowDefinitionModel.PropertyBag,
IsReadonly = workflowDefinitionModel.IsReadonly,
IsSystem = workflowDefinitionModel.IsSystem,
IsLatest = workflowDefinitionModel.IsLatest,
@ -99,6 +100,7 @@ public class StaticWorkflowDefinitionLinker(
Outputs = item.Outputs,
Outcomes = item.Outcomes,
CustomProperties = item.CustomProperties,
PropertyBag = item.PropertyBag,
IsReadonly = item.IsReadonly,
IsSystem = item.IsSystem,
IsLatest = item.IsLatest,

View file

@ -1,4 +1,5 @@
using System.ComponentModel;
using Elsa.Common.Models;
using Elsa.Expressions.Models;
using Elsa.Workflows.Attributes;
using Elsa.Workflows.Contracts;
@ -30,6 +31,7 @@ public class Workflow : Composite<object>, ICloneable
ICollection<OutputDefinition> outputs,
ICollection<string> outcomes,
IDictionary<string, object> customProperties,
PropertyBag propertyBag,
bool isReadonly,
bool isSystem)
{
@ -38,6 +40,7 @@ public class Workflow : Composite<object>, ICloneable
Inputs = inputs;
Outputs = outputs;
Outcomes = outcomes;
PropertyBag = propertyBag;
WorkflowMetadata = workflowMetadata;
Options = options;
Variables = variables;
@ -96,6 +99,11 @@ public class Workflow : Composite<object>, ICloneable
/// Gets or sets options for the workflow.
/// </summary>
public WorkflowOptions Options { get; set; } = new();
/// <summary>
/// A bag of properties that can be used by applications and modules to store information that can be shared with tooling.
/// </summary>
public PropertyBag PropertyBag { get; set; } = new();
/// <summary>
/// Make workflow definition readonly.

View file

@ -1,3 +1,4 @@
using Elsa.Common.Models;
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Contracts;
@ -7,25 +8,9 @@ using Elsa.Workflows.Models;
namespace Elsa.Workflows.Builders;
/// <inheritdoc />
public class WorkflowBuilder : IWorkflowBuilder
public class WorkflowBuilder(IActivityVisitor activityVisitor, IIdentityGraphService identityGraphService, IActivityRegistry activityRegistry, IIdentityGenerator identityGenerator)
: IWorkflowBuilder
{
private readonly IActivityVisitor _activityVisitor;
private readonly IIdentityGraphService _identityGraphService;
private readonly IActivityRegistry _activityRegistry;
private readonly IIdentityGenerator _identityGenerator;
/// <summary>
/// Constructor.
/// </summary>
public WorkflowBuilder(IActivityVisitor activityVisitor, IIdentityGraphService identityGraphService, IActivityRegistry activityRegistry, IIdentityGenerator identityGenerator)
{
_activityVisitor = activityVisitor;
_identityGraphService = identityGraphService;
_activityRegistry = activityRegistry;
_identityGenerator = identityGenerator;
Result = new Variable();
}
/// <inheritdoc />
public string? Id { get; set; }
@ -66,11 +51,14 @@ public class WorkflowBuilder : IWorkflowBuilder
public ICollection<string> Outcomes { get; set; } = new List<string>();
/// <inheritdoc />
public Variable? Result { get; set; }
public Variable? Result { get; set; } = new();
/// <inheritdoc />
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
/// <inheritdoc />
public PropertyBag PropertyBag { get; set; } = new();
/// <inheritdoc />
public WorkflowOptions WorkflowOptions { get; } = new();
@ -234,7 +222,7 @@ public class WorkflowBuilder : IWorkflowBuilder
var publication = WorkflowPublication.LatestAndPublished;
var name = string.IsNullOrEmpty(Name) ? definitionId : Name;
var workflowMetadata = new WorkflowMetadata(name, Description);
var workflow = new Workflow(identity, publication, workflowMetadata, WorkflowOptions, root, Variables, Inputs, Outputs, Outcomes, CustomProperties, IsReadonly, IsSystem);
var workflow = new Workflow(identity, publication, workflowMetadata, WorkflowOptions, root, Variables, Inputs, Outputs, Outcomes, CustomProperties, PropertyBag, IsReadonly, IsSystem);
// If a Result variable is defined, install it into the workflow, so we can capture the output into it.
if (Result != null)
@ -243,15 +231,15 @@ public class WorkflowBuilder : IWorkflowBuilder
workflow.Result = new Output<object>(Result);
}
var graph = await _activityVisitor.VisitAsync(workflow, cancellationToken);
var graph = await activityVisitor.VisitAsync(workflow, cancellationToken);
var nodes = graph.Flatten().ToList();
// Register all activity types first. The identity graph service will need to know about all activity types.
var distinctActivityTypes = nodes.Select(x => x.Activity.GetType()).Distinct().ToList();
await _activityRegistry.RegisterAsync(distinctActivityTypes, cancellationToken);
await activityRegistry.RegisterAsync(distinctActivityTypes, cancellationToken);
// Assign identities to all activities.
await _identityGraphService.AssignIdentitiesAsync(nodes);
await identityGraphService.AssignIdentitiesAsync(nodes);
// Give unnamed variables in each variable container a predictable name.
var variableContainers = nodes.Where(x => x.Activity is IVariableContainer).Select(x => (IVariableContainer)x.Activity).ToList();

View file

@ -1,3 +1,4 @@
using Elsa.Common.Models;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
@ -87,8 +88,14 @@ public interface IWorkflowBuilder
/// <summary>
/// A set of properties that can be used for storing application-specific information about the workflow being built.
/// </summary>
[Obsolete("Use PropertyBag instead")]
IDictionary<string, object> CustomProperties { get; }
/// <summary>
/// A set of properties that can be used for storing application-specific information about the workflow being built.
/// </summary>
PropertyBag PropertyBag { get; set; }
/// <summary>
/// A fluent method for setting the <see cref="DefinitionId"/> property.
/// </summary>

View file

@ -1,4 +1,5 @@
using Elsa.Common.Entities;
using Elsa.Common.Models;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
@ -54,11 +55,12 @@ public class WorkflowDefinition : VersionedEntity
/// </summary>
public ICollection<string> Outcomes { get; set; } = new List<string>();
/// <summary>
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
/// </summary>
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
public PropertyBag PropertyBag { get; set; } = new();
/// <summary>
/// The name of the workflow provider that created this workflow, if any.
/// </summary>
@ -98,6 +100,5 @@ public class WorkflowDefinition : VersionedEntity
/// <summary>
/// Creates and returns a shallow copy of the workflow definition.
/// </summary>
/// <returns></returns>
public WorkflowDefinition ShallowClone() => (WorkflowDefinition)MemberwiseClone();
}

View file

@ -45,6 +45,7 @@ public class WorkflowDefinitionMapper
source.Outputs,
source.Outcomes,
source.CustomProperties,
source.PropertyBag,
source.IsReadonly,
source.IsSystem);
}
@ -77,6 +78,7 @@ public class WorkflowDefinitionMapper
source.Outputs ?? new List<OutputDefinition>(),
source.Outcomes ?? new List<string>(),
source.CustomProperties ?? new Dictionary<string, object>(),
source.PropertyBag ?? new(),
source.IsReadonly,
source.IsSystem);
}
@ -116,6 +118,7 @@ public class WorkflowDefinitionMapper
workflowDefinition.Outputs,
workflowDefinition.Outcomes,
workflowDefinition.CustomProperties,
workflowDefinition.PropertyBag,
workflowDefinition.IsReadonly,
workflowDefinition.IsSystem,
workflowDefinition.IsLatest,
@ -148,6 +151,7 @@ public class WorkflowDefinitionMapper
workflow.Outputs,
workflow.Outcomes,
workflow.CustomProperties,
workflow.PropertyBag,
workflow.IsReadonly,
workflow.IsSystem,
workflow.Publication.IsLatest,

View file

@ -1,12 +1,11 @@
using Elsa.Common.Models;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Models;
using JetBrains.Annotations;
namespace Elsa.Workflows.Management.Models;
/// <summary>
/// Represents a serializable workflow definition.
/// </summary>
[PublicAPI]
public record WorkflowDefinitionModel(
string Id,
@ -21,7 +20,9 @@ public record WorkflowDefinitionModel(
ICollection<InputDefinition>? Inputs,
ICollection<OutputDefinition>? Outputs,
ICollection<string>? Outcomes,
[property: Obsolete("Use PropertyBag instead")]
IDictionary<string, object>? CustomProperties,
PropertyBag? PropertyBag,
bool IsReadonly,
bool IsSystem,
bool IsLatest,
@ -45,7 +46,8 @@ public record WorkflowDefinitionModel(
default!,
default!,
default!,
default!,
default,
default,
default!,
default!,
default!,

View file

@ -95,7 +95,7 @@ public class WorkflowDefinitionManager : IWorkflowDefinitionManager
{
if (definitionToDelete.IsPublished)
{
throw new Exception("Published version cannot be deleted before retracting it");
await _workflowPublisher.RetractAsync(definitionToDelete, cancellationToken);
}
await _notificationSender.SendAsync(new WorkflowDefinitionVersionDeleting(definitionToDelete), cancellationToken);

View file

@ -4,5 +4,5 @@
"_island": "[\r\n {\r\n \u0022path\u0022: \u0022folder1\u0022,\r\n \u0022command\u0022: \u0022add\u0022\r\n }\r\n]",
"_type": "Newtonsoft.Json.Linq.JArray, Newtonsoft.Json"
},
"_type": "ObjectDictionary"
"_type": "ObjectMap"
}

View file

@ -6,5 +6,5 @@
"command": "add"
}
],
"_type": "ObjectDictionary"
"_type": "ObjectMap"
}

View file

@ -4,5 +4,5 @@
"_island": "{\r\n \u0022file1\u0022: {\r\n \u0022script\u0022: [\r\n {\r\n \u0022path\u0022: \u0022folder1\u0022,\r\n \u0022command\u0022: \u0022add\u0022\r\n }\r\n ]\r\n }\r\n}",
"_type": "Newtonsoft.Json.Linq.JObject, Newtonsoft.Json"
},
"_type": "ObjectDictionary"
"_type": "ObjectMap"
}

View file

@ -10,5 +10,5 @@
]
}
},
"_type": "ObjectDictionary"
"_type": "ObjectMap"
}

View file

@ -4,5 +4,5 @@
"_island": "[\r\n {\r\n \u0022path\u0022: \u0022folder1\u0022,\r\n \u0022command\u0022: \u0022add\u0022\r\n }\r\n]",
"_type": "System.Text.Json.Nodes.JsonArray, System.Text.Json"
},
"_type": "ObjectDictionary"
"_type": "ObjectMap"
}

View file

@ -4,5 +4,5 @@
"_island": "{\r\n \u0022file1\u0022: {\r\n \u0022script\u0022: [\r\n {\r\n \u0022path\u0022: \u0022folder1\u0022,\r\n \u0022command\u0022: \u0022add\u0022\r\n }\r\n ]\r\n }\r\n}",
"_type": "System.Text.Json.Nodes.JsonObject, System.Text.Json"
},
"_type": "ObjectDictionary"
"_type": "ObjectMap"
}

View file

@ -10,5 +10,5 @@
]
}
},
"_type": "ObjectDictionary"
"_type": "ObjectMap"
}