Merge pull request #6420 from elsa-workflows/orchard

Improve APIs for extensibility and replaceability
This commit is contained in:
Sipke Schoorstra 2025-02-18 19:49:34 +01:00 committed by GitHub
commit 2c394e2353
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 275 additions and 234 deletions

View file

@ -662,7 +662,7 @@ services
elsa.UseTenantHttpRouting(tenantHttpRouting =>
{
// Override the tenant header name with a custom one.
tenantHttpRouting.WithTenantHeader("X-Company-Id");
tenantHttpRouting.WithTenantHeader("X-Tenant-ID");
});
}

View file

@ -12,5 +12,5 @@ public interface IRequestSender
/// <param name="cancellationToken">The cancellation token.</param>
/// <typeparam name="T">The type of the response.</typeparam>
/// <returns>The response.</returns>
Task<T?> SendAsync<T>(IRequest<T> request, CancellationToken cancellationToken = default);
Task<T> SendAsync<T>(IRequest<T> request, CancellationToken cancellationToken = default);
}

View file

@ -34,9 +34,9 @@ public class RequestContext
/// Gets the cancellation token.
/// </summary>
public CancellationToken CancellationToken { get; init; }
/// <summary>
/// Gets the response the request handler.
/// </summary>
public object? Response { get; set; }
public object Response { get; set; } = null!;
}

View file

@ -41,13 +41,13 @@ public class DefaultMediator : IMediator
}
/// <inheritdoc />
public async Task<T?> SendAsync<T>(IRequest<T> request, CancellationToken cancellationToken = default)
public async Task<T> SendAsync<T>(IRequest<T> request, CancellationToken cancellationToken = default)
{
var responseType = typeof(T);
var context = new RequestContext(request, responseType, cancellationToken);
await _requestPipeline.ExecuteAsync(context);
return (T?)context.Response;
return (T)context.Response;
}
/// <inheritdoc />

View file

@ -31,5 +31,5 @@ public class OrderDefinition<T, TProp>
/// <summary>
/// The key selector to use to order the results.
/// </summary>
public Expression<Func<T, TProp>> KeySelector { get; set; } = default!;
public Expression<Func<T, TProp>> KeySelector { get; set; } = null!;
}

View file

@ -15,7 +15,6 @@ using Elsa.Http.Services;
using Elsa.Http.Tasks;
using Elsa.Http.UIHints;
using Elsa.Workflows;
using Elsa.Workflows.Management.Requests;
using FluentStorage;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;

View file

