Merge pull request #6483 from elsa-workflows/blueberry

Merge blueberry into master
This commit is contained in:
Sipke Schoorstra 2025-03-11 19:08:36 +01:00 committed by GitHub
commit 02db706b7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 883 additions and 280 deletions

View file

@ -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:

View file

@ -1,199 +1,206 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Antlr4.Runtime.Standard" Version="4.13.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations" Version="0.5.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations.MySql" Version="0.5.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations.PostgreSQL" Version="0.5.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations.SQLite" Version="0.5.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations.SqlServer" Version="0.5.1" />
<PackageVersion Include="AutoMapper" Version="13.0.1" PrivateAssets="All" />
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.18.3" />
<PackageVersion Include="Azure.ResourceManager" Version="1.13.0" />
<PackageVersion Include="Azure.ResourceManager.AppContainers" Version="1.3.0" />
<PackageVersion Include="Azure.ResourceManager.Resources" Version="1.9.0" />
<PackageVersion Include="Azure.ResourceManager.ServiceBus" Version="1.1.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="Bogus" Version="35.6.1" />
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" PrivateAssets="All" />
<PackageVersion Include="Confluent.Kafka" Version="2.8.0" />
<PackageVersion Include="Confluent.SchemaRegistry.Serdes.Avro" Version="2.8.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" PrivateAssets="All" />
<PackageVersion Include="Cronos" Version="0.9.0" />
<PackageVersion Include="Dapper" Version="2.1.35" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.8" />
<PackageVersion Include="DistributedLock.FileSystem" Version="1.0.3" />
<PackageVersion Include="DistributedLock.Postgres" Version="1.2.1" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3" />
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="8.17.1" />
<PackageVersion Include="Elsa.Studio" Version="3.3.1" />
<PackageVersion Include="Elsa.Studio.Agents" Version="3.3.1" />
<PackageVersion Include="Elsa.Studio.Core.BlazorWasm" Version="3.3.1" />
<PackageVersion Include="Elsa.Studio.Login.BlazorWasm" Version="3.3.1" />
<PackageVersion Include="FastEndpoints" Version="5.33.0" />
<PackageVersion Include="FastEndpoints.Security" Version="5.33.0" />
<PackageVersion Include="FastEndpoints.Swagger" Version="5.33.0" />
<PackageVersion Include="FluentMigrator" Version="6.2.0" />
<PackageVersion Include="FluentMigrator.Runner" Version="6.2.0" />
<PackageVersion Include="FluentStorage" Version="5.6.0" />
<PackageVersion Include="FluentStorage.Azure.Blobs" Version="5.3.0" />
<PackageVersion Include="Fluid.Core" Version="2.19.0" />
<PackageVersion Include="Fody" Version="6.9.1" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="Google.Protobuf" Version="3.29.3" />
<PackageVersion Include="Grpc.Net.Client" Version="2.67.0" />
<PackageVersion Include="Grpc.Tools" Version="2.69.0" />
<PackageVersion Include="Hangfire" Version="1.8.17" />
<PackageVersion Include="Hangfire.MemoryStorage" Version="1.8.1.1" />
<PackageVersion Include="Hangfire.PostgreSql" Version="1.20.10" />
<PackageVersion Include="Hangfire.Storage.SQLite" Version="0.4.2" />
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
<PackageVersion Include="IronCompress" Version="1.6.3" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="Jint" Version="4.1.0" />
<PackageVersion Include="LinqKit.Core" Version="1.2.8" />
<PackageVersion Include="MailKit" Version="4.9.0" />
<PackageVersion Include="MassTransit" Version="8.3.4" />
<PackageVersion Include="MassTransit.Azure.ServiceBus.Core" Version="8.3.4" />
<PackageVersion Include="MassTransit.RabbitMQ" Version="8.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.12.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
<PackageVersion Include="Microsoft.Identity.Client" Version="4.67.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="MongoDB.Driver" Version="3.1.0" />
<PackageVersion Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.0.0" />
<PackageVersion Include="MongoDB.Driver.Extensions" Version="2.0.2" />
<PackageVersion Include="MySql.Data" Version="9.1.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="NuGet.Packaging" Version="6.12.1" />
<PackageVersion Include="NuGet.Protocol" Version="6.12.1" />
<PackageVersion Include="Nuke.Components" Version="9.0.4" />
<PackageVersion Include="Open.Linq.AsyncExtensions" Version="1.2.0" />
<PackageVersion Include="OpenTelemetry.AutoInstrumentation" Version="1.10.0" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.11.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.StackExchangeRedis" Version="1.11.0-beta.1" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="Proto.Actor" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster.AzureContainerApps" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster.CodeGen" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster.Kubernetes" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster.TestProvider" Version="1.7.0" />
<PackageVersion Include="Proto.OpenTelemetry" Version="1.7.0" />
<PackageVersion Include="Proto.Persistence" Version="1.7.0" />
<PackageVersion Include="Proto.Persistence.Sqlite" Version="1.7.0" />
<PackageVersion Include="Proto.Persistence.SqlServer" Version="1.7.0" />
<PackageVersion Include="Proto.Remote" Version="1.7.0" />
<PackageVersion Include="pythonnet" Version="3.1.0-preview2024-09-06" />
<PackageVersion Include="Quartz.Extensions.Hosting" Version="3.13.1" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.13.1" />
<PackageVersion Include="Scrutor" Version="6.0.1" />
<PackageVersion Include="ShortGuid" Version="2.0.1" />
<PackageVersion Include="StackExchange.Redis" Version="2.8.24" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.6.0-preview-03" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="Testcontainers" Version="4.1.0" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.1.0" />
<PackageVersion Include="Testcontainers.RabbitMq" Version="4.1.0" />
<PackageVersion Include="Testcontainers.Redis" Version="4.1.0" />
<PackageVersion Include="ThrottleDebounce" Version="2.0.0" />
<PackageVersion Include="WebhooksCore" Version="0.0.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
<PackageVersion Include="xunit.extensibility.core" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1" />
<PackageVersion Include="Yarp.ReverseProxy" Version="2.3.0" />
<PackageVersion Include="AspNetCore.Authentication.ApiKey" Version="8.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.12" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.12" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.12" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="8.0.12" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageVersion Include="Npgsql" Version="8.0.6" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="8.23.70" />
<PackageVersion Include="Polly" Version="8.5.1" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageVersion Include="Refit" Version="8.0.0" />
<PackageVersion Include="Refit.HttpClientFactory" Version="8.0.0" />
<PackageVersion Include="System.Formats.Asn1" Version="8.0.1" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.1" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.1" />
<PackageVersion Include="Npgsql" Version="9.0.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="9.23.60" />
<PackageVersion Include="Polly" Version="8.5.1" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.2.efcore.9.0.0" />
<PackageVersion Include="Refit" Version="8.0.0" />
<PackageVersion Include="Refit.HttpClientFactory" Version="8.0.0" />
<PackageVersion Include="System.Formats.Asn1" Version="9.0.1" />
<PackageVersion Include="System.Text.Json" Version="9.0.1" />
</ItemGroup>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Antlr4.Runtime.Standard" Version="4.13.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations" Version="0.5.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations.MySql" Version="0.5.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations.PostgreSQL" Version="0.5.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations.SQLite" Version="0.5.1" />
<PackageVersion Include="AppAny.Quartz.EntityFrameworkCore.Migrations.SqlServer" Version="0.5.1" />
<PackageVersion Include="AutoMapper" Version="13.0.1" PrivateAssets="All" />
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.18.3" />
<PackageVersion Include="Azure.ResourceManager" Version="1.13.0" />
<PackageVersion Include="Azure.ResourceManager.AppContainers" Version="1.3.0" />
<PackageVersion Include="Azure.ResourceManager.Resources" Version="1.9.0" />
<PackageVersion Include="Azure.ResourceManager.ServiceBus" Version="1.1.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="Bogus" Version="35.6.1" />
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" PrivateAssets="All" />
<PackageVersion Include="Confluent.Kafka" Version="2.8.0" />
<PackageVersion Include="Confluent.SchemaRegistry.Serdes.Avro" Version="2.8.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" PrivateAssets="All" />
<PackageVersion Include="Cronos" Version="0.9.0" />
<PackageVersion Include="Dapper" Version="2.1.35" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.8" />
<PackageVersion Include="DistributedLock.FileSystem" Version="1.0.3" />
<PackageVersion Include="DistributedLock.Postgres" Version="1.2.1" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3" />
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="8.17.1" />
<PackageVersion Include="Elsa.Studio" Version="3.3.1" />
<PackageVersion Include="Elsa.Studio.Agents" Version="3.3.1" />
<PackageVersion Include="Elsa.Studio.Core.BlazorWasm" Version="3.3.1" />
<PackageVersion Include="Elsa.Studio.Login.BlazorWasm" Version="3.3.1" />
<PackageVersion Include="FastEndpoints" Version="5.33.0" />
<PackageVersion Include="FastEndpoints.Security" Version="5.33.0" />
<PackageVersion Include="FastEndpoints.Swagger" Version="5.33.0" />
<PackageVersion Include="FluentMigrator" Version="6.2.0" />
<PackageVersion Include="FluentMigrator.Runner" Version="6.2.0" />
<PackageVersion Include="FluentStorage" Version="5.6.0" />
<PackageVersion Include="FluentStorage.Azure.Blobs" Version="5.3.0" />
<PackageVersion Include="Fluid.Core" Version="2.19.0" />
<PackageVersion Include="Fody" Version="6.9.1" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="Google.Protobuf" Version="3.29.3" />
<PackageVersion Include="Grpc.Net.Client" Version="2.67.0" />
<PackageVersion Include="Grpc.Tools" Version="2.69.0" />
<PackageVersion Include="Hangfire" Version="1.8.17" />
<PackageVersion Include="Hangfire.MemoryStorage" Version="1.8.1.1" />
<PackageVersion Include="Hangfire.PostgreSql" Version="1.20.10" />
<PackageVersion Include="Hangfire.Storage.SQLite" Version="0.4.2" />
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
<PackageVersion Include="IronCompress" Version="1.6.3" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="Jint" Version="4.1.0" />
<PackageVersion Include="LinqKit.Core" Version="1.2.8" />
<PackageVersion Include="MailKit" Version="4.9.0" />
<PackageVersion Include="MassTransit" Version="8.3.4" />
<PackageVersion Include="MassTransit.Azure.ServiceBus.Core" Version="8.3.4" />
<PackageVersion Include="MassTransit.Extensions.DependencyInjection" Version="7.3.1" />
<PackageVersion Include="MassTransit.RabbitMQ" Version="8.3.4" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.7" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.12.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.6" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
<PackageVersion Include="Microsoft.Identity.Client" Version="4.67.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="MongoDB.Driver" Version="3.1.0" />
<PackageVersion Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.0.0" />
<PackageVersion Include="MongoDB.Driver.Extensions" Version="2.0.2" />
<PackageVersion Include="MySql.Data" Version="9.1.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="NuGet.Packaging" Version="6.12.1" />
<PackageVersion Include="NuGet.Protocol" Version="6.12.1" />
<PackageVersion Include="Nuke.Components" Version="9.0.4" />
<PackageVersion Include="Open.Linq.AsyncExtensions" Version="1.2.0" />
<PackageVersion Include="OpenTelemetry.AutoInstrumentation" Version="1.10.0" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.11.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.StackExchangeRedis" Version="1.11.0-beta.1" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="Proto.Actor" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster.CodeGen" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster.AzureContainerApps" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster.Kubernetes" Version="1.7.0" />
<PackageVersion Include="Proto.Cluster.TestProvider" Version="1.7.0" />
<PackageVersion Include="Proto.OpenTelemetry" Version="1.7.0" />
<PackageVersion Include="Proto.Persistence" Version="1.7.0" />
<PackageVersion Include="Proto.Persistence.Sqlite" Version="1.7.0" />
<PackageVersion Include="Proto.Persistence.SqlServer" Version="1.7.0" />
<PackageVersion Include="Proto.Remote" Version="1.7.0" />
<PackageVersion Include="pythonnet" Version="3.1.0-preview2024-09-06" />
<PackageVersion Include="Quartz.Extensions.Hosting" Version="3.13.1" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.13.1" />
<PackageVersion Include="Scrutor" Version="6.0.1" />
<PackageVersion Include="ShortGuid" Version="2.0.1" />
<PackageVersion Include="StackExchange.Redis" Version="2.8.24" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.6.0-preview-03" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="Testcontainers" Version="4.1.0" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.1.0" />
<PackageVersion Include="Testcontainers.RabbitMq" Version="4.1.0" />
<PackageVersion Include="Testcontainers.Redis" Version="4.1.0" />
<PackageVersion Include="ThrottleDebounce" Version="2.0.0" />
<PackageVersion Include="WebhooksCore" Version="0.0.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
<PackageVersion Include="xunit.extensibility.core" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1" />
<PackageVersion Include="Yarp.ReverseProxy" Version="2.3.0" />
<PackageVersion Include="AspNetCore.Authentication.ApiKey" Version="8.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.12" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.12" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.12" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.12" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="8.0.12" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageVersion Include="Npgsql" Version="8.0.6" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="8.23.70" />
<PackageVersion Include="Polly" Version="8.5.1" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageVersion Include="Refit" Version="8.0.0" />
<PackageVersion Include="Refit.HttpClientFactory" Version="8.0.0" />
<PackageVersion Include="System.Formats.Asn1" Version="8.0.1" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="8.10.0" />
<PackageVersion Include="Microsoft.Extensions.Resilience" Version="8.10.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.2" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.2" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.2" />
<PackageVersion Include="Npgsql" Version="9.0.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="9.23.60" />
<PackageVersion Include="Polly" Version="8.5.1" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.2.efcore.9.0.0" />
<PackageVersion Include="Refit" Version="8.0.0" />
<PackageVersion Include="Refit.HttpClientFactory" Version="8.0.0" />
<PackageVersion Include="System.Formats.Asn1" Version="9.0.2" />
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.2.0" />
<PackageVersion Include="Microsoft.Extensions.Resilience" Version="9.2.0" />
</ItemGroup>
</Project>

