From 558486920bfcea3550bc9abe2919f677d0e3eb63 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 10:29:45 +0200 Subject: [PATCH 01/11] Add PropertyBag for non-polymorphic data transfer between server and client (#5735) * Fix issues with workflow deletion, newline, and DB context. Retract published workflows before deletion to avoid exceptions. Added missing newline at EOF for consistency. Removed redundant DisposeAsync call in migration service. * Add PropertyBag for storing custom workflow properties Replaced `CustomProperties` with `PropertyBag` class for more structured property management across workflows. Adjusted serializations, extensions, and middleware for seamless integration with the new `PropertyBag` structure. --- .../Extensions/PropertyBagExtensions.cs | 41 ++++++++++++ .../Models/WorkflowDefinition.cs | 5 ++ .../Models/WorkflowDefinitionModel.cs | 5 ++ .../Shared/Models/PropertyBag.cs | 20 ++++++ .../Shared/Models/WrappedInput.cs | 3 +- .../Extensions/PropertyBagExtensions.cs | 67 +++++++++++++++++++ src/modules/Elsa.Common/Models/PropertyBag.cs | 20 ++++++ .../Elsa.Common/Models/VersionOptions.cs | 2 +- .../RunMigrationsHostedService.cs | 1 - .../Modules/Management/Configurations.cs | 1 + .../Management/WorkflowDefinitionStore.cs | 13 +++- ...lowContextWorkflowDefinitionExtensions.cs} | 10 +-- ...kflowContextActivityExecutionMiddleware.cs | 41 ++++++------ .../StaticWorkflowDefinitionLinker.cs | 2 + .../Activities/Workflow.cs | 8 +++ .../Builders/WorkflowBuilder.cs | 38 ++++------- .../Contracts/IWorkflowBuilder.cs | 7 ++ .../Entities/WorkflowDefinition.cs | 7 +- .../Mappers/WorkflowDefinitionMapper.cs | 4 ++ .../Models/WorkflowDefinitionModel.cs | 8 ++- .../Services/WorkflowDefinitionManager.cs | 2 +- 21 files changed, 242 insertions(+), 63 deletions(-) create mode 100644 src/clients/Elsa.Api.Client/Extensions/PropertyBagExtensions.cs create mode 100644 src/clients/Elsa.Api.Client/Shared/Models/PropertyBag.cs create mode 100644 src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs create mode 100644 src/modules/Elsa.Common/Models/PropertyBag.cs rename src/modules/Elsa.WorkflowContexts/Extensions/{WorkflowDefinitionExtensions.cs => WorkflowContextWorkflowDefinitionExtensions.cs} (69%) diff --git a/src/clients/Elsa.Api.Client/Extensions/PropertyBagExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/PropertyBagExtensions.cs new file mode 100644 index 000000000..c27200c88 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Extensions/PropertyBagExtensions.cs @@ -0,0 +1,41 @@ +using System.Text.Json; +using Elsa.Api.Client.Shared.Models; + +namespace Elsa.Api.Client.Extensions; + +/// +/// Provides extension methods for the PropertyBag class. +/// +public static class PropertyBagExtensions +{ + /// + /// 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. + /// + /// The type of the value to retrieve. + /// The PropertyBag to retrieve the value from. + /// The key of the value to retrieve. + /// A function that returns the default value to be returned if the key does not exist in the PropertyBag. + /// The value associated with the key, or the default value if the key does not exist. + public static T TryGetValueOrDefault(this PropertyBag propertyBag, string key, Func defaultValue) + { + if (!propertyBag.TryGetValue(key, out var value)) + return defaultValue(); + + var json = (string)value; + return JsonSerializer.Deserialize(json); + } + + /// + /// Sets a value in the PropertyBag based on the provided key. + /// The value is serialized using JSON. + /// + /// The PropertyBag to set the value in. + /// The key to associate with the value. + /// The value to store in the PropertyBag. + public static void SetValue(this PropertyBag propertyBag, string key, object value) + { + var json = JsonSerializer.Serialize(value); + propertyBag[key] = json; + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinition.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinition.cs index f3fe5d0eb..4d2ba3ef5 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinition.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinition.cs @@ -58,6 +58,11 @@ public class WorkflowDefinition : LinkedEntity /// public IDictionary CustomProperties { get; set; } = new Dictionary(); + /// + /// 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(); + /// /// The name of the workflow provider that created this workflow, if any. /// diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinitionModel.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinitionModel.cs index a617b54f3..76dba636d 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinitionModel.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinitionModel.cs @@ -55,6 +55,11 @@ public class WorkflowDefinitionModel : LinkedEntity /// public IDictionary? CustomProperties { get; set; } + /// + /// 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(); + /// /// Gets or sets whether the workflow definition is read-only. /// diff --git a/src/clients/Elsa.Api.Client/Shared/Models/PropertyBag.cs b/src/clients/Elsa.Api.Client/Shared/Models/PropertyBag.cs new file mode 100644 index 000000000..9abe27abc --- /dev/null +++ b/src/clients/Elsa.Api.Client/Shared/Models/PropertyBag.cs @@ -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 +{ + /// + [JsonConstructor] + public PropertyBag() : base(StringComparer.OrdinalIgnoreCase) + { + } + + /// + public PropertyBag(IDictionary dictionary) : this() + { + foreach (var kvp in dictionary) + Add(kvp.Key, kvp.Value); + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Shared/Models/WrappedInput.cs b/src/clients/Elsa.Api.Client/Shared/Models/WrappedInput.cs index ea5c9225e..072f437fe 100644 --- a/src/clients/Elsa.Api.Client/Shared/Models/WrappedInput.cs +++ b/src/clients/Elsa.Api.Client/Shared/Models/WrappedInput.cs @@ -21,4 +21,5 @@ public class WrappedInput /// Gets or sets the memory reference of this input. /// public MemoryReference MemoryReference { get; set; } = default!; -} \ No newline at end of file +} + diff --git a/src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs b/src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs new file mode 100644 index 000000000..ad0359c21 --- /dev/null +++ b/src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using Elsa.Common.Models; + +// ReSharper disable once CheckNamespace +namespace Elsa.Extensions; + +/// +/// Provides extension methods for the PropertyBag class. +/// +public static class PropertyBagExtensions +{ + /// + /// 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. + /// + /// The type of the value to retrieve. + /// The PropertyBag to retrieve the value from. + /// The key of the value to retrieve. + /// A function that returns the default value to be returned if the key does not exist in the PropertyBag. + /// Optional JSON serializer options. + /// The value associated with the key, or the default value if the key does not exist. + public static T TryGetValueOrDefault(this PropertyBag propertyBag, string key, Func defaultValue, JsonSerializerOptions? options = null) + { + if (!propertyBag.TryGetValue(key, out var value)) + return defaultValue(); + + var json = (string)value; + return JsonSerializer.Deserialize(json, options); + } + + /// + /// 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. + /// + /// The type of the value to retrieve. + /// The PropertyBag to retrieve the value from. + /// The key of the value to retrieve. + /// The deserialized value. + /// Optional JSON serializer options. + /// True if the value exists, false otherwise. + public static bool TryGetValue(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(json, options); + return true; + } + + /// + /// Sets a value in the PropertyBag based on the provided key. + /// The value is serialized using JSON. + /// + /// The PropertyBag to set the value in. + /// The key to associate with the value. + /// The value to store in the PropertyBag. + /// /// Optional JSON serializer options. + public static void SetValue(this PropertyBag propertyBag, string key, object value, JsonSerializerOptions? options = null) + { + var json = JsonSerializer.Serialize(value); + propertyBag[key] = json; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Models/PropertyBag.cs b/src/modules/Elsa.Common/Models/PropertyBag.cs new file mode 100644 index 000000000..0f6209e81 --- /dev/null +++ b/src/modules/Elsa.Common/Models/PropertyBag.cs @@ -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 +{ + /// + [JsonConstructor] + public PropertyBag() : base(StringComparer.OrdinalIgnoreCase) + { + } + + /// + public PropertyBag(IDictionary dictionary) : this() + { + foreach (var kvp in dictionary) + Add(kvp.Key, kvp.Value); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Models/VersionOptions.cs b/src/modules/Elsa.Common/Models/VersionOptions.cs index f3c56f598..40ab3a0ad 100644 --- a/src/modules/Elsa.Common/Models/VersionOptions.cs +++ b/src/modules/Elsa.Common/Models/VersionOptions.cs @@ -114,4 +114,4 @@ public struct VersionOptions /// Returns a simple string representation of this . /// public override string ToString() => AllVersions ? "AllVersions" : IsDraft ? "Draft" : IsLatest ? "Latest" : IsPublished ? "Published" : IsLatestOrPublished ? "LatestOrPublished" : IsLatestAndPublished ? "LatestAndPublished" : Version.ToString(); -} +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsHostedService.cs b/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsHostedService.cs index ebfb92beb..5d5f4e047 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsHostedService.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsHostedService.cs @@ -26,7 +26,6 @@ public class RunMigrationsHostedService : IHostedService where TDbCo var dbContextFactory = scope.ServiceProvider.GetRequiredService>(); await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); await dbContext.Database.MigrateAsync(cancellationToken); - await dbContext.DisposeAsync(); } /// diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Configurations.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Configurations.cs index 02cdccc5c..7f25567dd 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Configurations.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Configurations.cs @@ -17,6 +17,7 @@ internal class Configurations : IEntityTypeConfiguration, 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("Data"); builder.Property("UsableAsActivity"); diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs index 2c6886075..7421e5938 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs @@ -152,7 +152,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore 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 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; @@ -165,7 +165,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore 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)) @@ -177,6 +177,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore entity.Outputs = data.Outputs; entity.Outcomes = data.Outcomes; entity.CustomProperties = data.CustomProperties; + entity.PropertyBag = data.PropertyBag; return ValueTask.CompletedTask; } @@ -225,7 +226,8 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore ICollection inputs, ICollection outputs, ICollection outcomes, - IDictionary customProperties + IDictionary customProperties, + PropertyBag propertyBag ) { Options = options; @@ -234,6 +236,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore Outputs = outputs; Outcomes = outcomes; CustomProperties = customProperties; + PropertyBag = propertyBag; } public WorkflowOptions Options { get; set; } = new(); @@ -241,6 +244,10 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore public ICollection Inputs { get; set; } = new List(); public ICollection Outputs { get; set; } = new List(); public ICollection Outcomes { get; set; } = new List(); + + [Obsolete("Use PropertyBag instead")] public IDictionary CustomProperties { get; set; } = new Dictionary(); + + public PropertyBag PropertyBag { get; set; } = new(); } } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowDefinitionExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowDefinitionExtensions.cs similarity index 69% rename from src/modules/Elsa.WorkflowContexts/Extensions/WorkflowDefinitionExtensions.cs rename to src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowDefinitionExtensions.cs index 1940aa46d..fc8279df0 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowDefinitionExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowDefinitionExtensions.cs @@ -4,16 +4,16 @@ using Elsa.Workflows.Management.Entities; // ReSharper disable once CheckNamespace namespace Elsa.Extensions; -/// /// Adds extension methods to . -/// -public static class WorkflowDefinitionExtensions +public static class WorkflowContextWorkflowDefinitionExtensions { /// /// Gets the workflow context provider types that are installed on the workflow definition. /// /// The workflow definition to get the provider types from. /// The workflow context provider types. - public static IEnumerable GetWorkflowContextProviderTypes(this WorkflowDefinition workflowDefinition) => - workflowDefinition.CustomProperties.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List()); + public static IEnumerable GetWorkflowContextProviderTypes(this WorkflowDefinition workflowDefinition) + { + return workflowDefinition.PropertyBag.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List()); + } } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs index ccb7d4ba2..7b1cda1bc 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs @@ -1,37 +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.Runtime.Middleware.Activities; +using Elsa.Workflows.Serialization.Converters; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; namespace Elsa.WorkflowContexts.Middleware; -/// /// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers. -/// -public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMiddleware +[UsedImplicitly] +public class WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory, IWellKnownTypeRegistry wellKnownTypeRegistry) : IActivityExecutionMiddleware { - private readonly ActivityMiddlewareDelegate _next; - private readonly IServiceScopeFactory _serviceScopeFactory; - - /// - /// Constructor. - /// - public WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory) + private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions { - _next = next; - _serviceScopeFactory = serviceScopeFactory; - } + PropertyNameCaseInsensitive = true + }.WithConverters(new TypeJsonConverter(wellKnownTypeRegistry)); /// public async ValueTask InvokeAsync(ActivityExecutionContext context) { // Check if the workflow contains any workflow context providers. - if (!context.WorkflowExecutionContext.Workflow.CustomProperties.TryGetValue>(Constants.WorkflowContextProviderTypesKey, out var providerTypes)) + if (!context.WorkflowExecutionContext.Workflow.PropertyBag.TryGetValue>(Constants.WorkflowContextProviderTypesKey, out var providerTypes, _jsonSerializerOptions)) { - await _next(context); + await next(context); + return; + } + + if (providerTypes.Count == 0) + { + await next(context); return; } @@ -41,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); @@ -55,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) @@ -65,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. diff --git a/src/modules/Elsa.Workflows.Api/Services/StaticWorkflowDefinitionLinker.cs b/src/modules/Elsa.Workflows.Api/Services/StaticWorkflowDefinitionLinker.cs index da2a3351e..d065b5818 100644 --- a/src/modules/Elsa.Workflows.Api/Services/StaticWorkflowDefinitionLinker.cs +++ b/src/modules/Elsa.Workflows.Api/Services/StaticWorkflowDefinitionLinker.cs @@ -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, diff --git a/src/modules/Elsa.Workflows.Core/Activities/Workflow.cs b/src/modules/Elsa.Workflows.Core/Activities/Workflow.cs index 1116beb80..d366bd17d 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Workflow.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Workflow.cs @@ -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, ICloneable ICollection outputs, ICollection outcomes, IDictionary customProperties, + PropertyBag propertyBag, bool isReadonly, bool isSystem) { @@ -38,6 +40,7 @@ public class Workflow : Composite, ICloneable Inputs = inputs; Outputs = outputs; Outcomes = outcomes; + PropertyBag = propertyBag; WorkflowMetadata = workflowMetadata; Options = options; Variables = variables; @@ -96,6 +99,11 @@ public class Workflow : Composite, ICloneable /// Gets or sets options for the workflow. /// public WorkflowOptions Options { get; set; } = new(); + + /// + /// A bag of properties that can be used by applications and modules to store information that can be shared with tooling. + /// + public PropertyBag PropertyBag { get; set; } = new(); /// /// Make workflow definition readonly. diff --git a/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs b/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs index 72dd989bd..246ee7bad 100644 --- a/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs @@ -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; /// -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; - - /// - /// Constructor. - /// - public WorkflowBuilder(IActivityVisitor activityVisitor, IIdentityGraphService identityGraphService, IActivityRegistry activityRegistry, IIdentityGenerator identityGenerator) - { - _activityVisitor = activityVisitor; - _identityGraphService = identityGraphService; - _activityRegistry = activityRegistry; - _identityGenerator = identityGenerator; - Result = new Variable(); - } - /// public string? Id { get; set; } @@ -63,11 +48,14 @@ public class WorkflowBuilder : IWorkflowBuilder public ICollection Outcomes { get; set; } = new List(); /// - public Variable? Result { get; set; } + public Variable? Result { get; set; } = new(); /// public IDictionary CustomProperties { get; set; } = new Dictionary(); + /// + public PropertyBag PropertyBag { get; set; } = new(); + /// public WorkflowOptions WorkflowOptions { get; } = new(); @@ -216,14 +204,14 @@ public class WorkflowBuilder : IWorkflowBuilder /// public async Task BuildWorkflowAsync(CancellationToken cancellationToken = default) { - var definitionId = DefinitionId ?? _identityGenerator.GenerateId(); - var id = Id ?? _identityGenerator.GenerateId(); + var definitionId = DefinitionId ?? identityGenerator.GenerateId(); + var id = Id ?? identityGenerator.GenerateId(); var root = Root ?? new Sequence(); var identity = new WorkflowIdentity(definitionId, Version, id); 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) @@ -232,15 +220,15 @@ public class WorkflowBuilder : IWorkflowBuilder workflow.Result = new Output(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(); diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs index b27933ab1..57bf044a3 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs @@ -1,3 +1,4 @@ +using Elsa.Common.Models; using Elsa.Workflows.Activities; using Elsa.Workflows.Memory; using Elsa.Workflows.Models; @@ -82,8 +83,14 @@ public interface IWorkflowBuilder /// /// A set of properties that can be used for storing application-specific information about the workflow being built. /// + [Obsolete("Use PropertyBag instead")] IDictionary CustomProperties { get; } + /// + /// A set of properties that can be used for storing application-specific information about the workflow being built. + /// + PropertyBag PropertyBag { get; set; } + /// /// A fluent method for setting the property. /// diff --git a/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs b/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs index 7c6f030f3..9405a4dc8 100644 --- a/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs +++ b/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs @@ -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 /// public ICollection Outcomes { get; set; } = new List(); - /// /// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow. - /// public IDictionary CustomProperties { get; set; } = new Dictionary(); + /// 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(); + /// /// The name of the workflow provider that created this workflow, if any. /// @@ -98,6 +100,5 @@ public class WorkflowDefinition : VersionedEntity /// /// Creates and returns a shallow copy of the workflow definition. /// - /// public WorkflowDefinition ShallowClone() => (WorkflowDefinition)MemberwiseClone(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs index a090db733..9f6ac512a 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs @@ -46,6 +46,7 @@ public class WorkflowDefinitionMapper source.Outputs, source.Outcomes, source.CustomProperties, + source.PropertyBag, source.IsReadonly, source.IsSystem); } @@ -78,6 +79,7 @@ public class WorkflowDefinitionMapper source.Outputs ?? new List(), source.Outcomes ?? new List(), source.CustomProperties ?? new Dictionary(), + 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, @@ -147,6 +150,7 @@ public class WorkflowDefinitionMapper workflow.Outputs, workflow.Outcomes, workflow.CustomProperties, + workflow.PropertyBag, workflow.IsReadonly, workflow.IsSystem, workflow.Publication.IsLatest, diff --git a/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs b/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs index f29087dcc..5f0332486 100644 --- a/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs +++ b/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs @@ -1,12 +1,11 @@ +using Elsa.Common.Models; using Elsa.Workflows.Contracts; using Elsa.Workflows.Models; using JetBrains.Annotations; namespace Elsa.Workflows.Management.Models; -/// /// Represents a serializable workflow definition. -/// [PublicAPI] public record WorkflowDefinitionModel( string Id, @@ -20,7 +19,9 @@ public record WorkflowDefinitionModel( ICollection? Inputs, ICollection? Outputs, ICollection? Outcomes, + [property: Obsolete("Use PropertyBag instead")] IDictionary? CustomProperties, + PropertyBag? PropertyBag, bool IsReadonly, bool IsSystem, bool IsLatest, @@ -44,7 +45,8 @@ public record WorkflowDefinitionModel( default!, default!, default!, - default!, + default, + default, default!, default!, default!, diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionManager.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionManager.cs index 657f72528..5bea2d957 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionManager.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionManager.cs @@ -96,7 +96,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); From dc89caefe8cbe02c65e414c1d15457a297f26c8b Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 11:38:09 +0200 Subject: [PATCH 02/11] Add ActivityNodeIds filter to journal requests (#5741) This commit introduces the ActivityNodeIds filter to the journal request models. This enhancement allows filtering logs based on specific activity node IDs, providing more granular control over workflow instance logs. Additionally, unnecessary summary comments have been removed for clarity. --- .../WorkflowInstances/Requests/JournalFilter.cs | 13 ++++--------- .../Journal/FilteredList/Endpoint.cs | 1 + .../Journal/FilteredList/Models.cs | 17 ++--------------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/JournalFilter.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/JournalFilter.cs index 537baf49a..55f71e5e6 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/JournalFilter.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/JournalFilter.cs @@ -1,22 +1,17 @@ namespace Elsa.Api.Client.Resources.WorkflowInstances.Requests; -/// /// Represents a request to list journal records. -/// public class JournalFilter { - /// /// Gets or sets the activity IDs to filter by. - /// public ICollection? ActivityIds { get; set; } - - /// + + /// Gets or sets the activity node IDs to filter by. + public ICollection? ActivityNodeIds { get; set; } + /// Gets or sets the activity types to filter out. - /// public ICollection? ExcludedActivityTypes { get; set; } - /// /// Gets or sets the event types to filter by. - /// public ICollection? EventNames { get; set; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Endpoint.cs index 4787b532e..f7614af09 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Endpoint.cs @@ -39,6 +39,7 @@ internal class Get : ElsaEndpoint { WorkflowInstanceId = request.WorkflowInstanceId, ActivityIds = request.Filter?.ActivityIds, + ActivityNodeIds = request.Filter?.ActivityNodeIds, ExcludeActivityTypes = request.Filter?.ExcludedActivityTypes, EventNames = request.Filter?.EventNames, }; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Models.cs index c9e56f52c..a3cbeb52c 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Models.cs @@ -5,45 +5,32 @@ using FastEndpoints; namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Journal.FilteredList; -/// /// Represents a request for a page of workflow execution log records. -/// internal class Request { - /// /// The ID of the workflow instance to get the execution log for. - /// [BindFrom("id")] public string WorkflowInstanceId { get; set; } = default!; - /// /// The filter to apply. - /// public JournalFilter? Filter { get; set; } - /// /// The zero-based page number to get. - /// public int? Page { get; set; } - /// /// The size of the page to get. - /// public int? PageSize { get; set; } - - /// + /// The number of records to skip. - /// public int? Skip { get; set; } - /// /// The number of records to take. - /// public int? Take { get; set; } } internal class JournalFilter { public ICollection? ActivityIds { get; set; } + public ICollection? ActivityNodeIds { get; set; } public ICollection? ExcludedActivityTypes { get; set; } public ICollection? EventNames { get; set; } } From e2f2f61400ac97fde3649bc5edde50316029441e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 13:51:32 +0200 Subject: [PATCH 03/11] Update version to 3.2.0-rc3 in GitHub actions workflow Changed the fallback version in the GitHub actions workflow from 3.2.0-rc2 to 3.2.0-rc3 to ensure the appropriate versioning reflects the latest release candidate. This keeps the versioning consistent and up-to-date. --- .github/workflows/packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 22012b2dd..5213d3487 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -62,7 +62,7 @@ jobs: TAG_NAME=${TAG_NAME#refs/tags/} # remove the refs/tags/ prefix echo "VERSION=${TAG_NAME}" >> $GITHUB_ENV else - echo "VERSION=3.2.0-rc2.${{github.run_number}}" >> $GITHUB_ENV + echo "VERSION=3.2.0-rc3.${{github.run_number}}" >> $GITHUB_ENV fi - name: Set up JDK 17 uses: actions/setup-java@v2 From 1e95b6f8479494f992df10ea50c0853cff10c8ea Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 15:05:31 +0200 Subject: [PATCH 04/11] Update ElsaStudio package version to use a variable Switched ElsaStudio package versions to use a property variable for easier management and consistency. Also, adjusted the YAML workflow to tag Docker images with the specific version 3.2.0-rc3. --- .github/workflows/elsa-server-and-studio.yml | 2 +- Directory.Build.props | 46 ++++++++++---------- Directory.Packages.props | 6 +-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.github/workflows/elsa-server-and-studio.yml b/.github/workflows/elsa-server-and-studio.yml index bd6c23243..07ac18c86 100644 --- a/.github/workflows/elsa-server-and-studio.yml +++ b/.github/workflows/elsa-server-and-studio.yml @@ -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:3.2.0-rc3 flavor: | latest=true # generate Docker tags based on the following events/attributes diff --git a/Directory.Build.props b/Directory.Build.props index adbd4a9e9..4c56d1528 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,30 +1,32 @@ - - Elsa Workflows Community - 2023 + + Elsa Workflows Community + 2023 - https://github.com/elsa-workflows/elsa-core - https://github.com/elsa-workflows/elsa-core - git + https://github.com/elsa-workflows/elsa-core + https://github.com/elsa-workflows/elsa-core + git - latest - enable - enable + latest + enable + enable - MIT - icon.png + MIT + icon.png - - https://v3.elsaworkflows.io/nuget-icon.png - - true - Default - latest + + https://v3.elsaworkflows.io/nuget-icon.png - - true - snupkg - true - + true + Default + latest + + true + snupkg + true + + + 3.2.0-rc3.417 + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 581449a8e..0663c9b3e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,9 +23,9 @@ - - - + + + From 847d6244ba1700b03da6abc37042d1a3d016558b Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 15:07:07 +0200 Subject: [PATCH 05/11] Update workflows to target patch/3.2.x branch and tag Docker images Changed the target branch from 'main' to 'patch/3.2.x' in the workflow YAML files. Updated Docker image tags to specific version '3.2.0-rc3' for both elsa-server and elsa-studio. --- .github/workflows/elsa-server-and-studio.yml | 2 +- .github/workflows/elsa-server.yml | 4 ++-- .github/workflows/elsa-studio.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/elsa-server-and-studio.yml b/.github/workflows/elsa-server-and-studio.yml index 07ac18c86..3e6c0ff79 100644 --- a/.github/workflows/elsa-server-and-studio.yml +++ b/.github/workflows/elsa-server-and-studio.yml @@ -3,7 +3,7 @@ on: workflow_dispatch: push: branches: - - main + - patch/3.2.x jobs: push_to_registry: diff --git a/.github/workflows/elsa-server.yml b/.github/workflows/elsa-server.yml index a5bc9572a..d1fd34494 100644 --- a/.github/workflows/elsa-server.yml +++ b/.github/workflows/elsa-server.yml @@ -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:3.2.0-rc3 flavor: | latest=true # generate Docker tags based on the following events/attributes diff --git a/.github/workflows/elsa-studio.yml b/.github/workflows/elsa-studio.yml index ebc412655..dace5bd85 100644 --- a/.github/workflows/elsa-studio.yml +++ b/.github/workflows/elsa-studio.yml @@ -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:3.2.0-rc3 flavor: | latest=true # generate Docker tags based on the following events/attributes From 90fba5f4bf12225f5310c8ff62befc5c75f257e1 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 15:09:12 +0200 Subject: [PATCH 06/11] Update Docker image tags in GitHub workflows Corrected Docker image tags in workflow files to ensure consistent naming conventions. This update affects elsa-server, elsa-studio, and elsa-server-and-studio YAML files. --- .github/workflows/elsa-server-and-studio.yml | 2 +- .github/workflows/elsa-server.yml | 2 +- .github/workflows/elsa-studio.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/elsa-server-and-studio.yml b/.github/workflows/elsa-server-and-studio.yml index 3e6c0ff79..46417543a 100644 --- a/.github/workflows/elsa-server-and-studio.yml +++ b/.github/workflows/elsa-server-and-studio.yml @@ -29,7 +29,7 @@ jobs: with: # list of Docker images to use as base name for tags images: | - elsaworkflows/elsa-server-and-studio-v3:3.2.0-rc3 + elsaworkflows/elsa-server-and-studio-v3-2-0-rc3 flavor: | latest=true # generate Docker tags based on the following events/attributes diff --git a/.github/workflows/elsa-server.yml b/.github/workflows/elsa-server.yml index d1fd34494..7c50ce0c8 100644 --- a/.github/workflows/elsa-server.yml +++ b/.github/workflows/elsa-server.yml @@ -29,7 +29,7 @@ jobs: with: # list of Docker images to use as base name for tags images: | - elsaworkflows/elsa-server-v3:3.2.0-rc3 + elsaworkflows/elsa-server-v3-2-0-rc3 flavor: | latest=true # generate Docker tags based on the following events/attributes diff --git a/.github/workflows/elsa-studio.yml b/.github/workflows/elsa-studio.yml index dace5bd85..35c8eb51a 100644 --- a/.github/workflows/elsa-studio.yml +++ b/.github/workflows/elsa-studio.yml @@ -29,7 +29,7 @@ jobs: with: # list of Docker images to use as base name for tags images: | - elsaworkflows/elsa-studio-v3:3.2.0-rc3 + elsaworkflows/elsa-studio-v3-2-0-rc3 flavor: | latest=true # generate Docker tags based on the following events/attributes From 073598c748f52035280cae50ea0995ed35c92fa3 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 15:28:50 +0200 Subject: [PATCH 07/11] Remove obsolete project reference Deleted the project reference to Elsa.Server.Web.csproj from Elsa.ServerAndStudio.Web.csproj. This reference is no longer needed and was causing redundancy in the project configuration. --- .../Elsa.ServerAndStudio.Web.csproj | 21 +++++++++---------- .../ElsaStudioWebAssembly.csproj | 4 ---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj b/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj index ecf4dfa62..2480935af 100644 --- a/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj +++ b/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj @@ -8,32 +8,31 @@ - + - + - - - - - + + + + + - - - + + - + diff --git a/src/bundles/ElsaStudioWebAssembly/ElsaStudioWebAssembly.csproj b/src/bundles/ElsaStudioWebAssembly/ElsaStudioWebAssembly.csproj index 1cc2f891c..eba2a2fd1 100644 --- a/src/bundles/ElsaStudioWebAssembly/ElsaStudioWebAssembly.csproj +++ b/src/bundles/ElsaStudioWebAssembly/ElsaStudioWebAssembly.csproj @@ -13,8 +13,4 @@ - - <_ContentIncludedByDefault Remove="wwwroot\index.html" /> - - From 4d41cd743bb248dce8b557e9ccb3cd76751c7da8 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 15:32:01 +0200 Subject: [PATCH 08/11] Add DistributedCachingTransport enum Introduced the DistributedCachingTransport enum to define caching transport options. Also cleaned up the code by removing an unnecessary using directive and a commented summary in the MassTransitBroker enum. --- .../Enums/DistributedCachingTransport.cs | 9 +++++++++ .../Elsa.ServerAndStudio.Web/Enums/MassTransitBroker.cs | 2 -- src/bundles/Elsa.ServerAndStudio.Web/Program.cs | 3 +-- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 src/bundles/Elsa.ServerAndStudio.Web/Enums/DistributedCachingTransport.cs diff --git a/src/bundles/Elsa.ServerAndStudio.Web/Enums/DistributedCachingTransport.cs b/src/bundles/Elsa.ServerAndStudio.Web/Enums/DistributedCachingTransport.cs new file mode 100644 index 000000000..68971a20c --- /dev/null +++ b/src/bundles/Elsa.ServerAndStudio.Web/Enums/DistributedCachingTransport.cs @@ -0,0 +1,9 @@ +namespace Elsa.ServerAndStudio.Web.Enums; + +/// Represents the transport options for distributed caching. +public enum DistributedCachingTransport +{ + None, + Memory, + MassTransit +} \ No newline at end of file diff --git a/src/bundles/Elsa.ServerAndStudio.Web/Enums/MassTransitBroker.cs b/src/bundles/Elsa.ServerAndStudio.Web/Enums/MassTransitBroker.cs index 2ab461e3a..84a81d86a 100644 --- a/src/bundles/Elsa.ServerAndStudio.Web/Enums/MassTransitBroker.cs +++ b/src/bundles/Elsa.ServerAndStudio.Web/Enums/MassTransitBroker.cs @@ -1,8 +1,6 @@ namespace Elsa.ServerAndStudio.Web.Enums; -/// /// Represents the type of messaging broker used in MassTransit. -/// public enum MassTransitBroker { Memory, diff --git a/src/bundles/Elsa.ServerAndStudio.Web/Program.cs b/src/bundles/Elsa.ServerAndStudio.Web/Program.cs index a6ca02528..c5e347d73 100644 --- a/src/bundles/Elsa.ServerAndStudio.Web/Program.cs +++ b/src/bundles/Elsa.ServerAndStudio.Web/Program.cs @@ -5,12 +5,11 @@ using Elsa.MassTransit.Options; using Elsa.Extensions; using Elsa.ServerAndStudio.Web.Extensions; using Elsa.MassTransit.Extensions; -using Elsa.Server.Web; +using Elsa.ServerAndStudio.Web.Enums; using Medallion.Threading.FileSystem; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Proto.Persistence.Sqlite; -using MassTransitBroker = Elsa.ServerAndStudio.Web.Enums.MassTransitBroker; const bool useMassTransit = true; const bool useProtoActor = false; From 59bc7bc6a2c394a133c9f3d58cc7a8f709e4102c Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 17:58:28 +0200 Subject: [PATCH 09/11] Add caching modules to project references Included Elsa.Caching.Distributed.MassTransit and Elsa.Caching.Distributed as project references in Elsa.ServerAndStudio.Web.csproj. This integration aims to enhance caching capabilities in the application. --- .../Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj b/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj index 2480935af..3cb1c9444 100644 --- a/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj +++ b/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj @@ -8,6 +8,8 @@ + + From c7b5caafdc01d3faded6250b95da4f6d1eddfeb9 Mon Sep 17 00:00:00 2001 From: raymonddenhaan <155616759+raymonddenhaan@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:38:11 +0200 Subject: [PATCH 10/11] Added aliases for all missing types used in UI (#5743) --- src/modules/Elsa.Expressions/Options/ExpressionOptions.cs | 7 ++++--- src/modules/Elsa.Http/Features/HttpFeature.cs | 5 +++++ .../Serialization/JsonSerialization/JArrayIsland.json | 2 +- .../Serialization/JsonSerialization/JArrayWithoutType.json | 2 +- .../Serialization/JsonSerialization/JObjectIsland.json | 2 +- .../JsonSerialization/JObjectWithoutType.json | 2 +- .../Serialization/JsonSerialization/JsonArrayIsland.json | 2 +- .../Serialization/JsonSerialization/JsonObjectIsland.json | 2 +- .../JsonSerialization/JsonObjectWithoutType.json | 2 +- 9 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/modules/Elsa.Expressions/Options/ExpressionOptions.cs b/src/modules/Elsa.Expressions/Options/ExpressionOptions.cs index 460835be1..ec5ac1c19 100644 --- a/src/modules/Elsa.Expressions/Options/ExpressionOptions.cs +++ b/src/modules/Elsa.Expressions/Options/ExpressionOptions.cs @@ -24,16 +24,17 @@ public class ExpressionOptions this.AddTypeAlias("Boolean"); this.AddTypeAlias("Decimal"); this.AddTypeAlias("Double"); + this.AddTypeAlias("ByteArray"); this.AddTypeAlias("Guid"); this.AddTypeAlias("DateTime"); this.AddTypeAlias("DateTimeOffset"); this.AddTypeAlias("TimeSpan"); - this.AddTypeAlias("ExpandoObject"); + this.AddTypeAlias("Stream"); this.AddTypeAlias("JSON"); - this.AddTypeAlias>("StringDictionary"); + this.AddTypeAlias>("StringDictionary"); + this.AddTypeAlias>("ObjectDictionary"); this.AddTypeAlias>("StringMap"); this.AddTypeAlias>("ObjectMap"); - this.AddTypeAlias>("ObjectDictionary"); } /// Gets the type alias dictionary. diff --git a/src/modules/Elsa.Http/Features/HttpFeature.cs b/src/modules/Elsa.Http/Features/HttpFeature.cs index 57e76dc51..f355a0ace 100644 --- a/src/modules/Elsa.Http/Features/HttpFeature.cs +++ b/src/modules/Elsa.Http/Features/HttpFeature.cs @@ -217,6 +217,11 @@ public class HttpFeature : FeatureBase Services.Configure(options => { + options.AddTypeAlias("HttpRequest"); + options.AddTypeAlias("HttpResponse"); + options.AddTypeAlias("HttpResponseMessage"); + options.AddTypeAlias("HttpHeaders"); + options.AddTypeAlias("RouteData"); options.AddTypeAlias("FormFile"); options.AddTypeAlias("FormFile[]"); options.AddTypeAlias("HttpFile"); diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JArrayIsland.json b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JArrayIsland.json index f78e01922..b5b0378dc 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JArrayIsland.json +++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JArrayIsland.json @@ -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" } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JArrayWithoutType.json b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JArrayWithoutType.json index 42c531e42..33b4f89f9 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JArrayWithoutType.json +++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JArrayWithoutType.json @@ -6,5 +6,5 @@ "command": "add" } ], - "_type": "ObjectDictionary" + "_type": "ObjectMap" } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JObjectIsland.json b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JObjectIsland.json index ddc4af5d9..088f33e15 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JObjectIsland.json +++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JObjectIsland.json @@ -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" } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JObjectWithoutType.json b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JObjectWithoutType.json index 9a42638c9..6050dd05c 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JObjectWithoutType.json +++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JObjectWithoutType.json @@ -10,5 +10,5 @@ ] } }, - "_type": "ObjectDictionary" + "_type": "ObjectMap" } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonArrayIsland.json b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonArrayIsland.json index 1f33d0c25..a73017d36 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonArrayIsland.json +++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonArrayIsland.json @@ -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" } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonObjectIsland.json b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonObjectIsland.json index cc7a4135e..661b6327e 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonObjectIsland.json +++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonObjectIsland.json @@ -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" } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonObjectWithoutType.json b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonObjectWithoutType.json index 9a42638c9..6050dd05c 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonObjectWithoutType.json +++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/JsonObjectWithoutType.json @@ -10,5 +10,5 @@ ] } }, - "_type": "ObjectDictionary" + "_type": "ObjectMap" } \ No newline at end of file From dc4f9041086483ba3be1fdbbbefe154c2163ecf0 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 10 Jul 2024 19:47:50 +0200 Subject: [PATCH 11/11] Update workflow revert to return summary and location Enhanced the Revert endpoint to return a summary of the newly created workflow definition and include its location. Adjusted the API contract and endpoint configuration accordingly. --- .../Contracts/IWorkflowDefinitionsApi.cs | 3 ++- .../Endpoints/WorkflowDefinitions/GetById/Endpoint.cs | 2 ++ .../Endpoints/WorkflowDefinitions/Version/Revert.cs | 9 +++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs index 102331af1..ab41f8aa1 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs @@ -209,6 +209,7 @@ public interface IWorkflowDefinitionsApi /// The definition ID of the workflow definition to revert. /// The version to revert to. /// An optional cancellation token. + /// A summary view of the newly created workflow definition version. [Post("/workflow-definitions/{definitionId}/revert/{version}")] - Task RevertVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default); + Task RevertVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetById/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetById/Endpoint.cs index 0ca279e93..6d62ad993 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetById/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetById/Endpoint.cs @@ -2,6 +2,7 @@ using Elsa.Abstractions; using Elsa.Workflows.Management.Contracts; 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) diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs index 707efee28..4c0ffc4ea 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs @@ -4,6 +4,7 @@ using Elsa.Workflows.Api.Constants; using Elsa.Workflows.Api.Requirements; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Filters; +using Elsa.Workflows.Management.Models; using JetBrains.Annotations; using Microsoft.AspNetCore.Authorization; @@ -46,8 +47,12 @@ internal class RevertVersion(IWorkflowDefinitionManager workflowDefinitionManage return; } - await workflowDefinitionManager.RevertVersionAsync(definitionId, version, cancellationToken); + var newDefinition = await workflowDefinitionManager.RevertVersionAsync(definitionId, version, cancellationToken); + var newDefinitionSummary = WorkflowDefinitionSummary.FromDefinition(newDefinition); - await SendOkAsync(cancellationToken); + await SendCreatedAtAsync("GetWorkflowDefinitionById", new + { + id = newDefinition.Id + }, newDefinitionSummary, cancellation: cancellationToken); } } \ No newline at end of file