Implement workflow filter

This commit is contained in:
Sipke Schoorstra 2020-12-16 23:03:08 +01:00
parent 8419424c98
commit 98b9957fae
13 changed files with 98 additions and 59 deletions

View file

@ -0,0 +1,8 @@
namespace Elsa.Client.Models
{
public enum OrderBy
{
Started,
Finished
}
}

View file

@ -19,30 +19,30 @@ namespace Elsa.Client.Models
PostScheduledActivities = new Stack<ScheduledActivity>();
}
[DataMember(Order = 1)] public int Id { get; set; }
[DataMember(Order = 2)] public string WorkflowInstanceId { get; set; } = default!;
[DataMember(Order = 3)] public string WorkflowDefinitionId { get; set; } = default!;
[DataMember(Order = 4)] public int Version { get; set; }
[DataMember(Order = 5)] public WorkflowStatus Status { get; set; }
[DataMember(Order = 6)] public string? CorrelationId { get; set; }
[DataMember(Order = 7)] public string? ContextId { get; set; }
[DataMember(Order = 8)] public Instant CreatedAt { get; set; }
[DataMember(Order = 9)] public Instant? LastExecutedAt { get; set; }
[DataMember(Order = 10)] public Instant? LastBurstAt { get; set; }
[DataMember(Order = 11)] public Instant? CompletedAt { get; set; }
[DataMember(Order = 12)] public Variables Variables { get; set; }
[DataMember(Order = 13)] public object? Output { get; set; }
[DataMember(Order = 14)] public ICollection<ActivityInstance> Activities { get; set; }
[DataMember(Order = 1)] public string EntityId { get; set; } = default!;
[DataMember(Order = 2)] public string DefinitionId { get; set; } = default!;
[DataMember(Order = 3)] public int Version { get; set; }
[DataMember(Order = 4)] public WorkflowStatus WorkflowStatus { get; set; }
[DataMember(Order = 5)] public string? CorrelationId { get; set; }
[DataMember(Order = 6)] public string? ContextId { get; set; }
[DataMember(Order = 7)] public Instant CreatedAt { get; set; }
[DataMember(Order = 8)] public Instant? LastExecutedAt { get; set; }
[DataMember(Order = 9)] public Instant? LastBurstAt { get; set; }
[DataMember(Order = 10)] public Instant? CompletedAt { get; set; }
[DataMember(Order = 11)] public Variables Variables { get; set; }
[DataMember(Order = 12)] public object? Output { get; set; }
[DataMember(Order = 13)] public ICollection<ActivityInstance> Activities { get; set; }
[DataMember(Order = 15)] public HashSet<BlockingActivity> BlockingActivities
[DataMember(Order = 14)]
public HashSet<BlockingActivity> BlockingActivities
{
get => _blockingActivities;
set => _blockingActivities = new HashSet<BlockingActivity>(value, BlockingActivityEqualityComparer.Instance);
}
[DataMember(Order = 16)] public ICollection<ExecutionLogEntry> ExecutionLog { get; set; }
[DataMember(Order = 17)] public WorkflowFault? Fault { get; set; }
[DataMember(Order = 18)] public Stack<ScheduledActivity> ScheduledActivities { get; set; }
[DataMember(Order = 19)] public Stack<ScheduledActivity> PostScheduledActivities { get; set; }
[DataMember(Order = 15)] public ICollection<ExecutionLogEntry> ExecutionLog { get; set; }
[DataMember(Order = 16)] public WorkflowFault? Fault { get; set; }
[DataMember(Order = 17)] public Stack<ScheduledActivity> ScheduledActivities { get; set; }
[DataMember(Order = 18)] public Stack<ScheduledActivity> PostScheduledActivities { get; set; }
}
}

View file

