Incremental work on dashboard + API

This commit is contained in:
Sipke Schoorstra 2020-12-08 22:23:52 +01:00
parent 4c9fa9350b
commit dac4610ac8
32 changed files with 595 additions and 29 deletions

View file

@ -4,15 +4,17 @@ namespace Elsa.Client
{
public class ElsaClient : IElsaClient
{
public ElsaClient(IActivitiesApi activitiesApi, IWorkflowDefinitionsApi workflowDefinitionsApi, IWorkflowRegistryApi workflowRegistry)
public ElsaClient(IActivitiesApi activities, IWorkflowDefinitionsApi workflowDefinitions, IWorkflowRegistryApi workflowRegistry, IWorkflowInstanceApi workflowInstances)
{
Activities = activitiesApi;
WorkflowDefinitions = workflowDefinitionsApi;
Activities = activities;
WorkflowDefinitions = workflowDefinitions;
WorkflowRegistry = workflowRegistry;
WorkflowInstances = workflowInstances;
}
public IActivitiesApi Activities { get; }
public IWorkflowDefinitionsApi WorkflowDefinitions { get; }
public IWorkflowRegistryApi WorkflowRegistry { get; }
public IWorkflowInstanceApi WorkflowInstances { get; }
}
}

View file

@ -7,5 +7,6 @@ namespace Elsa.Client
IActivitiesApi Activities { get; }
IWorkflowDefinitionsApi WorkflowDefinitions { get; }
IWorkflowRegistryApi WorkflowRegistry { get; }
IWorkflowInstanceApi WorkflowInstances { get; }
}
}

View file

@ -0,0 +1,16 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Client.Models;
using Refit;
namespace Elsa.Client.Services
{
public interface IWorkflowInstanceApi
{
[Get("/v1/workflow-instance/{id}")]
Task<WorkflowInstance?> GetByIdAsync(string id, CancellationToken cancellationToken = default);
[Get("/v1/workflow-instance")]
Task<PagedList<WorkflowInstance>> ListAsync(int? page = default, int? pageSize = default, CancellationToken cancellationToken = default);
}
}

View file

