Implement delete workflow definition

This commit is contained in:
Sipke Schoorstra 2020-12-28 17:34:44 +01:00
parent 1c006e3081
commit 460c75227d
17 changed files with 186 additions and 311 deletions

View file

@ -18,5 +18,8 @@ namespace Elsa.Client.Services
[Post("/v1/workflow-definitions")]
Task<WorkflowDefinition> SaveAsync([Body] SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken = default);
[Delete("/v1/workflow-definitions/{workflowDefinitionId}")]
Task DeleteAsync(string workflowDefinitionId, CancellationToken cancellationToken = default);
}
}

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
using Elsa.Models;
using MediatR;
namespace Elsa.Events
{
public class ManyWorkflowInstancesDeleted : INotification
{
public ManyWorkflowInstancesDeleted(IEnumerable<WorkflowInstance> workflowInstances)
{
WorkflowInstances = workflowInstances;
}
public IEnumerable<WorkflowInstance> WorkflowInstances { get; }
}
}

View file

@ -13,7 +13,7 @@ namespace Elsa
{
public static class WorkflowBlueprintExtensions
{
public static ValueTask<T> GetActivityPropertyValue<TActivity, T>(
public static ValueTask<T?> GetActivityPropertyValue<TActivity, T>(
this IWorkflowBlueprint workflowBlueprint,
string activityId,
Expression<Func<TActivity, T>> propertyExpression,
@ -25,15 +25,18 @@ namespace Elsa
return GetActivityPropertyValue<T>(workflowBlueprint, activityId, propertyName, activityExecutionContext, cancellationToken);
}
public static async ValueTask<T> GetActivityPropertyValue<T>(
public static async ValueTask<T?> GetActivityPropertyValue<T>(
this IWorkflowBlueprint workflowBlueprint,
string activityId,
string propertyName,
ActivityExecutionContext activityExecutionContext,
CancellationToken cancellationToken)
=> (T) await workflowBlueprint.GetActivityPropertyValue(activityId, propertyName, activityExecutionContext, cancellationToken);
{
var value = await workflowBlueprint.GetActivityPropertyValue(activityId, propertyName, activityExecutionContext, cancellationToken);
return value == null ? default : (T) value;
}
public static async ValueTask<object> GetActivityPropertyValue(
public static async ValueTask<object?> GetActivityPropertyValue(
this IWorkflowBlueprint workflowBlueprint,
string activityId,
string propertyName,

View file

@ -7,17 +7,11 @@ namespace Elsa.Services
public interface IWorkflowPublisher
{
WorkflowDefinition New();
Task<WorkflowDefinition?> PublishAsync(string workflowDefinitionId, CancellationToken cancellationToken = default);
Task<WorkflowDefinition> PublishAsync(
WorkflowDefinition workflowDefinition,
CancellationToken cancellationToken = default);
Task<WorkflowDefinition> PublishAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default);
Task<WorkflowDefinition?> GetDraftAsync(string workflowDefinitionId, CancellationToken cancellationToken= default);
Task<WorkflowDefinition> SaveDraftAsync(
WorkflowDefinition workflowDefinition,
CancellationToken cancellationToken = default);
Task<WorkflowDefinition> SaveDraftAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default);
Task DeleteAsync(string workflowDefinitionId, CancellationToken cancellationToken = default);
Task DeleteAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default);
}
}

View file

@ -1,3 +1,4 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Events;
@ -8,7 +9,7 @@ using MediatR;
namespace Elsa.Handlers
{
public class UpdateTriggers : INotificationHandler<WorkflowInstanceSaved>
public class UpdateTriggers : INotificationHandler<WorkflowInstanceSaved>, INotificationHandler<ManyWorkflowInstancesDeleted>
{
private readonly IWorkflowSelector _workflowSelector;
private readonly IWorkflowRegistry _workflowRegistry;
@ -22,11 +23,22 @@ namespace Elsa.Handlers
public async Task Handle(WorkflowInstanceSaved notification, CancellationToken cancellationToken)
{
var workflowInstance = notification.WorkflowInstance;
var workflowBlueprint = await _workflowRegistry.GetWorkflowAsync(workflowInstance.DefinitionId, workflowInstance.TenantId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken);
await UpdateTriggersAsync(workflowInstance.DefinitionId, workflowInstance.TenantId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken);
}
public async Task Handle(ManyWorkflowInstancesDeleted notification, CancellationToken cancellationToken)
{
var workflowInstance = notification.WorkflowInstances.First();
await UpdateTriggersAsync(workflowInstance.DefinitionId, workflowInstance.TenantId, VersionOptions.Latest, cancellationToken);
}
private async Task UpdateTriggersAsync(string workflowDefinitionId, string? tenantId, VersionOptions versionOptions, CancellationToken cancellationToken)
{
var workflowBlueprint = await _workflowRegistry.GetWorkflowAsync(workflowDefinitionId, tenantId, versionOptions, cancellationToken);
if (workflowBlueprint == null)
return;
await _workflowSelector.UpdateTriggersAsync(workflowBlueprint, cancellationToken);
}
}

View file

@ -1,10 +1,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Events;
using Elsa.Models;
using Elsa.Persistence.Specifications;
using MediatR;
using Open.Linq.AsyncExtensions;
namespace Elsa.Persistence.Decorators
{
@ -18,7 +20,7 @@ namespace Elsa.Persistence.Decorators
_store = store;
_mediator = mediator;
}
public async Task SaveAsync(WorkflowInstance entity, CancellationToken cancellationToken = default)
{
await _store.SaveAsync(entity, cancellationToken);
@ -33,11 +35,16 @@ namespace Elsa.Persistence.Decorators
public async Task<int> DeleteManyAsync(ISpecification<WorkflowInstance> specification, CancellationToken cancellationToken = default)
{
var instances = await FindManyAsync(specification, cancellationToken: cancellationToken);
var instances = await FindManyAsync(specification, cancellationToken: cancellationToken).ToList();
var count = await _store.DeleteManyAsync(specification, cancellationToken);
foreach (var instance in instances)
await _mediator.Publish(new WorkflowInstanceDeleted(instance), cancellationToken);
if (instances.Any())
{
foreach (var instance in instances)
await _mediator.Publish(new WorkflowInstanceDeleted(instance), cancellationToken);
await _mediator.Publish(new ManyWorkflowInstancesDeleted(instances), cancellationToken);
}
return count;
}

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Elsa.Models;
namespace Elsa.Persistence.Specifications
{
public class ManyWorkflowDefinitionVersionIdsSpecification : Specification<WorkflowDefinition>
{
public IEnumerable<string> Ids { get; set; }
public ManyWorkflowDefinitionVersionIdsSpecification(IEnumerable<string> ids) => Ids = ids;
public override Expression<Func<WorkflowDefinition, bool>> ToExpression() => x => Ids.Contains(x.DefinitionVersionId);
}
}

View file

@ -1,19 +1,23 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Persistence.Specifications;
namespace Elsa.Services
{
public class WorkflowPublisher : IWorkflowPublisher
{
private readonly IWorkflowDefinitionStore _workflowDefinitionStore;
private readonly IWorkflowInstanceStore _workflowInstanceStore;
private readonly IIdGenerator _idGenerator;
private readonly ICloner _cloner;
public WorkflowPublisher(IWorkflowDefinitionStore workflowDefinitionStore, IIdGenerator idGenerator, ICloner cloner)
public WorkflowPublisher(IWorkflowDefinitionStore workflowDefinitionStore, IWorkflowInstanceStore workflowInstanceStore, IIdGenerator idGenerator, ICloner cloner)
{
_workflowDefinitionStore = workflowDefinitionStore;
_workflowInstanceStore = workflowInstanceStore;
_idGenerator = idGenerator;
_cloner = cloner;
}
@ -35,9 +39,7 @@ namespace Elsa.Services
return definition;
}
public async Task<WorkflowDefinition?> PublishAsync(
string workflowDefinitionId,
CancellationToken cancellationToken)
public async Task<WorkflowDefinition?> PublishAsync(string workflowDefinitionId, CancellationToken cancellationToken)
{
var definition = await _workflowDefinitionStore.FindByIdAsync(
workflowDefinitionId,
@ -128,6 +130,17 @@ namespace Elsa.Services
return draft;
}
public async Task DeleteAsync(string workflowDefinitionId, CancellationToken cancellationToken)
{
var allVersions = await _workflowDefinitionStore.FindManyAsync(new WorkflowDefinitionIdSpecification(workflowDefinitionId), cancellationToken: cancellationToken);
var allVersionIds = allVersions.Select(x => x.DefinitionVersionId).ToList();
await _workflowInstanceStore.DeleteManyAsync(new WorkflowInstanceDefinitionIdSpecification(workflowDefinitionId), cancellationToken);
await _workflowDefinitionStore.DeleteManyAsync(new ManyWorkflowDefinitionVersionIdsSpecification(allVersionIds), cancellationToken);
}
public Task DeleteAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken) => DeleteAsync(workflowDefinition.Id, cancellationToken);
private WorkflowDefinition Initialize(WorkflowDefinition workflowDefinition)
{
if (workflowDefinition.Id == null!)

View file

@ -14,56 +14,7 @@
</span>
</div>
</div>
<!-- Pinned projects -->
<div class="px-4 mt-6 sm:px-6 lg:px-8">
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Pinned Workflows</h2>
<ul class="grid grid-cols-1 gap-4 sm:gap-6 sm:grid-cols-2 xl:grid-cols-4 mt-3">
<li x-data="{ open: false }" x-on:keydown.escape="open = false" x-on:click.away="open = false" class="relative col-span-1 flex shadow-sm rounded-md">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-pink-600 text-white text-sm leading-5 font-medium rounded-l-md">
DA
</div>
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 px-4 py-2 text-sm leading-5 truncate">
<a href="#" class="text-gray-900 font-medium hover:text-gray-600 transition ease-in-out duration-150">
Document Approval
</a>
<p class="text-gray-500">12 Instances</p>
</div>
<div class="flex-shrink-0 pr-2">
<button x-on:click="open = !open" id="pinned-project-options-menu-0" aria-haspopup="true" class="w-8 h-8 inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100 transition ease-in-out duration-150">
<!-- Heroicon name: dots-vertical -->
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
</svg>
</button>
<div x-show="open"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="z-10 mx-3 origin-top-right absolute right-10 top-3 w-48 mt-1 rounded-md shadow-lg">
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="pinned-project-options-menu-0">
<div class="py-1">
<a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Edit</a>
</div>
<div class="border-t border-gray-100"></div>
<div class="py-1">
<a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
Removed
from pinned
</a>
</div>
</div>
</div>
</div>
</div>
</li>
<!-- More project cards... -->
</ul>
</div>
<!-- Projects list (only on smallest breakpoint) -->
<div class="mt-10 sm:hidden">
<div class="px-4 sm:px-6">
@ -95,6 +46,9 @@
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
<span class="lg:pl-2">Name</span>
</th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
<span class="lg:pl-2">Display Name</span>
</th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Instances
</th>
@ -110,13 +64,20 @@
<tbody class="bg-white divide-y divide-gray-100">
@foreach (var workflowDefinition in Definitions.Items)
{
var displayName = workflowDefinition.DisplayName ?? workflowDefinition.Name ?? "Unnamed";
var viewUrl = $"workflow-definitions/{workflowDefinition.DefinitionVersionId}/viewer";
var name = string.IsNullOrWhiteSpace(workflowDefinition.Name) ? "-" : workflowDefinition.Name;
var displayName = !string.IsNullOrWhiteSpace(workflowDefinition.DisplayName) ? workflowDefinition.DisplayName : !string.IsNullOrWhiteSpace(workflowDefinition.Name) ? workflowDefinition.Name : "(Untitled)";
var viewUrl = $"workflow-definitions/{workflowDefinition.DefinitionVersionId}/designer";
<tr>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-pink-600"></div>
<a href="@viewUrl" class="truncate hover:text-gray-600">
<span>@name</span>
</a>
</div>
</td>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<a href="@viewUrl" class="truncate hover:text-gray-600">
<span>@displayName</span>
</a>
@ -163,63 +124,6 @@
}
</tbody>
</table>
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
<div class="flex-1 flex justify-between sm:hidden">
<a href="#" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
Previous
</a>
<a href="#" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
Next
</a>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm leading-5 text-gray-700">
Showing
<span class="font-medium">1</span>
to
<span class="font-medium">10</span>
of
<span class="font-medium">97</span>
results
</p>
</div>
<div>
<nav class="relative z-0 inline-flex shadow-sm">
<a href="#" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150" aria-label="Previous">
<svg class="h-5 w-5" x-description="Heroicon name: chevron-left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
1
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
2
</a>
<a href="#" class="hidden md:inline-flex -ml-px relative items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
3
</a>
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
...
</span>
<a href="#" class="hidden md:inline-flex -ml-px relative items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
8
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
9
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
10
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150" aria-label="Next">
<svg class="h-5 w-5" x-description="Heroicon name: chevron-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
</a>
</nav>
</div>
</div>
</div>
<Pager Page="@Definitions.Page" PageSize="@Definitions.PageSize" TotalCount="@Definitions.TotalCount"/>
</div>
</div>

View file

@ -26,12 +26,12 @@ namespace ElsaDashboard.Application.Pages.WorkflowDefinitions
private async Task OnDeleteWorkflowDefinitionClick(WorkflowDefinition workflowDefinition)
{
var result = await ConfirmDialogService.Show("Delete Workflow Instance", "Are you sure you want to delete this workflow instance?", "Delete");
var result = await ConfirmDialogService.Show("Delete Workflow Definition", "Are you sure you want to delete this workflow definition? This will also delete any and all of its workflow instances.", "Delete");
if (result.Cancelled)
return;
//await WorkflowDefinitionService.DeleteAsync(workflowInstance.Id);
await WorkflowDefinitionService.DeleteAsync(workflowDefinition.Id);
await LoadWorkflowDefinitionsAsync();
}
}

View file

@ -7,56 +7,6 @@
</h1>
</div>
</div>
<!-- Pinned projects -->
<div class="px-4 mt-6 sm:px-6 lg:px-8">
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Pinned Workflows</h2>
<ul class="grid grid-cols-1 gap-4 sm:gap-6 sm:grid-cols-2 xl:grid-cols-4 mt-3">
<li x-data="{ open: false }" x-on:keydown.escape="open = false" x-on:click.away="open = false" class="relative col-span-1 flex shadow-sm rounded-md">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-pink-600 text-white text-sm leading-5 font-medium rounded-l-md">
DA
</div>
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 px-4 py-2 text-sm leading-5 truncate">
<a href="#" class="text-gray-900 font-medium hover:text-gray-600 transition ease-in-out duration-150">
Document Approval
</a>
<p class="text-gray-500">12 Instances</p>
</div>
<div class="flex-shrink-0 pr-2">
<button x-on:click="open = !open" id="pinned-project-options-menu-0" aria-haspopup="true" class="w-8 h-8 inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100 transition ease-in-out duration-150">
<!-- Heroicon name: dots-vertical -->
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
</svg>
</button>
<div x-show="open"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="z-10 mx-3 origin-top-right absolute right-10 top-3 w-48 mt-1 rounded-md shadow-lg">
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="pinned-project-options-menu-0">
<div class="py-1">
<a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Edit</a>
</div>
<div class="border-t border-gray-100"></div>
<div class="py-1">
<a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
Removed
from pinned
</a>
</div>
</div>
</div>
</div>
</div>
</li>
<!-- More project cards... -->
</ul>
</div>
<!-- Projects list (only on smallest breakpoint) -->
<div class="mt-10 sm:hidden">
<div class="px-4 sm:px-6">
@ -108,11 +58,11 @@
var latestVersion = workflowBlueprints.First();
var publishedVersion = workflowBlueprints.FirstOrDefault(x => x.IsPublished);
var displayName = latestVersion.DisplayName ?? latestVersion.Name ?? "Unnamed";
var viewUrl = $"workflow-registry/{workflowBlueprintId}/viewer";
<tr>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-pink-600"></div>
<a href="@($"workflow-registry/{workflowBlueprintId}/viewer")" class="truncate hover:text-gray-600">
<a href="@viewUrl" class="truncate hover:text-gray-600">
<span>@displayName</span>
</a>
</div>
@ -135,121 +85,20 @@
@(publishedVersion?.Version.ToString() ?? "-")
</td>
<td class="pr-6">
<div x-data="{ open: false }" x-on:keydown.escape="open = false" x-on:click.away="open = false" class="relative flex justify-end items-center">
<button x-on:click="open = !open" id="project-options-menu-0" aria-has-popup="true" type="button" class="w-8 h-8 inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100 transition ease-in-out duration-150">
<!-- Heroicon name: dots-vertical -->
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
<ContextMenu>
<ContextMenuItem Url="@viewUrl">
<!-- Heroicon name: pencil-alt -->
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/>
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"/>
</svg>
</button>
<div x-show="open"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="z-10 mx-3 origin-top-right absolute right-7 top-0 w-48 mt-1 rounded-md shadow-lg">
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="project-options-menu-0">
<div class="py-1">
<a href="@($"workflow-registry/{workflowBlueprintId}/viewer")" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
<!-- Heroicon name: pencil-alt -->
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/>
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"/>
</svg>
Edit
</a>
<a href="#" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
<!-- Heroicon name: duplicate -->
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M7 9a2 2 0 012-2h6a2 2 0 012 2v6a2 2 0 01-2 2H9a2 2 0 01-2-2V9z"/>
<path d="M5 3a2 2 0 00-2 2v6a2 2 0 002 2V5h8a2 2 0 00-2-2H5z"/>
</svg>
Duplicate
</a>
<a href="#" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
<!-- Heroicon name: user-add -->
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"/>
</svg>
Share
</a>
</div>
<div class="border-t border-gray-100"></div>
<div class="py-1">
<a href="#" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
<!-- Heroicon name: trash -->
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
Delete
</a>
</div>
</div>
</div>
</div>
View
</ContextMenuItem>
</ContextMenu>
</td>
</tr>
}
</tbody>
</table>
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
<div class="flex-1 flex justify-between sm:hidden">
<a href="#" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
Previous
</a>
<a href="#" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
Next
</a>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm leading-5 text-gray-700">
Showing
<span class="font-medium">1</span>
to
<span class="font-medium">10</span>
of
<span class="font-medium">97</span>
results
</p>
</div>
<div>
<nav class="relative z-0 inline-flex shadow-sm">
<a href="#" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150" aria-label="Previous">
<svg class="h-5 w-5" x-description="Heroicon name: chevron-left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
1
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
2
</a>
<a href="#" class="hidden md:inline-flex -ml-px relative items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
3
</a>
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
...
</span>
<a href="#" class="hidden md:inline-flex -ml-px relative items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
8
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
9
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
10
</a>
<a href="#" class="-ml-px relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150" aria-label="Next">
<svg class="h-5 w-5" x-description="Heroicon name: chevron-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
</a>
</nav>
</div>
</div>
</div>
</div>
</div>

View file

@ -23,5 +23,6 @@ namespace ElsaDashboard.Backend.Rpc
public Task<WorkflowDefinition> GetByVersionIdAsync(GetWorkflowDefinitionByVersionIdRequest request, CallContext context) => _elsaClient.WorkflowDefinitions.GetByVersionIdAsync(request.WorkflowDefinitionVersionId, context.CancellationToken);
public async Task<WorkflowDefinition> SaveAsync(SaveWorkflowDefinitionRequest request, CallContext context) => await _elsaClient.WorkflowDefinitions.SaveAsync(request, context.CancellationToken);
public async Task DeleteAsync(DeleteWorkflowDefinitionRequest request, CallContext context = default) => await _elsaClient.WorkflowDefinitions.DeleteAsync(request.WorkflowDefinitionId, context.CancellationToken);
}
}

View file

@ -5,13 +5,9 @@ namespace ElsaDashboard.Shared.Rpc
{
public static class WorkflowDefinitionServiceExtensions
{
public static async Task<PagedList<WorkflowDefinition>> ListAsync(this IWorkflowDefinitionService service, int? page = default, int? pageSize = default, VersionOptions? versionOptions = default) =>
await service.ListAsync(new ListWorkflowDefinitionsRequest(page, pageSize, versionOptions));
public static async Task<WorkflowDefinition> GetByIdAsync(this IWorkflowDefinitionService service, string workflowDefinitionId, VersionOptions? versionOptions = default) =>
await service.GetByIdAsync(new GetWorkflowDefinitionByIdRequest(workflowDefinitionId, versionOptions));
public static async Task<WorkflowDefinition> GetByVersionIdAsync(this IWorkflowDefinitionService service, string workflowDefinitionVersionId) =>
await service.GetByVersionIdAsync(new GetWorkflowDefinitionByVersionIdRequest(workflowDefinitionVersionId));
public static async Task<PagedList<WorkflowDefinition>> ListAsync(this IWorkflowDefinitionService service, int? page = default, int? pageSize = default, VersionOptions? versionOptions = default) => await service.ListAsync(new ListWorkflowDefinitionsRequest(page, pageSize, versionOptions));
public static async Task<WorkflowDefinition> GetByIdAsync(this IWorkflowDefinitionService service, string workflowDefinitionId, VersionOptions? versionOptions = default) => await service.GetByIdAsync(new GetWorkflowDefinitionByIdRequest(workflowDefinitionId, versionOptions));
public static async Task<WorkflowDefinition> GetByVersionIdAsync(this IWorkflowDefinitionService service, string workflowDefinitionVersionId) => await service.GetByVersionIdAsync(new GetWorkflowDefinitionByVersionIdRequest(workflowDefinitionVersionId));
public static async Task DeleteAsync(this IWorkflowDefinitionService service, string workflowDefinitionId) => await service.DeleteAsync(new DeleteWorkflowDefinitionRequest(workflowDefinitionId));
}
}

View file

@ -0,0 +1,19 @@
using ProtoBuf;
namespace ElsaDashboard.Shared.Rpc
{
[ProtoContract]
public class DeleteWorkflowDefinitionRequest
{
public DeleteWorkflowDefinitionRequest()
{
}
public DeleteWorkflowDefinitionRequest(string workflowDefinitionId)
{
WorkflowDefinitionId = workflowDefinitionId;
}
[ProtoMember(1)] public string WorkflowDefinitionId { get; set; } = default!;
}
}

View file

@ -19,5 +19,8 @@ namespace ElsaDashboard.Shared.Rpc
[Operation]
Task<WorkflowDefinition> SaveAsync(SaveWorkflowDefinitionRequest request, CallContext context = default);
[Operation]
Task DeleteAsync(DeleteWorkflowDefinitionRequest request, CallContext context = default);
}
}

