diff --git a/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs b/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs
index f0583f08a..d988df328 100644
--- a/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs
+++ b/src/modules/Elsa.Dapper/Extensions/ParameterizedQueryBuilderExtensions.cs
@@ -107,7 +107,7 @@ public static class ParameterizedQueryBuilderExtensions
return query;
}
-
+
///
/// Appends a negating AND clause to the query if the value is not null.
///
@@ -231,6 +231,21 @@ public static class ParameterizedQueryBuilderExtensions
return query;
}
+ ///
+ /// Appends an ORDER BY clause to the query.
+ ///
+ /// The query.
+ /// The fields by which to order.
+ public static ParameterizedQuery OrderBy(this ParameterizedQuery query, params OrderField[] orderFields)
+ {
+ if (!orderFields.Any())
+ return query;
+
+ var clauses = string.Join(",", orderFields.Select(x => $"{x.Field} {(x.Direction == OrderDirection.Ascending ? "asc" : "desc")}"));
+ query.Sql.AppendLine($"order by {clauses}");
+ return query;
+ }
+
///
/// Appends an OFFSET clause to the query.
///
diff --git a/src/modules/Elsa.Dapper/Models/OrderField.cs b/src/modules/Elsa.Dapper/Models/OrderField.cs
new file mode 100644
index 000000000..a7d87ef5c
--- /dev/null
+++ b/src/modules/Elsa.Dapper/Models/OrderField.cs
@@ -0,0 +1,10 @@
+using Elsa.Common.Entities;
+
+namespace Elsa.Dapper.Models;
+
+///
+/// Represents a field by which to order.
+///
+/// The field.
+/// The direction.
+public record OrderField(string Field, OrderDirection Direction);
\ No newline at end of file
diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowStateRecord.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowStateRecord.cs
index 53829689c..5c76bc647 100644
--- a/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowStateRecord.cs
+++ b/src/modules/Elsa.Dapper/Modules/Runtime/Records/WorkflowStateRecord.cs
@@ -8,5 +8,6 @@ internal class WorkflowStateRecord
public string? CorrelationId { get; set; }
public string Status { get; set; } = default!;
public string SubStatus { get; set; } = default!;
+ public long ExecutionLogSequence { get; set; }
public string Props { get; set; } = default!;
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Dapper/Services/Store.cs b/src/modules/Elsa.Dapper/Services/Store.cs
index 5a24beeab..414532ab6 100644
--- a/src/modules/Elsa.Dapper/Services/Store.cs
+++ b/src/modules/Elsa.Dapper/Services/Store.cs
@@ -79,6 +79,19 @@ public class Store where T : notnull
public async Task> FindManyAsync(Action filter, PageArgs pageArgs, string orderKey, OrderDirection orderDirection, CancellationToken cancellationToken = default) =>
await FindManyAsync(filter, pageArgs, orderKey, orderDirection, cancellationToken);
+ ///
+ /// Returns a page of records in the specified shape.
+ ///
+ /// The conditions to apply to the query.
+ /// The page arguments.
+ /// The fields by which to order the results.
+ /// The cancellation token.
+ /// A page of records.
+ public async Task> FindManyAsync(Action filter, PageArgs pageArgs, IEnumerable orderFields, CancellationToken cancellationToken = default)
+ {
+ return await FindManyAsync(filter, pageArgs, orderFields, cancellationToken);
+ }
+
///
/// Returns a page of records in the specified shape.
///
@@ -90,12 +103,26 @@ public class Store where T : notnull
/// The shape type.
/// A page of records.
public async Task> FindManyAsync(Action filter, PageArgs pageArgs, string orderKey, OrderDirection orderDirection, CancellationToken cancellationToken = default)
+ {
+ return await FindManyAsync(filter, pageArgs, new[] { new OrderField(orderKey, orderDirection) }, cancellationToken);
+ }
+
+ ///
+ /// Returns a page of records in the specified shape.
+ ///
+ /// The conditions to apply to the query.
+ /// The page arguments.
+ /// The fields by which to order the results.
+ /// The cancellation token.
+ /// The shape type.
+ /// A page of records.
+ public async Task> FindManyAsync(Action filter, PageArgs pageArgs, IEnumerable orderFields, CancellationToken cancellationToken = default)
{
using var connection = _dbConnectionProvider.GetConnection();
var query = _dbConnectionProvider.CreateQuery().From(TableName);
filter(query);
- query = query.OrderBy(orderKey, orderDirection).Page(pageArgs);
+ query = query.OrderBy(orderFields.ToArray()).Page(pageArgs);
var countQuery = _dbConnectionProvider.CreateQuery().Count(TableName);
filter(countQuery);
diff --git a/src/modules/Elsa.Elasticsearch/Modules/Runtime/WorkflowExecutionLogStore.cs b/src/modules/Elsa.Elasticsearch/Modules/Runtime/WorkflowExecutionLogStore.cs
index 95aa56b6d..fe568a6d6 100644
--- a/src/modules/Elsa.Elasticsearch/Modules/Runtime/WorkflowExecutionLogStore.cs
+++ b/src/modules/Elsa.Elasticsearch/Modules/Runtime/WorkflowExecutionLogStore.cs
@@ -3,6 +3,7 @@ using Elastic.Clients.Elasticsearch.QueryDsl;
using Elsa.Common.Entities;
using Elsa.Common.Models;
using Elsa.Elasticsearch.Common;
+using Elsa.Elasticsearch.Shared.Models;
using Elsa.Extensions;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Entities;
@@ -64,11 +65,21 @@ public class ElasticWorkflowExecutionLogStore : IWorkflowExecutionLogStore
private static SearchRequestDescriptor Sort(SearchRequestDescriptor descriptor, WorkflowExecutionLogRecordOrder order)
{
- var sortDescriptor = new SortOptionsDescriptor();
var propName = order.KeySelector.GetProperty()!.Name;
- var sortOrder = order.Direction == OrderDirection.Ascending ? SortOrder.Asc : SortOrder.Desc;
- sortDescriptor.Field(propName, f => f.Order(sortOrder));
-
+ var orderField = new OrderField(propName, order.Direction);
+ return Sort(descriptor, orderField);
+ }
+
+ private static SearchRequestDescriptor Sort(SearchRequestDescriptor descriptor, params OrderField[] orderFields)
+ {
+ var sortDescriptor = new SortOptionsDescriptor();
+
+ foreach (var orderField in orderFields)
+ {
+ var sortOrder = orderField.Direction == OrderDirection.Ascending ? SortOrder.Asc : SortOrder.Desc;
+ sortDescriptor.Field(orderField.Field, f => f.Order(sortOrder));
+ }
+
descriptor.Sort(sortDescriptor);
return descriptor;
}
diff --git a/src/modules/Elsa.Elasticsearch/Shared/Models/OrderField.cs b/src/modules/Elsa.Elasticsearch/Shared/Models/OrderField.cs
new file mode 100644
index 000000000..9de28785f
--- /dev/null
+++ b/src/modules/Elsa.Elasticsearch/Shared/Models/OrderField.cs
@@ -0,0 +1,10 @@
+using Elsa.Common.Entities;
+
+namespace Elsa.Elasticsearch.Shared.Models;
+
+///
+/// Represents a field by which to order.
+///
+/// The field.
+/// The direction.
+public record OrderField(string Field, OrderDirection Direction);
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Get/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Get/Models.cs
index 5929df37e..f34900a19 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Get/Models.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Get/Models.cs
@@ -19,7 +19,11 @@ internal class Response
public WorkflowSubStatus SubStatus { get; set; }
public string? CorrelationId { get; set; }
public string? Name { get; set; }
- public IDictionary Properties { get; set; }
+
+ [Obsolete("Use WorkflowState.Properties instead.")]
+ public IDictionary Properties { get; set; } = new Dictionary();
+
+ [Obsolete("Use WorkflowState.Fault instead.")]
public WorkflowFaultState? Fault { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset? LastExecutedAt { get; set; }
diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowTriggerExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowTriggerExtensions.cs
index 4ae078fec..202eb110a 100644
--- a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowTriggerExtensions.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowTriggerExtensions.cs
@@ -5,8 +5,16 @@ using Elsa.Workflows.Runtime.Entities;
// ReSharper disable once CheckNamespace
namespace Elsa.Extensions;
+///
+/// Extension methods for .
+///
public static class WorkflowTriggerExtensions
{
+ ///
+ /// Filters a collection of by trigger type.
+ ///
+ /// The collection of triggers to filter.
+ /// The trigger type.
public static IEnumerable Filter(this IEnumerable triggers) where T : ITrigger
{
var triggerName = ActivityTypeNameHelper.GenerateTypeName();