View file

@ -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);

View file

@ -17,4 +17,8 @@
<PackageReference Include="Refit.HttpClientFactory" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\modules\Elsa.Expressions\Elsa.Expressions.csproj" />
</ItemGroup>
</Project>

View file

@ -15,7 +15,10 @@ public static class RefitSettingsHelper
/// </summary>
public static RefitSettings CreateRefitSettings(IServiceProvider serviceProvider, Action<IServiceProvider, JsonSerializerOptions>? configureJsonSerializerOptions = null)
{
var settings = new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions)) };
var settings = new RefitSettings
{
ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions))
};
return settings;
}

View file

@ -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;
/// </summary>
public class AlterationWorkflowInstanceFilter
{
/// <summary>
/// If the filter is empty, all records are matched.
/// </summary>
public bool EmptyFilterSelectsAll { get; set; }
/// <summary>
/// The IDs of the workflow instances that this plan applies to.
/// </summary>
@ -16,11 +22,26 @@ public class AlterationWorkflowInstanceFilter
/// The correlation IDs of the workflow instances that this plan applies to.
/// </summary>
public IEnumerable<string>? CorrelationIds { get; set; }
/// <summary>
/// A collection of names associated with the workflow instances being filtered.
/// </summary>
public ICollection<string>? Names { get; set; }
/// <summary>
/// A search term used to filter workflow instances based on matching criteria.
/// </summary>
public string? SearchTerm { get; set; }
/// <summary>
/// A collection of timestamp filters used for filtering data based on specified timestamp columns and operators.
/// </summary>
public IEnumerable<TimestampFilter>? TimestampFilters { get; set; }
/// <summary>
/// The IDs of the workflow definitions that this plan applies to.
/// </summary>
public ICollection<string>? DefinitionIds { get; set; }
/// <summary>
/// The IDs of the workflow definitions that this plan applies to.
@ -37,6 +58,16 @@ public class AlterationWorkflowInstanceFilter
/// </summary>
public bool? IsSystem { get; set; } = false;
/// <summary>
/// Represents the workflow statuses included in the filter.
/// </summary>
public ICollection<WorkflowStatus>? Statuses { get; set; }
/// <summary>
/// A collection of sub-statuses used to filter workflow instances by their specific sub-state.
/// </summary>
public ICollection<WorkflowSubStatus>? SubStatuses { get; set; }
/// <summary>
/// Represents a collection of filters for activities.
/// </summary>

View file

@ -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
{
/// <summary>
/// If the filter is empty, all records are matched.
/// </summary>
public bool EmptyFilterSelectsAll { get; set; }
/// <summary>
/// The IDs of the workflow instances that this plan applies to.
/// </summary>
@ -19,11 +25,26 @@ public class AlterationWorkflowInstanceFilter
/// </summary>
public IEnumerable<string>? CorrelationIds { get; set; }
/// <summary>
/// A collection of names associated with the workflow instances being filtered.
/// </summary>
public ICollection<string>? Names { get; set; }
/// <summary>
/// A search term used to filter workflow instances based on matching criteria.
/// </summary>
public string? SearchTerm { get; set; }
/// <summary>
/// A collection of timestamp filters used for filtering data based on specified timestamp columns and operators.
/// </summary>
public IEnumerable<TimestampFilter>? TimestampFilters { get; set; }
/// <summary>
/// The IDs of the workflow definitions that this plan applies to.
/// </summary>
public ICollection<string>? DefinitionIds { get; set; }
/// <summary>
/// The IDs of the workflow definitions that this plan applies to.
/// </summary>
@ -38,6 +59,16 @@ public class AlterationWorkflowInstanceFilter
/// Whether the workflow instances to match are system workflows. Defaults to <c>false</c>.
/// </summary>
public bool? IsSystem { get; set; } = false;
/// <summary>
/// Represents the workflow statuses included in the filter.
/// </summary>
public ICollection<WorkflowStatus>? Statuses { get; set; }
/// <summary>
/// A collection of sub-statuses used to filter workflow instances by their specific sub-state.
/// </summary>
public ICollection<WorkflowSubStatus>? SubStatuses { get; set; }
/// <summary>
/// Represents a collection of filters for activities.

View file

@ -34,7 +34,7 @@ public class AlterationSerializationOptionConfigurator(IOptions<AlterationOption
foreach (var alterationType in alterationTypes.ToList())
{
polymorphismOptions.DerivedTypes.Add(new JsonDerivedType(alterationType, alterationType.Name));
polymorphismOptions.DerivedTypes.Add(new(alterationType, alterationType.Name));
}
typeInfo.PolymorphismOptions = polymorphismOptions;

View file

@ -17,12 +17,16 @@ public class WorkflowInstanceFinder(IWorkflowInstanceStore workflowInstanceStore
var workflowInstanceFilter = new WorkflowInstanceFilter
{
Ids = filter.WorkflowInstanceIds?.ToList(),
DefinitionIds = filter.DefinitionIds,
DefinitionVersionIds = filter.DefinitionVersionIds?.ToList(),
CorrelationIds = filter.CorrelationIds?.ToList(),
HasIncidents = filter.HasIncidents,
IsSystem = filter.IsSystem,
TimestampFilters = filter.TimestampFilters?.ToList(),
WorkflowStatus = WorkflowStatus.Running
WorkflowStatuses = filter.Statuses?.ToList(),
WorkflowSubStatuses = filter.SubStatuses?.ToList(),
Names = filter.Names?.ToList(),
SearchTerm = filter.SearchTerm,
};
var activityExecutionFilters = filter.ActivityFilters?.Select(x => 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<string>().ToHashSet()
: (await workflowInstanceStore.FindManyIdsAsync(workflowInstanceFilter, cancellationToken)).ToHashSet();

View file

@ -13,7 +13,7 @@ namespace Elsa.Alterations.Activities;
/// Marks an alteration plan as completed.
/// </summary>
[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
{
/// <inheritdoc />

View file

@ -15,7 +15,7 @@ namespace Elsa.Alterations.Activities;
/// Submits an alteration plan for execution.
/// </summary>
[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
{
/// <inheritdoc />

View file

@ -19,7 +19,7 @@ namespace Elsa.Alterations.Activities;
/// Submits an alteration plan for execution.
/// </summary>
[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<int>
{
/// <inheritdoc />

View file

@ -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;
/// <summary>
/// Upgrades the version of the workflow instance.
/// </summary>
[UsedImplicitly]
public class CancelHandler : AlterationHandlerBase<Cancel>
{
/// <inheritdoc />
protected override ValueTask HandleAsync(AlterationContext context, Cancel alteration)
{
context.WorkflowExecutionContext.Cancel();
context.Succeed();
return ValueTask.CompletedTask;
}
}

View file

@ -0,0 +1,10 @@
using Elsa.Alterations.Core.Abstractions;
using JetBrains.Annotations;
namespace Elsa.Alterations.AlterationTypes;
/// <summary>
/// Cancels the workflow instances in an alteration plan.
/// </summary>
[UsedImplicitly]
public class Cancel : AlterationBase;

View file

@ -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);
}

View file

@ -20,6 +20,7 @@ public static class ServiceCollectionExtensions
services.AddAlteration<ModifyVariable, ModifyVariableHandler>();
services.AddAlteration<ScheduleActivity, ScheduleActivityHandler>();
services.AddAlteration<CancelActivity, CancelActivityHandler>();
services.AddAlteration<Cancel, CancelHandler>();
services.AddNotificationHandlersFrom<AlterationsFeature>();
return services;
}

View file

@ -14,7 +14,7 @@
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Dapper" />

View file

@ -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;
/// <summary>
/// Provides extension methods to perform bulk upsert operations for entities
/// in an Entity Framework Core context, supporting multiple database providers.
/// </summary>
public static class BulkUpsertExtensions
{
/// <summary>
/// Performs a bulk upsert operation on a list of entities in the specified database context using a key selector.
/// </summary>
/// <typeparam name="TDbContext">The type of the database context.</typeparam>
/// <typeparam name="TEntity">The type of the entity being upserted.</typeparam>
/// <param name="dbContext">The database context where the bulk upsert operation will be executed.</param>
/// <param name="entities">The list of entities to be upserted.</param>
/// <param name="keySelector">An expression used to determine the key for upsert operations.</param>
/// <param name="cancellationToken">A token to observe while waiting for the operation to complete.</param>
public static async Task BulkUpsertAsync<TDbContext, TEntity>(
this TDbContext dbContext,
IList<TEntity> entities,
Expression<Func<TEntity, string>> keySelector,
CancellationToken cancellationToken = default)
where TDbContext : DbContext
where TEntity : class, new()
{
await BulkUpsertAsync(dbContext, entities, keySelector, 50, cancellationToken);
}
/// <summary>
/// Performs a bulk upsert operation on a list of entities in the specified database context using a key selector and optional batch size.
/// </summary>
/// <typeparam name="TDbContext">The type of the database context.</typeparam>
/// <typeparam name="TEntity">The type of the entity being upserted.</typeparam>
/// <param name="dbContext">The database context where the bulk upsert operation will be executed.</param>
/// <param name="entities">The list of entities to be upserted.</param>
/// <param name="keySelector">An expression used to determine the key for upsert operations.</param>
/// <param name="batchSize">The size of each batch for processing the upsert operation. Defaults to 50.</param>
/// <param name="cancellationToken">A token to observe while waiting for the operation to complete.</param>
/// <exception cref="NotSupportedException">Thrown if the database provider for the context is not supported.</exception>
public static async Task BulkUpsertAsync<TDbContext, TEntity>(
this TDbContext dbContext,
IList<TEntity> entities,
Expression<Func<TEntity, string>> 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<DbContext, IList<TEntity>, Expression<Func<TEntity, string>>, (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<TEntity>(
DbContext dbContext,
IList<TEntity> entities,
Expression<Func<TEntity, string>> 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<object>();
var parameterCount = 0;
for (var i = 0; i < entities.Count; i++)
{
var entity = entities[i];
var values = new List<string>();
foreach (var property in props)
{
var paramName = $"{{{parameterCount++}}}";
// If it's a shadow property, retrieve value via Entry(..).Property(..)
object? value = property.IsShadowProperty()
? dbContext.Entry(entity).Property(property.Name).CurrentValue
: property.PropertyInfo?.GetValue(entity);
var converter = property.GetTypeMapping().Converter;
if (converter != null)
value = converter.ConvertToProvider(value);
values.Add(paramName);
parameters.Add(value);
}
var line = $"({string.Join(", ", values)}){(i < entities.Count - 1 ? "," : string.Empty)}";
mergeSql.AppendLine(line);
}
mergeSql.AppendLine($") AS Source ({string.Join(", ", columnNames)})");
mergeSql.AppendLine($"ON Target.{keyColumnName} = Source.{keyColumnName}");
mergeSql.AppendLine("WHEN MATCHED THEN");
mergeSql.AppendLine($" UPDATE SET {string.Join(", ", columnNames.Where(c => c != keyColumnName).Select(c => $"Target.{c} = Source.{c}"))}");
mergeSql.AppendLine("WHEN NOT MATCHED THEN");
mergeSql.AppendLine($" INSERT ({string.Join(", ", columnNames)})");
mergeSql.AppendLine($" VALUES ({string.Join(", ", columnNames.Select(c => $"Source.{c}"))});");
return (mergeSql.ToString(), parameters.ToArray());
}
private static (string, object[]) GenerateSqliteUpsert<TEntity>(
DbContext dbContext,
IList<TEntity> entities,
Expression<Func<TEntity, string>> keySelector)
where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity))!;
var tableName = entityType.GetTableName();
var storeObject = StoreObjectIdentifier.Table(tableName!, entityType.GetSchema());
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 sb = new StringBuilder();
var parameters = new List<object>();
var parameterCount = 0;
sb.Append($"INSERT INTO \"{tableName}\" ({string.Join(", ", columnNames.Select(c => $"\"{c}\""))}) VALUES ");
for (var i = 0; i < entities.Count; i++)
{
var entity = entities[i];
var placeholders = new List<string>();
foreach (var property in props)
{
var paramName = $"{{{parameterCount++}}}";
object? value = property.IsShadowProperty()
? dbContext.Entry(entity).Property(property.Name).CurrentValue
: property.PropertyInfo?.GetValue(entity);
var converter = property.GetTypeMapping().Converter;
if (converter != null)
value = converter.ConvertToProvider(value);
placeholders.Add(paramName);
parameters.Add(value);
}
sb.Append($"({string.Join(", ", placeholders)})");
if (i < entities.Count - 1)
sb.Append(", ");
}
sb.AppendLine();
sb.AppendLine($"ON CONFLICT(\"{keyColumnName}\") DO UPDATE SET");
var updateAssignments = columnNames
.Where(c => c != keyColumnName)
.Select(c => $"\"{c}\"=excluded.\"{c}\"");
sb.AppendLine(string.Join(", ", updateAssignments) + ";");
return (sb.ToString(), parameters.ToArray());
}
private static (string, object[]) GeneratePostgresUpsert<TEntity>(
DbContext dbContext,
IList<TEntity> entities,
Expression<Func<TEntity, string>> keySelector)
where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity))!;
var tableName = entityType.GetTableName();
var storeObject = StoreObjectIdentifier.Table(tableName!, entityType.GetSchema());
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 sb = new StringBuilder();
var parameters = new List<object>();
var parameterCount = 0;
sb.Append($"INSERT INTO \"{storeObject.Schema}\".\"{storeObject.Name}\" ({string.Join(", ", columnNames.Select(c => $"\"{c}\""))}) VALUES ");
for (var i = 0; i < entities.Count; i++)
{
var entity = entities[i];
var placeholders = new List<string>();
foreach (var property in props)
{
var paramName = $"{{{parameterCount++}}}";
object? value = property.IsShadowProperty()
? dbContext.Entry(entity).Property(property.Name).CurrentValue
: property.PropertyInfo?.GetValue(entity);
var converter = property.GetTypeMapping().Converter;
if (converter != null)
value = converter.ConvertToProvider(value);
placeholders.Add(paramName);
parameters.Add(value);
}
sb.Append($"({string.Join(", ", placeholders)})");
if (i < entities.Count - 1)
sb.Append(", ");
}
sb.AppendLine();
sb.AppendLine($"ON CONFLICT (\"{keyColumnName}\") DO UPDATE SET");
var updateAssignments = columnNames
.Where(c => c != keyColumnName)
.Select(c => $"\"{c}\" = EXCLUDED.\"{c}\"");
sb.AppendLine(string.Join(", ", updateAssignments) + ";");
return (sb.ToString(), parameters.ToArray());
}
private static (string, object[]) GenerateMySqlUpsert<TEntity>(
DbContext dbContext,
IList<TEntity> entities,
Expression<Func<TEntity, string>> keySelector)
where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity))!;
var tableName = entityType.GetTableName();
var storeObject = StoreObjectIdentifier.Table(tableName!, entityType.GetSchema());
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 sb = new StringBuilder();
var parameters = new List<object>();
var parameterCount = 0;
sb.Append($"INSERT INTO `{tableName}` ({string.Join(", ", columnNames.Select(c => $"`{c}`"))}) VALUES ");
for (var i = 0; i < entities.Count; i++)
{
var entity = entities[i];
var placeholders = new List<string>();
foreach (var property in props)
{
var paramName = $"{{{parameterCount++}}}";
object? value = property.IsShadowProperty()
? dbContext.Entry(entity).Property(property.Name).CurrentValue
: property.PropertyInfo?.GetValue(entity);
var converter = property.GetTypeMapping().Converter;
if (converter != null)
value = converter.ConvertToProvider(value);
placeholders.Add(paramName);
parameters.Add(value);
}
sb.Append($"({string.Join(", ", placeholders)})");
if (i < entities.Count - 1)
sb.Append(", ");
}
sb.AppendLine();
sb.AppendLine("ON DUPLICATE KEY UPDATE");
var updateAssignments = columnNames
.Where(c => c != keyColumnName)
.Select(c => $"`{c}` = VALUES(`{c}`)");
sb.AppendLine(string.Join(", ", updateAssignments) + ";");
return (sb.ToString(), parameters.ToArray());
}
private static (string, object[]) GenerateOracleUpsert<TEntity>(
DbContext dbContext,
IList<TEntity> entities,
Expression<Func<TEntity, string>> keySelector)
where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity))!;
var schema = entityType.GetSchema();
var tableName = entityType.GetTableName();
var storeObject = StoreObjectIdentifier.Table(tableName!, schema);
var fullName = !string.IsNullOrEmpty(schema) ? $"{schema}.{tableName}" : tableName;
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 sb = new StringBuilder();
var parameters = new List<object>();
var parameterCount = 0;
sb.AppendLine($"MERGE INTO {fullName} Target");
sb.AppendLine("USING (SELECT");
for (var i = 0; i < entities.Count; i++)
{
var entity = entities[i];
var lineParts = new List<string>();
foreach (var property in props)
{
var paramName = $"{{{parameterCount++}}}";
object? value = property.IsShadowProperty()
? dbContext.Entry(entity).Property(property.Name).CurrentValue
: property.PropertyInfo?.GetValue(entity);
var converter = property.GetTypeMapping().Converter;
if (converter != null)
value = converter.ConvertToProvider(value);
parameters.Add(value);
// Oracle aliases must match the column name
var alias = property.GetColumnName(storeObject);
lineParts.Add($"{paramName} AS {alias}");
}
// Comma if not last
var suffix = (i < entities.Count - 1) ? " FROM DUAL UNION ALL SELECT" : " FROM DUAL";
sb.AppendLine(string.Join(", ", lineParts) + suffix);
}
sb.AppendLine($") Source ON (Target.{keyColumnName} = Source.{keyColumnName})");
sb.AppendLine("WHEN MATCHED THEN UPDATE SET");
var updateSetClauses = columnNames
.Where(c => c != keyColumnName)
.Select(c => $"Target.{c} = Source.{c}");
sb.AppendLine(string.Join(", ", updateSetClauses));
sb.AppendLine("WHEN NOT MATCHED THEN");
sb.AppendLine($"INSERT ({string.Join(", ", columnNames)})");
sb.AppendLine($"VALUES ({string.Join(", ", columnNames.Select(c => $"Source.{c}"))});");
return (sb.ToString(), parameters.ToArray());
}
}

