From 311f59894e2b9a75c47a13ab90a434eddfc6422e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 6 Nov 2024 11:58:13 +0100 Subject: [PATCH] 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. --- .../Contracts/IChildWorkflowFinder.cs | 17 ++++ .../Contracts/IWorkflowGraphNetworkBuilder.cs | 16 +++ .../Features/WorkflowManagementFeature.cs | 2 + .../Handlers/UpdateConsumingWorkflows.cs | 13 +++ .../Models/WorkflowGraphNetwork.cs | 3 + .../Models/WorkflowGraphNode.cs | 24 +++++ .../Services/WorkflowGraphNetworkBuilder.cs | 99 +++++++++++++++++++ 7 files changed, 174 insertions(+) create mode 100644 src/modules/Elsa.Workflows.Management/Contracts/IChildWorkflowFinder.cs create mode 100644 src/modules/Elsa.Workflows.Management/Contracts/IWorkflowGraphNetworkBuilder.cs create mode 100644 src/modules/Elsa.Workflows.Management/Handlers/UpdateConsumingWorkflows.cs create mode 100644 src/modules/Elsa.Workflows.Management/Models/WorkflowGraphNetwork.cs create mode 100644 src/modules/Elsa.Workflows.Management/Models/WorkflowGraphNode.cs create mode 100644 src/modules/Elsa.Workflows.Management/Services/WorkflowGraphNetworkBuilder.cs diff --git a/src/modules/Elsa.Workflows.Management/Contracts/IChildWorkflowFinder.cs b/src/modules/Elsa.Workflows.Management/Contracts/IChildWorkflowFinder.cs new file mode 100644 index 000000000..351493fc5 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Contracts/IChildWorkflowFinder.cs @@ -0,0 +1,17 @@ +using Elsa.Workflows.Models; + +namespace Elsa.Workflows.Management.Contracts; + +/// +/// Finds child workflows for a given workflow graph. +/// +public interface IChildWorkflowFinder +{ + /// + /// Finds child workflows for a given workflow graph. + /// + /// The workflow graph. + /// The cancellation token. + /// A collection of child workflows. + Task> FindChildWorkflowsAsync(WorkflowGraph workflowGraph, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowGraphNetworkBuilder.cs b/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowGraphNetworkBuilder.cs new file mode 100644 index 000000000..a22fd8aac --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowGraphNetworkBuilder.cs @@ -0,0 +1,16 @@ +using Elsa.Workflows.Management.Models; + +namespace Elsa.Workflows.Management.Contracts; + +/// +/// Defines a visitor that can traverse and process workflow definitions. +/// +public interface IWorkflowGraphNetworkBuilder +{ + /// + /// Builds a network of workflow graphs and their consumers. + /// + /// The cancellation token. + /// A network of workflow graphs and their consumers. + Task BuildAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs index 3fdc5adf8..75b36b111 100644 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs @@ -221,6 +221,7 @@ public class WorkflowManagementFeature : FeatureBase .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddSingleton() .AddSingleton() @@ -233,6 +234,7 @@ public class WorkflowManagementFeature : FeatureBase Services .AddNotificationHandler() .AddNotificationHandler() + .AddNotificationHandler() ; Services.Configure(options => diff --git a/src/modules/Elsa.Workflows.Management/Handlers/UpdateConsumingWorkflows.cs b/src/modules/Elsa.Workflows.Management/Handlers/UpdateConsumingWorkflows.cs new file mode 100644 index 000000000..c031fe183 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Handlers/UpdateConsumingWorkflows.cs @@ -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 +{ + public async Task HandleAsync(WorkflowDefinitionPublished notification, CancellationToken cancellationToken) + { + var network = await workflowGraphNetworkBuilder.BuildAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Models/WorkflowGraphNetwork.cs b/src/modules/Elsa.Workflows.Management/Models/WorkflowGraphNetwork.cs new file mode 100644 index 000000000..bb6ddc551 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Models/WorkflowGraphNetwork.cs @@ -0,0 +1,3 @@ +namespace Elsa.Workflows.Management.Models; + +public record WorkflowGraphNetwork(HashSet Nodes); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Models/WorkflowGraphNode.cs b/src/modules/Elsa.Workflows.Management/Models/WorkflowGraphNode.cs new file mode 100644 index 000000000..765f1c92b --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Models/WorkflowGraphNode.cs @@ -0,0 +1,24 @@ +using Elsa.Workflows.Models; + +namespace Elsa.Workflows.Management.Models; + +/// +/// Represents an activity in the context of an hierarchical tree structure, providing access to its siblings, parents and children. +/// +public class WorkflowGraphNode(WorkflowGraph workflowGraph) +{ + /// + /// Gets the workflow graph associated with this node. + /// + public WorkflowGraph WorkflowGraph { get; } = workflowGraph; + + /// + /// Gets the parents of this node. + /// + public HashSet Predecessors { get; set; } = new(); + + /// + /// Gets the children of this node. + /// + public HashSet Successors { get; set; } = new(); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowGraphNetworkBuilder.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowGraphNetworkBuilder.cs new file mode 100644 index 000000000..c725fbe3e --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowGraphNetworkBuilder.cs @@ -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; + +/// +public class WorkflowGraphNetworkBuilder(IWorkflowDefinitionStore workflowDefinitionStore, IWorkflowDefinitionService workflowDefinitionService) : IWorkflowGraphNetworkBuilder +{ + /// + public async Task BuildAsync(CancellationToken cancellationToken = default) + { + var workflowGraphs = (await GetAllWorkflowGraphsAsync(cancellationToken)).ToList(); + var nodes = new HashSet(); + + 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> GetAllWorkflowGraphsAsync(CancellationToken cancellationToken) + { + var workflowDefinitionFilter = new WorkflowDefinitionFilter + { + VersionOptions = VersionOptions.LatestOrPublished + }; + var workflowDefinitionSummaries = await workflowDefinitionStore.FindSummariesAsync(workflowDefinitionFilter, cancellationToken); + var workflowGraphs = new List(); + + 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 WorkflowGraphs { get; set; } = new List(); + public HashSet VisitedNodes { get; set; } = new(); + } +} \ No newline at end of file