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