diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml
index 143a3c452..3c0b37617 100644
--- a/.github/workflows/packages.yml
+++ b/.github/workflows/packages.yml
@@ -59,28 +59,28 @@ jobs:
else
echo "VERSION=${{env.base_version}}-${PACKAGE_PREFIX}.${{github.run_number}}" >> $GITHUB_ENV
fi
-# - name: Set up JDK 17
-# uses: actions/setup-java@v2
-# with:
-# java-version: '17'
-# distribution: 'adopt'
+ # - name: Set up JDK 17
+ # uses: actions/setup-java@v2
+ # with:
+ # java-version: '17'
+ # distribution: 'adopt'
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.x
-# - name: Install SonarScanner for .NET
-# run: dotnet tool install --global dotnet-sonarscanner
-# - name: Install Coverlet for code coverage
-# run: dotnet tool install --global coverlet.console
-# - name: Begin SonarCloud analysis
-# env:
-# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
-# run: dotnet sonarscanner begin /k:"elsa-workflows_elsa-core" /o:"elsa-workflows" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.exclusions=**/obj/**,**/*.dll,build/**,samples/**,src/apps/** /d:"sonar.verbose=true" /d:sonar.cs.opencover.reportsPaths=**/testresults/**/coverage.opencover.xml
+ # - name: Install SonarScanner for .NET
+ # run: dotnet tool install --global dotnet-sonarscanner
+ # - name: Install Coverlet for code coverage
+ # run: dotnet tool install --global coverlet.console
+ # - name: Begin SonarCloud analysis
+ # env:
+ # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ # run: dotnet sonarscanner begin /k:"elsa-workflows_elsa-core" /o:"elsa-workflows" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.exclusions=**/obj/**,**/*.dll,build/**,samples/**,src/apps/** /d:"sonar.verbose=true" /d:sonar.cs.opencover.reportsPaths=**/testresults/**/coverage.opencover.xml
- name: Compile+Test+Pack
run: ./build.sh Compile+Test+Pack --version ${VERSION} --analyseCode true
-# - name: End SonarCloud analysis
-# env:
-# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
-# run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
+ # - name: End SonarCloud analysis
+ # env:
+ # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ # run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 24aeb0565..9fd4a9c79 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,199 +1,206 @@
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs
index 2818a232f..4035178eb 100644
--- a/src/apps/Elsa.Server.Web/Program.cs
+++ b/src/apps/Elsa.Server.Web/Program.cs
@@ -212,7 +212,7 @@ services
else if (sqlDatabaseProvider == SqlDatabaseProvider.Citus)
ef.UsePostgreSql(citusConnectionString);
else if (sqlDatabaseProvider == SqlDatabaseProvider.YugabyteDb)
- ef.UsePostgreSql(yugabyteDbConnectionString, configure: dbContextOptions => dbContextOptions.With());
+ ef.UsePostgreSql(yugabyteDbConnectionString);
#if !NET9_0
else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
ef.UseMySql(mySqlConnectionString);
diff --git a/src/clients/Elsa.Api.Client/Elsa.Api.Client.csproj b/src/clients/Elsa.Api.Client/Elsa.Api.Client.csproj
index f8246f150..ef83a5aee 100644
--- a/src/clients/Elsa.Api.Client/Elsa.Api.Client.csproj
+++ b/src/clients/Elsa.Api.Client/Elsa.Api.Client.csproj
@@ -17,4 +17,8 @@
+
+
+
+
diff --git a/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs b/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs
index 8a87220a3..446b012ec 100644
--- a/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs
+++ b/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs
@@ -15,7 +15,10 @@ public static class RefitSettingsHelper
///
public static RefitSettings CreateRefitSettings(IServiceProvider serviceProvider, Action? configureJsonSerializerOptions = null)
{
- var settings = new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions)) };
+ var settings = new RefitSettings
+ {
+ ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions))
+ };
return settings;
}
diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationWorkflowInstanceFilter.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationWorkflowInstanceFilter.cs
index 6f4419114..e741c240e 100644
--- a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationWorkflowInstanceFilter.cs
+++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationWorkflowInstanceFilter.cs
@@ -1,3 +1,4 @@
+using Elsa.Api.Client.Resources.WorkflowInstances.Enums;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Resources.Alterations.Models;
@@ -7,6 +8,11 @@ namespace Elsa.Api.Client.Resources.Alterations.Models;
///
public class AlterationWorkflowInstanceFilter
{
+ ///
+ /// If the filter is empty, all records are matched.
+ ///
+ public bool EmptyFilterSelectsAll { get; set; }
+
///
/// The IDs of the workflow instances that this plan applies to.
///
@@ -16,11 +22,26 @@ public class AlterationWorkflowInstanceFilter
/// The correlation IDs of the workflow instances that this plan applies to.
///
public IEnumerable? CorrelationIds { get; set; }
+
+ ///
+ /// A collection of names associated with the workflow instances being filtered.
+ ///
+ public ICollection? Names { get; set; }
+
+ ///
+ /// A search term used to filter workflow instances based on matching criteria.
+ ///
+ public string? SearchTerm { get; set; }
///
/// A collection of timestamp filters used for filtering data based on specified timestamp columns and operators.
///
public IEnumerable? TimestampFilters { get; set; }
+
+ ///
+ /// The IDs of the workflow definitions that this plan applies to.
+ ///
+ public ICollection? DefinitionIds { get; set; }
///
/// The IDs of the workflow definitions that this plan applies to.
@@ -37,6 +58,16 @@ public class AlterationWorkflowInstanceFilter
///
public bool? IsSystem { get; set; } = false;
+ ///
+ /// Represents the workflow statuses included in the filter.
+ ///
+ public ICollection? Statuses { get; set; }
+
+ ///
+ /// A collection of sub-statuses used to filter workflow instances by their specific sub-state.
+ ///
+ public ICollection? SubStatuses { get; set; }
+
///
/// Represents a collection of filters for activities.
///
diff --git a/src/modules/Elsa.Alterations.Core/Models/AlterationWorkflowInstanceFilter.cs b/src/modules/Elsa.Alterations.Core/Models/AlterationWorkflowInstanceFilter.cs
index de83e2c32..51f2cd680 100644
--- a/src/modules/Elsa.Alterations.Core/Models/AlterationWorkflowInstanceFilter.cs
+++ b/src/modules/Elsa.Alterations.Core/Models/AlterationWorkflowInstanceFilter.cs
@@ -1,3 +1,4 @@
+using Elsa.Workflows;
using Elsa.Workflows.Management.Models;
using JetBrains.Annotations;
@@ -9,6 +10,11 @@ namespace Elsa.Alterations.Core.Models;
[UsedImplicitly]
public class AlterationWorkflowInstanceFilter
{
+ ///
+ /// If the filter is empty, all records are matched.
+ ///
+ public bool EmptyFilterSelectsAll { get; set; }
+
///
/// The IDs of the workflow instances that this plan applies to.
///
@@ -19,11 +25,26 @@ public class AlterationWorkflowInstanceFilter
///
public IEnumerable? CorrelationIds { get; set; }
+ ///
+ /// A collection of names associated with the workflow instances being filtered.
+ ///
+ public ICollection? Names { get; set; }
+
+ ///
+ /// A search term used to filter workflow instances based on matching criteria.
+ ///
+ public string? SearchTerm { get; set; }
+
///
/// A collection of timestamp filters used for filtering data based on specified timestamp columns and operators.
///
public IEnumerable? TimestampFilters { get; set; }
+ ///
+ /// The IDs of the workflow definitions that this plan applies to.
+ ///
+ public ICollection? DefinitionIds { get; set; }
+
///
/// The IDs of the workflow definitions that this plan applies to.
///
@@ -38,6 +59,16 @@ public class AlterationWorkflowInstanceFilter
/// Whether the workflow instances to match are system workflows. Defaults to false.
///
public bool? IsSystem { get; set; } = false;
+
+ ///
+ /// Represents the workflow statuses included in the filter.
+ ///
+ public ICollection? Statuses { get; set; }
+
+ ///
+ /// A collection of sub-statuses used to filter workflow instances by their specific sub-state.
+ ///
+ public ICollection? SubStatuses { get; set; }
///
/// Represents a collection of filters for activities.
diff --git a/src/modules/Elsa.Alterations.Core/Serialization/AlterationSerializationOptionConfigurator.cs b/src/modules/Elsa.Alterations.Core/Serialization/AlterationSerializationOptionConfigurator.cs
index d9de788bf..217331a5f 100644
--- a/src/modules/Elsa.Alterations.Core/Serialization/AlterationSerializationOptionConfigurator.cs
+++ b/src/modules/Elsa.Alterations.Core/Serialization/AlterationSerializationOptionConfigurator.cs
@@ -34,7 +34,7 @@ public class AlterationSerializationOptionConfigurator(IOptions new ActivityExecutionRecordFilter
{
@@ -33,9 +37,10 @@ public class WorkflowInstanceFinder(IWorkflowInstanceStore workflowInstanceStore
Status = x.Status,
}).ToList();
+ var emptyFilterSelectsAll = filter.EmptyFilterSelectsAll;
var workflowInstanceFilterIsEmpty = WorkflowFilterIsEmpty(workflowInstanceFilter);
- var workflowInstanceIds = workflowInstanceFilterIsEmpty
+ var workflowInstanceIds = workflowInstanceFilterIsEmpty && !emptyFilterSelectsAll
? Enumerable.Empty().ToHashSet()
: (await workflowInstanceStore.FindManyIdsAsync(workflowInstanceFilter, cancellationToken)).ToHashSet();
diff --git a/src/modules/Elsa.Alterations/Activities/CompleteAlterationPlan.cs b/src/modules/Elsa.Alterations/Activities/CompleteAlterationPlan.cs
index b63cd9715..17ec70101 100644
--- a/src/modules/Elsa.Alterations/Activities/CompleteAlterationPlan.cs
+++ b/src/modules/Elsa.Alterations/Activities/CompleteAlterationPlan.cs
@@ -13,7 +13,7 @@ namespace Elsa.Alterations.Activities;
/// Marks an alteration plan as completed.
///
[Browsable(false)]
-[Activity("Elsa", "Alterations", "Dispatches jobs for the specified Alteration Plan", Kind = ActivityKind.Job)]
+[Activity("Elsa", "Alterations", "Dispatches jobs for the specified Alteration Plan", Kind = ActivityKind.Task)]
public class CompleteAlterationPlan : CodeActivity
{
///
diff --git a/src/modules/Elsa.Alterations/Activities/DispatchAlterationJobs.cs b/src/modules/Elsa.Alterations/Activities/DispatchAlterationJobs.cs
index a8c2c6fe5..a22a45652 100644
--- a/src/modules/Elsa.Alterations/Activities/DispatchAlterationJobs.cs
+++ b/src/modules/Elsa.Alterations/Activities/DispatchAlterationJobs.cs
@@ -15,7 +15,7 @@ namespace Elsa.Alterations.Activities;
/// Submits an alteration plan for execution.
///
[Browsable(false)]
-[Activity("Elsa", "Alterations", "Dispatches jobs for the specified Alteration Plan", Kind = ActivityKind.Job)]
+[Activity("Elsa", "Alterations", "Dispatches jobs for the specified Alteration Plan", Kind = ActivityKind.Task)]
public class DispatchAlterationJobs : CodeActivity
{
///
diff --git a/src/modules/Elsa.Alterations/Activities/GenerateAlterationJobs.cs b/src/modules/Elsa.Alterations/Activities/GenerateAlterationJobs.cs
index 9abda6ede..be949b0ee 100644
--- a/src/modules/Elsa.Alterations/Activities/GenerateAlterationJobs.cs
+++ b/src/modules/Elsa.Alterations/Activities/GenerateAlterationJobs.cs
@@ -19,7 +19,7 @@ namespace Elsa.Alterations.Activities;
/// Submits an alteration plan for execution.
///
[Browsable(false)]
-[Activity("Elsa", "Alterations", "Generates jobs for the specified Alteration Plan", Kind = ActivityKind.Job)]
+[Activity("Elsa", "Alterations", "Generates jobs for the specified Alteration Plan", Kind = ActivityKind.Task)]
public class GenerateAlterationJobs : CodeActivity
{
///
diff --git a/src/modules/Elsa.Alterations/AlterationHandlers/CancelHandler.cs b/src/modules/Elsa.Alterations/AlterationHandlers/CancelHandler.cs
new file mode 100644
index 000000000..38bc32404
--- /dev/null
+++ b/src/modules/Elsa.Alterations/AlterationHandlers/CancelHandler.cs
@@ -0,0 +1,22 @@
+using Elsa.Alterations.AlterationTypes;
+using Elsa.Alterations.Core.Abstractions;
+using Elsa.Alterations.Core.Contexts;
+using JetBrains.Annotations;
+
+namespace Elsa.Alterations.AlterationHandlers;
+
+///
+/// Upgrades the version of the workflow instance.
+///
+[UsedImplicitly]
+public class CancelHandler : AlterationHandlerBase
+{
+ ///
+ protected override ValueTask HandleAsync(AlterationContext context, Cancel alteration)
+ {
+ context.WorkflowExecutionContext.Cancel();
+
+ context.Succeed();
+ return ValueTask.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Alterations/AlterationTypes/Cancel.cs b/src/modules/Elsa.Alterations/AlterationTypes/Cancel.cs
new file mode 100644
index 000000000..c0a5f7fde
--- /dev/null
+++ b/src/modules/Elsa.Alterations/AlterationTypes/Cancel.cs
@@ -0,0 +1,10 @@
+using Elsa.Alterations.Core.Abstractions;
+using JetBrains.Annotations;
+
+namespace Elsa.Alterations.AlterationTypes;
+
+///
+/// Cancels the workflow instances in an alteration plan.
+///
+[UsedImplicitly]
+public class Cancel : AlterationBase;
\ No newline at end of file
diff --git a/src/modules/Elsa.Alterations/Endpoints/Alterations/DryRun/Endpoint.cs b/src/modules/Elsa.Alterations/Endpoints/Alterations/DryRun/Endpoint.cs
index cbf0d5471..f40175522 100644
--- a/src/modules/Elsa.Alterations/Endpoints/Alterations/DryRun/Endpoint.cs
+++ b/src/modules/Elsa.Alterations/Endpoints/Alterations/DryRun/Endpoint.cs
@@ -22,8 +22,6 @@ public class DryRun(IWorkflowInstanceFinder workflowInstanceFinder) : ElsaEndpoi
public override async Task HandleAsync(AlterationWorkflowInstanceFilter filter, CancellationToken cancellationToken)
{
var workflowInstanceIds = await workflowInstanceFinder.FindAsync(filter, cancellationToken);
-
- // Write response.
var response = new Response(workflowInstanceIds.ToList());
await SendOkAsync(response, cancellationToken);
}
diff --git a/src/modules/Elsa.Alterations/Extensions/ServiceCollectionExtensions.cs b/src/modules/Elsa.Alterations/Extensions/ServiceCollectionExtensions.cs
index 76a6818b8..b930d2878 100644
--- a/src/modules/Elsa.Alterations/Extensions/ServiceCollectionExtensions.cs
+++ b/src/modules/Elsa.Alterations/Extensions/ServiceCollectionExtensions.cs
@@ -20,6 +20,7 @@ public static class ServiceCollectionExtensions
services.AddAlteration();
services.AddAlteration();
services.AddAlteration();
+ services.AddAlteration();
services.AddNotificationHandlersFrom();
return services;
}
diff --git a/src/modules/Elsa.Dapper/Elsa.Dapper.csproj b/src/modules/Elsa.Dapper/Elsa.Dapper.csproj
index aaceef3cf..6bdc0eaca 100644
--- a/src/modules/Elsa.Dapper/Elsa.Dapper.csproj
+++ b/src/modules/Elsa.Dapper/Elsa.Dapper.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/Extensions/BulkUpsertExtensions.cs b/src/modules/Elsa.EntityFrameworkCore.Common/Extensions/BulkUpsertExtensions.cs
new file mode 100644
index 000000000..810b43ac9
--- /dev/null
+++ b/src/modules/Elsa.EntityFrameworkCore.Common/Extensions/BulkUpsertExtensions.cs
@@ -0,0 +1,403 @@
+using System.Text;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using System.Linq.Expressions;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.EntityFrameworkCore.Extensions;
+
+///
+/// Provides extension methods to perform bulk upsert operations for entities
+/// in an Entity Framework Core context, supporting multiple database providers.
+///
+public static class BulkUpsertExtensions
+{
+ ///
+ /// Performs a bulk upsert operation on a list of entities in the specified database context using a key selector.
+ ///
+ /// The type of the database context.
+ /// The type of the entity being upserted.
+ /// The database context where the bulk upsert operation will be executed.
+ /// The list of entities to be upserted.
+ /// An expression used to determine the key for upsert operations.
+ /// A token to observe while waiting for the operation to complete.
+ public static async Task BulkUpsertAsync(
+ this TDbContext dbContext,
+ IList entities,
+ Expression> keySelector,
+ CancellationToken cancellationToken = default)
+ where TDbContext : DbContext
+ where TEntity : class, new()
+ {
+ await BulkUpsertAsync(dbContext, entities, keySelector, 50, cancellationToken);
+ }
+
+ ///
+ /// Performs a bulk upsert operation on a list of entities in the specified database context using a key selector and optional batch size.
+ ///
+ /// The type of the database context.
+ /// The type of the entity being upserted.
+ /// The database context where the bulk upsert operation will be executed.
+ /// The list of entities to be upserted.
+ /// An expression used to determine the key for upsert operations.
+ /// The size of each batch for processing the upsert operation. Defaults to 50.
+ /// A token to observe while waiting for the operation to complete.
+ /// Thrown if the database provider for the context is not supported.
+ public static async Task BulkUpsertAsync(
+ this TDbContext dbContext,
+ IList entities,
+ Expression> keySelector,
+ int batchSize = 50,
+ CancellationToken cancellationToken = default)
+ where TDbContext : DbContext
+ where TEntity : class, new()
+ {
+ if (entities.Count == 0)
+ return;
+
+ // Identify the current provider (e.g., "Microsoft.EntityFrameworkCore.SqlServer")
+ var providerName = dbContext.Database.ProviderName?.ToLowerInvariant() ?? string.Empty;
+
+ // Determine the method for generating SQL based on the provider
+ Func, Expression>, (string, object[])> generateSql = providerName switch
+ {
+ var pn when pn.Contains("sqlserver") => GenerateSqlServerUpsert,
+ var pn when pn.Contains("sqlite") => GenerateSqliteUpsert,
+ var pn when pn.Contains("postgres") => GeneratePostgresUpsert,
+ var pn when pn.Contains("mysql") => GenerateMySqlUpsert,
+ var pn when pn.Contains("oracle") => GenerateOracleUpsert,
+ _ => throw new NotSupportedException($"Provider '{providerName}' is not supported.")
+ };
+
+ // Loop through batched entities
+ foreach (var batch in entities.Chunk(batchSize))
+ {
+ // Generate SQL and parameters
+ var (sql, parameters) = generateSql(dbContext, batch, keySelector);
+
+ await dbContext.Database.ExecuteSqlRawAsync(sql, parameters, cancellationToken);
+ }
+ }
+
+ private static (string, object[]) GenerateSqlServerUpsert(
+ DbContext dbContext,
+ IList entities,
+ Expression> keySelector)
+ where TEntity : class
+ {
+ var entityType = dbContext.Model.FindEntityType(typeof(TEntity))!;
+ var tableName = $"[{entityType.GetSchema()}].[{entityType.GetTableName()}]";
+ var storeObject = StoreObjectIdentifier.Table(entityType.GetTableName()!, entityType.GetSchema());
+
+ // Include shadow properties
+ var props = entityType.GetProperties().ToList();
+
+ var keyProp = entityType.FindProperty(keySelector.GetMemberAccess().Name)!;
+ var keyColumnName = $"[{keyProp.GetColumnName(storeObject)}]";
+ var columnNames = props
+ .Select(p => $"[{p.GetColumnName(storeObject)}]")
+ .ToList();
+
+ var mergeSql = new StringBuilder();
+ mergeSql.AppendLine($"MERGE {tableName} AS Target");
+ mergeSql.AppendLine("USING (VALUES");
+
+ var parameters = new List
-
+
diff --git a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.Cancel.cs b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.Cancel.cs
index 74a21f7f6..c4f7fe4f6 100644
--- a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.Cancel.cs
+++ b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.Cancel.cs
@@ -24,7 +24,7 @@ public partial class WorkflowExecutionContext
Bookmarks.Clear();
_completionCallbackEntries.Clear();
- if (Status != WorkflowStatus.Running && SubStatus != WorkflowSubStatus.Faulted)
+ if (!CanTransitionTo(WorkflowSubStatus.Cancelled))
return;
AddExecutionLogEntry("Workflow cancelled");
diff --git a/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs b/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs
index 2fa5d1ac2..c225a1b63 100644
--- a/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs
+++ b/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs
@@ -116,6 +116,11 @@ public class WorkflowInstanceFilter
///
public ICollection? TimestampFilters { get; set; }
+ ///
+ /// Filter workflow instances by name.
+ ///
+ public List? Names { get; set; }
+
///
/// Applies the filter to the specified query.
///
@@ -134,6 +139,7 @@ public class WorkflowInstanceFilter
if (filter.ParentWorkflowInstanceIds != null) query = query.Where(x => x.ParentWorkflowInstanceId != null && filter.ParentWorkflowInstanceIds.Contains(x.ParentWorkflowInstanceId));
if (!string.IsNullOrWhiteSpace(filter.CorrelationId)) query = query.Where(x => x.CorrelationId == filter.CorrelationId);
if (filter.CorrelationIds != null) query = query.Where(x => filter.CorrelationIds.Contains(x.CorrelationId!));
+ if (filter.Names != null) query = query.Where(x => filter.Names.Contains(x.Name!));
if (filter.WorkflowStatus != null) query = query.Where(x => x.Status == filter.WorkflowStatus);
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));
diff --git a/src/modules/Elsa.Workflows.Runtime/Bookmarks/ExecuteWorkflowPayload.cs b/src/modules/Elsa.Workflows.Runtime/Bookmarks/ExecuteWorkflowPayload.cs
new file mode 100644
index 000000000..c6cbbec68
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Runtime/Bookmarks/ExecuteWorkflowPayload.cs
@@ -0,0 +1,9 @@
+using Elsa.Workflows.Runtime.Activities;
+
+namespace Elsa.Workflows.Runtime.Bookmarks;
+
+///
+/// Bookmark payload for the activity.
+///
+/// The instance ID of the child workflow that was created by the activity.
+public record ExecuteWorkflowPayload(string ChildInstanceId);
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowDispatcherExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowDispatcherExtensions.cs
index c8f532767..ff8a14685 100644
--- a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowDispatcherExtensions.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowDispatcherExtensions.cs
@@ -14,7 +14,7 @@ public static class WorkflowDispatcherExtensions
///
public static Task DispatchAsync(this IWorkflowDispatcher workflowDispatcher, DispatchWorkflowDefinitionRequest request, CancellationToken cancellationToken = default)
{
- return workflowDispatcher.DispatchAsync(request, new DispatchWorkflowOptions(), cancellationToken);
+ return workflowDispatcher.DispatchAsync(request, new(), cancellationToken);
}
///
@@ -22,7 +22,7 @@ public static class WorkflowDispatcherExtensions
///
public static Task DispatchAsync(this IWorkflowDispatcher workflowDispatcher, DispatchWorkflowInstanceRequest request, CancellationToken cancellationToken = default)
{
- return workflowDispatcher.DispatchAsync(request, new DispatchWorkflowOptions(), cancellationToken);
+ return workflowDispatcher.DispatchAsync(request, new(), cancellationToken);
}
///
@@ -30,7 +30,7 @@ public static class WorkflowDispatcherExtensions
///
public static Task DispatchAsync(this IWorkflowDispatcher workflowDispatcher, DispatchTriggerWorkflowsRequest request, CancellationToken cancellationToken = default)
{
- return workflowDispatcher.DispatchAsync(request, new DispatchWorkflowOptions(), cancellationToken);
+ return workflowDispatcher.DispatchAsync(request, new(), cancellationToken);
}
///
@@ -38,6 +38,6 @@ public static class WorkflowDispatcherExtensions
///
public static Task DispatchAsync(this IWorkflowDispatcher workflowDispatcher, DispatchResumeWorkflowsRequest request, CancellationToken cancellationToken = default)
{
- return workflowDispatcher.DispatchAsync(request, new DispatchWorkflowOptions(), cancellationToken);
+ return workflowDispatcher.DispatchAsync(request, new(), cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs
index 109286bfd..ec5ab8537 100644
--- a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs
@@ -31,13 +31,8 @@ namespace Elsa.Workflows.Runtime.Features;
/// Installs and configures workflow runtime features.
///
[DependsOn(typeof(SystemClockFeature))]
-public class WorkflowRuntimeFeature : FeatureBase
+public class WorkflowRuntimeFeature(IModule module) : FeatureBase(module)
{
- ///
- public WorkflowRuntimeFeature(IModule module) : base(module)
- {
- }
-
private IDictionary WorkflowDispatcherChannels { get; set; } = new Dictionary();
///
@@ -142,6 +137,24 @@ public class WorkflowRuntimeFeature : FeatureBase
///
public Action BookmarkQueuePurgeOptions { get; set; } = _ => { };
+ ///
+ /// Enables the workflow inbox cleanup job.
+ ///
+ public WorkflowRuntimeFeature EnableWorkflowInboxCleanupJob()
+ {
+ Services.Configure(options => { options.IsEnabled = true; });
+ return this;
+ }
+
+ ///
+ /// Disables the workflow inbox cleanup job.
+ ///
+ public WorkflowRuntimeFeature DisableWorkflowInboxCleanupJob()
+ {
+ Services.Configure(options => { options.IsEnabled = false; });
+ return this;
+ }
+
///
/// Register the specified workflow type.
///
diff --git a/src/modules/Elsa.Workflows.Runtime/Options/WorkflowInboxCleanupOptions.cs b/src/modules/Elsa.Workflows.Runtime/Options/WorkflowInboxCleanupOptions.cs
index fc694689e..7aa1266d8 100644
--- a/src/modules/Elsa.Workflows.Runtime/Options/WorkflowInboxCleanupOptions.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Options/WorkflowInboxCleanupOptions.cs
@@ -14,4 +14,9 @@ public class WorkflowInboxCleanupOptions
/// The number of messages to clean up per sweep.
///
public int BatchSize { get; set; } = 1000;
+
+ ///
+ /// Whether the workflow inbox cleanup is enabled.
+ ///
+ public bool IsEnabled { get; set; } = true;
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Runtime/Results/WorkflowExecutionResult.cs b/src/modules/Elsa.Workflows.Runtime/Results/WorkflowExecutionResult.cs
index 298c7357b..d4c5832e1 100644
--- a/src/modules/Elsa.Workflows.Runtime/Results/WorkflowExecutionResult.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Results/WorkflowExecutionResult.cs
@@ -2,4 +2,11 @@ using Elsa.Workflows.Models;
namespace Elsa.Workflows.Runtime.Results;
-public record WorkflowExecutionResult(string WorkflowInstanceId, WorkflowStatus Status, WorkflowSubStatus SubStatus, ICollection Bookmarks, ICollection Incidents, string? TriggeredActivityId = null);
\ No newline at end of file
+public record WorkflowExecutionResult(
+ string WorkflowInstanceId,
+ WorkflowStatus Status,
+ WorkflowSubStatus SubStatus,
+ ICollection Bookmarks,
+ ICollection Incidents,
+ string? TriggeredActivityId,
+ IDictionary Output);
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Runtime/Services/ObsoleteWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime/Services/ObsoleteWorkflowRuntime.cs
index aa26779bc..33cafbe82 100644
--- a/src/modules/Elsa.Workflows.Runtime/Services/ObsoleteWorkflowRuntime.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Services/ObsoleteWorkflowRuntime.cs
@@ -66,7 +66,7 @@ public class ObsoleteWorkflowRuntime(
TriggerActivityId = options?.TriggerActivityId
};
var response = await client.CreateAndRunInstanceAsync(createRequest, cancellationToken);
- return new(response.WorkflowInstanceId, response.Status, response.SubStatus, response.Bookmarks, response.Incidents);
+ return new(response.WorkflowInstanceId, response.Status, response.SubStatus, response.Bookmarks, response.Incidents, null, null);
}
public async Task> StartWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null)
@@ -81,7 +81,7 @@ public class ObsoleteWorkflowRuntime(
Input = options?.Input
};
var result = await stimulusSender.SendAsync(activityTypeName, bookmarkPayload, metadata, cancellationToken);
- var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents)).ToList();
+ var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents, null, null)).ToList();
return results;
}
@@ -109,7 +109,7 @@ public class ObsoleteWorkflowRuntime(
var response = await workflowClient.RunInstanceAsync(runWorkflowRequest, cancellationToken);
- return new(response.WorkflowInstanceId, response.Status, response.SubStatus, response.Bookmarks, response.Incidents);
+ return new(response.WorkflowInstanceId, response.Status, response.SubStatus, response.Bookmarks, response.Incidents,null, null);
}
public async Task> ResumeWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null)
@@ -124,7 +124,7 @@ public class ObsoleteWorkflowRuntime(
Input = options?.Input
};
var result = await stimulusSender.SendAsync(activityTypeName, bookmarkPayload, metadata, cancellationToken);
- var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents)).ToList();
+ var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents, null, null)).ToList();
return results;
}
@@ -140,7 +140,7 @@ public class ObsoleteWorkflowRuntime(
Input = options?.Input
};
var result = await stimulusSender.SendAsync(activityTypeName, bookmarkPayload, metadata, cancellationToken);
- var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents)).ToList();
+ var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents, null, null)).ToList();
return new(results);
}
diff --git a/src/modules/Elsa.Workflows.Runtime/Services/StimulusProxyWorkflowInbox.cs b/src/modules/Elsa.Workflows.Runtime/Services/StimulusProxyWorkflowInbox.cs
index 53f1573c3..e7c79f754 100644
--- a/src/modules/Elsa.Workflows.Runtime/Services/StimulusProxyWorkflowInbox.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Services/StimulusProxyWorkflowInbox.cs
@@ -1,4 +1,5 @@
using Elsa.Common;
+using Elsa.Extensions;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Entities;
@@ -180,7 +181,9 @@ public class StimulusProxyWorkflowInbox(
response.Status,
response.SubStatus,
new List(),
- response.Incidents)
+ response.Incidents,
+ null,
+ null)
);
}
}
\ No newline at end of file