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.
This commit is contained in:
Sipke Schoorstra 2024-07-10 10:29:45 +02:00 committed by GitHub
parent 218f3b46e4
commit 558486920b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 242 additions and 63 deletions

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

@ -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

@ -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

@ -114,4 +114,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();
}
}

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

@ -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<InputDefinition> inputs,
ICollection<OutputDefinition> outputs,
ICollection<string> outcomes,
IDictionary<string, object> customProperties
IDictionary<string, object> 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<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

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

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; }
@ -63,11 +48,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();
@ -216,14 +204,14 @@ public class WorkflowBuilder : IWorkflowBuilder
/// <inheritdoc />
public async Task<Workflow> 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<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;
@ -82,8 +83,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

@ -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<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,
@ -147,6 +150,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,
@ -20,7 +19,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,
@ -44,7 +45,8 @@ public record WorkflowDefinitionModel(
default!,
default!,
default!,
default!,
default,
default,
default!,
default!,
default!,

View file

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