From 1902307cf548e668bbbcbacaf9977f28c46f83dc Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 20 Jul 2023 22:12:45 +0200 Subject: [PATCH] Add support for filtering journal by activity IDs --- .../Contracts/IWorkflowDefinitionsApi.cs | 11 ++- .../ListWorkflowDefinitionsResponse.cs | 10 --- .../Contracts/IWorkflowInstancesApi.cs | 13 +++- .../Requests/GetFilteredJournalRequest.cs | 12 +++ .../Requests/JournalFilter.cs | 13 ++++ src/modules/Elsa.Common/Models/PageArgs.cs | 4 +- .../Runtime/Services/DapperBookmarkStore.cs | 1 + .../Runtime/Services/DapperTriggerStore.cs | 2 + .../DapperWorkflowExecutionLogStore.cs | 2 + .../WorkflowDefinitions/GetById/Endpoint.cs | 2 +- .../GetManyById/Endpoint.cs | 52 +++++++++++++ .../WorkflowDefinitions/GetManyById/Models.cs | 11 +++ .../WorkflowDefinitions/List/Endpoint.cs | 8 +- .../WorkflowDefinitions/List/Models.cs | 12 --- .../WorkflowDefinitions/Version/Revert.cs | 4 +- .../Journal/FilteredList/Endpoint.cs | 64 ++++++++++++++++ .../Journal/FilteredList/Models.cs | 74 +++++++++++++++++++ .../Mappers/WorkflowDefinitionMapper.cs | 9 +++ .../WorkflowExecutionLogRecordFilter.cs | 14 +++- 19 files changed, 285 insertions(+), 33 deletions(-) delete mode 100644 src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Responses/ListWorkflowDefinitionsResponse.cs create mode 100644 src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/GetFilteredJournalRequest.cs create mode 100644 src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/JournalFilter.cs create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetManyById/Endpoint.cs create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetManyById/Models.cs create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Endpoint.cs create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Models.cs diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs index a1f185917..fd39e76be 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs @@ -21,7 +21,7 @@ public interface IWorkflowDefinitionsApi /// The version options. /// The cancellation token. [Get("/workflow-definitions?versionOptions={versionOptions}")] - Task ListAsync([Query]ListWorkflowDefinitionsRequest request, [Query]VersionOptions? versionOptions = default, CancellationToken cancellationToken = default); + Task> ListAsync([Query]ListWorkflowDefinitionsRequest request, [Query]VersionOptions? versionOptions = default, CancellationToken cancellationToken = default); /// /// Gets a workflow definition by definition ID. @@ -42,6 +42,15 @@ public interface IWorkflowDefinitionsApi [Get("/workflow-definitions/by-id/{id}?includeCompositeRoot={includeCompositeRoot}")] Task GetByIdAsync(string id, bool includeCompositeRoot = false, CancellationToken cancellationToken = default); + /// + /// Gets a workflow definition by ID. + /// + /// The IDs of the workflow definition versions to get. + /// Whether to include the root activity of composite activities. + /// The cancellation token. + [Get("/workflow-definitions/many-by-id")] + Task> GetManyByIdAsync([Query(CollectionFormat.Multi)]ICollection ids, bool includeCompositeRoot = false, CancellationToken cancellationToken = default); + /// /// Gets the number of workflow definitions. /// diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Responses/ListWorkflowDefinitionsResponse.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Responses/ListWorkflowDefinitionsResponse.cs deleted file mode 100644 index f967304e9..000000000 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Responses/ListWorkflowDefinitionsResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Elsa.Api.Client.Resources.WorkflowDefinitions.Models; - -namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Responses; - -/// -/// Represents a response from listing workflow definitions. -/// -/// The workflow definitions. -/// The total number of workflow definitions. -public record ListWorkflowDefinitionsResponse(ICollection Items, int TotalCount); \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs index 49e49daab..e0fb69dec 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs @@ -25,7 +25,7 @@ public interface IWorkflowInstancesApi Task GetAsync(string id, CancellationToken cancellationToken = default); /// - /// Returns a workflow instance. + /// Returns a page of journal records for the specified workflow instance. /// /// The ID of the workflow instance for which to return the journal. /// The number of records to skip. @@ -34,6 +34,17 @@ public interface IWorkflowInstancesApi [Get("/workflow-instances/{workflowInstanceId}/journal")] Task> GetJournalAsync(string workflowInstanceId, int? skip = default, int? take = default, CancellationToken cancellationToken = default); + /// + /// Returns a page of journal records for the specified workflow instance. + /// + /// The ID of the workflow instance for which to return the journal. + /// The filter to apply. + /// The number of records to skip. + /// The number of records to return. + /// The cancellation token. + [Post("/workflow-instances/{workflowInstanceId}/journal")] + Task> GetFilteredJournalAsync(string workflowInstanceId, GetFilteredJournalRequest? filter, int? skip = default, int? take = default, CancellationToken cancellationToken = default); + /// /// Deletes a workflow instance. /// diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/GetFilteredJournalRequest.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/GetFilteredJournalRequest.cs new file mode 100644 index 000000000..e41797d99 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/GetFilteredJournalRequest.cs @@ -0,0 +1,12 @@ +namespace Elsa.Api.Client.Resources.WorkflowInstances.Requests; + +/// +/// Represents a request to list journal records. +/// +public class GetFilteredJournalRequest +{ + /// + /// Gets or sets the filter to apply. + /// + public JournalFilter? Filter { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/JournalFilter.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/JournalFilter.cs new file mode 100644 index 000000000..ed4eee2ef --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/JournalFilter.cs @@ -0,0 +1,13 @@ +namespace Elsa.Api.Client.Resources.WorkflowInstances.Requests; + +/// +/// Represents a request to list journal records. +/// +public class JournalFilter +{ + /// + /// Gets or sets the activity ids to filter by. + /// + public ICollection? ActivityIds { get; set; } + +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Models/PageArgs.cs b/src/modules/Elsa.Common/Models/PageArgs.cs index 60cce764e..9c0264440 100644 --- a/src/modules/Elsa.Common/Models/PageArgs.cs +++ b/src/modules/Elsa.Common/Models/PageArgs.cs @@ -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); } /// diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperBookmarkStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperBookmarkStore.cs index 6aba18009..09971b276 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperBookmarkStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperBookmarkStore.cs @@ -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) diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperTriggerStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperTriggerStore.cs index bb37ef38d..c3e6931b9 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperTriggerStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperTriggerStore.cs @@ -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) diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperWorkflowExecutionLogStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperWorkflowExecutionLogStore.cs index c19a06365..100b904b7 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperWorkflowExecutionLogStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Services/DapperWorkflowExecutionLogStore.cs @@ -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) diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetById/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetById/Endpoint.cs index 7ee33287b..b38618d57 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetById/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetById/Endpoint.cs @@ -36,7 +36,7 @@ internal class GetById : ElsaEndpoint Id = request.Id }; - var definition = (await _store.FindManyAsync(filter, cancellationToken)).FirstOrDefault(); + var definition = await _store.FindAsync(filter, cancellationToken); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetManyById/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetManyById/Endpoint.cs new file mode 100644 index 000000000..867200462 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetManyById/Endpoint.cs @@ -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 +{ + 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(models); + await HttpContext.Response.WriteAsJsonAsync(response, serializerOptions, cancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetManyById/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetManyById/Models.cs new file mode 100644 index 000000000..250db765f --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetManyById/Models.cs @@ -0,0 +1,11 @@ +namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.GetManyById; + +internal class Request +{ + public ICollection Ids { get; set; } = default!; + + /// + /// True if the response should include the root activity of composite activities. + /// + public bool IncludeCompositeRoot { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs index cb5dae8ab..4e9e47b0d 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs @@ -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 +internal class List : ElsaEndpoint> { private readonly IWorkflowDefinitionStore _store; @@ -25,13 +26,12 @@ internal class List : ElsaEndpoint ConfigurePermissions("read:workflow-definitions"); } - public override async Task ExecuteAsync(Request request, CancellationToken cancellationToken) + public override async Task> 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(summaries); } private WorkflowDefinitionFilter CreateFilter(Request request) diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Models.cs index bbc36e974..e2dfe782b 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Models.cs @@ -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 items, long totalCount) - { - Items = items; - TotalCount = totalCount; - } - - public ICollection Items { get; set; } - public long TotalCount { get; set; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs index af43e4005..64f0e0589 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs @@ -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; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Endpoint.cs new file mode 100644 index 000000000..ada4e7086 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Endpoint.cs @@ -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; + +/// +/// Gets the journal for a workflow instance. +/// +[PublicAPI] +internal class Get : ElsaEndpoint +{ + private readonly IWorkflowExecutionLogStore _store; + + /// + public Get(IWorkflowExecutionLogStore store) + { + _store = store; + } + + /// + public override void Configure() + { + Post("/workflow-instances/{id}/journal"); + ConfigurePermissions("read:workflow-instances"); + } + + /// + public override async Task 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(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); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Models.cs new file mode 100644 index 000000000..6589f0777 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Journal/FilteredList/Models.cs @@ -0,0 +1,74 @@ +using FastEndpoints; + +// ReSharper disable NotAccessedPositionalProperty.Global + +namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Journal.FilteredList; + +/// +/// Represents a request for a page of workflow execution log records. +/// +internal class Request +{ + /// + /// The ID of the workflow instance to get the execution log for. + /// + [BindFrom("id")] public string WorkflowInstanceId { get; set; } = default!; + + /// + /// The filter to apply. + /// + public JournalFilter? Filter { get; set; } + + /// + /// The zero-based page number to get. + /// + public int? Page { get; set; } + + /// + /// The size of the page to get. + /// + public int? PageSize { get; set; } + + /// + /// The number of records to skip. + /// + public int? Skip { get; set; } + + /// + /// The number of records to take. + /// + public int? Take { get; set; } +} + +internal class JournalFilter +{ + public ICollection? ActivityIds { get; set; } +} + +internal class Response +{ + public Response(ICollection items, long totalCount) + { + Items = items; + TotalCount = totalCount; + } + + public ICollection 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? ActivityState, + object? Payload); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs index a7229965c..02e17f55a 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs @@ -80,6 +80,15 @@ public class WorkflowDefinitionMapper source.IsReadonly); } + /// + /// Maps many s to many s. + /// + /// The source s. + /// An optional cancellation token. + /// The mapped s. + public async Task> MapAsync(IEnumerable source, CancellationToken cancellationToken = default) => + await Task.WhenAll(source.Select(async x => await MapAsync(x, cancellationToken))); + /// /// Maps a to a . /// diff --git a/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowExecutionLogRecordFilter.cs b/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowExecutionLogRecordFilter.cs index 3d47c73ec..f4c707e1e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowExecutionLogRecordFilter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowExecutionLogRecordFilter.cs @@ -27,10 +27,20 @@ public class WorkflowExecutionLogRecordFilter /// public ICollection? WorkflowInstanceIds { get; set; } + /// + /// The ID of the parent activity instance. + /// + public string? ParentActivityInstanceId { get; set; } + /// /// The ID of the activity. /// public string? ActivityId { get; set; } + + /// + /// The IDs of the activities. + /// + public ICollection? ActivityIds { get; set; } /// /// The name of the event. @@ -41,7 +51,7 @@ public class WorkflowExecutionLogRecordFilter /// Match any of these event names. /// public ICollection? AnyEventName { get; set; } - + /// /// Applies the filter to the specified queryable. /// @@ -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!));