@ -9,10 +9,16 @@ namespace Elsa.Client.Services
{
[Get("/v1/workflow-instances/{id}")]
Task<WorkflowInstance?> GetByIdAsync(string id, CancellationToken cancellationToken = default);
[Get("/v1/workflow-instances")]
Task<PagedList<WorkflowInstance>> ListAsync(int? page = default, int? pageSize = default, CancellationToken cancellationToken = default);
Task<PagedList<WorkflowInstance>> ListAsync(
int? page = default,
int? pageSize = default,
[AliasAs("workflow")] string? workflowDefinitionId = default,
[AliasAs("status")] WorkflowStatus? workflowStatus = default,
OrderBy? orderBy = default,
CancellationToken cancellationToken = default);
[Delete("/v1/workflow-instances/{id}")]
Task DeleteAsync(string id, CancellationToken cancellationToken = default);
}

View file

@ -24,6 +24,5 @@ namespace Elsa.Builders
IActivityBuilder WithName(string? name);
IActivityBuilder LoadWorkflowContext(bool value = true);
IActivityBuilder SaveWorkflowContext(bool value = true);
//Func<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> BuildActivityAsync();
}
}

View file

@ -88,18 +88,18 @@
<tbody class="bg-white divide-y divide-gray-100">
@foreach (var workflowInstance in WorkflowInstances.Items)
{
var workflowBlueprint = WorkflowBlueprints[(workflowInstance.WorkflowDefinitionId, workflowInstance.Version)];
var workflowBlueprint = WorkflowBlueprints[(workflowInstance.DefinitionId, workflowInstance.Version)];
var displayName = workflowBlueprint.DisplayName;
var statusColor = GetStatusColor(workflowInstance.Status);
var viewUrl = $"workflow-instances/{workflowInstance.WorkflowInstanceId}/viewer";
var statusColor = GetStatusColor(workflowInstance.WorkflowStatus);
var viewUrl = $"workflow-instances/{workflowInstance.EntityId}/viewer";
<tr>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
<a href="@($"workflow-instances/{workflowInstance.WorkflowInstanceId}/viewer")" class="truncate hover:text-gray-600">
@workflowInstance.WorkflowInstanceId
<a href="@($"workflow-instances/{workflowInstance.EntityId}/viewer")" class="truncate hover:text-gray-600">
@workflowInstance.EntityId
</a>
</td>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900 text-left">
<a href="@($"workflow-registry/{workflowInstance.WorkflowDefinitionId}/viewer")" class="truncate hover:text-gray-600">
<a href="@($"workflow-registry/{workflowInstance.DefinitionId}/viewer")" class="truncate hover:text-gray-600">
@displayName
</a>
</td>
@ -109,7 +109,7 @@
<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 items-center space-x-3 lg:pl-2">
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-@statusColor-600"></div>
<span>@workflowInstance.Status</span>
<span>@workflowInstance.WorkflowStatus</span>
</div>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-left">

View file

@ -24,23 +24,9 @@ namespace ElsaDashboard.Application.Pages.WorkflowInstances
[Inject] private IConfirmDialogService ConfirmDialogService { get; set; } = default!;
private PagedList<WorkflowInstance> WorkflowInstances { get; set; } = new();
private IDictionary<(string, int), WorkflowBlueprint> WorkflowBlueprints { get; set; } = new Dictionary<(string, int), WorkflowBlueprint>();
private IEnumerable<ButtonDropdownItem> WorkflowFilterItems => WorkflowBlueprints.Values
.OrderByDescending(x => x.Version)
.GroupBy(x => x.Id)
.Select(x => x.First())
.Select(x => (x.Id, x.DisplayName))
.Select(x => new ButtonDropdownItem(x.DisplayName!, x.Id, $"workflow-instances?workflow={x.Id}", x.Id == SelectedWorkflowId));
private string SelectedWorkflowText =>
SelectedWorkflowId == null
? "Workflow"
: WorkflowBlueprints
.GroupBy(x => x.Value.Id, x => x.Value)
.FirstOrDefault(x => x.Key == SelectedWorkflowId)
?.OrderByDescending(x => x.Version)
?.FirstOrDefault()
?.DisplayName ?? "Workflow";
private IEnumerable<WorkflowBlueprint> LatestWorkflowBlueprints => GetLatestVersions(WorkflowBlueprints.Values);
private IEnumerable<ButtonDropdownItem> WorkflowFilterItems => LatestWorkflowBlueprints.Select(x => new ButtonDropdownItem(x.DisplayName!, x.Id, $"workflow-instances?workflow={x.Id}", x.Id == SelectedWorkflowId));
private string SelectedWorkflowText => SelectedWorkflowId == null ? "Workflow" : LatestWorkflowBlueprints.FirstOrDefault(x => x.Id == SelectedWorkflowId)?.DisplayName ?? "Workflow";
public void Dispose()
{
@ -66,6 +52,11 @@ namespace ElsaDashboard.Application.Pages.WorkflowInstances
await LoadWorkflowInstancesAsync();
}
private async Task LoadWorkflowInstancesAsync()
{
WorkflowInstances = await WorkflowInstanceService.ListAsync(Page, PageSize, SelectedWorkflowId);
}
private async Task OnDeleteWorkflowInstanceClick(WorkflowInstance workflowInstance)
{
var result = await ConfirmDialogService.Show("Delete Workflow Instance", "Are you sure you want to delete this workflow instance?", "Delete");
@ -73,15 +64,10 @@ namespace ElsaDashboard.Application.Pages.WorkflowInstances
if (result.Cancelled)
return;
await WorkflowInstanceService.DeleteAsync(workflowInstance.WorkflowInstanceId);
await WorkflowInstanceService.DeleteAsync(workflowInstance.EntityId);
await LoadWorkflowInstancesAsync();
}
private async Task LoadWorkflowInstancesAsync()
{
WorkflowInstances = await WorkflowInstanceService.ListAsync(Page, PageSize);
}
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
this.SetParametersFromQueryString(NavigationManager);
@ -99,5 +85,7 @@ namespace ElsaDashboard.Application.Pages.WorkflowInstances
WorkflowStatus.Cancelled => "yellow",
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
};
private static IEnumerable<WorkflowBlueprint> GetLatestVersions(IEnumerable<WorkflowBlueprint> workflowBlueprints) => workflowBlueprints.GroupBy(x => x.Id).Select(x => x.OrderByDescending(y => y.Version).First());
}
}