@ -21,7 +21,16 @@ namespace Elsa.Extensions
WorkflowStatus status,
CancellationToken cancellationToken = default) =>
await manager.Query<WorkflowInstanceIndex>(x => x.CorrelationId == correlationId && x.WorkflowStatus == status).FirstOrDefaultAsync();
public static async Task<int> CountAsync(this IWorkflowInstanceManager manager, CancellationToken cancellationToken = default) => await manager.Query<WorkflowInstanceIndex>().CountAsync();
public static async Task<IEnumerable<WorkflowInstance>> ListAsync(
this IWorkflowInstanceManager manager,
int page,
int pageSize,
CancellationToken cancellationToken = default) =>
await manager.Query<WorkflowInstanceIndex>().Skip(page * pageSize).Take(pageSize).ListAsync();
public static async Task<IEnumerable<WorkflowInstance>> ListByCorrelationIdAsync(
this IWorkflowInstanceManager manager,
string correlationId,

View file

@ -1,9 +1,9 @@
@page "/workflows/designer"
@page "/workflows/{workflowDefinitionId}/designer"
@page "/workflow-definitions/designer"
@page "/workflow-definitions/{workflowDefinitionId}/designer"
<div class="flex-0 border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Workflow Editor
Workflow Definition Editor
</h1>
</div>
<div class="mt-4 flex sm:mt-0 sm:ml-4">

View file

@ -8,7 +8,7 @@ using ElsaDashboard.Application.Services;
using ElsaDashboard.Shared.Rpc;
using Microsoft.AspNetCore.Components;
namespace ElsaDashboard.Application.Pages
namespace ElsaDashboard.Application.Pages.WorkflowDefinitions
{
partial class Designer
{

View file

@ -8,7 +8,7 @@
</div>
<div class="mt-4 flex sm:mt-0 sm:ml-4">
<span class="order-0 sm:order-1 sm:ml-3 shadow-sm rounded-md">
<a href="workflows/designer" type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-500 hover:bg-blue-400 focus:outline-none focus:shadow-outline-blue focus:border-blue-700 active:bg-blue-700 transition duration-150 ease-in-out">
<a href="workflow-definitions/designer" type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-500 hover:bg-blue-400 focus:outline-none focus:shadow-outline-blue focus:border-blue-700 active:bg-blue-700 transition duration-150 ease-in-out">
Create
</a>
</span>
@ -115,7 +115,7 @@
<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="@($"workflows/{workflowDefinition.WorkflowDefinitionId}/designer")" class="truncate hover:text-gray-600">
<a href="@($"workflow-definitions/{workflowDefinition.WorkflowDefinitionId}/designer")" class="truncate hover:text-gray-600">
<span>@displayName</span>
</a>
</div>
@ -154,7 +154,7 @@
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="@($"workflows/{workflowDefinition.WorkflowDefinitionVersionId}/designer")" 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">
<a href="@($"workflow-definitions/{workflowDefinition.WorkflowDefinitionVersionId}/designer")" 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"/>

View file

@ -3,9 +3,9 @@ using Elsa.Client.Models;
using ElsaDashboard.Shared.Rpc;
using Microsoft.AspNetCore.Components;
namespace ElsaDashboard.Application.Pages
namespace ElsaDashboard.Application.Pages.WorkflowDefinitions
{
partial class WorkflowDefinitions
partial class List
{
[Inject] private IWorkflowDefinitionService WorkflowDefinitionService { get; set; } = default!;
private PagedList<WorkflowDefinition> Definitions { get; set; } = new();

View file

@ -0,0 +1,211 @@
@page "/workflow-instances"
<!-- Page title & actions -->
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Workflow Instances
</h1>
</div>
</div>
<!-- Workflow instances (only on smallest breakpoint) -->
<div class="mt-10 sm:hidden">
<div class="px-4 sm:px-6">
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Workflow Instances</h2>
</div>
<ul class="mt-3 border-t border-gray-200 divide-y divide-gray-100">
<li>
<a href="#" class="flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<div class="flex items-center truncate space-x-3">
<div class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-pink-600"></div>
<p class="font-medium truncate text-sm leading-6">Document Approval</p>
</div>
<!-- Heroicon name: chevron-right -->
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" 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"/>
</svg>
</a>
</li>
<!-- More project rows... -->
</ul>
</div>
<!-- Workflow instances table (small breakpoint and up) -->
<div class="hidden mt-8 sm:block">
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="min-w-full">
<thead>
<tr class="border-t border-gray-200">
<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">Workflow</span>
</th>
<th class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Version
</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">
Created
</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">
Completed
</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">
Status
</th>
<th class="pr-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"></th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
@foreach (var workflowBlueprint in WorkflowBlueprints.Items)
{
var displayName = workflowBlueprint.DisplayName ?? workflowBlueprint.Name ?? "Unnamed";
<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">
<a href="@($"workflow-registry/{workflowBlueprint.Id}/viewer")" class="truncate hover:text-gray-600">
<span>@displayName</span>
</a>
</div>
</td>
<td class="px-6 py-3 text-sm leading-5 text-gray-500 font-medium">
<div class="flex items-center space-x-2">
<div class="flex items-center space-x-2">
<div class="flex flex-shrink-0 -space-x-1">
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-blue-500 hover:bg-blue-400" href="#">4</a>
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-green-500 hover:bg-green-400" href="#">999</a>
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-red-500 hover:bg-red-400" href="#">75</a>
</div>
</div>
</div>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
1
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-green-600"></div>
Completed
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
Today
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
Today
</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"/>
</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/{workflowBlueprint.Id}/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>
</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

@ -0,0 +1,18 @@
using System.Threading.Tasks;
using Elsa.Client.Models;
using ElsaDashboard.Shared.Rpc;
using Microsoft.AspNetCore.Components;
namespace ElsaDashboard.Application.Pages.WorkflowInstances
{
partial class List
{
[Inject] private IWorkflowRegistryService WorkflowRegistryService { get; set; } = default!;
private PagedList<WorkflowBlueprint> WorkflowBlueprints { get; set; } = new();
protected override async Task OnInitializedAsync()
{
WorkflowBlueprints = await WorkflowRegistryService.ListAsync();
}
}
}

View file

@ -108,7 +108,7 @@
<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/{workflowBlueprint.Id}/designer")" class="truncate hover:text-gray-600">
<a href="@($"workflow-registry/{workflowBlueprint.Id}/viewer")" class="truncate hover:text-gray-600">
<span>@displayName</span>
</a>
</div>
@ -147,7 +147,7 @@
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/{workflowBlueprint.Id}/designer")" 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">
<a href="@($"workflow-registry/{workflowBlueprint.Id}/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"/>

View file

@ -3,9 +3,9 @@ using Elsa.Client.Models;
using ElsaDashboard.Shared.Rpc;
using Microsoft.AspNetCore.Components;
namespace ElsaDashboard.Application.Pages
namespace ElsaDashboard.Application.Pages.WorkflowRegistry
{
partial class WorkflowRegistry
partial class List
{
[Inject] private IWorkflowRegistryService WorkflowRegistryService { get; set; } = default!;
private PagedList<WorkflowBlueprint> WorkflowBlueprints { get; set; } = new();

View file

@ -0,0 +1,16 @@
@page "/workflow-registry/{workflowDefinitionId}/viewer"
<div class="flex-0 border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Workflow Viewer
</h1>
</div>
<div class="mt-4 flex sm:mt-0 sm:ml-4">
<span class="order-0 sm:order-1 sm:ml-3 shadow-sm rounded-md">
<button type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-500 hover:bg-blue-400 focus:outline-none focus:shadow-outline-blue focus:border-blue-700 active:bg-blue-700 transition duration-150 ease-in-out">
Print
</button>
</span>
</div>
</div>
<WorkflowDesigner Model="WorkflowModel" />

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Elsa.Client.Models;
using ElsaDashboard.Application.Models;
using ElsaDashboard.Shared.Rpc;
using Microsoft.AspNetCore.Components;
namespace ElsaDashboard.Application.Pages.WorkflowRegistry
{
partial class Viewer
{
[Parameter] public string WorkflowDefinitionId { get; set; } = default!;
[Inject] private IWorkflowRegistryService WorkflowRegistryService { get; set; } = default!;
[Inject] private IActivityService ActivityService { get; set; } = default!;
private IDictionary<string, ActivityInfo> ActivityDescriptors { get; set; } = default!;
private WorkflowBlueprint WorkflowBlueprint { get; set; } = default!;
private WorkflowModel WorkflowModel { get; set; } = WorkflowModel.Blank();
protected override async Task OnInitializedAsync()
{
ActivityDescriptors = (await ActivityService.GetActivitiesAsync()).ToDictionary(x => x.Type);
}
protected override async Task OnParametersSetAsync()
{
WorkflowBlueprint = await WorkflowRegistryService.GetById(WorkflowDefinitionId, VersionOptions.Latest);
WorkflowModel = CreateWorkflowModel(WorkflowBlueprint);
}
private WorkflowModel CreateWorkflowModel(WorkflowBlueprint workflowBlueprint)
{
return new()
{
Name = workflowBlueprint.Name,
Activities = workflowBlueprint.Activities.Select(CreateActivityModel).ToImmutableList(),
Connections = workflowBlueprint.Connections.Select(CreateConnectionModel).ToImmutableList()
};
}
private ConnectionModel CreateConnectionModel(ConnectionDefinition connectionDefinition)
{
return new(connectionDefinition.SourceActivityId, connectionDefinition.TargetActivityId, connectionDefinition.Outcome);
}
private ActivityModel CreateActivityModel(ActivityBlueprint activityBlueprint)
{
var descriptor = ActivityDescriptors[activityBlueprint.Type];
return new ActivityModel(activityBlueprint.Id, activityBlueprint.Type, descriptor.Outcomes);
}
}
}

View file

@ -11,8 +11,11 @@ namespace ElsaDashboard.Backend.Extensions
.UseCors()
.UseGrpcWeb()
.UseEndpoints(endpoints => endpoints
.MapGrpcEndpoint<IActivityService>()
.MapGrpcEndpoint<IWorkflowDefinitionService>());
.MapGrpcEndpoint<IActivityService>()
.MapGrpcEndpoint<IWorkflowDefinitionService>()
.MapGrpcEndpoint<IWorkflowRegistryService>()
.MapGrpcEndpoint<IWorkflowInstanceService>()
);
}
}
}

View file

@ -2,6 +2,7 @@
using System.IO.Compression;
using Elsa.Client.Extensions;
using Elsa.Client.Options;
using Elsa.Client.Services;
using ElsaDashboard.Backend.Rpc;
using ElsaDashboard.Shared.Rpc;
using ElsaDashboard.Shared.Surrogates;
@ -22,6 +23,7 @@ namespace ElsaDashboard.Backend.Extensions
services.AddScoped<IActivityService, ActivityService>();
services.AddScoped<IWorkflowDefinitionService, WorkflowDefinitionService>();
services.AddScoped<IWorkflowRegistryService, WorkflowRegistryService>();
services.AddScoped<IWorkflowInstanceService, WorkflowInstanceService>();
return services;
}

View file

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Elsa.Client;
using Elsa.Client.Models;
using ElsaDashboard.Shared.Rpc;
using ProtoBuf.Grpc;
namespace ElsaDashboard.Backend.Rpc
{
public class WorkflowInstanceService : IWorkflowInstanceService
{
private readonly IElsaClient _elsaClient;
public WorkflowInstanceService(IElsaClient elsaClient) => _elsaClient = elsaClient;
public Task<PagedList<WorkflowInstance>> ListAsync(int? page = default, int? pageSize = default, CallContext context = default) =>
_elsaClient.WorkflowInstances.ListAsync(page, pageSize, context.CancellationToken);
public Task<WorkflowInstance?> GetById(string id, CallContext context = default) => _elsaClient.WorkflowInstances.GetByIdAsync(id, context.CancellationToken);
}
}

View file

@ -0,0 +1,14 @@
using System.Threading.Tasks;
using Elsa.Client.Models;
using ProtoBuf.Grpc;
using ProtoBuf.Grpc.Configuration;
namespace ElsaDashboard.Shared.Rpc
{
[Service]
public interface IWorkflowInstanceService
{
Task<PagedList<WorkflowInstance>> ListAsync(int? page = default, int? pageSize = default, CallContext context = default);
Task<WorkflowInstance?> GetById(string id, CallContext context = default);
}
}

View file

@ -4,6 +4,7 @@ using Elsa.Extensions;
using Elsa.Models;
using Elsa.Serialization;
using Elsa.Server.Api.Swagger;
using Elsa.Server.Api.Swagger.Examples;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

View file

@ -4,6 +4,7 @@ using Elsa.Extensions;
using Elsa.Models;
using Elsa.Serialization;
using Elsa.Server.Api.Swagger;
using Elsa.Server.Api.Swagger.Examples;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

View file

@ -5,6 +5,7 @@ using Elsa.Models;
using Elsa.Serialization;
using Elsa.Server.Api.Models;
using Elsa.Server.Api.Swagger;
using Elsa.Server.Api.Swagger.Examples;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

View file

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Server.Api.Swagger;
using Elsa.Server.Api.Swagger.Examples;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

View file

@ -0,0 +1,44 @@
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
using Elsa.Server.Api.Models;
using Elsa.Server.Api.Swagger.Examples;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Endpoints.WorkflowInstances
{
[ApiController]
[ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflow-instances/{id}")]
[Produces("application/json")]
public class Get : Controller
{
private readonly IWorkflowInstanceManager _workflowInstanceManager;
public Get(IWorkflowInstanceManager workflowInstanceManager)
{
_workflowInstanceManager = workflowInstanceManager;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WorkflowInstance))]
[SwaggerOperation(
Summary = "Returns a single workflow instance.",
Description = "Returns a single workflow instance.",
OperationId = "WorkflowInstances.Get",
Tags = new[] { "WorkflowInstances" })
]
public async Task<ActionResult<WorkflowInstance>> Handle(string id, CancellationToken cancellationToken = default)
{
var workflowInstance = await _workflowInstanceManager.GetByIdAsync(id, cancellationToken);
return workflowInstance ?? (ActionResult<WorkflowInstance>) NotFound();
}
}
}

View file

@ -0,0 +1,42 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Server.Api.Models;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Open.Linq.AsyncExtensions;
using Swashbuckle.AspNetCore.Annotations;
namespace Elsa.Server.Api.Endpoints.WorkflowInstances
{
[ApiController]
[ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflow-instances")]
[Produces("application/json")]
public class List : Controller
{
private readonly IWorkflowInstanceManager _workflowInstanceManager;
public List(IWorkflowInstanceManager workflowInstanceManager)
{
_workflowInstanceManager = workflowInstanceManager;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList<WorkflowInstance>))]
[SwaggerOperation(
Summary = "Returns a list of workflow instances.",
Description = "Returns paginated a list of workflow instances.",
OperationId = "WorkflowInstances.List",
Tags = new[] { "WorkflowInstances" })
]
public async Task<ActionResult<PagedList<WorkflowInstance>>> Handle(int page = 0, int pageSize = 50, CancellationToken cancellationToken = default)
{
var totalCount = await _workflowInstanceManager.CountAsync(cancellationToken);
var workflowInstances = await _workflowInstanceManager.ListAsync(page, pageSize, cancellationToken).ToList();
return new PagedList<WorkflowInstance>(workflowInstances, page, pageSize, totalCount);
}
}
}

View file

@ -0,0 +1,4 @@
namespace Elsa.Server.Api.Endpoints.WorkflowInstances
{
}

View file

@ -0,0 +1,50 @@
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Models;
using Elsa.Server.Api.Models;
using Elsa.Server.Api.Swagger.Examples;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Endpoints.WorkflowRegistry
{
[ApiController]
[ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflow-registry/{id}/{versionOptions?}")]
[Produces("application/json")]
public class Get : Controller
{
private readonly IWorkflowRegistry _workflowRegistry;
private readonly IMapper _mapper;
public Get(IWorkflowRegistry workflowRegistry, IMapper mapper)
{
_workflowRegistry = workflowRegistry;
_mapper = mapper;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList<WorkflowBlueprintModel>))]
[SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowBlueprintModelExample))]
[SwaggerOperation(
Summary = "Returns a single workflow blueprint.",
Description = "Returns a single workflow blueprint. When no version options are specified, the latest version is returned.",
OperationId = "WorkflowBlueprints.Get",
Tags = new[] { "WorkflowBlueprints" })
]
public async Task<ActionResult<WorkflowBlueprintModel>> Handle(string id, VersionOptions? versionOptions = default, CancellationToken cancellationToken = default)
{
versionOptions ??= VersionOptions.Latest;
var workflowBlueprint = await _workflowRegistry.GetWorkflowAsync(id, null, versionOptions.Value, cancellationToken);
if (workflowBlueprint == null)
return NotFound();
return _mapper.Map<WorkflowBlueprintModel>(workflowBlueprint);
}
}
}

