Add workflow graph network builder and child workflow finder

Introduce IWorkflowGraphNetworkBuilder and IChildWorkflowFinder interfaces with their implementations to build and manage workflow graph networks. Additionally, register new services and handlers to support these functionalities in the Workflow Management feature.
This commit is contained in:
Sipke Schoorstra 2024-11-06 11:58:13 +01:00
parent 6ed6c8a30e
commit 311f59894e
7 changed files with 174 additions and 0 deletions

View file

@ -0,0 +1,17 @@
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Management.Contracts;
/// <summary>
/// Finds child workflows for a given workflow graph.
/// </summary>
public interface IChildWorkflowFinder
{
/// <summary>
/// Finds child workflows for a given workflow graph.
/// </summary>
/// <param name="workflowGraph">The workflow graph.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A collection of child workflows.</returns>
Task<IEnumerable<WorkflowGraph>> FindChildWorkflowsAsync(WorkflowGraph workflowGraph, CancellationToken cancellationToken = default);
}

View file

@ -0,0 +1,16 @@
using Elsa.Workflows.Management.Models;
namespace Elsa.Workflows.Management.Contracts;
/// <summary>
/// Defines a visitor that can traverse and process workflow definitions.
/// </summary>
public interface IWorkflowGraphNetworkBuilder
{
/// <summary>
/// Builds a network of workflow graphs and their consumers.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A network of workflow graphs and their consumers.</returns>
Task<WorkflowGraphNetwork> BuildAsync(CancellationToken cancellationToken = default);
}

View file

@ -221,6 +221,7 @@ public class WorkflowManagementFeature : FeatureBase
.AddScoped<IWorkflowMaterializer, ClrWorkflowMaterializer>()
.AddScoped<IWorkflowMaterializer, JsonWorkflowMaterializer>()
.AddScoped<IActivityResolver, WorkflowDefinitionActivityResolver>()
.AddScoped<IWorkflowGraphNetworkBuilder, WorkflowGraphNetworkBuilder>()
.AddScoped<WorkflowDefinitionMapper>()
.AddSingleton<VariableDefinitionMapper>()
.AddSingleton<WorkflowStateMapper>()
@ -233,6 +234,7 @@ public class WorkflowManagementFeature : FeatureBase
Services
.AddNotificationHandler<DeleteWorkflowInstances>()
.AddNotificationHandler<RefreshActivityRegistry>()
.AddNotificationHandler<UpdateConsumingWorkflows>()
;
Services.Configure<ManagementOptions>(options =>

View file

@ -0,0 +1,13 @@
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Management.Notifications;
namespace Elsa.Workflows.Management.Handlers;
public class UpdateConsumingWorkflows(IWorkflowGraphNetworkBuilder workflowGraphNetworkBuilder) : INotificationHandler<WorkflowDefinitionPublished>
{
public async Task HandleAsync(WorkflowDefinitionPublished notification, CancellationToken cancellationToken)
{
var network = await workflowGraphNetworkBuilder.BuildAsync(cancellationToken);
}
}

View file

@ -0,0 +1,3 @@
namespace Elsa.Workflows.Management.Models;
public record WorkflowGraphNetwork(HashSet<WorkflowGraphNode> Nodes);

View file

@ -0,0 +1,24 @@
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Management.Models;
/// <summary>
/// Represents an activity in the context of an hierarchical tree structure, providing access to its siblings, parents and children.
/// </summary>
public class WorkflowGraphNode(WorkflowGraph workflowGraph)
{
/// <summary>
/// Gets the workflow graph associated with this node.
/// </summary>
public WorkflowGraph WorkflowGraph { get; } = workflowGraph;
/// <summary>
/// Gets the parents of this node.
/// </summary>
public HashSet<WorkflowGraphNode> Predecessors { get; set; } = new();
/// <summary>
/// Gets the children of this node.
/// </summary>
public HashSet<WorkflowGraphNode> Successors { get; set; } = new();
}

View file

@ -0,0 +1,99 @@
using Elsa.Common.Models;
using Elsa.Workflows.Management.Activities.WorkflowDefinitionActivity;
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Management.Services;
/// <inheritdoc />
public class WorkflowGraphNetworkBuilder(IWorkflowDefinitionStore workflowDefinitionStore, IWorkflowDefinitionService workflowDefinitionService) : IWorkflowGraphNetworkBuilder
{
/// <inheritdoc />
public async Task<WorkflowGraphNetwork> BuildAsync(CancellationToken cancellationToken = default)
{
var workflowGraphs = (await GetAllWorkflowGraphsAsync(cancellationToken)).ToList();
var nodes = new HashSet<WorkflowGraphNode>();
var context = new WorkflowGraphNetworkBuilderContext
{
WorkflowGraphs = workflowGraphs,
VisitedNodes = nodes
};
foreach (var workflowGraph in context.WorkflowGraphs)
VisitWorkflowGraph(workflowGraph, context);
return new WorkflowGraphNetwork(nodes);
}
private void VisitWorkflowGraph(WorkflowGraph workflowGraph, WorkflowGraphNetworkBuilderContext context)
{
var activityNodes = workflowGraph.Nodes;
var node = context.VisitedNodes.FirstOrDefault(x => x.WorkflowGraph == workflowGraph);
if (node == null)
{
node = new WorkflowGraphNode(workflowGraph);
context.VisitedNodes.Add(node);
}
var workflowDefinitionNodes = activityNodes
.Where(x => x.Activity is WorkflowDefinitionActivity)
.Select(x => (WorkflowDefinitionActivity)x.Activity)
.ToList();
foreach (var workflowDefinitionNode in workflowDefinitionNodes)
{
VisitConsumingActivityNode(node, workflowDefinitionNode, context);
}
}
private void VisitConsumingActivityNode(WorkflowGraphNode node, WorkflowDefinitionActivity workflowDefinitionNode, WorkflowGraphNetworkBuilderContext context)
{
var consumedWorkflowDefinitionId = workflowDefinitionNode.WorkflowDefinitionId;
var consumedWorkflowGraph = context.WorkflowGraphs.FirstOrDefault(x => x.Workflow.Identity.DefinitionId == consumedWorkflowDefinitionId);
if (consumedWorkflowGraph == null)
return;
var childNode = context.VisitedNodes.FirstOrDefault(x => x.WorkflowGraph == consumedWorkflowGraph);
if (childNode == null)
{
childNode = new WorkflowGraphNode(consumedWorkflowGraph);
context.VisitedNodes.Add(childNode);
VisitWorkflowGraph(childNode.WorkflowGraph, context);
}
node.Successors.Add(childNode);
childNode.Predecessors.Add(node);
}
private async Task<IEnumerable<WorkflowGraph>> GetAllWorkflowGraphsAsync(CancellationToken cancellationToken)
{
var workflowDefinitionFilter = new WorkflowDefinitionFilter
{
VersionOptions = VersionOptions.LatestOrPublished
};
var workflowDefinitionSummaries = await workflowDefinitionStore.FindSummariesAsync(workflowDefinitionFilter, cancellationToken);
var workflowGraphs = new List<WorkflowGraph>();
foreach (var workflowDefinitionSummary in workflowDefinitionSummaries)
{
var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionSummary.DefinitionId, VersionOptions.LatestOrPublished, cancellationToken);
if (workflowGraph != null)
workflowGraphs.Add(workflowGraph);
}
return workflowGraphs;
}
private class WorkflowGraphNetworkBuilderContext
{
public ICollection<WorkflowGraph> WorkflowGraphs { get; set; } = new List<WorkflowGraph>();
public HashSet<WorkflowGraphNode> VisitedNodes { get; set; } = new();
}
}