From c80f80fd81cbea43d9b90256ee37c56c12294c43 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 18 Feb 2024 23:40:03 +0100 Subject: [PATCH] Enhanced Workflow Instance Filters (#4957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add timestamp filtering for workflow instances Implemented a new feature in the workflow instances' querying API, enabling filtering based on timestamps. The changes include adding a new TimestampFilter class and modifying existing classes accordingly. This new filter supports various operators such as greater than and less than, providing flexibility in filtering workflow instances. * Improve timestamp filtering efficiency and update UI in WorkflowInstanceList Timestamp filtering within WorkflowInstanceList is refactored using System.Linq.Dynamic.Core for more compact expression. This change results in simpler and more efficient code, reducing repetitive code lines. The UI updates include changing the MudGrid structure to table for timestamp filters display, offering a more organized view. Also, protection against null or minimum date values during parsing and comparisons is added, increasing stability and safety of operations. * Refactor HTTP verbs and routes configuration in Endpoint.cs The previous Get and Post method calls for handling '/workflow-instances' have been replaced with a Verbs method for HTTP verbs and a Routes method for setting the route. * Update WorkflowInstanceFilter to use nullable HasIncidents The HasIncidents property in WorkflowInstanceFilter has been updated from a bool to a nullable bool so that it can represent three states (true, false, or null) instead of only two. This allows not only to filter workflow instances that have incidents, but also those that don't have any, enhancing the flexibility of the filter in the process. * Improve search functionality and UI in WorkflowInstanceList Updated search term comparison in WorkflowInstanceFilter to use "Contains" instead of "Equals" to improve search flexibility. Also, enhanced the user interface in WorkflowInstanceList by adding debounce interval for the search input for performance reasons and by adding a Close button for convenience. * Add input validation to WorkflowInstances endpoint The code modifies the API endpoint for WorkflowInstances. It now checks that the input provided in the request is valid before proceeding. Furthermore, a new file called 'RequiredMembers.cs' has been added, which introduces two new attributes 'RequiredMemberAttribute' and 'CompilerFeatureRequiredAttribute'. * Update src/modules/Elsa.Workflows.Management/Models/TimestampFilter.cs Co-authored-by: Béchir BEN AMEUR <32399944+bbenameur@users.noreply.github.com> * Update src/modules/Elsa.Workflows.Management/Models/TimestampFilter.cs Co-authored-by: Béchir BEN AMEUR <32399944+bbenameur@users.noreply.github.com> * Update src/modules/Elsa.Workflows.Management/Models/TimestampFilter.cs Co-authored-by: Béchir BEN AMEUR <32399944+bbenameur@users.noreply.github.com> --------- Co-authored-by: Béchir BEN AMEUR <32399944+bbenameur@users.noreply.github.com> --- Directory.Packages.props | 1 + NuGet.Config | 10 ++- .../Contracts/IWorkflowInstancesApi.cs | 2 +- .../Requests/ListWorkflowInstancesRequest.cs | 38 +++++--- .../Shared/Enums/TimestampFilterOperator.cs | 37 ++++++++ .../Shared/Models/TimestampFilter.cs | 26 ++++++ .../WorkflowInstances/List/Endpoint.cs | 89 ++++++++++++++----- .../WorkflowInstances/List/Models.cs | 22 ++--- .../Elsa.Workflows.Management.csproj | 2 +- .../Enums/TimestampFilterOperator.cs | 37 ++++++++ .../Filters/WorkflowInstanceFilter.cs | 57 +++++++++--- .../Models/TimestampFilter.cs | 26 ++++++ .../RequiredMembers.cs | 8 ++ 13 files changed, 288 insertions(+), 67 deletions(-) create mode 100644 src/clients/Elsa.Api.Client/Shared/Enums/TimestampFilterOperator.cs create mode 100644 src/clients/Elsa.Api.Client/Shared/Models/TimestampFilter.cs create mode 100644 src/modules/Elsa.Workflows.Management/Enums/TimestampFilterOperator.cs create mode 100644 src/modules/Elsa.Workflows.Management/Models/TimestampFilter.cs create mode 100644 src/modules/Elsa.Workflows.Management/RequiredMembers.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 3f079533a..302aca426 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -87,6 +87,7 @@ + diff --git a/NuGet.Config b/NuGet.Config index 0bcd55102..7f38c9b03 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,8 +3,14 @@ - + + + + + + + + \ 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 6496b50f5..73b11b092 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Contracts/IWorkflowInstancesApi.cs @@ -14,7 +14,7 @@ public interface IWorkflowInstancesApi /// /// Returns a list of workflow instances. /// - [Get("/workflow-instances")] + [Post("/workflow-instances")] Task> ListAsync(ListWorkflowInstancesRequest request, CancellationToken cancellationToken = default); /// diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/ListWorkflowInstancesRequest.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/ListWorkflowInstancesRequest.cs index 11ab8cac5..0b4283a8b 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/ListWorkflowInstancesRequest.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Requests/ListWorkflowInstancesRequest.cs @@ -13,64 +13,74 @@ public class ListWorkflowInstancesRequest /// Gets or sets the page number. /// public int? Page { get; set; } - + /// /// Gets or sets the page size. /// public int? PageSize { get; set; } - + /// /// Gets or sets the search term. /// public string? SearchTerm { get; set; } - + /// /// Gets or sets the workflow definition id. /// public string? DefinitionId { get; set; } - + /// /// Gets or sets the workflow definition ids. /// - [Query(CollectionFormat.Multi)] public ICollection? DefinitionIds { get; set; } - + public ICollection? DefinitionIds { get; set; } + /// /// Gets or sets the correlation id. /// public string? CorrelationId { get; set; } - + /// /// Gets or sets the version. /// public int? Version { get; set; } - + + /// + /// Gets or sets whether the workflow instances have incidents or not. + /// + public bool? HasIncidents { get; set; } + /// /// Gets or sets the status. /// public WorkflowStatus? Status { get; set; } - + /// /// Gets or sets the statuses. /// - [Query(CollectionFormat.Multi)] public ICollection? Statuses { get; set; } - + public ICollection? Statuses { get; set; } + /// /// Gets or sets the sub status. /// public WorkflowSubStatus? SubStatus { get; set; } - + /// /// Gets or sets the sub statuses. /// - [Query(CollectionFormat.Multi)] public ICollection? SubStatuses { get; set; } + public ICollection? SubStatuses { get; set; } /// /// Gets or sets the key to order by. /// public OrderByWorkflowInstance? OrderBy { get; set; } - + /// /// Gets or sets the order direction. /// public OrderDirection? OrderDirection { get; set; } + + /// + /// Gets or sets the timestamp filters. + /// + public ICollection? TimestampFilters { get; set; } } \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Shared/Enums/TimestampFilterOperator.cs b/src/clients/Elsa.Api.Client/Shared/Enums/TimestampFilterOperator.cs new file mode 100644 index 000000000..cabedd9cf --- /dev/null +++ b/src/clients/Elsa.Api.Client/Shared/Enums/TimestampFilterOperator.cs @@ -0,0 +1,37 @@ +namespace Elsa.Api.Client.Shared.Enums; + +/// +/// Specifies the operators for filtering timestamps in a TimestampFilter object. +/// +public enum TimestampFilterOperator +{ + /// + /// The timestamp is equal to the specified value. + /// + Is, + + /// + /// The timestamp is not equal to the specified value. + /// + IsNot, + + /// + /// The timestamp is before the specified value. + /// + LessThan, + + /// + /// The timestamp is greater than the specified value. + /// + GreaterThan, + + /// + /// The timestamp is less than or equal to the specified value. + /// + LessThanOrEqual, + + /// + /// The timestamp is greater than or equal to the specified value. + /// + GreaterThanOrEqual +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Shared/Models/TimestampFilter.cs b/src/clients/Elsa.Api.Client/Shared/Models/TimestampFilter.cs new file mode 100644 index 000000000..0e08217cd --- /dev/null +++ b/src/clients/Elsa.Api.Client/Shared/Models/TimestampFilter.cs @@ -0,0 +1,26 @@ +using Elsa.Api.Client.Shared.Enums; +using JetBrains.Annotations; + +namespace Elsa.Api.Client.Shared.Models; + +/// +/// Represents a timestamp filter used for filtering data based on a specified timestamp column and operator. +/// +[UsedImplicitly] +public class TimestampFilter +{ + /// + /// Gets or sets the column to filter by. + /// + public string Column { get; set; } = default!; + + /// + /// Gets or sets the operator to use for filtering. + /// + public TimestampFilterOperator Operator { get; set; } + + /// + /// Gets or sets the timestamp to filter by. + /// + public DateTimeOffset Timestamp { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Endpoint.cs index 3b56ef650..96bbed0c1 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Endpoint.cs @@ -6,57 +6,98 @@ using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Filters; using Elsa.Workflows.Management.Models; using JetBrains.Annotations; +using Microsoft.AspNetCore.Http; namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.List; -[PublicAPI] -internal class List : ElsaEndpoint +[UsedImplicitly] +internal class List(IWorkflowInstanceStore store) : ElsaEndpoint { - private readonly IWorkflowInstanceStore _store; - - public List(IWorkflowInstanceStore store) - { - _store = store; - } - public override void Configure() { - Get("/workflow-instances"); + Verbs(FastEndpoints.Http.GET, FastEndpoints.Http.POST); + Routes("/workflow-instances"); ConfigurePermissions("read:workflow-instances"); } - public override async Task ExecuteAsync(Request request, CancellationToken cancellationToken) + public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { var pageArgs = PageArgs.FromPage(request.Page, request.PageSize); + if (!await ValidateInputAsync(request, cancellationToken)) + { + await SendErrorsAsync(StatusCodes.Status400BadRequest, cancellationToken); + return; + } + var filter = new WorkflowInstanceFilter { SearchTerm = request.SearchTerm, DefinitionId = request.DefinitionId, - DefinitionIds = request.DefinitionIds, + DefinitionIds = request.DefinitionIds?.Any() == true ? request.DefinitionIds : null, Version = request.Version, CorrelationId = request.CorrelationId, WorkflowStatus = request.Status, WorkflowSubStatus = request.SubStatus, - WorkflowStatuses = request.Statuses?.Select(Enum.Parse).ToList(), - WorkflowSubStatuses = request.SubStatuses?.Select(Enum.Parse).ToList(), - ToCreatedAt = request.ToCreatedAt, - FromCreatedAt = request.FromCreatedAt, + WorkflowStatuses = request.Statuses?.Any() == true ? request.Statuses : null, + WorkflowSubStatuses = request.SubStatuses?.Any() == true ? request.SubStatuses : null, + HasIncidents = request.HasIncidents, + TimestampFilters = request.TimestampFilters?.Any() == true ? request.TimestampFilters : null, }; var summaries = await FindAsync(request, filter, pageArgs, cancellationToken); - return new Response(summaries.Items, summaries.TotalCount); + var response = new Response(summaries.Items, summaries.TotalCount); + await SendOkAsync(response, cancellationToken); + } + + private async Task ValidateInputAsync(Request request, CancellationToken cancellationToken) + { + if (request.Page is < 0) + { + AddError("Page must be greater than or equal to 1."); + return false; + } + + if (request.PageSize is < 1) + { + AddError("Page size must be greater than or equal to 1."); + return false; + } + + var columnWhitelist = new[] + { + "CreatedAt", "UpdatedAt", "FinishedAt" + }; + + if (request.TimestampFilters?.Any() == true) + { + foreach (var timestampFilter in request.TimestampFilters) + { + if (string.IsNullOrWhiteSpace(timestampFilter.Column)) + { + AddError("Column must be specified."); + return false; + } + + if (!columnWhitelist.Contains(timestampFilter.Column)) + { + AddError($"Invalid column '{timestampFilter.Column}'."); + return false; + } + } + } + + return true; } private async Task> FindAsync(Request request, WorkflowInstanceFilter filter, PageArgs pageArgs, CancellationToken cancellationToken) { - request.OrderBy = request.OrderBy ?? OrderByWorkflowInstance.Created; - var direction = request.OrderBy == OrderByWorkflowInstance.Name ? (request.OrderDirection ?? OrderDirection.Ascending) : (request.OrderDirection ?? OrderDirection.Descending); + request.OrderBy ??= OrderByWorkflowInstance.Created; + var direction = request.OrderBy == OrderByWorkflowInstance.Name ? request.OrderDirection ?? OrderDirection.Ascending : request.OrderDirection ?? OrderDirection.Descending; switch (request.OrderBy) { default: - case OrderByWorkflowInstance.Created: { var o = new WorkflowInstanceOrder { @@ -64,7 +105,7 @@ internal class List : ElsaEndpoint Direction = direction }; - return await _store.SummarizeManyAsync(filter, pageArgs, o, cancellationToken); + return await store.SummarizeManyAsync(filter, pageArgs, o, cancellationToken); } case OrderByWorkflowInstance.UpdatedAt: { @@ -74,7 +115,7 @@ internal class List : ElsaEndpoint Direction = direction }; - return await _store.SummarizeManyAsync(filter, pageArgs, o, cancellationToken); + return await store.SummarizeManyAsync(filter, pageArgs, o, cancellationToken); } case OrderByWorkflowInstance.Finished: { @@ -84,7 +125,7 @@ internal class List : ElsaEndpoint Direction = direction }; - return await _store.SummarizeManyAsync(filter, pageArgs, o, cancellationToken); + return await store.SummarizeManyAsync(filter, pageArgs, o, cancellationToken); } case OrderByWorkflowInstance.Name: { @@ -94,7 +135,7 @@ internal class List : ElsaEndpoint Direction = direction }; - return await _store.SummarizeManyAsync(filter, pageArgs, o, cancellationToken); + return await store.SummarizeManyAsync(filter, pageArgs, o, cancellationToken); } } } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Models.cs index 2e9048e6f..b37960763 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Models.cs @@ -12,25 +12,21 @@ public class Request public string? DefinitionId { get; set; } public ICollection? DefinitionIds { get; set; } public string? CorrelationId { get; set; } + public string? Name { get; set; } public int? Version { get; set; } + public bool? HasIncidents { get; set; } public WorkflowStatus? Status { get; set; } - public ICollection? Statuses { get; set; } + public ICollection? Statuses { get; set; } public WorkflowSubStatus? SubStatus { get; set; } - public ICollection? SubStatuses { get; set; } + public ICollection? SubStatuses { get; set; } public OrderByWorkflowInstance? OrderBy { get; set; } public OrderDirection? OrderDirection { get; set; } - public DateTimeOffset? FromCreatedAt { get; set; } - public DateTimeOffset? ToCreatedAt { get; set; } + public ICollection? TimestampFilters { get; set; } } -public class Response +internal class Response(ICollection items, long totalCount) { - public Response(ICollection items, long totalCount) - { - Items = items; - TotalCount = totalCount; - } + public ICollection Items { get; set; } = items; + public long TotalCount { get; set; } = totalCount; +} - public ICollection Items { get; set; } - public long TotalCount { get; set; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Elsa.Workflows.Management.csproj b/src/modules/Elsa.Workflows.Management/Elsa.Workflows.Management.csproj index 8e2be0af0..e4baba018 100644 --- a/src/modules/Elsa.Workflows.Management/Elsa.Workflows.Management.csproj +++ b/src/modules/Elsa.Workflows.Management/Elsa.Workflows.Management.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/modules/Elsa.Workflows.Management/Enums/TimestampFilterOperator.cs b/src/modules/Elsa.Workflows.Management/Enums/TimestampFilterOperator.cs new file mode 100644 index 000000000..ff6913129 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Enums/TimestampFilterOperator.cs @@ -0,0 +1,37 @@ +namespace Elsa.Workflows.Management.Enums; + +/// +/// Specifies the operators for filtering timestamps in a TimestampFilter object. +/// +public enum TimestampFilterOperator +{ + /// + /// The timestamp is equal to the specified value. + /// + Is, + + /// + /// The timestamp is not equal to the specified value. + /// + IsNot, + + /// + /// The timestamp is before the specified value. + /// + LessThan, + + /// + /// The timestamp is greater than the specified value. + /// + GreaterThan, + + /// + /// The timestamp is less than or equal to the specified value. + /// + LessThanOrEqual, + + /// + /// The timestamp is greater than or equal to the specified value. + /// + GreaterThanOrEqual +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs b/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs index f22feec89..b1e53519f 100644 --- a/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs +++ b/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs @@ -1,4 +1,8 @@ +using System.Diagnostics.CodeAnalysis; using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Enums; +using Elsa.Workflows.Management.Models; +using System.Linq.Dynamic.Core; namespace Elsa.Workflows.Management.Filters; @@ -61,7 +65,7 @@ public class WorkflowInstanceFilter /// Filter workflow instances by status. /// public WorkflowStatus? WorkflowStatus { get; set; } - + /// /// Filter workflow instances by a set of statuses. /// @@ -71,25 +75,26 @@ public class WorkflowInstanceFilter /// Filter workflow instances by sub-status. /// public WorkflowSubStatus? WorkflowSubStatus { get; set; } - + /// /// Filter workflow instances by a set of sub-status. /// public ICollection? WorkflowSubStatuses { get; set; } /// - /// Filter workflow instances by created date time, start from this date time. + /// Filter workflow instances by whether they have incidents. /// - public DateTimeOffset? FromCreatedAt { get; set; } + public bool? HasIncidents { get; set; } /// - /// Filter workflow instances by created date time, to this date time. + /// Filter workflow instances by timestamp. /// - public DateTimeOffset? ToCreatedAt { get; set; } + public ICollection? TimestampFilters { get; set; } /// /// Applies the filter to the specified query. /// + [RequiresUnreferencedCode("The method uses reflection to create an expression tree.")] public IQueryable Apply(IQueryable query) { var filter = this; @@ -106,8 +111,36 @@ public class WorkflowInstanceFilter if (filter.WorkflowSubStatus != null) query = query.Where(x => x.SubStatus == filter.WorkflowSubStatus); if (filter.WorkflowStatuses != null) query = query.Where(x => filter.WorkflowStatuses.Contains(x.Status)); if (filter.WorkflowSubStatuses != null) query = query.Where(x => filter.WorkflowSubStatuses.Contains(x.SubStatus)); - if (filter.ToCreatedAt is not null) query = query.Where(x => filter.ToCreatedAt >= x.CreatedAt); - if (filter.FromCreatedAt is not null) query = query.Where(x => filter.FromCreatedAt <= x.CreatedAt); + if (filter.HasIncidents != null) query = filter.HasIncidents == true ? query.Where(x => x.IncidentCount > 0) : query.Where(x => x.IncidentCount == 0); + + if (TimestampFilters != null) + { + foreach (TimestampFilter timestampFilter in TimestampFilters) + { + var column = timestampFilter.Column; + var timestamp = timestampFilter.Timestamp; + var isZeroTime = timestamp.TimeOfDay == TimeSpan.Zero; + var startDay = new DateTimeOffset(timestamp.Date); + var endDay = startDay.AddDays(1); + + query = timestampFilter.Operator switch + { + TimestampFilterOperator.Is when isZeroTime => query.Where($"{column} >= @0 && {column} < @1", startDay, endDay), + TimestampFilterOperator.Is => query.Where($"{column} == @0", timestamp), + TimestampFilterOperator.IsNot when isZeroTime => query.Where($"{column} < @0 || {column} >= @1", startDay, endDay), + TimestampFilterOperator.IsNot => query.Where($"{column} != @0", timestamp), + TimestampFilterOperator.GreaterThan when isZeroTime => query.Where($"{column} > @0", endDay), + TimestampFilterOperator.GreaterThan => query.Where($"{column} > @0", timestamp), + TimestampFilterOperator.GreaterThanOrEqual when isZeroTime => query.Where($"{column} >= @0", startDay), + TimestampFilterOperator.GreaterThanOrEqual => query.Where($"{column} >= @0", timestamp), + TimestampFilterOperator.LessThan when isZeroTime => query.Where($"{column} < @0", startDay), + TimestampFilterOperator.LessThan => query.Where($"{column} < @0", timestamp), + TimestampFilterOperator.LessThanOrEqual when isZeroTime => query.Where($"{column} <= @0", endDay), + TimestampFilterOperator.LessThanOrEqual => query.Where($"{column} <= @0", timestamp), + _ => query + }; + } + } var searchTerm = filter.SearchTerm; if (!string.IsNullOrWhiteSpace(searchTerm)) @@ -115,10 +148,10 @@ public class WorkflowInstanceFilter query = from instance in query where instance.Name!.Contains(searchTerm) - || instance.DefinitionVersionId.Equals(searchTerm) - || instance.Id.Equals(searchTerm) - || instance.DefinitionId.Equals(searchTerm) - || instance.CorrelationId!.Equals(searchTerm) + || instance.DefinitionVersionId.Contains(searchTerm) + || instance.DefinitionId.Contains(searchTerm) + || instance.Id.Contains(searchTerm) + || instance.CorrelationId!.Contains(searchTerm) select instance; } diff --git a/src/modules/Elsa.Workflows.Management/Models/TimestampFilter.cs b/src/modules/Elsa.Workflows.Management/Models/TimestampFilter.cs new file mode 100644 index 000000000..1d02f2970 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Models/TimestampFilter.cs @@ -0,0 +1,26 @@ +using Elsa.Workflows.Management.Enums; +using JetBrains.Annotations; + +namespace Elsa.Workflows.Management.Models; + +/// +/// Represents a timestamp filter used for filtering data based on a specified timestamp column and operator. +/// +[UsedImplicitly] +public class TimestampFilter +{ + /// + /// Gets or sets the column to filter by. + /// + public required string Column { get; set; } = default!; + + /// + /// Gets or sets the operator to use for filtering. + /// + public required TimestampFilterOperator Operator { get; set; } + + /// + /// Gets or sets the timestamp to filter by. + /// + public required DateTimeOffset Timestamp { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/RequiredMembers.cs b/src/modules/Elsa.Workflows.Management/RequiredMembers.cs new file mode 100644 index 000000000..e1a4aad6d --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/RequiredMembers.cs @@ -0,0 +1,8 @@ +// See: https://github.com/dotnet/core/issues/8016 +namespace System.Runtime.CompilerServices; + +public class RequiredMemberAttribute : Attribute { } +public class CompilerFeatureRequiredAttribute : Attribute +{ + public CompilerFeatureRequiredAttribute(string name) { } +} \ No newline at end of file