View file

@ -12,31 +12,6 @@ namespace Elsa.EntityFrameworkCore.Extensions;
[PublicAPI]
public static class QueryableExtensions
{
/// <summary>
/// Inserts or updates a list of entities in bulk.
/// </summary>
public static async Task BulkUpsertAsync<TDbContext, TEntity>(this TDbContext dbContext, IList<TEntity> entities, Expression<Func<TEntity, string>> keySelector, CancellationToken cancellationToken = default) where TDbContext : DbContext where TEntity : class, new()
{
var set = dbContext.Set<TEntity>();
var compiledKeySelector = keySelector.Compile();
var containsLambda = entities.Any() ? keySelector.BuildContainsExpression(entities) : default;
var existingEntitiesQuery = set.AsNoTracking();
if (containsLambda != null)
existingEntitiesQuery = existingEntitiesQuery.Where(containsLambda);
var existingEntities = await existingEntitiesQuery.ToListAsync(cancellationToken);
var entitiesToUpdate = entities.IntersectBy(existingEntities.Select(compiledKeySelector), compiledKeySelector).ToList();
var entitiesToInsert = entities.Except(entitiesToUpdate).ToList();
if (entitiesToUpdate.Any())
set.UpdateRange(entitiesToUpdate);
if (entitiesToInsert.Any())
await set.AddRangeAsync(entitiesToInsert, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
}
/// <summary>
/// Inserts a list of entities in bulk.
/// </summary>

View file

@ -1,3 +1,4 @@
using System.Net;
using System.Net.Http.Headers;
using Elsa.Extensions;
using Elsa.Http.ContentWriters;
@ -7,6 +8,7 @@ using Elsa.Workflows.Attributes;
using Elsa.Workflows.UIHints;
using Elsa.Workflows.Models;
using Microsoft.Extensions.Logging;
using Polly;
namespace Elsa.Http;
@ -89,6 +91,11 @@ public abstract class SendHttpRequestBase(string? source = default, int? line =
)]
public Input<HttpHeaders?> RequestHeaders { get; set; } = new(new HttpHeaders());
/// <summary>
/// Indicates whether resiliency mechanisms should be enabled for the HTTP request.
/// </summary>
public Input<bool> EnableResiliency { get; set; } = default!;
/// <summary>
/// The HTTP response status code
/// </summary>
@ -130,15 +137,15 @@ public abstract class SendHttpRequestBase(string? source = default, int? line =
private async Task TrySendAsync(ActivityExecutionContext context)
{
var request = PrepareRequest(context);
var logger = (ILogger)context.GetRequiredService(typeof(ILogger<>).MakeGenericType(GetType()));
var httpClientFactory = context.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient(nameof(SendHttpRequestBase));
var cancellationToken = context.CancellationToken;
var resiliencyEnabled = EnableResiliency.GetOrDefault(context, () => false);
try
{
var response = await httpClient.SendAsync(request, cancellationToken);
var response = await SendRequestAsync();
var parsedContent = await ParseContentAsync(context, response);
var statusCode = (int)response.StatusCode;
var responseHeaders = new HttpHeaders(response.Headers);
@ -155,7 +162,7 @@ public abstract class SendHttpRequestBase(string? source = default, int? line =
logger.LogWarning(e, "An error occurred while sending an HTTP request");
context.AddExecutionLogEntry("Error", e.Message, payload: new
{
StackTrace = e.StackTrace
e.StackTrace
});
context.JournalData.Add("Error", e.Message);
await HandleRequestExceptionAsync(context, e);
@ -165,11 +172,30 @@ public abstract class SendHttpRequestBase(string? source = default, int? line =
logger.LogWarning(e, "An error occurred while sending an HTTP request");
context.AddExecutionLogEntry("Error", e.Message, payload: new
{
StackTrace = e.StackTrace
e.StackTrace
});
context.JournalData.Add("Cancelled", true);
await HandleTaskCanceledExceptionAsync(context, e);
}
return;
async Task<HttpResponseMessage> SendRequestAsync()
{
if (resiliencyEnabled)
{
var pipeline = BuildResiliencyPipeline(context);
return await pipeline.ExecuteAsync(async ct => await SendRequestAsyncCore(ct), cancellationToken);
}
return await SendRequestAsyncCore();
}
async Task<HttpResponseMessage> SendRequestAsyncCore(CancellationToken ct = default)
{
var request = PrepareRequest(context);
return await httpClient.SendAsync(request, ct);
}
}
private async Task<object?> ParseContentAsync(ActivityExecutionContext context, HttpResponseMessage httpResponse)
@ -238,4 +264,46 @@ public abstract class SendHttpRequestBase(string? source = default, int? line =
var parsedContentType = new System.Net.Mime.ContentType(contentType);
return factories.FirstOrDefault(httpContentFactory => httpContentFactory.SupportedContentTypes.Any(c => c == parsedContentType.MediaType)) ?? new JsonContentFactory();
}
private ResiliencePipeline<HttpResponseMessage> BuildResiliencyPipeline(ActivityExecutionContext context)
{
// Docs: https://www.pollydocs.org/strategies/retry
var pipelineBuilder = new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddRetry(new()
{
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<TimeoutException>() // Specific timeout exception
.Handle<HttpRequestException>() // Any HTTP exception
.HandleResult(response => IsTransientStatusCode(response.StatusCode)),
MaxRetryAttempts = 8,
UseJitter = false, // If enabled, adds a random value between -25% and +25% of the calculated Delay, except if BackoffType is Exponential, where a DecorrelatedJitterBackoffV2 formula is used for jitter calculation. That formula is based on Polly.Contrib.WaitAndRetry.
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Exponential // Delay * 2^AttemptNumber, e.g. [ 2s, 4s, 8s, 16s ]. Total secs: 2 + 4 + 8 + 16 = 30
// If BackoffType is Exponential, then the calculated Delay is multiplied by a random value between -25% and +25% of the calculated Delay, except if BackoffType is Exponential, where a DecorrelatedJitterBackoffV2 formula is used for jitter calculation. That formula is based on Polly.Contrib.WaitAndRetry.
});
return pipelineBuilder.Build();
}
// Helper method to identify transient status codes.
private static bool IsTransientStatusCode(HttpStatusCode? statusCode)
{
if (statusCode is null)
{
// No status code -> Assume network failure, worth retrying.
return true;
}
return statusCode.Value switch
{
HttpStatusCode.RequestTimeout => true, // 408
HttpStatusCode.TooManyRequests => true, // 429 (if no Retry-After header is respected)
HttpStatusCode.InternalServerError => true, // 500
HttpStatusCode.BadGateway => true, // 502
HttpStatusCode.ServiceUnavailable => true, // 503
HttpStatusCode.GatewayTimeout => true, // 504
HttpStatusCode.Conflict => true, // 409 - Can be transient in concurrency cases
_ => false // Other errors are not transient
};
}
}

View file

@ -8,16 +8,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentStorage" />
<PackageReference Include="FluentStorage"/>
<PackageReference Include="Microsoft.Extensions.Http.Resilience"/>
<PackageReference Include="Microsoft.Extensions.Resilience"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Elsa.Liquid\Elsa.Liquid.csproj" />
<ProjectReference Include="..\Elsa.SasTokens\Elsa.SasTokens.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
<ProjectReference Include="..\Elsa.JavaScript\Elsa.JavaScript.csproj" />
<ProjectReference Include="..\Elsa.Liquid\Elsa.Liquid.csproj"/>
<ProjectReference Include="..\Elsa.SasTokens\Elsa.SasTokens.csproj"/>
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj"/>
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj"/>
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj"/>
<ProjectReference Include="..\Elsa.JavaScript\Elsa.JavaScript.csproj"/>
</ItemGroup>
</Project>

View file

@ -14,7 +14,7 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageReference Include="Quartz.Serialization.Json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Elsa.EntityFrameworkCore.Common\Elsa.EntityFrameworkCore.Common.csproj" />
<ProjectReference Include="..\Elsa.Quartz\Elsa.Quartz.csproj" />

View file

@ -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");

View file

@ -116,6 +116,11 @@ public class WorkflowInstanceFilter
/// </summary>
public ICollection<TimestampFilter>? TimestampFilters { get; set; }
/// <summary>
/// Filter workflow instances by name.
/// </summary>
public List<string>? Names { get; set; }
/// <summary>
/// Applies the filter to the specified query.
/// </summary>
@ -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));

View file

@ -0,0 +1,9 @@
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.Runtime.Bookmarks;
/// <summary>
/// Bookmark payload for the <see cref="ExecuteWorkflow"/> activity.
/// </summary>
/// <param name="ChildInstanceId">The instance ID of the child workflow that was created by the <see cref="ExecuteWorkflow"/> activity.</param>
public record ExecuteWorkflowPayload(string ChildInstanceId);

View file

@ -14,7 +14,7 @@ public static class WorkflowDispatcherExtensions
/// </summary>
public static Task<DispatchWorkflowResponse> DispatchAsync(this IWorkflowDispatcher workflowDispatcher, DispatchWorkflowDefinitionRequest request, CancellationToken cancellationToken = default)
{
return workflowDispatcher.DispatchAsync(request, new DispatchWorkflowOptions(), cancellationToken);
return workflowDispatcher.DispatchAsync(request, new(), cancellationToken);
}
/// <summary>
@ -22,7 +22,7 @@ public static class WorkflowDispatcherExtensions
/// </summary>
public static Task<DispatchWorkflowResponse> DispatchAsync(this IWorkflowDispatcher workflowDispatcher, DispatchWorkflowInstanceRequest request, CancellationToken cancellationToken = default)
{
return workflowDispatcher.DispatchAsync(request, new DispatchWorkflowOptions(), cancellationToken);
return workflowDispatcher.DispatchAsync(request, new(), cancellationToken);
}
/// <summary>
@ -30,7 +30,7 @@ public static class WorkflowDispatcherExtensions
/// </summary>
public static Task<DispatchWorkflowResponse> DispatchAsync(this IWorkflowDispatcher workflowDispatcher, DispatchTriggerWorkflowsRequest request, CancellationToken cancellationToken = default)
{
return workflowDispatcher.DispatchAsync(request, new DispatchWorkflowOptions(), cancellationToken);
return workflowDispatcher.DispatchAsync(request, new(), cancellationToken);
}
/// <summary>
@ -38,6 +38,6 @@ public static class WorkflowDispatcherExtensions
/// </summary>
public static Task<DispatchWorkflowResponse> DispatchAsync(this IWorkflowDispatcher workflowDispatcher, DispatchResumeWorkflowsRequest request, CancellationToken cancellationToken = default)
{
return workflowDispatcher.DispatchAsync(request, new DispatchWorkflowOptions(), cancellationToken);
return workflowDispatcher.DispatchAsync(request, new(), cancellationToken);
}
}

