From cce2a8731fd44481f56482880d92f440b31fcaab Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 27 May 2022 10:53:22 +0200 Subject: [PATCH] Implement cascading workflow labels deletion --- .../EFCoreWorkflowDefinitionLabelStore.cs | 24 ++++++++++---- .../Configuration/LabelsConfigurator.cs | 6 ++++ src/modules/Elsa.Labels/Elsa.Labels.csproj | 2 ++ .../Handlers/DeleteWorkflowLabels.cs | 21 ++++++++++++ .../InMemoryWorkflowDefinitionLabelStore.cs | 26 +++++++++++++++ .../Services/IWorkflowDefinitionLabelStore.cs | 23 +++++++++++-- .../WorkflowDefinitions/BulkDelete.cs | 11 +++---- .../Endpoints/WorkflowDefinitions/Delete.cs | 10 ++++-- .../WorkflowManagementConfigurator.cs | 4 +++ .../WorkflowDefinitionManager.cs | 33 +++++++++++++++++++ .../WorkflowDefinitionDeleted.cs | 5 +++ .../WorkflowDefinitionsDeleted.cs | 5 +++ .../Services/IWorkflowDefinitionManager.cs | 7 ++++ 13 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 src/modules/Elsa.Labels/Handlers/DeleteWorkflowLabels.cs create mode 100644 src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionManager.cs create mode 100644 src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionDeleted.cs create mode 100644 src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionsDeleted.cs create mode 100644 src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionManager.cs diff --git a/src/modules/Elsa.Labels.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionLabelStore.cs b/src/modules/Elsa.Labels.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionLabelStore.cs index 99f4beb5a..dbb28162c 100644 --- a/src/modules/Elsa.Labels.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionLabelStore.cs +++ b/src/modules/Elsa.Labels.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionLabelStore.cs @@ -13,12 +13,6 @@ public class EFCoreWorkflowDefinitionLabelStore : IWorkflowDefinitionLabelStore public async Task SaveManyAsync(IEnumerable records, CancellationToken cancellationToken = default) => await _store.SaveManyAsync(records, cancellationToken); public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) => await _store.DeleteWhereAsync(x => x.Id == id, cancellationToken) > 0; - public async Task DeleteManyAsync(IEnumerable ids, CancellationToken cancellationToken = default) - { - var idList = ids.ToList(); - return await _store.DeleteWhereAsync(x => idList.Contains(x.Id), cancellationToken); - } - public async Task> FindByWorkflowDefinitionVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default) => await _store.FindManyAsync(x => x.WorkflowDefinitionVersionId == workflowDefinitionVersionId, cancellationToken); @@ -27,4 +21,22 @@ public class EFCoreWorkflowDefinitionLabelStore : IWorkflowDefinitionLabelStore await _store.DeleteManyAsync(removed, cancellationToken); await _store.SaveManyAsync(added, cancellationToken); } + + public async Task DeleteByWorkflowDefinitionIdAsync(string workflowDefinitionId, CancellationToken cancellationToken = default) => + await _store.DeleteWhereAsync(x => x.WorkflowDefinitionId == workflowDefinitionId, cancellationToken); + + public async Task DeleteByWorkflowDefinitionVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default) => + await _store.DeleteWhereAsync(x => x.WorkflowDefinitionVersionId == workflowDefinitionVersionId, cancellationToken); + + public async Task DeleteByWorkflowDefinitionIdsAsync(IEnumerable workflowDefinitionIds, CancellationToken cancellationToken = default) + { + var ids = workflowDefinitionIds.ToList(); + return await _store.DeleteWhereAsync(x => ids.Contains(x.WorkflowDefinitionId), cancellationToken); + } + + public async Task DeleteByWorkflowDefinitionVersionIdsAsync(IEnumerable workflowDefinitionVersionIds, CancellationToken cancellationToken = default) + { + var ids = workflowDefinitionVersionIds.ToList(); + return await _store.DeleteWhereAsync(x => ids.Contains(x.WorkflowDefinitionVersionId), cancellationToken); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Labels/Configuration/LabelsConfigurator.cs b/src/modules/Elsa.Labels/Configuration/LabelsConfigurator.cs index 2868a3ecc..319d4a4e5 100644 --- a/src/modules/Elsa.Labels/Configuration/LabelsConfigurator.cs +++ b/src/modules/Elsa.Labels/Configuration/LabelsConfigurator.cs @@ -1,13 +1,17 @@ using Elsa.Labels.Entities; using Elsa.Labels.Implementations; using Elsa.Labels.Services; +using Elsa.Mediator.Configuration; +using Elsa.Mediator.Extensions; using Elsa.Persistence.Common.Extensions; using Elsa.ServiceConfiguration.Abstractions; +using Elsa.ServiceConfiguration.Attributes; using Elsa.ServiceConfiguration.Services; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Labels.Configuration; +[Dependency(typeof(MediatorConfigurator))] public class LabelsConfigurator : ConfiguratorBase { public LabelsConfigurator(IServiceConfiguration serviceConfiguration) : base(serviceConfiguration) @@ -37,5 +41,7 @@ public class LabelsConfigurator : ConfiguratorBase .AddSingleton(LabelStore) .AddSingleton(WorkflowDefinitionLabelStore) ; + + Services.AddNotificationHandlersFrom(GetType()); } } \ No newline at end of file diff --git a/src/modules/Elsa.Labels/Elsa.Labels.csproj b/src/modules/Elsa.Labels/Elsa.Labels.csproj index bd053fc7b..a305aea0f 100644 --- a/src/modules/Elsa.Labels/Elsa.Labels.csproj +++ b/src/modules/Elsa.Labels/Elsa.Labels.csproj @@ -16,8 +16,10 @@ + + diff --git a/src/modules/Elsa.Labels/Handlers/DeleteWorkflowLabels.cs b/src/modules/Elsa.Labels/Handlers/DeleteWorkflowLabels.cs new file mode 100644 index 000000000..60d22e5cf --- /dev/null +++ b/src/modules/Elsa.Labels/Handlers/DeleteWorkflowLabels.cs @@ -0,0 +1,21 @@ +using Elsa.Labels.Services; +using Elsa.Mediator.Services; +using Elsa.Workflows.Management.Notifications; + +namespace Elsa.Labels.Handlers; + +// ReSharper disable once UnusedType.Global +/// +/// A handler that removes associated workflow labels in response to workflow definitions being deleted. +/// +public class DeleteWorkflowLabels : INotificationHandler, INotificationHandler +{ + private readonly IWorkflowDefinitionLabelStore _workflowDefinitionLabelStore; + public DeleteWorkflowLabels(IWorkflowDefinitionLabelStore workflowDefinitionLabelStore) => _workflowDefinitionLabelStore = workflowDefinitionLabelStore; + + public async Task HandleAsync(WorkflowDefinitionDeleted notification, CancellationToken cancellationToken) => + await _workflowDefinitionLabelStore.DeleteByWorkflowDefinitionIdAsync(notification.DefinitionId, cancellationToken); + + public async Task HandleAsync(WorkflowDefinitionsDeleted notification, CancellationToken cancellationToken) => + await _workflowDefinitionLabelStore.DeleteByWorkflowDefinitionIdsAsync(notification.DefinitionIds, cancellationToken); +} \ No newline at end of file diff --git a/src/modules/Elsa.Labels/Implementations/InMemoryWorkflowDefinitionLabelStore.cs b/src/modules/Elsa.Labels/Implementations/InMemoryWorkflowDefinitionLabelStore.cs index 027b96146..50eb8c1d2 100644 --- a/src/modules/Elsa.Labels/Implementations/InMemoryWorkflowDefinitionLabelStore.cs +++ b/src/modules/Elsa.Labels/Implementations/InMemoryWorkflowDefinitionLabelStore.cs @@ -49,4 +49,30 @@ public class InMemoryWorkflowDefinitionLabelStore : IWorkflowDefinitionLabelStor _store.SaveMany(added); return Task.CompletedTask; } + + public Task DeleteByWorkflowDefinitionIdAsync(string workflowDefinitionId, CancellationToken cancellationToken = default) + { + var result = _store.DeleteWhere(x => x.WorkflowDefinitionId == workflowDefinitionId); + return Task.FromResult(result); + } + + public Task DeleteByWorkflowDefinitionVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default) + { + var result = _store.DeleteWhere(x => x.WorkflowDefinitionVersionId == workflowDefinitionVersionId); + return Task.FromResult(result); + } + + public Task DeleteByWorkflowDefinitionIdsAsync(IEnumerable workflowDefinitionIds, CancellationToken cancellationToken = default) + { + var ids = workflowDefinitionIds.ToList(); + var result = _store.DeleteWhere(x => ids.Contains(x.WorkflowDefinitionId)); + return Task.FromResult(result); + } + + public Task DeleteByWorkflowDefinitionVersionIdsAsync(IEnumerable workflowDefinitionVersionIds, CancellationToken cancellationToken = default) + { + var ids = workflowDefinitionVersionIds.ToList(); + var result = _store.DeleteWhere(x => ids.Contains(x.WorkflowDefinitionVersionId)); + return Task.FromResult(result); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Labels/Services/IWorkflowDefinitionLabelStore.cs b/src/modules/Elsa.Labels/Services/IWorkflowDefinitionLabelStore.cs index 360d03210..7f1787b6a 100644 --- a/src/modules/Elsa.Labels/Services/IWorkflowDefinitionLabelStore.cs +++ b/src/modules/Elsa.Labels/Services/IWorkflowDefinitionLabelStore.cs @@ -7,8 +7,7 @@ public interface IWorkflowDefinitionLabelStore Task SaveAsync(WorkflowDefinitionLabel record, CancellationToken cancellationToken = default); Task SaveManyAsync(IEnumerable records, CancellationToken cancellationToken = default); Task DeleteAsync(string id, CancellationToken cancellationToken = default); - Task DeleteManyAsync(IEnumerable ids, CancellationToken cancellationToken = default); - + /// /// Returns a list of label IDs associated with the specified workflow definition version ID. /// @@ -18,4 +17,24 @@ public interface IWorkflowDefinitionLabelStore /// Removes and adds the specified sets of label IDs for the specified workflow definition version ID. /// Task ReplaceAsync(IEnumerable removed, IEnumerable added, CancellationToken cancellationToken = default); + + /// + /// Deletes all associated labels for the specified workflow definition ID. + /// + Task DeleteByWorkflowDefinitionIdAsync(string workflowDefinitionId, CancellationToken cancellationToken = default); + + /// + /// Deletes all associated labels for the specified workflow definition version ID. + /// + Task DeleteByWorkflowDefinitionVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default); + + /// + /// Deletes all associated labels for the specified workflow definition IDs. + /// + Task DeleteByWorkflowDefinitionIdsAsync(IEnumerable workflowDefinitionIds, CancellationToken cancellationToken = default); + + /// + /// Deletes all associated labels for the specified workflow definition version IDs. + /// + Task DeleteByWorkflowDefinitionVersionIdsAsync(IEnumerable workflowDefinitionVersionIds, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDelete.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDelete.cs index 7a3d7d22b..e7910afdc 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDelete.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDelete.cs @@ -2,10 +2,9 @@ using System.Collections.Generic; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using Elsa.AspNetCore; using Elsa.AspNetCore.Attributes; using Elsa.Workflows.Core.Serialization; -using Elsa.Workflows.Persistence.Services; +using Elsa.Workflows.Management.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -15,12 +14,12 @@ namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions; [ApiEndpoint(ControllerNames.WorkflowDefinitions, "BulkDelete")] public class BulkDelete : Controller { - private readonly IWorkflowDefinitionStore _store; + private readonly IWorkflowDefinitionManager _workflowDefinitionManager; private readonly WorkflowSerializerOptionsProvider _serializerOptionsProvider; - public BulkDelete(IWorkflowDefinitionStore store, WorkflowSerializerOptionsProvider serializerOptionsProvider) + public BulkDelete(IWorkflowDefinitionManager workflowDefinitionManager, WorkflowSerializerOptionsProvider serializerOptionsProvider) { - _store = store; + _workflowDefinitionManager = workflowDefinitionManager; _serializerOptionsProvider = serializerOptionsProvider; } @@ -29,7 +28,7 @@ public class BulkDelete : Controller { var serializerOptions = _serializerOptionsProvider.CreateApiOptions(); var model = await Request.ReadFromJsonAsync(serializerOptions, cancellationToken); - var count = await _store.DeleteManyByDefinitionIdsAsync(model!.DefinitionIds, cancellationToken); + var count = await _workflowDefinitionManager.BulkDeleteByDefinitionIdsAsync(model!.DefinitionIds, cancellationToken); return Json(new BulkDeleteWorkflowDefinitionsResponse(count), serializerOptions); } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Delete.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Delete.cs index 27f5ffe3d..a5f5c4d94 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Delete.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Delete.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Elsa.AspNetCore; using Elsa.AspNetCore.Attributes; +using Elsa.Workflows.Management.Services; using Elsa.Workflows.Persistence.Services; using Microsoft.AspNetCore.Mvc; @@ -11,13 +12,16 @@ namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions; [ApiEndpoint(ControllerNames.WorkflowDefinitions, "Delete")] public class Delete : Controller { - private readonly IWorkflowDefinitionStore _store; - public Delete(IWorkflowDefinitionStore store) => _store = store; + private readonly IWorkflowDefinitionManager _workflowDefinitionManager; + public Delete(IWorkflowDefinitionManager workflowDefinitionManager) + { + _workflowDefinitionManager = workflowDefinitionManager; + } [HttpDelete] public async Task HandleAsync(string definitionId, CancellationToken cancellationToken) { - var result = await _store.DeleteByDefinitionIdAsync(definitionId, cancellationToken); + var result = await _workflowDefinitionManager.DeleteByDefinitionIdAsync(definitionId, cancellationToken); return result == 0 ? NotFound() : NoContent(); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Configuration/WorkflowManagementConfigurator.cs b/src/modules/Elsa.Workflows.Management/Configuration/WorkflowManagementConfigurator.cs index 3ebfac12e..946270bb9 100644 --- a/src/modules/Elsa.Workflows.Management/Configuration/WorkflowManagementConfigurator.cs +++ b/src/modules/Elsa.Workflows.Management/Configuration/WorkflowManagementConfigurator.cs @@ -1,4 +1,6 @@ using Elsa.Expressions.Services; +using Elsa.Mediator.Configuration; +using Elsa.Mediator.Extensions; using Elsa.ServiceConfiguration.Abstractions; using Elsa.ServiceConfiguration.Attributes; using Elsa.ServiceConfiguration.Services; @@ -16,6 +18,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Elsa.Workflows.Management.Configuration; +[Dependency(typeof(MediatorConfigurator))] [Dependency(typeof(WorkflowsConfigurator))] [Dependency(typeof(WorkflowPersistenceConfigurator))] public class WorkflowManagementConfigurator : ConfiguratorBase @@ -36,6 +39,7 @@ public class WorkflowManagementConfigurator : ConfiguratorBase { Services .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionManager.cs b/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionManager.cs new file mode 100644 index 000000000..8b2e1c1cb --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionManager.cs @@ -0,0 +1,33 @@ +using Elsa.Mediator.Services; +using Elsa.Workflows.Management.Notifications; +using Elsa.Workflows.Management.Services; +using Elsa.Workflows.Persistence.Services; + +namespace Elsa.Workflows.Management.Implementations; + +public class WorkflowDefinitionManager : IWorkflowDefinitionManager +{ + private readonly IWorkflowDefinitionStore _store; + private readonly IEventPublisher _eventPublisher; + + public WorkflowDefinitionManager(IWorkflowDefinitionStore store, IEventPublisher eventPublisher) + { + _store = store; + _eventPublisher = eventPublisher; + } + + public async Task DeleteByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default) + { + var count = await _store.DeleteByDefinitionIdAsync(definitionId, cancellationToken); + await _eventPublisher.PublishAsync(new WorkflowDefinitionDeleted(definitionId), cancellationToken); + return count; + } + + public async Task BulkDeleteByDefinitionIdsAsync(IEnumerable definitionIds, CancellationToken cancellationToken = default) + { + var ids = definitionIds.ToList(); + var count = await _store.DeleteManyByDefinitionIdsAsync(ids, cancellationToken); + await _eventPublisher.PublishAsync(new WorkflowDefinitionsDeleted(ids), cancellationToken); + return count; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionDeleted.cs b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionDeleted.cs new file mode 100644 index 000000000..149ab9d65 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionDeleted.cs @@ -0,0 +1,5 @@ +using Elsa.Mediator.Services; + +namespace Elsa.Workflows.Management.Notifications; + +public record WorkflowDefinitionDeleted(string DefinitionId) : INotification; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionsDeleted.cs b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionsDeleted.cs new file mode 100644 index 000000000..a6c66e1f7 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionsDeleted.cs @@ -0,0 +1,5 @@ +using Elsa.Mediator.Services; + +namespace Elsa.Workflows.Management.Notifications; + +public record WorkflowDefinitionsDeleted(ICollection DefinitionIds) : INotification; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionManager.cs b/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionManager.cs new file mode 100644 index 000000000..7ea2ecdd0 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionManager.cs @@ -0,0 +1,7 @@ +namespace Elsa.Workflows.Management.Services; + +public interface IWorkflowDefinitionManager +{ + Task DeleteByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default); + Task BulkDeleteByDefinitionIdsAsync(IEnumerable definitionIds, CancellationToken cancellationToken = default); +} \ No newline at end of file