Add support for filtering journal by activity IDs
This commit is contained in:
parent
142af4e3b9
commit
1902307cf5
|
|
@ -21,7 +21,7 @@ public interface IWorkflowDefinitionsApi
|
|||
/// <param name="versionOptions">The version options.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
[Get("/workflow-definitions?versionOptions={versionOptions}")]
|
||||
Task<ListWorkflowDefinitionsResponse> ListAsync([Query]ListWorkflowDefinitionsRequest request, [Query]VersionOptions? versionOptions = default, CancellationToken cancellationToken = default);
|
||||
Task<PagedListResponse<WorkflowDefinitionSummary>> ListAsync([Query]ListWorkflowDefinitionsRequest request, [Query]VersionOptions? versionOptions = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a workflow definition by definition ID.
|
||||
|
|
@ -42,6 +42,15 @@ public interface IWorkflowDefinitionsApi
|
|||
[Get("/workflow-definitions/by-id/{id}?includeCompositeRoot={includeCompositeRoot}")]
|
||||
Task<WorkflowDefinition?> GetByIdAsync(string id, bool includeCompositeRoot = false, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a workflow definition by ID.
|
||||
/// </summary>
|
||||
/// <param name="ids">The IDs of the workflow definition versions to get.</param>
|
||||
/// <param name="includeCompositeRoot">Whether to include the root activity of composite activities.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
[Get("/workflow-definitions/many-by-id")]
|
||||
Task<ListResponse<WorkflowDefinition>> GetManyByIdAsync([Query(CollectionFormat.Multi)]ICollection<string> ids, bool includeCompositeRoot = false, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of workflow definitions.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a response from listing workflow definitions.
|
||||
/// </summary>
|
||||
/// <param name="Items">The workflow definitions.</param>
|
||||
/// <param name="TotalCount">The total number of workflow definitions.</param>
|
||||
public record ListWorkflowDefinitionsResponse(ICollection<WorkflowDefinitionSummary> Items, int TotalCount);
|
||||
|
|
@ -25,7 +25,7 @@ public interface IWorkflowInstancesApi
|
|||
Task<WorkflowInstance> GetAsync(string id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a workflow instance.
|
||||
/// Returns a page of journal records for the specified workflow instance.
|
||||
/// </summary>
|
||||
/// <param name="workflowInstanceId">The ID of the workflow instance for which to return the journal.</param>
|
||||
/// <param name="skip">The number of records to skip.</param>
|
||||
|
|
@ -34,6 +34,17 @@ public interface IWorkflowInstancesApi
|
|||
[Get("/workflow-instances/{workflowInstanceId}/journal")]
|
||||
Task<PagedListResponse<ExecutionLogRecord>> GetJournalAsync(string workflowInstanceId, int? skip = default, int? take = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a page of journal records for the specified workflow instance.
|
||||
/// </summary>
|
||||
/// <param name="workflowInstanceId">The ID of the workflow instance for which to return the journal.</param>
|
||||
/// <param name="filter">The filter to apply.</param>
|
||||
/// <param name="skip">The number of records to skip.</param>
|
||||
/// <param name="take">The number of records to return.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
[Post("/workflow-instances/{workflowInstanceId}/journal")]
|
||||
Task<PagedListResponse<ExecutionLogRecord>> GetFilteredJournalAsync(string workflowInstanceId, GetFilteredJournalRequest? filter, int? skip = default, int? take = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a workflow instance.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
namespace Elsa.Api.Client.Resources.WorkflowInstances.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a request to list journal records.
|
||||
/// </summary>
|
||||
public class GetFilteredJournalRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the filter to apply.
|
||||
/// </summary>
|
||||
public JournalFilter? Filter { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
namespace Elsa.Api.Client.Resources.WorkflowInstances.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a request to list journal records.
|
||||
/// </summary>
|
||||
public class JournalFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the activity ids to filter by.
|
||||
/// </summary>
|
||||
public ICollection<string>? ActivityIds { get; set; }
|
||||
|
||||
}
|
||||
|
|
@ -34,8 +34,8 @@ public record PageArgs
|
|||
|
||||
if(offset != null && limit != null)
|
||||
return FromRange(offset, limit);
|
||||
|
||||
throw new ArgumentException("Either page and pageSize or offset and limit must be specified.");
|
||||
|
||||
return FromPage(0, 100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ public class DapperBookmarkStore : IBookmarkStore
|
|||
query
|
||||
.Is(nameof(StoredBookmarkRecord.Hash), filter.Hash)
|
||||
.Is(nameof(StoredBookmarkRecord.WorkflowInstanceId), filter.WorkflowInstanceId)
|
||||
.In(nameof(StoredBookmarkRecord.WorkflowInstanceId), filter.WorkflowInstanceIds)
|
||||
.Is(nameof(StoredBookmarkRecord.CorrelationId), filter.CorrelationId)
|
||||
.Is(nameof(StoredBookmarkRecord.ActivityTypeName), filter.ActivityTypeName)
|
||||
.In(nameof(StoredBookmarkRecord.ActivityTypeName), filter.ActivityTypeNames)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ public class DapperTriggerStore : ITriggerStore
|
|||
.In(nameof(StoredTriggerRecord.Id), filter.Ids)
|
||||
.Is(nameof(StoredTriggerRecord.WorkflowDefinitionId), filter.WorkflowDefinitionId)
|
||||
.In(nameof(StoredTriggerRecord.WorkflowDefinitionId), filter.WorkflowDefinitionIds)
|
||||
.Is(nameof(StoredTriggerRecord.WorkflowDefinitionVersionId), filter.WorkflowDefinitionVersionId)
|
||||
.In(nameof(StoredTriggerRecord.WorkflowDefinitionVersionId), filter.WorkflowDefinitionVersionIds)
|
||||
.Is(nameof(StoredTriggerRecord.Name), filter.Name)
|
||||
.In(nameof(StoredTriggerRecord.Name), filter.Names)
|
||||
.Is(nameof(StoredTriggerRecord.Hash), filter.Hash)
|
||||
|
|
|
|||
|
|
@ -89,7 +89,9 @@ public class DapperWorkflowExecutionLogStore : IWorkflowExecutionLogStore
|
|||
query
|
||||
.Is(nameof(WorkflowExecutionLogRecordRecord.Id), filter.Id)
|
||||
.In(nameof(WorkflowExecutionLogRecordRecord.Id), filter.Ids)
|
||||
.Is(nameof(WorkflowExecutionLogRecordRecord.ParentActivityInstanceId), filter.ParentActivityInstanceId)
|
||||
.Is(nameof(WorkflowExecutionLogRecordRecord.ActivityId), filter.ActivityId)
|
||||
.In(nameof(WorkflowExecutionLogRecordRecord.ActivityId), filter.ActivityIds)
|
||||
.Is(nameof(WorkflowExecutionLogRecordRecord.WorkflowInstanceId), filter.WorkflowInstanceId)
|
||||
.In(nameof(WorkflowExecutionLogRecordRecord.WorkflowInstanceId), filter.WorkflowInstanceIds)
|
||||
.Is(nameof(WorkflowExecutionLogRecordRecord.EventName), filter.EventName)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ internal class GetById : ElsaEndpoint<Request>
|
|||
Id = request.Id
|
||||
};
|
||||
|
||||
var definition = (await _store.FindManyAsync(filter, cancellationToken)).FirstOrDefault();
|
||||
var definition = await _store.FindAsync(filter, cancellationToken);
|
||||
|
||||
if (definition == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Models;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Serialization.Converters;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.GetManyById;
|
||||
|
||||
[PublicAPI]
|
||||
internal class GetManyById : ElsaEndpoint<Request>
|
||||
{
|
||||
private readonly IWorkflowDefinitionStore _store;
|
||||
private readonly IApiSerializer _apiSerializer;
|
||||
private readonly WorkflowDefinitionMapper _mapper;
|
||||
|
||||
public GetManyById(IWorkflowDefinitionStore store, IApiSerializer apiSerializer, WorkflowDefinitionMapper mapper)
|
||||
{
|
||||
_store = store;
|
||||
_apiSerializer = apiSerializer;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/workflow-definitions/many-by-id");
|
||||
ConfigurePermissions("read:workflow-definitions");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
var filter = new WorkflowDefinitionFilter
|
||||
{
|
||||
Ids = request.Ids
|
||||
};
|
||||
|
||||
var definitions = (await _store.FindManyAsync(filter, cancellationToken)).ToList();
|
||||
var models = (await _mapper.MapAsync(definitions, cancellationToken)).ToList();
|
||||
var serializerOptions = _apiSerializer.CreateOptions();
|
||||
|
||||
// If the root of composite activities is not requested, exclude them from being serialized.
|
||||
if (!request.IncludeCompositeRoot)
|
||||
serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory());
|
||||
|
||||
var response = new ListResponse<WorkflowDefinitionModel>(models);
|
||||
await HttpContext.Response.WriteAsJsonAsync(response, serializerOptions, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.GetManyById;
|
||||
|
||||
internal class Request
|
||||
{
|
||||
public ICollection<string> Ids { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// True if the response should include the root activity of composite activities.
|
||||
/// </summary>
|
||||
public bool IncludeCompositeRoot { get; set; }
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Common.Entities;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Models;
|
||||
using Elsa.Workflows.Api.Models;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
|
|
@ -10,7 +11,7 @@ using JetBrains.Annotations;
|
|||
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.List;
|
||||
|
||||
[PublicAPI]
|
||||
internal class List : ElsaEndpoint<Request, Response>
|
||||
internal class List : ElsaEndpoint<Request, PagedListResponse<WorkflowDefinitionSummary>>
|
||||
{
|
||||
private readonly IWorkflowDefinitionStore _store;
|
||||
|
||||
|
|
@ -25,13 +26,12 @@ internal class List : ElsaEndpoint<Request, Response>
|
|||
ConfigurePermissions("read:workflow-definitions");
|
||||
}
|
||||
|
||||
public override async Task<Response> ExecuteAsync(Request request, CancellationToken cancellationToken)
|
||||
public override async Task<PagedListResponse<WorkflowDefinitionSummary>> ExecuteAsync(Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
var pageArgs = PageArgs.FromPage(request.Page, request.PageSize);
|
||||
var filter = CreateFilter(request);
|
||||
|
||||
var summaries = await FindAsync(request, filter, pageArgs, cancellationToken);
|
||||
return new Response(summaries.Items, summaries.TotalCount);
|
||||
return new PagedListResponse<WorkflowDefinitionSummary>(summaries);
|
||||
}
|
||||
|
||||
private WorkflowDefinitionFilter CreateFilter(Request request)
|
||||
|
|
|
|||
|
|
@ -17,16 +17,4 @@ internal class Request
|
|||
public OrderByWorkflowDefinition? OrderBy { get; set; }
|
||||
public OrderDirection? OrderDirection { get; set; }
|
||||
public string? SearchTerm { get; set; }
|
||||
}
|
||||
|
||||
internal class Response
|
||||
{
|
||||
public Response(ICollection<WorkflowDefinitionSummary> items, long totalCount)
|
||||
{
|
||||
Items = items;
|
||||
TotalCount = totalCount;
|
||||
}
|
||||
|
||||
public ICollection<WorkflowDefinitionSummary> Items { get; set; }
|
||||
public long TotalCount { get; set; }
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
using Elsa.Workflows.Management.Contracts;
|
||||
using FastEndpoints;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Version;
|
||||
|
||||
public class RevertVersion : EndpointWithoutRequest
|
||||
[PublicAPI]
|
||||
internal class RevertVersion : EndpointWithoutRequest
|
||||
{
|
||||
private readonly IWorkflowDefinitionManager _workflowDefinitionManager;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Common.Entities;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Filters;
|
||||
using Elsa.Workflows.Runtime.OrderDefinitions;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Journal.FilteredList;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the journal for a workflow instance.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
internal class Get : ElsaEndpoint<Request, Response>
|
||||
{
|
||||
private readonly IWorkflowExecutionLogStore _store;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Get(IWorkflowExecutionLogStore store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/workflow-instances/{id}/journal");
|
||||
ConfigurePermissions("read:workflow-instances");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Response> ExecuteAsync(Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
var pageArgs = PageArgs.From(request.Page, request.PageSize, request.Skip, request.Take);
|
||||
var filter = new WorkflowExecutionLogRecordFilter
|
||||
{
|
||||
WorkflowInstanceId = request.WorkflowInstanceId,
|
||||
ActivityIds = request.Filter?.ActivityIds
|
||||
};
|
||||
var order = new WorkflowExecutionLogRecordOrder<long>(x => x.Sequence, OrderDirection.Ascending);
|
||||
var pageOfRecords = await _store.FindManyAsync(filter, pageArgs, order, cancellationToken);
|
||||
|
||||
var models = pageOfRecords.Items.Select(x =>
|
||||
new ExecutionLogRecord(
|
||||
x.Id,
|
||||
x.ActivityInstanceId,
|
||||
x.ParentActivityInstanceId,
|
||||
x.ActivityId,
|
||||
x.ActivityType,
|
||||
x.ActivityTypeVersion,
|
||||
x.NodeId,
|
||||
x.Timestamp,
|
||||
x.Sequence,
|
||||
x.EventName,
|
||||
x.Message,
|
||||
x.Source,
|
||||
x.ActivityState,
|
||||
x.Payload))
|
||||
.ToList();
|
||||
|
||||
return new(models, pageOfRecords.TotalCount);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using FastEndpoints;
|
||||
|
||||
// ReSharper disable NotAccessedPositionalProperty.Global
|
||||
|
||||
namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Journal.FilteredList;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a request for a page of workflow execution log records.
|
||||
/// </summary>
|
||||
internal class Request
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the workflow instance to get the execution log for.
|
||||
/// </summary>
|
||||
[BindFrom("id")] public string WorkflowInstanceId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The filter to apply.
|
||||
/// </summary>
|
||||
public JournalFilter? Filter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The zero-based page number to get.
|
||||
/// </summary>
|
||||
public int? Page { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The size of the page to get.
|
||||
/// </summary>
|
||||
public int? PageSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of records to skip.
|
||||
/// </summary>
|
||||
public int? Skip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of records to take.
|
||||
/// </summary>
|
||||
public int? Take { get; set; }
|
||||
}
|
||||
|
||||
internal class JournalFilter
|
||||
{
|
||||
public ICollection<string>? ActivityIds { get; set; }
|
||||
}
|
||||
|
||||
internal class Response
|
||||
{
|
||||
public Response(ICollection<ExecutionLogRecord> items, long totalCount)
|
||||
{
|
||||
Items = items;
|
||||
TotalCount = totalCount;
|
||||
}
|
||||
|
||||
public ICollection<ExecutionLogRecord> Items { get; }
|
||||
public long TotalCount { get; }
|
||||
}
|
||||
|
||||
internal record ExecutionLogRecord(
|
||||
string Id,
|
||||
string ActivityInstanceId,
|
||||
string? ParentActivityInstanceId,
|
||||
string ActivityId,
|
||||
string ActivityType,
|
||||
int ActivityTypeVersion,
|
||||
string NodeId,
|
||||
DateTimeOffset Timestamp,
|
||||
long Sequence,
|
||||
string? EventName,
|
||||
string? Message,
|
||||
string? Source,
|
||||
IDictionary<string, object>? ActivityState,
|
||||
object? Payload);
|
||||
|
|
@ -80,6 +80,15 @@ public class WorkflowDefinitionMapper
|
|||
source.IsReadonly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps many <see cref="WorkflowDefinition"/>s to many <see cref="WorkflowDefinitionModel"/>s.
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="WorkflowDefinition"/>s.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>The mapped <see cref="WorkflowDefinitionModel"/>s.</returns>
|
||||
public async Task<IEnumerable<WorkflowDefinitionModel>> MapAsync(IEnumerable<WorkflowDefinition> source, CancellationToken cancellationToken = default) =>
|
||||
await Task.WhenAll(source.Select(async x => await MapAsync(x, cancellationToken)));
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="WorkflowDefinition"/> to a <see cref="Workflow"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -27,10 +27,20 @@ public class WorkflowExecutionLogRecordFilter
|
|||
/// </summary>
|
||||
public ICollection<string>? WorkflowInstanceIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the parent activity instance.
|
||||
/// </summary>
|
||||
public string? ParentActivityInstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the activity.
|
||||
/// </summary>
|
||||
public string? ActivityId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IDs of the activities.
|
||||
/// </summary>
|
||||
public ICollection<string>? ActivityIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the event.
|
||||
|
|
@ -41,7 +51,7 @@ public class WorkflowExecutionLogRecordFilter
|
|||
/// Match any of these event names.
|
||||
/// </summary>
|
||||
public ICollection<string>? AnyEventName { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Applies the filter to the specified queryable.
|
||||
/// </summary>
|
||||
|
|
@ -52,7 +62,9 @@ public class WorkflowExecutionLogRecordFilter
|
|||
if (filter.Ids != null) queryable = queryable.Where(x => filter.Ids.Contains(x.Id!));
|
||||
if (filter.WorkflowInstanceId != null) queryable = queryable.Where(x => x.WorkflowInstanceId == filter.WorkflowInstanceId);
|
||||
if (filter.WorkflowInstanceIds != null) queryable = queryable.Where(x => filter.WorkflowInstanceIds.Contains(x.WorkflowInstanceId!));
|
||||
if (filter.ParentActivityInstanceId != null) queryable = queryable.Where(x => x.ParentActivityInstanceId == filter.ParentActivityInstanceId);
|
||||
if (filter.ActivityId != null) queryable = queryable.Where(x => x.ActivityId == filter.ActivityId);
|
||||
if (filter.ActivityIds != null) queryable = queryable.Where(x => filter.ActivityIds.Contains(x.ActivityId));
|
||||
if (filter.EventName != null) queryable = queryable.Where(x => x.EventName == filter.EventName);
|
||||
if (filter.AnyEventName != null) queryable = queryable.Where(x => filter.AnyEventName.Contains(x.EventName!));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue