Skip to content

Commit

Permalink
Add DryRun endpoint and workflow instance finder (#5110)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
sfmskywalker committed Mar 21, 2024
1 parent e67a6fe commit d117543
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion src/modules/Elsa.Alterations.Core/Models/ActivityFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
33 changes: 2 additions & 31 deletions src/modules/Elsa.Alterations/Activities/GenerateAlterationJobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,37 +84,8 @@ private async Task UpdatePlanStatusAsync(ActivityExecutionContext context, Alter
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit d117543

Please sign in to comment.