View file

@ -3,9 +3,9 @@ using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Models;
using Elsa.Serialization;
using Elsa.Server.Api.Models;
using Elsa.Server.Api.Swagger;
using Elsa.Server.Api.Swagger.Examples;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@ -21,19 +21,17 @@ namespace Elsa.Server.Api.Endpoints.WorkflowRegistry
public class List : Controller
{
private readonly IWorkflowRegistry _workflowRegistry;
private readonly IContentSerializer _serializer;
private readonly IMapper _mapper;
public List(IWorkflowRegistry workflowRegistry, IContentSerializer serializer, IMapper mapper)
public List(IWorkflowRegistry workflowRegistry, IMapper mapper)
{
_workflowRegistry = workflowRegistry;
_serializer = serializer;
_mapper = mapper;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList<WorkflowDefinition>))]
[SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowDefinitionPagedListExample))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList<WorkflowBlueprintModel>))]
[SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowBlueprintModelPagedListExample))]
[SwaggerOperation(
Summary = "Returns a list of workflow blueprints.",
Description = "Returns paginated a list of workflow blueprints. When no version options are specified, the latest version is returned.",

View file

@ -4,6 +4,7 @@ using Elsa.Models;
using Elsa.Server.Api.Extensions;
using Elsa.Server.Api.Mapping;
using Elsa.Server.Api.Swagger;
using Elsa.Server.Api.Swagger.Examples;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;

View file

@ -0,0 +1,43 @@
using Elsa.Models;
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Swagger.Examples
{
public class WorkflowBlueprintModelExample : IExamplesProvider<WorkflowBlueprintModel>
{
public WorkflowBlueprintModel GetExamples()
{
return new()
{
Id = "my-workflow",
Name = "ProcessOrderWorkflow",
DisplayName = "Process Order Workflow",
Description = "Process new orders",
Version = 1,
IsPublished = true,
ContextOptions = new WorkflowContextOptions
{
ContextFidelity = WorkflowContextFidelity.Burst,
ContextType = typeof(string)
},
Activities = new[]
{
new ActivityBlueprintModel
{
Id = "activity-1",
Type = "WriteLine",
Name = "Activity1"
},
new ActivityBlueprintModel
{
Id = "activity-2",
Type = "WriteLine",
Name = "Activity2",
}
},
Connections = new[] { new ConnectionModel { SourceId = "activity-1", TargetId = "activity-2", Outcome = OutcomeNames.Done } }
};
}
}
}

View file

@ -0,0 +1,16 @@
using System.Linq;
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
using Elsa.Server.Api.Models;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Swagger.Examples
{
public class WorkflowBlueprintModelPagedListExample : IExamplesProvider<PagedList<WorkflowBlueprintModel>>
{
public PagedList<WorkflowBlueprintModel> GetExamples()
{
var examples = Enumerable.Range(1, 3).Select(_ => new WorkflowBlueprintModelExample().GetExamples()).ToList();
return new PagedList<WorkflowBlueprintModel>(examples, 0, 10, examples.Count);
}
}
}

View file

@ -3,13 +3,13 @@ using Elsa.Expressions;
using Elsa.Models;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Swagger
namespace Elsa.Server.Api.Swagger.Examples
{
public class WorkflowDefinitionExample : IExamplesProvider<WorkflowDefinition>
{
public WorkflowDefinition GetExamples()
{
return new WorkflowDefinition
return new()
{
Id = 1,
Name = "ProcessOrderWorkflow",
@ -51,7 +51,7 @@ namespace Elsa.Server.Api.Swagger
}
}
},
Connections = new[] {new ConnectionDefinition("activity-1", "activity-2", OutcomeNames.Done)}
Connections = new[] { new ConnectionDefinition("activity-1", "activity-2", OutcomeNames.Done) }
};
}
}

View file

@ -3,7 +3,7 @@ using Elsa.Models;
using Elsa.Server.Api.Models;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Swagger
namespace Elsa.Server.Api.Swagger.Examples
{
public class WorkflowDefinitionPagedListExample : IExamplesProvider<PagedList<WorkflowDefinition>>
{