View file

@ -10,7 +10,10 @@ namespace ElsaDashboard.Backend.Rpc
{
private readonly IElsaClient _elsaClient;
public WorkflowInstanceService(IElsaClient elsaClient) => _elsaClient = elsaClient;
public Task<PagedList<WorkflowInstance>> ListAsync(ListWorkflowInstancesRequest request, CallContext context = default) => _elsaClient.WorkflowInstances.ListAsync(request.Page, request.PageSize, context.CancellationToken);
public Task<PagedList<WorkflowInstance>> ListAsync(ListWorkflowInstancesRequest request, CallContext context = default) =>
_elsaClient.WorkflowInstances.ListAsync(request.Page, request.PageSize, request.WorkflowDefinitionId, request.WorkflowStatus, request.OrderBy, context.CancellationToken);
public Task<WorkflowInstance?> GetByIdAsync(GetWorkflowInstanceByIdRequest request, CallContext context = default) => _elsaClient.WorkflowInstances.GetByIdAsync(request.WorkflowInstanceId, context.CancellationToken);
public Task DeleteAsync(DeleteWorkflowInstanceRequest request, CallContext context = default) => _elsaClient.WorkflowInstances.DeleteAsync(request.WorkflowInstanceId, context.CancellationToken);
}

View file

@ -6,7 +6,15 @@ namespace ElsaDashboard.Shared.Rpc
{
public static class WorkflowInstanceServiceExtensions
{
public static Task<PagedList<WorkflowInstance>> ListAsync(this IWorkflowInstanceService service, int page = 0, int pageSize = 50) => service.ListAsync(new ListWorkflowInstancesRequest(page, pageSize));
public static Task<PagedList<WorkflowInstance>> ListAsync(
this IWorkflowInstanceService service,
int page = 0,
int pageSize = 50,
string? workflowDefinitionId = default,
WorkflowStatus? workflowStatus = default,
OrderBy? orderBy = default) =>
service.ListAsync(new ListWorkflowInstancesRequest(page, pageSize, workflowDefinitionId, workflowStatus, orderBy));
public static Task<WorkflowInstance?> GetByIdAsync(this IWorkflowInstanceService service, string workflowInstanceId) => service.GetByIdAsync(new GetWorkflowInstanceByIdRequest(workflowInstanceId));
public static Task DeleteAsync(this IWorkflowInstanceService service, string workflowInstanceId) => service.DeleteAsync(new DeleteWorkflowInstanceRequest(workflowInstanceId));
}

View file

@ -1,4 +1,5 @@
using ProtoBuf;
using Elsa.Client.Models;
using ProtoBuf;
// ReSharper disable once CheckNamespace
namespace ElsaDashboard.Shared.Rpc
@ -10,13 +11,19 @@ namespace ElsaDashboard.Shared.Rpc
{
}
public ListWorkflowInstancesRequest(int page, int pageSize = 50)
public ListWorkflowInstancesRequest(int page, int pageSize = 50, string? workflowDefinitionId = default, WorkflowStatus? workflowStatus = default, OrderBy? orderBy = default)
{
Page = page;
PageSize = pageSize;
WorkflowDefinitionId = workflowDefinitionId;
WorkflowStatus = workflowStatus;
OrderBy = orderBy;
}
[ProtoMember(1)] public int Page { get; set; }
[ProtoMember(2)] public int PageSize { get; set; } = 50;
[ProtoMember(3)] public string? WorkflowDefinitionId { get; set; }
[ProtoMember(4)] public WorkflowStatus? WorkflowStatus { get; set; }
[ProtoMember(5)] public OrderBy? OrderBy { get; set; }
}
}

