Add DryRun endpoint and workflow instance finder (#5110)
* Add DryRun endpoint and workflow instance finder Added a new DryRun endpoint for executing alteration plans and implemented the IWorkflowInstanceFinder interface to search for workflow instances. Refactored existing code to use new workflow instance finder service, resulting in cleaner activity implementation. * Fix empty check in WorkflowInstanceFinder Change the conditional logic to use `IsEmpty` method on filters rather than checking if the collection `workflowInstanceIds` is empty. This ensures that matching workflow instances are correctly identified when no specific filters are set. * Refactor WorkflowInstanceFinder with WorkflowStatus default The WorkflowInstanceFinder service has been refactored to include a default workflow status of Running and to utilize a new method WorkflowFilterIsEmpty, which checks if the workflow instance filter is empty. This improves code readability and encapsulates the logic for determining an empty filter.
This commit is contained in:
parent
e67a6fe752
commit
d11754324e
|
|
@ -0,0 +1,14 @@
|
|||
using Elsa.Alterations.Core.Models;
|
||||
|
||||
namespace Elsa.Alterations.Core.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a service that can find workflow instances based on specified filters.
|
||||
/// </summary>
|
||||
public interface IWorkflowInstanceFinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds workflow instances based on the specified filter.
|
||||
/// </summary>
|
||||
Task<IEnumerable<string>> FindAsync(AlterationWorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Elsa.Alterations.Core.Contracts;
|
||||
using Elsa.Alterations.Core.Options;
|
||||
using Elsa.Alterations.Core.Serialization;
|
||||
using Elsa.Alterations.Core.Services;
|
||||
using Elsa.Common.Contracts;
|
||||
using Elsa.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -19,7 +20,8 @@ public static class ServiceCollectionExtensions
|
|||
public static IServiceCollection AddAlterationsCore(this IServiceCollection services)
|
||||
{
|
||||
services.Configure<AlterationOptions>(_ => { }); // Ensure that the options are configured even if the application doesn't do so.
|
||||
services.AddScoped<IAlteredWorkflowDispatcher, DefaultAlteredWorkflowDispatcher>();
|
||||
services.AddScoped<IAlteredWorkflowDispatcher, AlteredWorkflowDispatcher>();
|
||||
services.AddScoped<IWorkflowInstanceFinder, WorkflowInstanceFinder>();
|
||||
services.AddSingleton<IAlterationSerializer, AlterationSerializer>();
|
||||
services.AddSerializationOptionsConfigurator<AlterationSerializationOptionConfigurator>();
|
||||
return services;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,12 @@ public class ActivityFilter
|
|||
/// <summary>
|
||||
/// The ID of the activity.
|
||||
/// </summary>
|
||||
public string? Id { get; set; }
|
||||
public string? ActivityId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the activity instance.
|
||||
/// </summary>
|
||||
public string? ActivityInstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The node ID of the activity.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ public class AlterationWorkflowInstanceFilter
|
|||
/// Whether the workflow instances to match have incidents.
|
||||
/// </summary>
|
||||
public bool? HasIncidents { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the workflow instances to match are system workflows. Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool? IsSystem { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of filters for activities.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
using Elsa.Alterations.Core.Contracts;
|
||||
using Elsa.Alterations.Core.Results;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Requests;
|
||||
|
||||
namespace Elsa.Alterations.Core.Contracts;
|
||||
namespace Elsa.Alterations.Core.Services;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class DefaultAlteredWorkflowDispatcher : IAlteredWorkflowDispatcher
|
||||
public class AlteredWorkflowDispatcher : IAlteredWorkflowDispatcher
|
||||
{
|
||||
private readonly IWorkflowDispatcher _workflowDispatcher;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultAlteredWorkflowDispatcher"/> class.
|
||||
/// Initializes a new instance of the <see cref="AlteredWorkflowDispatcher"/> class.
|
||||
/// </summary>
|
||||
public DefaultAlteredWorkflowDispatcher(IWorkflowDispatcher workflowDispatcher)
|
||||
public AlteredWorkflowDispatcher(IWorkflowDispatcher workflowDispatcher)
|
||||
{
|
||||
_workflowDispatcher = workflowDispatcher;
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using Elsa.Alterations.Core.Contracts;
|
||||
using Elsa.Alterations.Core.Models;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Filters;
|
||||
|
||||
namespace Elsa.Alterations.Core.Services;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class WorkflowInstanceFinder(IWorkflowInstanceStore workflowInstanceStore, IActivityExecutionStore activityExecutionStore) : IWorkflowInstanceFinder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<string>> FindAsync(AlterationWorkflowInstanceFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var workflowInstanceFilter = new WorkflowInstanceFilter
|
||||
{
|
||||
Ids = filter.WorkflowInstanceIds?.ToList(),
|
||||
DefinitionVersionIds = filter.DefinitionVersionIds?.ToList(),
|
||||
CorrelationIds = filter.CorrelationIds?.ToList(),
|
||||
HasIncidents = filter.HasIncidents,
|
||||
IsSystem = filter.IsSystem,
|
||||
TimestampFilters = filter.TimestampFilters?.ToList(),
|
||||
WorkflowStatus = WorkflowStatus.Running
|
||||
};
|
||||
var activityExecutionFilters = filter.ActivityFilters?.Select(x => new ActivityExecutionRecordFilter
|
||||
{
|
||||
ActivityId = x.ActivityId,
|
||||
Id = x.ActivityInstanceId,
|
||||
ActivityNodeId = x.NodeId,
|
||||
Name = x.Name,
|
||||
Status = x.Status,
|
||||
}).ToList();
|
||||
|
||||
var workflowInstanceFilterIsEmpty = WorkflowFilterIsEmpty(workflowInstanceFilter);
|
||||
|
||||
var workflowInstanceIds = workflowInstanceFilterIsEmpty
|
||||
? Enumerable.Empty<string>().ToHashSet()
|
||||
: (await workflowInstanceStore.FindManyIdsAsync(workflowInstanceFilter, cancellationToken)).ToHashSet();
|
||||
|
||||
if (activityExecutionFilters == null)
|
||||
return workflowInstanceIds;
|
||||
|
||||
foreach (ActivityExecutionRecordFilter activityExecutionFilter in activityExecutionFilters.Where(x => !x.IsEmpty))
|
||||
{
|
||||
var activityExecutionRecords = await activityExecutionStore.FindManySummariesAsync(activityExecutionFilter, cancellationToken);
|
||||
var matchingWorkflowInstanceIds = activityExecutionRecords.Select(x => x.WorkflowInstanceId).ToHashSet();
|
||||
|
||||
if (workflowInstanceFilterIsEmpty)
|
||||
workflowInstanceIds = matchingWorkflowInstanceIds;
|
||||
else
|
||||
workflowInstanceIds.IntersectWith(matchingWorkflowInstanceIds);
|
||||
}
|
||||
|
||||
return workflowInstanceIds;
|
||||
}
|
||||
|
||||
private bool WorkflowFilterIsEmpty(WorkflowInstanceFilter filter)
|
||||
{
|
||||
return filter.Id == null &&
|
||||
filter.Ids == null &&
|
||||
filter.DefinitionId == null &&
|
||||
filter.DefinitionVersionId == null &&
|
||||
filter.DefinitionIds == null &&
|
||||
filter.DefinitionVersionIds == null &&
|
||||
filter.Version == null &&
|
||||
filter.CorrelationId == null &&
|
||||
filter.CorrelationIds == null &&
|
||||
filter.HasIncidents == null &&
|
||||
filter.TimestampFilters == null
|
||||
&& string.IsNullOrWhiteSpace(filter.SearchTerm);
|
||||
}
|
||||
}
|
||||
|
|
@ -84,37 +84,8 @@ public class GenerateAlterationJobs : CodeActivity<int>
|
|||
private async Task<IEnumerable<string>> FindMatchingWorkflowInstanceIdsAsync(ActivityExecutionContext context, AlterationWorkflowInstanceFilter filter)
|
||||
{
|
||||
var cancellationToken = context.CancellationToken;
|
||||
var workflowInstanceFilter = new WorkflowInstanceFilter
|
||||
{
|
||||
Ids = filter.WorkflowInstanceIds?.ToList(),
|
||||
DefinitionVersionIds = filter.DefinitionVersionIds?.ToList(),
|
||||
CorrelationIds = filter.CorrelationIds?.ToList(),
|
||||
HasIncidents = filter.HasIncidents,
|
||||
TimestampFilters = filter.TimestampFilters?.ToList(),
|
||||
};
|
||||
var activityExecutionFilters = filter.ActivityFilters?.Select(x => new ActivityExecutionRecordFilter
|
||||
{
|
||||
ActivityId = x.Id,
|
||||
ActivityNodeId = x.NodeId,
|
||||
Name = x.Name,
|
||||
Status = x.Status,
|
||||
}).ToList();
|
||||
|
||||
var workflowInstanceStore = context.GetRequiredService<IWorkflowInstanceStore>();
|
||||
var activityExecutionStore = context.GetRequiredService<IActivityExecutionStore>();
|
||||
var workflowInstanceIds = workflowInstanceFilter.IsEmpty ? Enumerable.Empty<string>().ToHashSet() : (await workflowInstanceStore.FindManyIdsAsync(workflowInstanceFilter, cancellationToken)).ToHashSet();
|
||||
|
||||
if (activityExecutionFilters != null)
|
||||
{
|
||||
foreach (ActivityExecutionRecordFilter activityExecutionFilter in activityExecutionFilters.Where(x => !x.IsEmpty))
|
||||
{
|
||||
var activityExecutionRecords = await activityExecutionStore.FindManySummariesAsync(activityExecutionFilter, cancellationToken);
|
||||
var matchingWorkflowInstanceIds = activityExecutionRecords.Select(x => x.WorkflowInstanceId).ToHashSet();
|
||||
workflowInstanceIds.UnionWith(matchingWorkflowInstanceIds);
|
||||
}
|
||||
}
|
||||
|
||||
return workflowInstanceIds;
|
||||
var workflowInstanceFinder = context.GetRequiredService<IWorkflowInstanceFinder>();
|
||||
return await workflowInstanceFinder.FindAsync(filter, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task GenerateJobsAsync(ActivityExecutionContext context, AlterationPlan plan, IEnumerable<string> workflowInstanceIds)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Alterations.Core.Contracts;
|
||||
using Elsa.Alterations.Core.Models;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Alterations.Endpoints.Alterations.DryRun;
|
||||
|
||||
/// <summary>
|
||||
/// Executes an alteration plan.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class DryRun(IWorkflowInstanceFinder workflowInstanceFinder) : ElsaEndpoint<AlterationWorkflowInstanceFilter, Response>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/alterations/dry-run");
|
||||
ConfigurePermissions("run:alterations");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task HandleAsync(AlterationWorkflowInstanceFilter filter, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflowInstanceIds = await workflowInstanceFinder.FindAsync(filter, cancellationToken);
|
||||
|
||||
// Write response.
|
||||
var response = new Response(workflowInstanceIds.ToList());
|
||||
await SendOkAsync(response, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Elsa.Alterations.Endpoints.Alterations.DryRun;
|
||||
|
||||
/// <summary>
|
||||
/// The response from the <see cref="DryRun"/> endpoint.
|
||||
/// </summary>
|
||||
public record Response(ICollection<string> WorkflowInstanceIds);
|
||||
|
|
@ -95,28 +95,6 @@ public class WorkflowInstanceFilter
|
|||
/// Filter workflow instances by timestamp.
|
||||
/// </summary>
|
||||
public ICollection<TimestampFilter>? TimestampFilters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the filter is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty =>
|
||||
Id == null &&
|
||||
Ids == null &&
|
||||
DefinitionId == null &&
|
||||
DefinitionVersionId == null &&
|
||||
DefinitionIds == null &&
|
||||
DefinitionVersionIds == null &&
|
||||
Version == null &&
|
||||
CorrelationId == null &&
|
||||
CorrelationIds == null &&
|
||||
WorkflowStatus == null &&
|
||||
WorkflowSubStatus == null &&
|
||||
WorkflowStatuses == null &&
|
||||
WorkflowSubStatuses == null &&
|
||||
HasIncidents == null &&
|
||||
IsSystem == null &&
|
||||
TimestampFilters == null
|
||||
&& string.IsNullOrWhiteSpace(SearchTerm);
|
||||
|
||||
/// <summary>
|
||||
/// Applies the filter to the specified query.
|
||||
|
|
|
|||
Loading…
Reference in a new issue