View file

@ -1,3 +1,4 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
@ -8,6 +9,7 @@ using Elsa.Persistence.YesSql.Documents;
using Elsa.Persistence.YesSql.Extensions;
using Elsa.Persistence.YesSql.Indexes;
using YesSql;
using YesSql.Services;
using IIdGenerator = Elsa.Services.IIdGenerator;
namespace Elsa.Persistence.YesSql.Stores
@ -24,9 +26,10 @@ namespace Elsa.Persistence.YesSql.Stores
{
return specification switch
{
EntityIdSpecification<WorkflowDefinition> entityIdSpecification => Query<WorkflowDefinitionIndex>(x => x.DefinitionId == entityIdSpecification.Id),
VersionOptionsSpecification versionOptionsSpecification => Query<WorkflowDefinitionIndex>().WithVersion(versionOptionsSpecification.VersionOptions),
WorkflowDefinitionIdSpecification definitionIdSpecification => definitionIdSpecification.VersionOptions == null ? Query<WorkflowDefinitionIndex>(x => x.DefinitionId == definitionIdSpecification.Id) : Query<WorkflowDefinitionIndex>(x => x.DefinitionId == definitionIdSpecification.Id).WithVersion(definitionIdSpecification.VersionOptions),
EntityIdSpecification<WorkflowDefinition> s => Query<WorkflowDefinitionIndex>(x => x.DefinitionId == s.Id),
VersionOptionsSpecification s => Query<WorkflowDefinitionIndex>().WithVersion(s.VersionOptions),
WorkflowDefinitionIdSpecification s => s.VersionOptions == null ? Query<WorkflowDefinitionIndex>(x => x.DefinitionId == s.Id) : Query<WorkflowDefinitionIndex>(x => x.DefinitionId == s.Id).WithVersion(s.VersionOptions),
ManyWorkflowDefinitionVersionIdsSpecification s => Query<WorkflowDefinitionIndex>(x => x.DefinitionVersionId.IsIn(s.Ids)),
_ => AutoMapSpecification<WorkflowDefinitionIndex>(specification)
};
}

View file

@ -0,0 +1,37 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
{
[ApiController]
[ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflow-definitions/{id}")]
[Produces("application/json")]
public class Delete : ControllerBase
{
private readonly IWorkflowPublisher _workflowPublisher;
public Delete(IWorkflowPublisher workflowPublisher)
{
_workflowPublisher = workflowPublisher;
}
[HttpDelete]
[ProducesResponseType(StatusCodes.Status202Accepted)]
[SwaggerOperation(
Summary = "Deletes a workflow definition and all of its versions and workflow instances.",
Description = "Deletes a workflow definition and all of its versions and workflow instances.",
OperationId = "WorkflowDefinitions.Delete",
Tags = new[] { "WorkflowDefinitions" })
]
public async Task<IActionResult> Handle(string id, CancellationToken cancellationToken)
{
await _workflowPublisher.DeleteAsync(id, cancellationToken);
return Accepted();
}
}
}