View file

@ -32,9 +32,19 @@ namespace Elsa.Server.Api.Endpoints.WorkflowInstances
OperationId = "WorkflowInstances.List",
Tags = new[] { "WorkflowInstances" })
]
public async Task<ActionResult<PagedList<WorkflowInstance>>> Handle(int page = 0, int pageSize = 50, CancellationToken cancellationToken = default)
public async Task<ActionResult<PagedList<WorkflowInstance>>> Handle(
[FromQuery(Name = "workflow")] string? workflowDefinitionId = default,
[FromQuery(Name = "status")] WorkflowStatus? workflowStatus = default,
[FromQuery] OrderBy? orderBy = default,
int page = 0,
int pageSize = 50,
CancellationToken cancellationToken = default)
{
var specification = Specification<WorkflowInstance>.All;
if (!string.IsNullOrWhiteSpace(workflowDefinitionId))
specification = specification.WithWorkflowDefinition(workflowDefinitionId);
var totalCount = await _workflowInstanceStore.CountAsync(specification, cancellationToken: cancellationToken);
var paging = Paging.Page(page, pageSize);
var workflowInstances = await _workflowInstanceStore.FindManyAsync(specification, paging: paging, cancellationToken: cancellationToken).ToList();

View file

@ -0,0 +1,8 @@
namespace Elsa.Server.Api.Models
{
public enum OrderBy
{
Started,
Finished
}
}

View file

@ -9,6 +9,7 @@ namespace Elsa.Server.Host.Workflows
public void Build(IWorkflowBuilder workflow)
{
workflow
.WithWorkflowDefinitionId("HelloWorld")
.WithVersion(1)
.WithDisplayName("Hello World!")
.HttpRequestReceived("/hello-world")

View file

@ -9,9 +9,10 @@ namespace Elsa.Server.Host.Workflows
public void Build(IWorkflowBuilder workflow)
{
workflow
.WithWorkflowDefinitionId("HelloWorld")
.WithVersion(2)
.WithDisplayName("Hello World!")
.HttpRequestReceived("/hello-world")
.HttpRequestReceived("/hello-world/v2")
.WriteHttpResponse(HttpStatusCode.OK, "Hello World V2!", "text/plain");
}
}