@ -29,16 +29,16 @@ public class OpenTelemetryTracingActivityExecutionMiddleware(ActivityMiddlewareD
span.SetTag("activity.type", activity.Type);
span.SetTag("activity.name", activity.Name);
span.SetTag("activityInstance.id", context.Id);
span.SetTag("activityExecution.startTimeUtc", span.StartTimeUtc);
span.SetTag("activityExecution.startTimeUtc", span.StartTimeUtc);
span.SetTag("tenantId", context.WorkflowExecutionContext.Workflow.Identity.TenantId);
span.AddEvent(new ActivityEvent("Executing", tags: CreateStatusTags(context)));
span.AddEvent(new("Executing", tags: CreateStatusTags(context)));
await next(context);
if (context.Status == ActivityStatus.Faulted)
{
span.AddEvent(new ActivityEvent("Faulted", tags: CreateStatusTags(context)));
span.AddEvent(new("Faulted", tags: CreateStatusTags(context)));
span.SetStatus(ActivityStatusCode.Error);
span.SetTag("error", true);
span.SetTag("activityInstance.hasIncidents", true);
@ -51,7 +51,7 @@ public class OpenTelemetryTracingActivityExecutionMiddleware(ActivityMiddlewareD
}
else
{
span.AddEvent(new ActivityEvent("Executed", tags: CreateStatusTags(context)));
span.AddEvent(new("Executed", tags: CreateStatusTags(context)));
span.SetStatus(ActivityStatusCode.Ok);
}
@ -62,7 +62,7 @@ public class OpenTelemetryTracingActivityExecutionMiddleware(ActivityMiddlewareD
private ActivityTagsCollection CreateStatusTags(ActivityExecutionContext context)
{
return new ActivityTagsCollection(new Dictionary<string, object?>
return new(new Dictionary<string, object?>
{
["activityInstance.status"] = context.Status.ToString()
});

View file

@ -1,13 +1,13 @@
using Elsa.Abstractions;
using Elsa.Common.Models;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Requests;
using Elsa.Workflows.Management;
using Elsa.Workflows.Models;
using JetBrains.Annotations;
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.GetByDefinitionId;
[PublicAPI]
internal class GetByDefinitionId(IMediator mediator, IWorkflowDefinitionLinker linker) : ElsaEndpoint<Request>
internal class GetByDefinitionId(IWorkflowDefinitionStore store, IWorkflowDefinitionLinker linker) : ElsaEndpoint<Request>
{
public override void Configure()
{
@ -18,8 +18,8 @@ internal class GetByDefinitionId(IMediator mediator, IWorkflowDefinitionLinker l
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest;
var findRequest = new FindWorkflowDefinitionRequest(request.DefinitionId, versionOptions);
var definition = await mediator.SendAsync(findRequest, cancellationToken);
var filter = WorkflowDefinitionHandle.ByDefinitionId(request.DefinitionId, versionOptions).ToFilter();
var definition = await store.FindAsync(filter, cancellationToken);
if (definition == null)
{

View file

@ -50,7 +50,7 @@ internal class Post(
// Create a new workflow in case no existing definition was found.
if (isNew)
{
draft = workflowDefinitionPublisher.New();
draft = await workflowDefinitionPublisher.NewAsync(cancellationToken: cancellationToken);
if (!string.IsNullOrWhiteSpace(definitionId))
draft.DefinitionId = definitionId;

View file

@ -3,4 +3,7 @@ using Elsa.Workflows.Management.Models;
namespace Elsa.Workflows.Api.Models;
public record LinkedWorkflowDefinitionModel(Link[]? Links) : WorkflowDefinitionModel;
public class LinkedWorkflowDefinitionModel(Link[]? links) : WorkflowDefinitionModel
{
public Link[]? Links { get; init; } = links;
}

View file

@ -6,12 +6,12 @@ namespace Elsa.Workflows;
public abstract class Trigger : Activity, ITrigger
{
/// <inheritdoc />
protected Trigger(string? source = default, int? line = default) : base(source, line)
protected Trigger(string? source = null, int? line = null) : base(source, line)
{
}
/// <inheritdoc />
protected Trigger(string activityType, int version = 1, string? source = default, int? line = default) : base(activityType, version, source, line)
protected Trigger(string activityType, int version = 1, string? source = null, int? line = null) : base(activityType, version, source, line)
{
}
@ -39,11 +39,11 @@ public abstract class Trigger : Activity, ITrigger
public abstract class Trigger<TResult> : Activity<TResult>, ITrigger
{
protected Trigger(string? source = default, int? line = default) : base(source, line)
protected Trigger(string? source = null, int? line = null) : base(source, line)
{
}
protected Trigger(string activityType, int version = 1, string? source = default, int? line = default) : base(activityType, version, source, line)
protected Trigger(string activityType, int version = 1, string? source = null, int? line = null) : base(activityType, version, source, line)
{
}

View file

@ -0,0 +1,35 @@
using System.Reflection;
namespace Elsa.Workflows.UIHints.CheckList;
/// <summary>
/// A base class for providing options to populate a checklist UI component. This class is intended to be inherited to implement
/// custom checklist data logic by overriding the `GetItemsAsync` method.
/// </summary>
public abstract class CheckListOptionsProviderBase : IPropertyUIHandler
{
/// <inheritdoc />
public async ValueTask<IDictionary<string, object>> GetUIPropertiesAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken = default)
{
var items = await GetItemsAsync(propertyInfo, context, cancellationToken);
var props = new CheckListProps
{
CheckList = new()
{
Items = items.ToList()
}
};
var options = new Dictionary<string, object>
{
[InputUIHints.CheckList] = props
};
return options;
}
/// <summary>
/// Implement this to provide items to the dropdown list.
/// </summary>
protected abstract ValueTask<ICollection<CheckListItem>> GetItemsAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken);
}

View file

@ -25,7 +25,7 @@ public class StaticCheckListOptionsProvider : IPropertyUIHandler
var props = new CheckListProps
{
CheckList = new CheckList
CheckList = new()
{
Items = selectListItems.ToList()
}

View file

@ -17,7 +17,16 @@ public interface IWorkflowDefinitionPublisher
/// </summary>
/// <param name="root">Optionally provide the root activity. If not specified, <see cref="Sequence" /> will be used/></param>
/// <returns>The new workflow definition.</returns>
WorkflowDefinition New(IActivity? root = default);
[Obsolete( "Use NewAsync instead.", error: false)]
WorkflowDefinition New(IActivity? root = null);
/// <summary>
/// Creates a new workflow definition.
/// </summary>
/// <param name="root">Optionally provide the root activity. If not specified, <see cref="Sequence" /> will be used/></param>
/// <param name="cancellationToken">A cancellation token</param>
/// <returns>The new workflow definition.</returns>
Task<WorkflowDefinition> NewAsync(IActivity? root = null, CancellationToken cancellationToken = default);
/// <summary>
/// Publishes a workflow definition.

View file

@ -0,0 +1,17 @@
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Management;
public static class WorkflowDefinitionHandleExtensions
{
public static WorkflowDefinitionFilter ToFilter(this WorkflowDefinitionHandle handle)
{
return new()
{
DefinitionId = handle.DefinitionId,
Id = handle.DefinitionVersionId,
VersionOptions = handle.VersionOptions
};
}
}

View file

@ -15,6 +15,6 @@ public static class WorkflowInstanceStoreExtensions
/// </summary>
public static async ValueTask<WorkflowInstance?> FindAsync(this IWorkflowInstanceStore store, string id, CancellationToken cancellationToken = default)
{
return await store.FindAsync(new WorkflowInstanceFilter{ Id = id }, cancellationToken);
return await store.FindAsync(new() { Id = id }, cancellationToken);
}
}

View file

@ -1,7 +1,7 @@
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Elsa.Workflows.Management.Handlers;
using Elsa.Workflows.Management.Handlers.Notification;
using Elsa.Workflows.Management.Handlers.Notifications;
using Elsa.Workflows.Management.Services;
using Elsa.Workflows.Management.Stores;
using Microsoft.Extensions.DependencyInjection;

View file

@ -1,9 +1,5 @@
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Management.Handlers.Request;
using Elsa.Workflows.Management.Requests;
using Elsa.Workflows.Management.Stores;
using Microsoft.Extensions.DependencyInjection;
@ -12,25 +8,17 @@ namespace Elsa.Workflows.Management.Features;
/// <summary>
/// Configures workflow definition storage.
/// </summary>
public class WorkflowDefinitionsFeature : FeatureBase
public class WorkflowDefinitionsFeature(IModule module) : FeatureBase(module)
{
/// <inheritdoc />
public WorkflowDefinitionsFeature(IModule module) : base(module)
{
}
/// <summary>
/// The factory to create new instances of <see cref="IWorkflowDefinitionStore"/>.
/// </summary>
public Func<IServiceProvider, IWorkflowDefinitionStore> WorkflowDefinitionStore { get; set; } = sp => sp.GetRequiredService<MemoryWorkflowDefinitionStore>();
public Func<Type> FindWorkflowDefinitionHandler { get; set; } = () => typeof(FindWorkflowDefinitionHandler);
/// <inheritdoc />
public override void Apply()
{
Services
.AddScoped(WorkflowDefinitionStore)
.AddScoped(typeof(IRequestHandler), FindWorkflowDefinitionHandler())
;
.AddScoped(WorkflowDefinitionStore);
}
}

View file

@ -18,7 +18,7 @@ using Elsa.Workflows.Management.Compression;
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Management.Handlers;
using Elsa.Workflows.Management.Handlers.Notification;
using Elsa.Workflows.Management.Handlers.Notifications;
using Elsa.Workflows.Management.Mappers;
using Elsa.Workflows.Management.Materializers;
using Elsa.Workflows.Management.Models;
@ -41,8 +41,8 @@ namespace Elsa.Workflows.Management.Features;
[DependsOn(typeof(WorkflowsFeature))]
[DependsOn(typeof(WorkflowDefinitionsFeature))]
[DependsOn(typeof(WorkflowInstancesFeature))]
[PublicAPI]
public class WorkflowManagementFeature : FeatureBase
[UsedImplicitly]
public class WorkflowManagementFeature(IModule module) : FeatureBase(module)
{
private const string PrimitivesCategory = "Primitives";
private const string LookupsCategory = "Lookups";
@ -50,15 +50,12 @@ public class WorkflowManagementFeature : FeatureBase
private const string DataCategory = "Data";
private const string SystemCategory = "System";
private Func<IServiceProvider, IWorkflowDefinitionPublisher> _workflowDefinitionPublisher = sp => ActivatorUtilities.CreateInstance<WorkflowDefinitionPublisher>(sp);
private string CompressionAlgorithm { get; set; } = nameof(None);
private LogPersistenceMode LogPersistenceMode { get; set; } = LogPersistenceMode.Include;
private bool IsReadOnlyMode { get; set; }
/// <inheritdoc />
public WorkflowManagementFeature(IModule module) : base(module)
{
}
/// <summary>
/// A set of activity types to make available to the system.
/// </summary>
@ -194,6 +191,12 @@ public class WorkflowManagementFeature : FeatureBase
return this;
}
public WorkflowManagementFeature WithWorkflowDefinitionPublisher(Func<IServiceProvider, IWorkflowDefinitionPublisher> workflowDefinitionPublisher)
{
_workflowDefinitionPublisher = workflowDefinitionPublisher;
return this;
}
/// <inheritdoc />
[RequiresUnreferencedCode("The assembly containing the specified marker type will be scanned for activity types.")]
public override void Configure()
@ -214,7 +217,7 @@ public class WorkflowManagementFeature : FeatureBase
.AddScoped<IWorkflowDefinitionService, WorkflowDefinitionService>()
.AddScoped<IWorkflowSerializer, WorkflowSerializer>()
.AddScoped<IWorkflowValidator, WorkflowValidator>()
.AddScoped<IWorkflowDefinitionPublisher, WorkflowDefinitionPublisher>()
.AddScoped(_workflowDefinitionPublisher)
.AddScoped<IWorkflowDefinitionImporter, WorkflowDefinitionImporter>()
.AddScoped<IWorkflowDefinitionManager, WorkflowDefinitionManager>()
.AddScoped<IWorkflowInstanceManager, WorkflowInstanceManager>()

View file

@ -134,7 +134,7 @@ public class WorkflowInstanceFilter
if (TimestampFilters != null)
{
foreach (TimestampFilter timestampFilter in TimestampFilters)
foreach (var timestampFilter in TimestampFilters)
{
var column = timestampFilter.Column;
var timestamp = timestampFilter.Timestamp;

View file

@ -3,7 +3,7 @@ using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Management.Notifications;
using JetBrains.Annotations;
namespace Elsa.Workflows.Management.Handlers.Notification;
namespace Elsa.Workflows.Management.Handlers.Notifications;
/// <summary>
/// Deletes workflow instances when a workflow definition or version is deleted.

View file

@ -2,7 +2,7 @@ using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Notifications;
using JetBrains.Annotations;
namespace Elsa.Workflows.Management.Handlers.Notification;
namespace Elsa.Workflows.Management.Handlers.Notifications;
/// <summary>
/// A workflow definition notifications handler for evicting the cache of the workflow definition service.

View file

@ -5,7 +5,7 @@ using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Management.Notifications;
using JetBrains.Annotations;
namespace Elsa.Workflows.Management.Handlers.Notification;
namespace Elsa.Workflows.Management.Handlers.Notifications;
/// <summary>
/// Refreshes the <see cref="IActivityRegistry"/> for the <see cref="WorkflowDefinitionActivityProvider"/> provider whenever an <see cref="WorkflowDefinition"/> is published, retracted or deleted.

View file

@ -2,7 +2,7 @@ using Elsa.Extensions;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Notifications;
namespace Elsa.Workflows.Management.Handlers.Notification;
namespace Elsa.Workflows.Management.Handlers.Notifications;
/// <summary>
/// Updates consuming workflows when a workflow definition is published.

View file

@ -3,7 +3,7 @@ using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Management.Notifications;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Management.Handlers.Notification;
namespace Elsa.Workflows.Management.Handlers.Notifications;
public class ValidateWorkflow : INotificationHandler<WorkflowDefinitionValidating>
{

View file

@ -1,23 +0,0 @@
using Elsa.Common.Entities;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Management.Requests;
namespace Elsa.Workflows.Management.Handlers.Request;
public class FindWorkflowDefinitionHandler(IWorkflowDefinitionStore store) : IRequestHandler<FindWorkflowDefinitionRequest, WorkflowDefinition?>
{
public async Task<WorkflowDefinition?> HandleAsync(FindWorkflowDefinitionRequest request, CancellationToken cancellationToken)
{
var filter = new WorkflowDefinitionFilter
{
DefinitionId = request.DefinitionId,
VersionOptions = request.VersionOptions
};
var order = new WorkflowDefinitionOrder<int>(x => x.Version, OrderDirection.Descending);
var definition = (await store.FindManyAsync(filter, order, cancellationToken: cancellationToken)).FirstOrDefault();
return definition;
}
}

View file

@ -8,7 +8,7 @@
/// <summary>
/// The workflow definition to save.
/// </summary>
public WorkflowDefinitionModel Model { get; set; } = default!;
public WorkflowDefinitionModel Model { get; set; } = null!;
/// <summary>
/// Whether the workflow definition should be published.

View file

@ -7,52 +7,80 @@ namespace Elsa.Workflows.Management.Models;
/// Represents a serializable workflow definition.
/// </summary>
[PublicAPI]
public record WorkflowDefinitionModel(
string Id,
string DefinitionId,
string? TenantId,
string? Name,
string? Description,
DateTimeOffset CreatedAt,
int Version,
Version? ToolVersion,
ICollection<VariableDefinition>? Variables,
ICollection<InputDefinition>? Inputs,
ICollection<OutputDefinition>? Outputs,
ICollection<string>? Outcomes,
IDictionary<string, object>? CustomProperties,
bool IsReadonly,
bool IsSystem,
bool IsLatest,
bool IsPublished,
WorkflowOptions? Options,
[property: Obsolete("Use Options.UsableAsActivity instead")]
bool? UsableAsActivity,
IActivity? Root
)
public class WorkflowDefinitionModel
{
/// <inheritdoc />
public WorkflowDefinitionModel() : this(
default!,
default!,
default!,
default!,
default!,
default!,
default!,
default!,
default!,
default!,
default!,
default,
default!,
default!,
default!,
default!,
default!,
default!,
default!,
default!)
/// <summary>
/// Represents a serializable workflow definition.
/// </summary>
public WorkflowDefinitionModel() { }
/// <summary>
/// Represents a serializable workflow definition.
/// </summary>
public WorkflowDefinitionModel(string id,
string definitionId,
string? tenantId,
string? name,
string? description,
DateTimeOffset createdAt,
int version,
Version? toolVersion,
ICollection<VariableDefinition>? variables,
ICollection<InputDefinition>? inputs,
ICollection<OutputDefinition>? outputs,
ICollection<string>? outcomes,
IDictionary<string, object>? customProperties,
bool isReadonly,
bool isSystem,
bool isLatest,
bool isPublished,
WorkflowOptions? options,
bool? usableAsActivity,
IActivity? root)
{
Id = id;
DefinitionId = definitionId;
TenantId = tenantId;
Name = name;
Description = description;
CreatedAt = createdAt;
Version = version;
ToolVersion = toolVersion;
Variables = variables;
Inputs = inputs;
Outputs = outputs;
Outcomes = outcomes;
CustomProperties = customProperties;
IsReadonly = isReadonly;
IsSystem = isSystem;
IsLatest = isLatest;
IsPublished = isPublished;
Options = options;
UsableAsActivity = usableAsActivity;
Root = root;
}
public string Id { get; set; }
public string DefinitionId { get; set; }
public string? TenantId { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public int Version { get; set; }
public Version? ToolVersion { get; set; }
public ICollection<VariableDefinition>? Variables { get; set; }
public ICollection<InputDefinition>? Inputs { get; set; }
public ICollection<OutputDefinition>? Outputs { get; set; }
public ICollection<string>? Outcomes { get; set; }
public IDictionary<string, object>? CustomProperties { get; set; }
public bool IsReadonly { get; set; }
public bool IsSystem { get; set; }
public bool IsLatest { get; set; }
public bool IsPublished { get; set; }
public WorkflowOptions? Options { get; set; }
[Obsolete("Use Options.UsableAsActivity instead")]
public bool? UsableAsActivity { get; set; }
public IActivity? Root { get; set; }
}

View file

@ -51,12 +51,12 @@ public class WorkflowDefinitionSummary
/// <summary>
/// The version ID of the workflow definition.
/// </summary>
public string Id { get; set; } = default!;
public string Id { get; set; } = null!;
/// <summary>
/// The ID of the workflow definition.
/// </summary>
public string DefinitionId { get; set; } = default!;
public string DefinitionId { get; set; } = null!;
/// <summary>
/// The name of the workflow definition.
@ -96,7 +96,7 @@ public class WorkflowDefinitionSummary
/// <summary>
/// The materializer name of the workflow definition.
/// </summary>
public string MaterializerName { get; set; } = default!;
public string MaterializerName { get; set; } = null!;
/// <summary>
/// The timestamp when the workflow definition was created.

View file

@ -33,32 +33,31 @@ public class WorkflowInstanceSummary
/// <summary>
/// Returns a summary view of the specified <see cref="WorkflowInstance"/>.
/// </summary>
public static Expression<Func<WorkflowInstance, WorkflowInstanceSummary>> FromInstanceExpression()
=> workflowInstance => new WorkflowInstanceSummary
{
Id = workflowInstance.Id,
DefinitionId = workflowInstance.DefinitionId,
DefinitionVersionId = workflowInstance.DefinitionVersionId,
Version = workflowInstance.Version,
Status = workflowInstance.Status,
SubStatus = workflowInstance.SubStatus,
CorrelationId = workflowInstance.CorrelationId,
Name = workflowInstance.Name,
IncidentCount = workflowInstance.IncidentCount,
CreatedAt = workflowInstance.CreatedAt,
UpdatedAt = workflowInstance.UpdatedAt,
FinishedAt = workflowInstance.FinishedAt
};
public static Expression<Func<WorkflowInstance, WorkflowInstanceSummary>> FromInstanceExpression() => workflowInstance => new()
{
Id = workflowInstance.Id,
DefinitionId = workflowInstance.DefinitionId,
DefinitionVersionId = workflowInstance.DefinitionVersionId,
Version = workflowInstance.Version,
Status = workflowInstance.Status,
SubStatus = workflowInstance.SubStatus,
CorrelationId = workflowInstance.CorrelationId,
Name = workflowInstance.Name,
IncidentCount = workflowInstance.IncidentCount,
CreatedAt = workflowInstance.CreatedAt,
UpdatedAt = workflowInstance.UpdatedAt,
FinishedAt = workflowInstance.FinishedAt
};
/// <summary>The ID of the workflow instance.</summary>
public string Id { get; set; } = default!;
public string Id { get; set; } = null!;
/// <summary>The ID of the workflow definition.</summary>
public string DefinitionId { get; set; } = default!;
public string DefinitionId { get; set; } = null!;
/// <summary>The version ID of the workflow definition.</summary>
public string DefinitionVersionId { get; set; } = default!;
public string DefinitionVersionId { get; set; } = null!;
/// <summary>The version of the workflow definition.</summary>
public int Version { get; set; }

View file

@ -1,12 +0,0 @@
using Elsa.Common.Models;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Entities;
namespace Elsa.Workflows.Management.Requests;
/// <summary>
/// A request to find a workflow definition.
/// </summary>
/// <param name="DefinitionId">The ID of the workflow definition.</param>
/// <param name="VersionOptions">The version options.</param>
public record FindWorkflowDefinitionRequest(string DefinitionId, VersionOptions VersionOptions) : IRequest<WorkflowDefinition?>;

View file

@ -43,7 +43,7 @@ namespace Elsa.Workflows.Management.Services
// Create a new workflow in case no existing definition was found.
if (isNew)
{
draft = _workflowDefinitionPublisher.New();
draft = await _workflowDefinitionPublisher.NewAsync(cancellationToken: cancellationToken);
if (!string.IsNullOrWhiteSpace(definitionId))
draft.DefinitionId = definitionId;

View file

@ -3,13 +3,12 @@ using Elsa.Common.Entities;
using Elsa.Common.Models;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Management.Activities.WorkflowDefinitionActivity;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Management.Materializers;
using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Management.Notifications;
using Elsa.Workflows.Management.Requests;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Management.Services;
@ -18,9 +17,9 @@ public class WorkflowDefinitionPublisher(
IWorkflowDefinitionService workflowDefinitionService,
IWorkflowDefinitionStore workflowDefinitionStore,
IWorkflowValidator workflowValidator,
INotificationSender notificationSender,
IIdentityGenerator identityGenerator,
IActivitySerializer activitySerializer,
IMediator mediator,
ISystemClock systemClock)
: IWorkflowDefinitionPublisher
{
@ -45,16 +44,34 @@ public class WorkflowDefinitionPublisher(
};
}
public Task<WorkflowDefinition> NewAsync(IActivity? root = null, CancellationToken cancellationToken = default)
{
root ??= new Sequence();
var id = identityGenerator.GenerateId();
var definitionId = identityGenerator.GenerateId();
const int version = 1;
var workflowDefinition = new WorkflowDefinition
{
Id = id,
DefinitionId = definitionId,
Version = version,
IsLatest = true,
IsPublished = false,
CreatedAt = systemClock.UtcNow,
StringData = activitySerializer.Serialize(root),
MaterializerName = JsonWorkflowMaterializer.MaterializerName
};
return Task.FromResult(workflowDefinition);
}
/// <inheritdoc />
public async Task<PublishWorkflowDefinitionResult> PublishAsync(string definitionId, CancellationToken cancellationToken = default)
{
var filter = new WorkflowDefinitionFilter
{
DefinitionId = definitionId,
VersionOptions = VersionOptions.Latest
};
var filter = WorkflowDefinitionHandle.ByDefinitionId(definitionId, VersionOptions.Latest).ToFilter();
var definition = await workflowDefinitionStore.FindAsync(filter, cancellationToken);
if (definition == null)
return new(false, new List<WorkflowValidationError>
{
@ -73,16 +90,15 @@ public class WorkflowDefinitionPublisher(
if (validationErrors.Any())
return new(false, validationErrors, new([]));
await notificationSender.SendAsync(new WorkflowDefinitionPublishing(definition), cancellationToken);
await mediator.SendAsync(new WorkflowDefinitionPublishing(definition), cancellationToken);
var definitionId = definition.DefinitionId;
// Reset current latest and published definitions.
var filter = new WorkflowDefinitionFilter
var publishedWorkflows = await workflowDefinitionStore.FindManyAsync(new()
{
DefinitionId = definitionId,
VersionOptions = VersionOptions.LatestOrPublished
};
var publishedWorkflows = await workflowDefinitionStore.FindManyAsync(filter, cancellationToken);
}, cancellationToken);
foreach (var publishedAndOrLatestWorkflow in publishedWorkflows)
{
@ -92,27 +108,23 @@ public class WorkflowDefinitionPublisher(
await workflowDefinitionStore.SaveAsync(publishedAndOrLatestWorkflow, cancellationToken);
if (isPublished)
await notificationSender.SendAsync(new WorkflowDefinitionVersionRetracted(publishedAndOrLatestWorkflow), cancellationToken);
await mediator.SendAsync(new WorkflowDefinitionVersionRetracted(publishedAndOrLatestWorkflow), cancellationToken);
}
// Save the new published definition.
// Save the newly published definition.
definition.IsPublished = true;
definition = Initialize(definition);
await workflowDefinitionStore.SaveAsync(definition, cancellationToken);
var affectedWorkflows = new AffectedWorkflows(new List<WorkflowDefinition>());
await notificationSender.SendAsync(new WorkflowDefinitionPublished(definition, affectedWorkflows), cancellationToken);
await mediator.SendAsync(new WorkflowDefinitionPublished(definition, affectedWorkflows), cancellationToken);
return new(true, validationErrors, affectedWorkflows);
}
/// <inheritdoc />
public async Task<WorkflowDefinition?> RetractAsync(string definitionId, CancellationToken cancellationToken = default)
{
var filter = new WorkflowDefinitionFilter
{
DefinitionId = definitionId,
VersionOptions = VersionOptions.Published
};
var filter = WorkflowDefinitionHandle.ByDefinitionId(definitionId, VersionOptions.Published).ToFilter();
var definition = await workflowDefinitionStore.FindAsync(filter, cancellationToken);
if (definition == null)
@ -129,9 +141,9 @@ public class WorkflowDefinitionPublisher(
definition.IsPublished = false;
await notificationSender.SendAsync(new WorkflowDefinitionRetracting(definition), cancellationToken);
await mediator.SendAsync(new WorkflowDefinitionRetracting(definition), cancellationToken);
await workflowDefinitionStore.SaveAsync(definition, cancellationToken);
await notificationSender.SendAsync(new WorkflowDefinitionRetracted(definition), cancellationToken);
await mediator.SendAsync(new WorkflowDefinitionRetracted(definition), cancellationToken);
return definition;
}
@ -184,10 +196,8 @@ public class WorkflowDefinitionPublisher(
await workflowDefinitionStore.SaveAsync(draft, cancellationToken);
if (lastVersion is null)
{
await notificationSender.SendAsync(new WorkflowDefinitionCreated(definition), cancellationToken);
}
if (lastVersion is null)
await mediator.SendAsync(new WorkflowDefinitionCreated(definition), cancellationToken);
if (lastVersion is { IsPublished: true, IsLatest: true })
{

View file

@ -19,40 +19,30 @@ public class WorkflowDefinitionService(
var materializer = workflowMaterializers.FirstOrDefault(x => x.Name == definition.MaterializerName);
if (materializer == null)
throw new Exception("Provider not found");
throw new("Provider not found");
var workflow = await materializer.MaterializeAsync(definition, cancellationToken);
return await workflowGraphBuilder.BuildAsync(workflow, cancellationToken);
}
/// <inheritdoc />
public async Task<WorkflowDefinition?> FindWorkflowDefinitionAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default)
public Task<WorkflowDefinition?> FindWorkflowDefinitionAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default)
{
var filter = new WorkflowDefinitionFilter
{
DefinitionId = definitionId,
VersionOptions = versionOptions
};
return await workflowDefinitionStore.FindAsync(filter, cancellationToken);
var handle = WorkflowDefinitionHandle.ByDefinitionId(definitionId, versionOptions);
return FindWorkflowDefinitionAsync(handle, cancellationToken);
}
/// <inheritdoc />
public async Task<WorkflowDefinition?> FindWorkflowDefinitionAsync(string definitionVersionId, CancellationToken cancellationToken = default)
public Task<WorkflowDefinition?> FindWorkflowDefinitionAsync(string definitionVersionId, CancellationToken cancellationToken = default)
{
var filter = new WorkflowDefinitionFilter
{
Id = definitionVersionId
};
return await workflowDefinitionStore.FindAsync(filter, cancellationToken);
var handle = WorkflowDefinitionHandle.ByDefinitionVersionId(definitionVersionId);
return FindWorkflowDefinitionAsync(handle, cancellationToken);
}
/// <inheritdoc />
public async Task<WorkflowDefinition?> FindWorkflowDefinitionAsync(WorkflowDefinitionHandle handle, CancellationToken cancellationToken = default)
{
var filter = new WorkflowDefinitionFilter
{
DefinitionHandle = handle
};
var filter = handle.ToFilter();
return await workflowDefinitionStore.FindAsync(filter, cancellationToken);
}
@ -87,12 +77,12 @@ public class WorkflowDefinitionService(
/// <inheritdoc />
public async Task<WorkflowGraph?> FindWorkflowGraphAsync(WorkflowDefinitionHandle definitionHandle, CancellationToken cancellationToken = default)
{
var filter = new WorkflowDefinitionFilter
{
DefinitionHandle = definitionHandle
};
var definition = await FindWorkflowDefinitionAsync(definitionHandle, cancellationToken);
return await FindWorkflowGraphAsync(filter, cancellationToken);
if (definition == null)
return null;
return await MaterializeWorkflowAsync(definition, cancellationToken);
}
/// <inheritdoc />

View file

@ -10,22 +10,22 @@ public class StoredTrigger : Entity
/// <summary>
/// The ID of the workflow definition.
/// </summary>
public string WorkflowDefinitionId { get; set; } = default!;
public string WorkflowDefinitionId { get; set; } = null!;
/// <summary>
/// The version ID of the workflow definition.
/// </summary>
public string WorkflowDefinitionVersionId { get; set; } = default!;
public string WorkflowDefinitionVersionId { get; set; } = null!;
/// <summary>
/// The name of the trigger.
/// </summary>
public string Name { get; set; } = default!;
public string Name { get; set; } = null!;
/// <summary>
/// The ID of the activity associated with the trigger.
/// </summary>
public string ActivityId { get; set; } = default!;
public string ActivityId { get; set; } = null!;
/// <summary>
/// The hash of the trigger.

View file

@ -11,17 +11,17 @@ public class WorkflowExecutionLogRecord : Entity, ILogRecord
/// <summary>
/// The ID of the workflow definition.
/// </summary>
public string WorkflowDefinitionId { get; set; } = default!;
public string WorkflowDefinitionId { get; set; } = null!;
/// <summary>
/// The version ID of the workflow definition.
/// </summary>
public string WorkflowDefinitionVersionId { get; set; } = default!;
public string WorkflowDefinitionVersionId { get; set; } = null!;
/// <summary>
/// The ID of the workflow instance.
/// </summary>
public string WorkflowInstanceId { get; set; } = default!;
public string WorkflowInstanceId { get; set; } = null!;
/// <summary>
/// The version of the workflow definition.
@ -31,7 +31,7 @@ public class WorkflowExecutionLogRecord : Entity, ILogRecord
/// <summary>
/// The ID of the activity instance.
/// </summary>
public string ActivityInstanceId { get; set; } = default!;
public string ActivityInstanceId { get; set; } = null!;
/// <summary>
/// The ID of the parent activity instance.
@ -41,12 +41,12 @@ public class WorkflowExecutionLogRecord : Entity, ILogRecord
/// <summary>
/// The ID of the activity.
/// </summary>
public string ActivityId { get; set; } = default!;
public string ActivityId { get; set; } = null!;
/// <summary>
/// The type of the activity.
/// </summary>
public string ActivityType { get; set; } = default!;
public string ActivityType { get; set; } = null!;
/// <summary>
/// The version of the activity type.
@ -61,7 +61,7 @@ public class WorkflowExecutionLogRecord : Entity, ILogRecord
/// <summary>
/// The unique ID of the node within the workflow graph.
/// </summary>
public string ActivityNodeId { get; set; } = default!;
public string ActivityNodeId { get; set; } = null!;
/// <summary>
/// The time stamp of the log entry.

View file

@ -11,9 +11,9 @@ public class TaskReporter(IBookmarkQueue bookmarkQueue, IStimulusHasher stimulus
private static readonly string ActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<RunTask>();
/// <inheritdoc />
public async Task ReportCompletionAsync(string taskId, object? result = default, CancellationToken cancellationToken = default)
public async Task ReportCompletionAsync(string taskId, object? result = null, CancellationToken cancellationToken = default)
{
var stimulus = new RunTaskStimulus(taskId, default!);
var stimulus = new RunTaskStimulus(taskId, null!);
var input = new Dictionary<string, object>
{
@ -24,7 +24,7 @@ public class TaskReporter(IBookmarkQueue bookmarkQueue, IStimulusHasher stimulus
{
ActivityTypeName = ActivityTypeName,
StimulusHash = stimulusHasher.Hash(ActivityTypeName, stimulus),
Options = new ResumeBookmarkOptions
Options = new()
{
Input = input
}

View file

@ -15,10 +15,10 @@ public static class ModuleExtensions
/// <summary>
/// Creates a new Elsa module and adds the <see cref="ElsaFeature"/> to it.
/// </summary>
public static IModule AddElsa(this IServiceCollection services, Action<IModule>? configure = default)
public static IModule AddElsa(this IServiceCollection services, Action<IModule>? configure = null)
{
var module = services.GetOrCreateModule();
module.Configure<AppFeature>(app => app.Configurator = configure);
module.Configure<AppFeature>(app => app.Configurator += configure);
module.Apply();
return module;
@ -27,7 +27,7 @@ public static class ModuleExtensions
/// <summary>
/// Configures the Elsa module.
/// </summary>
public static IModule ConfigureElsa(this IServiceCollection services, Action<IModule>? configure = default)
public static IModule ConfigureElsa(this IServiceCollection services, Action<IModule>? configure = null)
{
var module = services.GetOrCreateModule();

View file

@ -8,13 +8,8 @@ namespace Elsa.Features;
/// A wrapper for invoking application-specific configuration, ensuring it is invoked lastly.
/// </summary>
[DependsOn(typeof(ElsaFeature))]
public class AppFeature : FeatureBase
public class AppFeature(IModule module) : FeatureBase(module)
{
/// <inheritdoc />
public AppFeature(IModule module) : base(module)
{
}
/// <summary>
/// The configurator to invoke.
/// </summary>