View file

@ -31,13 +31,8 @@ namespace Elsa.Workflows.Runtime.Features;
/// Installs and configures workflow runtime features.
/// </summary>
[DependsOn(typeof(SystemClockFeature))]
public class WorkflowRuntimeFeature : FeatureBase
public class WorkflowRuntimeFeature(IModule module) : FeatureBase(module)
{
/// <inheritdoc />
public WorkflowRuntimeFeature(IModule module) : base(module)
{
}
private IDictionary<string, DispatcherChannel> WorkflowDispatcherChannels { get; set; } = new Dictionary<string, DispatcherChannel>();
/// <summary>
@ -142,6 +137,24 @@ public class WorkflowRuntimeFeature : FeatureBase
/// </summary>
public Action<BookmarkQueuePurgeOptions> BookmarkQueuePurgeOptions { get; set; } = _ => { };
/// <summary>
/// Enables the workflow inbox cleanup job.
/// </summary>
public WorkflowRuntimeFeature EnableWorkflowInboxCleanupJob()
{
Services.Configure<WorkflowInboxCleanupOptions>(options => { options.IsEnabled = true; });
return this;
}
/// <summary>
/// Disables the workflow inbox cleanup job.
/// </summary>
public WorkflowRuntimeFeature DisableWorkflowInboxCleanupJob()
{
Services.Configure<WorkflowInboxCleanupOptions>(options => { options.IsEnabled = false; });
return this;
}
/// <summary>
/// Register the specified workflow type.
/// </summary>

View file

@ -14,4 +14,9 @@ public class WorkflowInboxCleanupOptions
/// The number of messages to clean up per sweep.
/// </summary>
public int BatchSize { get; set; } = 1000;
/// <summary>
/// Whether the workflow inbox cleanup is enabled.
/// </summary>
public bool IsEnabled { get; set; } = true;
}

View file

@ -2,4 +2,11 @@ using Elsa.Workflows.Models;
namespace Elsa.Workflows.Runtime.Results;
public record WorkflowExecutionResult(string WorkflowInstanceId, WorkflowStatus Status, WorkflowSubStatus SubStatus, ICollection<Bookmark> Bookmarks, ICollection<ActivityIncident> Incidents, string? TriggeredActivityId = null);
public record WorkflowExecutionResult(
string WorkflowInstanceId,
WorkflowStatus Status,
WorkflowSubStatus SubStatus,
ICollection<Bookmark> Bookmarks,
ICollection<ActivityIncident> Incidents,
string? TriggeredActivityId,
IDictionary<string, object> Output);

View file

@ -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<ICollection<WorkflowExecutionResult>> 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<ICollection<WorkflowExecutionResult>> 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);
}

View file

@ -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<Bookmark>(),
response.Incidents)
response.Incidents,
null,
null)
);
}
}