Merge branch 'main' into feature/batch-processing
This commit is contained in:
commit
1293b7af29
8
.github/workflows/packages.yml
vendored
8
.github/workflows/packages.yml
vendored
|
|
@ -31,12 +31,8 @@ jobs:
|
|||
else
|
||||
echo "VERSION=3.1.0-preview.${{github.run_number}}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Build
|
||||
run: dotnet build --configuration Release /p:Version=${VERSION}
|
||||
- name: Test
|
||||
run: dotnet test --configuration Release /p:Version=${VERSION} --no-build
|
||||
- name: Pack
|
||||
run: dotnet pack --configuration Release /p:Version=${VERSION} /p:PackageOutputPath=$(pwd)/packages
|
||||
- name: Compile+Test+Pack
|
||||
run: ./build.sh Compile+Test+Pack --version ${VERSION}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
|
|
|||
44
.github/workflows/pr.yml
vendored
Normal file
44
.github/workflows/pr.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
# <auto-generated>
|
||||
#
|
||||
# This code was generated.
|
||||
#
|
||||
# - To turn off auto-generation set:
|
||||
#
|
||||
# [CustomGitHubActions (AutoGenerate = false)]
|
||||
#
|
||||
# - To trigger manual generation invoke:
|
||||
#
|
||||
# nuke --generate-configuration GitHubActions_pr --host GitHubActions
|
||||
#
|
||||
# </auto-generated>
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
name: pr
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'v3*'
|
||||
paths:
|
||||
- '**/*'
|
||||
- '!docs/**/*'
|
||||
- '!package.json'
|
||||
- '!package-lock.json'
|
||||
- '!readme.md'
|
||||
|
||||
jobs:
|
||||
ubuntu-latest:
|
||||
name: ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- if: ${{ runner.os == 'Windows' }}
|
||||
name: 'Use GNU tar'
|
||||
shell: cmd
|
||||
run: |
|
||||
echo "Adding GNU tar to PATH"
|
||||
echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%"
|
||||
- uses: actions/checkout@v3
|
||||
- name: 'Run: Compile, Test, Pack'
|
||||
run: ./build.cmd Compile Test Pack
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -51,9 +51,6 @@ node_modules/
|
|||
.stylelintrc.yml
|
||||
.eslintrc.yml
|
||||
|
||||
#github
|
||||
.github/
|
||||
|
||||
#volatile
|
||||
App_Data/
|
||||
*.db
|
||||
|
|
@ -76,3 +73,6 @@ src/designer/elsa-workflows-designer/loader
|
|||
src/modules/Elsa.Workflows.Designer/package-lock.json
|
||||
|
||||
unlist.sh
|
||||
|
||||
# build artifacts
|
||||
/artifacts
|
||||
|
|
|
|||
120
.nuke/build.schema.json
Normal file
120
.nuke/build.schema.json
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$ref": "#/definitions/build",
|
||||
"title": "Build Schema",
|
||||
"definitions": {
|
||||
"build": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Configuration": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Debug",
|
||||
"Release"
|
||||
]
|
||||
},
|
||||
"Continue": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates to continue a previously failed build attempt"
|
||||
},
|
||||
"Help": {
|
||||
"type": "boolean",
|
||||
"description": "Shows the help text for this build assembly"
|
||||
},
|
||||
"Host": {
|
||||
"type": "string",
|
||||
"description": "Host for execution. Default is 'automatic'",
|
||||
"enum": [
|
||||
"AppVeyor",
|
||||
"AzurePipelines",
|
||||
"Bamboo",
|
||||
"Bitbucket",
|
||||
"Bitrise",
|
||||
"GitHubActions",
|
||||
"GitLab",
|
||||
"Jenkins",
|
||||
"Rider",
|
||||
"SpaceAutomation",
|
||||
"TeamCity",
|
||||
"Terminal",
|
||||
"TravisCI",
|
||||
"VisualStudio",
|
||||
"VSCode"
|
||||
]
|
||||
},
|
||||
"IgnoreFailedSources": {
|
||||
"type": "boolean",
|
||||
"description": "Ignore unreachable sources during Restore"
|
||||
},
|
||||
"NoLogo": {
|
||||
"type": "boolean",
|
||||
"description": "Disables displaying the NUKE logo"
|
||||
},
|
||||
"Partition": {
|
||||
"type": "string",
|
||||
"description": "Partition to use on CI"
|
||||
},
|
||||
"Plan": {
|
||||
"type": "boolean",
|
||||
"description": "Shows the execution plan (HTML)"
|
||||
},
|
||||
"Profile": {
|
||||
"type": "array",
|
||||
"description": "Defines the profiles to load",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"Root": {
|
||||
"type": "string",
|
||||
"description": "Root directory during build execution"
|
||||
},
|
||||
"Skip": {
|
||||
"type": "array",
|
||||
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Clean",
|
||||
"Compile",
|
||||
"Pack",
|
||||
"Restore",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Solution": {
|
||||
"type": "string",
|
||||
"description": "Path to a solution file that is automatically loaded"
|
||||
},
|
||||
"Target": {
|
||||
"type": "array",
|
||||
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Clean",
|
||||
"Compile",
|
||||
"Pack",
|
||||
"Restore",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Verbosity": {
|
||||
"type": "string",
|
||||
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||
"enum": [
|
||||
"Minimal",
|
||||
"Normal",
|
||||
"Quiet",
|
||||
"Verbose"
|
||||
]
|
||||
},
|
||||
"Version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
.nuke/parameters.json
Normal file
4
.nuke/parameters.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "./build.schema.json",
|
||||
"Solution": "Elsa.sln"
|
||||
}
|
||||
40
Elsa.sln
40
Elsa.sln
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.7.34003.232
|
||||
|
|
@ -16,12 +17,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{7D
|
|||
common.props = common.props
|
||||
configureawait.props = configureawait.props
|
||||
docker-compose.yml = docker-compose.yml
|
||||
frameworks.props = frameworks.props
|
||||
icon.png = icon.png
|
||||
NuGet.Config = NuGet.Config
|
||||
packages.props = packages.props
|
||||
README.md = README.md
|
||||
update-migrations.sh = update-migrations.sh
|
||||
frameworks.props = frameworks.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0354F050-3992-4DD4-B0EE-5FBA04AC72B6}"
|
||||
|
|
@ -254,51 +255,52 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.My
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj", "{84AB9951-3DBF-43A4-8F5D-AEED1102CE88}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Alterations.MassTransit", "src\modules\Elsa.Alterations.MassTransit\Elsa.Alterations.MassTransit.csproj", "{1F621382-08FB-45EA-B94C-96A7FECB9489}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations.MassTransit", "src\modules\Elsa.Alterations.MassTransit\Elsa.Alterations.MassTransit.csproj", "{1F621382-08FB-45EA-B94C-96A7FECB9489}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.UnitTests", "test\unit\Elsa.Activities.UnitTests\Elsa.Activities.UnitTests.csproj", "{E6562B0F-AF64-472A-B009-BF6D40DDE99B}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.UnitTests", "test\unit\Elsa.Activities.UnitTests\Elsa.Activities.UnitTests.csproj", "{E6562B0F-AF64-472A-B009-BF6D40DDE99B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.ServiceBus.IntegrationTests", "test\integration\Elsa.ServiceBus.IntegrationTests\Elsa.ServiceBus.IntegrationTests.csproj", "{9504E3F3-F77F-437A-8644-D23D7F5FCF8B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElsaStudioWebAssembly", "src\bundles\ElsaStudioWebAssembly\ElsaStudioWebAssembly.csproj", "{E12A1BDF-5D65-493B-835D-AFBC480A5650}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaStudioWebAssembly", "src\bundles\ElsaStudioWebAssembly\ElsaStudioWebAssembly.csproj", "{E12A1BDF-5D65-493B-835D-AFBC480A5650}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.EntityFrameworkCore.Common", "src\modules\Elsa.EntityFrameworkCore.Common\Elsa.EntityFrameworkCore.Common.csproj", "{FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.Common", "src\modules\Elsa.EntityFrameworkCore.Common\Elsa.EntityFrameworkCore.Common.csproj", "{FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Quartz.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Quartz.EntityFrameworkCore.Sqlite\Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj", "{A71557F2-D5FB-4772-9391-F7D18A3B6C44}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Quartz.EntityFrameworkCore.Sqlite\Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj", "{A71557F2-D5FB-4772-9391-F7D18A3B6C44}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Quartz.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Quartz.EntityFrameworkCore.SqlServer\Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj", "{05F3B23E-CF28-467B-AD3C-595EA4ED6B96}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Quartz.EntityFrameworkCore.SqlServer\Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj", "{05F3B23E-CF28-467B-AD3C-595EA4ED6B96}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Quartz.EntityFrameworkCore.MySql", "src\modules\Elsa.Quartz.EntityFrameworkCore.MySql\Elsa.Quartz.EntityFrameworkCore.MySql.csproj", "{74ACD9D0-8B4C-42FA-A582-E93BF0075023}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.MySql", "src\modules\Elsa.Quartz.EntityFrameworkCore.MySql\Elsa.Quartz.EntityFrameworkCore.MySql.csproj", "{74ACD9D0-8B4C-42FA-A582-E93BF0075023}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Quartz.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Quartz.EntityFrameworkCore.PostgreSql\Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj", "{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Quartz.EntityFrameworkCore.PostgreSql\Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj", "{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.FileStorage", "src\modules\Elsa.FileStorage\Elsa.FileStorage.csproj", "{732BF088-6AD7-4C4D-9A48-8074253596D4}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.FileStorage", "src\modules\Elsa.FileStorage\Elsa.FileStorage.csproj", "{732BF088-6AD7-4C4D-9A48-8074253596D4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "storage", "storage", "{B818988E-639C-4E6E-85C1-B231BCAD9DAB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.CSharp", "src\modules\Elsa.CSharp\Elsa.CSharp.csproj", "{24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.CSharp", "src\modules\Elsa.CSharp\Elsa.CSharp.csproj", "{24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Python", "src\modules\Elsa.Python\Elsa.Python.csproj", "{790E94F2-5393-47DF-AC52-D9247F5B243A}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Python", "src\modules\Elsa.Python\Elsa.Python.csproj", "{790E94F2-5393-47DF-AC52-D9247F5B243A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.MassTransit.RabbitMq", "src\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj", "{169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MassTransit.RabbitMq", "src\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj", "{169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.MassTransit.AzureServiceBus", "src\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj", "{5B1AF00E-1030-4BBA-9C1A-1DC5AEFDD44A}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MassTransit.AzureServiceBus", "src\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj", "{5B1AF00E-1030-4BBA-9C1A-1DC5AEFDD44A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.Heartbeats", "src\samples\aspnet\Elsa.Samples.AspNet.Heartbeats\Elsa.Samples.AspNet.Heartbeats.csproj", "{26888832-DBEA-4B23-8DC2-84478A702CC4}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.AspNet.Heartbeats", "src\samples\aspnet\Elsa.Samples.AspNet.Heartbeats\Elsa.Samples.AspNet.Heartbeats.csproj", "{26888832-DBEA-4B23-8DC2-84478A702CC4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{B789B743-0FBE-4BBC-9F45-84C20C8D4814}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\elsa-server-and-studio.yml = .github\workflows\elsa-server-and-studio.yml
|
||||
.github\workflows\elsa-server.yml = .github\workflows\elsa-server.yml
|
||||
.github\workflows\packages.yml = .github\workflows\packages.yml
|
||||
.github\workflows\elsa-studio.yml = .github\workflows\elsa-studio.yml
|
||||
.github\workflows\packages.yml = .github\workflows\packages.yml
|
||||
.github\workflows\pr.yml = .github\workflows\pr.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Studio.Web", "src\bundles\Elsa.Studio.Web\Elsa.Studio.Web.csproj", "{D5C149EE-276C-4C59-98F9-37F0EA2A7866}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Studio.Web", "src\bundles\Elsa.Studio.Web\Elsa.Studio.Web.csproj", "{D5C149EE-276C-4C59-98F9-37F0EA2A7866}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.AspNet.CustomUIHandler", "src\samples\aspnet\Elsa.Samples.AspNet.CustomUIHandler\Elsa.Samples.AspNet.CustomUIHandler.csproj", "{832D6FF5-16D3-41D1-8FDD-D9BEC26BDA6A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.MassTransit.RabbitMq", "src\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj", "{169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.MassTransit.AzureServiceBus", "src\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj", "{5B1AF00E-1030-4BBA-9C1A-1DC5AEFDD44A}"
|
||||
EndProject
|
||||
|
|
@ -881,8 +883,8 @@ Global
|
|||
{05F3B23E-CF28-467B-AD3C-595EA4ED6B96} = {AB797AF0-C12C-46DE-A157-7E25625C6200}
|
||||
{74ACD9D0-8B4C-42FA-A582-E93BF0075023} = {AB797AF0-C12C-46DE-A157-7E25625C6200}
|
||||
{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D} = {AB797AF0-C12C-46DE-A157-7E25625C6200}
|
||||
{B818988E-639C-4E6E-85C1-B231BCAD9DAB} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{732BF088-6AD7-4C4D-9A48-8074253596D4} = {B818988E-639C-4E6E-85C1-B231BCAD9DAB}
|
||||
{B818988E-639C-4E6E-85C1-B231BCAD9DAB} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76}
|
||||
{790E94F2-5393-47DF-AC52-D9247F5B243A} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76}
|
||||
{169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6} = {DD089B8B-DA73-492A-9010-F772D1C178DA}
|
||||
|
|
|
|||
7
build.cmd
Executable file
7
build.cmd
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
:; set -eo pipefail
|
||||
:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||
:; ${SCRIPT_DIR}/build.sh "$@"
|
||||
:; exit $?
|
||||
|
||||
@ECHO OFF
|
||||
powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
|
||||
74
build.ps1
Normal file
74
build.ps1
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
||||
[string[]]$BuildArguments
|
||||
)
|
||||
|
||||
Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
|
||||
|
||||
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
|
||||
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
||||
|
||||
###########################################################################
|
||||
# CONFIGURATION
|
||||
###########################################################################
|
||||
|
||||
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
|
||||
$TempDirectory = "$PSScriptRoot\\.nuke\temp"
|
||||
|
||||
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
|
||||
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
|
||||
$DotNetChannel = "STS"
|
||||
|
||||
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
|
||||
$env:DOTNET_NOLOGO = 1
|
||||
|
||||
###########################################################################
|
||||
# EXECUTION
|
||||
###########################################################################
|
||||
|
||||
function ExecSafe([scriptblock] $cmd) {
|
||||
& $cmd
|
||||
if ($LASTEXITCODE) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
# If dotnet CLI is installed globally and it matches requested version, use for execution
|
||||
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
|
||||
$(dotnet --version) -and $LASTEXITCODE -eq 0) {
|
||||
$env:DOTNET_EXE = (Get-Command "dotnet").Path
|
||||
}
|
||||
else {
|
||||
# Download install script
|
||||
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
|
||||
New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
|
||||
|
||||
# If global.json exists, load expected version
|
||||
if (Test-Path $DotNetGlobalFile) {
|
||||
$DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
|
||||
if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
|
||||
$DotNetVersion = $DotNetGlobal.sdk.version
|
||||
}
|
||||
}
|
||||
|
||||
# Install by channel or version
|
||||
$DotNetDirectory = "$TempDirectory\dotnet-win"
|
||||
if (!(Test-Path variable:DotNetVersion)) {
|
||||
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
|
||||
} else {
|
||||
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
|
||||
}
|
||||
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
|
||||
$env:PATH = "$DotNetDirectory;$env:PATH"
|
||||
}
|
||||
|
||||
Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
|
||||
|
||||
if (Test-Path env:NUKE_ENTERPRISE_TOKEN) {
|
||||
& $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null
|
||||
& $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null
|
||||
}
|
||||
|
||||
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
|
||||
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
|
||||
67
build.sh
Executable file
67
build.sh
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
bash --version 2>&1 | head -n 1
|
||||
|
||||
set -eo pipefail
|
||||
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||
|
||||
###########################################################################
|
||||
# CONFIGURATION
|
||||
###########################################################################
|
||||
|
||||
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
|
||||
TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
|
||||
|
||||
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
|
||||
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
|
||||
DOTNET_CHANNEL="STS"
|
||||
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
export DOTNET_NOLOGO=1
|
||||
|
||||
###########################################################################
|
||||
# EXECUTION
|
||||
###########################################################################
|
||||
|
||||
function FirstJsonValue {
|
||||
perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
|
||||
}
|
||||
|
||||
# If dotnet CLI is installed globally and it matches requested version, use for execution
|
||||
if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
|
||||
export DOTNET_EXE="$(command -v dotnet)"
|
||||
else
|
||||
# Download install script
|
||||
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
|
||||
mkdir -p "$TEMP_DIRECTORY"
|
||||
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
|
||||
chmod +x "$DOTNET_INSTALL_FILE"
|
||||
|
||||
# If global.json exists, load expected version
|
||||
if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
|
||||
DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
|
||||
if [[ "$DOTNET_VERSION" == "" ]]; then
|
||||
unset DOTNET_VERSION
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install by channel or version
|
||||
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
|
||||
if [[ -z ${DOTNET_VERSION+x} ]]; then
|
||||
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
|
||||
else
|
||||
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
|
||||
fi
|
||||
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
|
||||
export PATH="$DOTNET_DIRECTORY:$PATH"
|
||||
fi
|
||||
|
||||
echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
|
||||
|
||||
if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then
|
||||
"$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true
|
||||
"$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true
|
||||
fi
|
||||
|
||||
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
|
||||
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
|
||||
11
build/.editorconfig
Normal file
11
build/.editorconfig
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[*.cs]
|
||||
dotnet_style_qualification_for_field = false:warning
|
||||
dotnet_style_qualification_for_property = false:warning
|
||||
dotnet_style_qualification_for_method = false:warning
|
||||
dotnet_style_qualification_for_event = false:warning
|
||||
dotnet_style_require_accessibility_modifiers = never:warning
|
||||
|
||||
csharp_style_expression_bodied_methods = true:silent
|
||||
csharp_style_expression_bodied_properties = true:warning
|
||||
csharp_style_expression_bodied_indexers = true:warning
|
||||
csharp_style_expression_bodied_accessors = true:warning
|
||||
56
build/Build.CI.GitHubActions.cs
Normal file
56
build/Build.CI.GitHubActions.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using System.Collections.Generic;
|
||||
using Nuke.Common.CI.GitHubActions;
|
||||
using Nuke.Common.CI.GitHubActions.Configuration;
|
||||
using Nuke.Common.Execution;
|
||||
using Nuke.Common.Utilities;
|
||||
using Nuke.Components;
|
||||
|
||||
[CustomGitHubActions(
|
||||
"pr",
|
||||
GitHubActionsImage.UbuntuLatest,
|
||||
OnPullRequestBranches = ["main"],
|
||||
OnPullRequestIncludePaths = ["**/*"],
|
||||
OnPullRequestExcludePaths = ["docs/**/*", "package.json", "package-lock.json", "readme.md"],
|
||||
PublishArtifacts = false,
|
||||
InvokedTargets = [nameof(ICompile.Compile), nameof(ITest.Test), nameof(IPack.Pack)],
|
||||
CacheKeyFiles = []
|
||||
)
|
||||
]
|
||||
public partial class Build;
|
||||
|
||||
class CustomGitHubActionsAttribute : GitHubActionsAttribute
|
||||
{
|
||||
public CustomGitHubActionsAttribute(string name, GitHubActionsImage image, params GitHubActionsImage[] images) : base(name, image, images)
|
||||
{
|
||||
}
|
||||
|
||||
protected override GitHubActionsJob GetJobs(GitHubActionsImage image, IReadOnlyCollection<ExecutableTarget> relevantTargets)
|
||||
{
|
||||
var job = base.GetJobs(image, relevantTargets);
|
||||
|
||||
var newSteps = new List<GitHubActionsStep>(job.Steps);
|
||||
newSteps.Insert(0, new GitHubActionsUseGnuTarStep());
|
||||
|
||||
job.Steps = newSteps.ToArray();
|
||||
return job;
|
||||
}
|
||||
}
|
||||
|
||||
class GitHubActionsUseGnuTarStep : GitHubActionsStep
|
||||
{
|
||||
public override void Write(CustomFileWriter writer)
|
||||
{
|
||||
writer.WriteLine("- if: ${{ runner.os == 'Windows' }}");
|
||||
using (writer.Indent())
|
||||
{
|
||||
writer.WriteLine("name: 'Use GNU tar'");
|
||||
writer.WriteLine("shell: cmd");
|
||||
writer.WriteLine("run: |");
|
||||
using (writer.Indent())
|
||||
{
|
||||
writer.WriteLine("echo \"Adding GNU tar to PATH\"");
|
||||
writer.WriteLine("echo C:\\Program Files\\Git\\usr\\bin>>\"%GITHUB_PATH%\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
build/Build.cs
Normal file
69
build/Build.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Nuke.Common;
|
||||
using Nuke.Common.CI;
|
||||
using Nuke.Common.Git;
|
||||
using Nuke.Common.IO;
|
||||
using Nuke.Common.ProjectModel;
|
||||
using Nuke.Common.Tooling;
|
||||
using Nuke.Common.Tools.DotNet;
|
||||
using Nuke.Common.Utilities.Collections;
|
||||
using Nuke.Components;
|
||||
using Serilog;
|
||||
|
||||
[ShutdownDotNetAfterServerBuild]
|
||||
partial class Build : NukeBuild, ITest, IPack
|
||||
{
|
||||
public static int Main() => Execute<Build>(x => ((ICompile) x).Compile);
|
||||
|
||||
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
|
||||
|
||||
[GitRepository] readonly GitRepository GitRepository;
|
||||
|
||||
AbsolutePath SourceDirectory => RootDirectory / "src";
|
||||
|
||||
public AbsolutePath PackagesDirectory => RootDirectory / "packages";
|
||||
|
||||
string TagVersion => GitRepository.Tags.SingleOrDefault(x => "v".StartsWith(x))?[1..];
|
||||
bool IsTaggedBuild => !string.IsNullOrWhiteSpace(TagVersion);
|
||||
|
||||
string VersionSuffix;
|
||||
|
||||
[Parameter]
|
||||
string Version;
|
||||
|
||||
protected override void OnBuildInitialized()
|
||||
{
|
||||
VersionSuffix = !IsTaggedBuild
|
||||
? $"preview-{DateTime.UtcNow:yyyyMMdd-HHmm}"
|
||||
: "";
|
||||
|
||||
if (IsLocalBuild)
|
||||
{
|
||||
VersionSuffix = $"dev-{DateTime.UtcNow:yyyyMMdd-HHmm}";
|
||||
}
|
||||
|
||||
Log.Information("BUILD SETUP");
|
||||
Log.Information("Configuration:\t{Configuration}", Configuration);
|
||||
Log.Information("Version suffix:\t{VersionSuffix}", VersionSuffix);
|
||||
Log.Information("Version:\t\t{Version}", Version);
|
||||
Log.Information("Tagged build:\t{IsTaggedBuild}", IsTaggedBuild);
|
||||
}
|
||||
|
||||
Target Clean => _ => _
|
||||
.Before<IRestore>(x => x.Restore)
|
||||
.Executes(() =>
|
||||
{
|
||||
SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(x => x.DeleteDirectory());
|
||||
((IHazArtifacts) this).ArtifactsDirectory.CreateOrCleanDirectory();
|
||||
});
|
||||
|
||||
public Configure<DotNetBuildSettings> CompileSettings => _ => _
|
||||
// ensure we don't generate too much output in CI run
|
||||
// 0 Turns off emission of all warning messages
|
||||
// 1 Displays severe warning messages
|
||||
.SetWarningLevel(IsServerBuild ? 0 : 1);
|
||||
|
||||
public IEnumerable<Project> TestProjects => ((IHazSolution) this).Solution.AllProjects.Where(x => x.Name.EndsWith("Tests"));
|
||||
}
|
||||
16
build/Configuration.cs
Normal file
16
build/Configuration.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Nuke.Common.Tooling;
|
||||
|
||||
[TypeConverter(typeof(TypeConverter<Configuration>))]
|
||||
public class Configuration : Enumeration
|
||||
{
|
||||
public static Configuration Debug = new Configuration { Value = nameof(Debug) };
|
||||
public static Configuration Release = new Configuration { Value = nameof(Release) };
|
||||
|
||||
public static implicit operator string(Configuration configuration)
|
||||
{
|
||||
return configuration.Value;
|
||||
}
|
||||
}
|
||||
7
build/Directory.Build.props
Normal file
7
build/Directory.Build.props
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<UseArtifactsOutput>false</UseArtifactsOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
8
build/Directory.Build.targets
Normal file
8
build/Directory.Build.targets
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!-- This file prevents unintended imports of unrelated MSBuild files -->
|
||||
<!-- Uncomment to include parent Directory.Build.targets file -->
|
||||
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />-->
|
||||
|
||||
</Project>
|
||||
22
build/_build.csproj
Normal file
22
build/_build.csproj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace></RootNamespace>
|
||||
<NoWarn>CS0649;CS0169;CA1050;CA1822;CA2211;IDE1006</NoWarn>
|
||||
<NukeRootDirectory>..</NukeRootDirectory>
|
||||
<NukeScriptDirectory>..</NukeScriptDirectory>
|
||||
<NukeTelemetryVersion>1</NukeTelemetryVersion>
|
||||
<NukeExcludeLogs>true</NukeExcludeLogs>
|
||||
<NukeExcludeDirectoryBuild>true</NukeExcludeDirectoryBuild>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nuke.Components" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
28
build/_build.csproj.DotSettings
Normal file
28
build/_build.csproj.DotSettings
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=HeapView_002EDelegateAllocation/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableHidesOuterVariable/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeMadeStatic_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InterpolatedStringExpressionIsNotIFormattable/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Implicit</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_PRIVATE_MODIFIER/@EntryValue">Implicit</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">0</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">False</s:Boolean>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ATTRIBUTE_LENGTH_FOR_SAME_LINE/@EntryValue">120</s:Int64>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">IF_OWNER_IS_SINGLE_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
|
|
@ -10,7 +10,6 @@ using Elsa.EntityFrameworkCore.Modules.Identity;
|
|||
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Http.Handlers;
|
||||
using Elsa.Http.Options;
|
||||
using Elsa.MongoDb.Extensions;
|
||||
using Elsa.MongoDb.Modules.Identity;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
"Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;",
|
||||
"MongoDb": "mongodb://localhost:27017/elsa-workflows",
|
||||
"AzureServiceBus": "",
|
||||
"RabbitMq": "amqp://guest:guest@localhost:5672/elsa"
|
||||
"RabbitMq": "amqp://guest:guest@localhost:5672"
|
||||
},
|
||||
"Smtp": {
|
||||
"Host": "localhost",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<ProjectReference Include="..\..\modules\Elsa.Identity\Elsa.Identity.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Python\Elsa.Python.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz\Elsa.Quartz.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Webhooks\Elsa.Webhooks.csproj"/>
|
||||
|
|
@ -34,8 +35,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Elsa.Studio" Version="3.0.3"/>
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm" Version="3.0.3"/>
|
||||
<PackageReference Include="Elsa.Studio" Version="3.0.4"/>
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm" Version="3.0.4"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -3,9 +3,10 @@ using Elsa.EntityFrameworkCore.Extensions;
|
|||
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Webhooks.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
const bool useMassTransit = true;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.WebHost.UseStaticWebAssets();
|
||||
var services = builder.Services;
|
||||
|
|
@ -16,32 +17,39 @@ var identityTokenSection = identitySection.GetSection("Tokens");
|
|||
|
||||
// Add Elsa services.
|
||||
services
|
||||
.AddElsa(elsa => elsa
|
||||
.UseSasTokens()
|
||||
.UseIdentity(identity =>
|
||||
.AddElsa(elsa =>
|
||||
{
|
||||
elsa
|
||||
.UseSasTokens()
|
||||
.UseIdentity(identity =>
|
||||
{
|
||||
identity.IdentityOptions = options => identitySection.Bind(options);
|
||||
identity.TokenOptions = options => identityTokenSection.Bind(options);
|
||||
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
|
||||
})
|
||||
.UseDefaultAuthentication()
|
||||
.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
|
||||
.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
|
||||
.UseScheduling()
|
||||
.UseJavaScript(options => options.AllowClrAccess = true)
|
||||
.UseLiquid()
|
||||
.UseCSharp()
|
||||
.UsePython()
|
||||
.UseHttp(http => http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options))
|
||||
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options))
|
||||
.UseWebhooks(webhooks => webhooks.WebhookOptions = options => builder.Configuration.GetSection("Webhooks").Bind(options))
|
||||
.UseWorkflowsApi()
|
||||
.UseRealTimeWorkflows()
|
||||
.AddActivitiesFrom<Program>()
|
||||
.AddWorkflowsFrom<Program>();
|
||||
|
||||
if (useMassTransit)
|
||||
{
|
||||
identity.IdentityOptions = options => identitySection.Bind(options);
|
||||
identity.TokenOptions = options => identityTokenSection.Bind(options);
|
||||
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
|
||||
})
|
||||
.UseDefaultAuthentication()
|
||||
.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
|
||||
.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
|
||||
.UseScheduling()
|
||||
.UseJavaScript(options => options.AllowClrAccess = true)
|
||||
.UseLiquid()
|
||||
.UseCSharp()
|
||||
.UsePython()
|
||||
.UseHttp(http => http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options))
|
||||
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options))
|
||||
.UseWebhooks(webhooks => webhooks.WebhookOptions = options => builder.Configuration.GetSection("Webhooks").Bind(options))
|
||||
.UseWorkflowsApi()
|
||||
.UseRealTimeWorkflows()
|
||||
.AddActivitiesFrom<Program>()
|
||||
.AddWorkflowsFrom<Program>()
|
||||
);
|
||||
elsa.UseMassTransit();
|
||||
}
|
||||
});
|
||||
|
||||
services.AddHealthChecks();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;"
|
||||
"Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;",
|
||||
"RabbitMq": "rabbitmq://guest:guest@localhost:5672"
|
||||
},
|
||||
"Hosting": {
|
||||
"BasePath": ""
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Elsa.Studio" Version="3.0.2"/>
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm" Version="3.0.2"/>
|
||||
<PackageReference Include="Elsa.Studio" Version="3.0.4"/>
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm" Version="3.0.4"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Elsa.Studio" Version="3.0.2"/>
|
||||
<PackageReference Include="Elsa.Studio.Core.BlazorWasm" Version="3.0.2"/>
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm" Version="3.0.2"/>
|
||||
<PackageReference Include="Elsa.Studio" Version="3.0.4"/>
|
||||
<PackageReference Include="Elsa.Studio.Core.BlazorWasm" Version="3.0.4"/>
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm" Version="3.0.4"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -145,11 +145,26 @@ public static class ActivityExtensions
|
|||
/// Sets the root activity in the specified activity.
|
||||
/// </summary>
|
||||
public static void SetRoot(this JsonObject container, JsonObject root) => container.SetProperty(root, "root");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root activity in the specified activity.
|
||||
/// </summary>
|
||||
public static string? GetWorkflowDefinitionId(this JsonObject activity) => activity.GetProperty<string?>("workflowDefinitionId");
|
||||
public static string? GetWorkflowDefinitionId(this JsonObject activity)
|
||||
{
|
||||
// Determine if this activity represents a workflow definition activity based on the presence of the following properties.
|
||||
var workflowDefinitionIdNode = activity.GetProperty("workflowDefinitionId");
|
||||
var workflowDefinitionVersionId = activity.GetProperty("workflowDefinitionVersionId");
|
||||
var latestAvailablePublishedVersion = activity.GetProperty("latestAvailablePublishedVersion");
|
||||
var latestAvailablePublishedVersionId = activity.GetProperty("latestAvailablePublishedVersionId");
|
||||
|
||||
if (workflowDefinitionIdNode == null || workflowDefinitionVersionId == null || latestAvailablePublishedVersion == null || latestAvailablePublishedVersionId == null)
|
||||
return null; // Not a workflow definition activity.
|
||||
|
||||
if (workflowDefinitionIdNode is JsonValue value)
|
||||
return value.ToString();
|
||||
|
||||
return null; // Not a workflow definition activity.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the given activity is a workflow definition activity.
|
||||
|
|
@ -157,7 +172,7 @@ public static class ActivityExtensions
|
|||
/// <param name="activity">The JsonObject representing the activity.</param>
|
||||
/// <returns>Returns true if the activity is a workflow definition activity; otherwise, false.</returns>
|
||||
public static bool GetIsWorkflowDefinitionActivity(this JsonObject activity) => activity.ContainsKey("workflowDefinitionId") && activity.ContainsKey("workflowDefinitionVersionId");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified activity can trigger the workflow.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class ScheduleActivityHandler : AlterationHandlerBase<ScheduleActivity>
|
|||
{
|
||||
// If the activity is in a faulted state, reset it to Running.
|
||||
if (existingActivityExecutionContext.Status == ActivityStatus.Faulted)
|
||||
existingActivityExecutionContext.Status = ActivityStatus.Running;
|
||||
existingActivityExecutionContext.TransitionTo(ActivityStatus.Running);
|
||||
|
||||
// Schedule the activity execution context.
|
||||
var parentContext = existingActivityExecutionContext.ParentActivityExecutionContext;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
<PackageReference Include="Dapper" Version="2.1.24"/>
|
||||
<PackageReference Include="FluentMigrator" Version="3.3.2"/>
|
||||
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2"/>
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.5"/>
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.6"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -211,8 +211,8 @@ public class HttpEndpoint : Trigger<HttpRequest>
|
|||
var routeData = GetRouteData(httpContext, path);
|
||||
|
||||
var routeDictionary = routeData.Values.ToDictionary(route => route.Key, route => route.Value!);
|
||||
var queryStringDictionary = httpContext.Request.Query.ToDictionary<KeyValuePair<string, StringValues>, string, object>(queryString => queryString.Key, queryString => queryString.Value[0]!);
|
||||
var headersDictionary = httpContext.Request.Headers.ToDictionary<KeyValuePair<string, StringValues>, string, object>(header => header.Key, header => header.Value[0]!);
|
||||
var queryStringDictionary = httpContext.Request.Query.ToObjectDictionary();
|
||||
var headersDictionary = httpContext.Request.Headers.ToObjectDictionary();
|
||||
|
||||
context.Set(RouteData, routeDictionary);
|
||||
context.Set(QueryStringData, queryStringDictionary);
|
||||
|
|
@ -225,48 +225,60 @@ public class HttpEndpoint : Trigger<HttpRequest>
|
|||
return;
|
||||
}
|
||||
|
||||
// Read content, if any.
|
||||
try
|
||||
// Handle Form Fields
|
||||
if (request.HasFormContentType)
|
||||
{
|
||||
var content = await ParseContentAsync(context, request);
|
||||
ParsedContent.Set(context, content);
|
||||
|
||||
var formFields = request.Form.ToObjectDictionary();
|
||||
|
||||
ParsedContent.Set(context, formFields);
|
||||
|
||||
// Read files, if any.
|
||||
var files = ReadFilesAsync(context, request);
|
||||
|
||||
if (files.Any())
|
||||
{
|
||||
if (!ValidateFileSizes(context, httpContext, files))
|
||||
{
|
||||
await HandleFileSizeTooLargeAsync(context, httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidateFileExtensionWhitelist(context, httpContext, files))
|
||||
{
|
||||
await HandleInvalidFileExtensionWhitelistAsync(context, httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidateFileExtensionBlacklist(context, httpContext, files))
|
||||
{
|
||||
await HandleInvalidFileExtensionBlacklistAsync(context, httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidateFileMimeTypes(context, httpContext, files))
|
||||
{
|
||||
await HandleInvalidFileMimeTypesAsync(context, httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
Files.Set(context, files.ToArray());
|
||||
}
|
||||
}
|
||||
catch (JsonException e)
|
||||
else
|
||||
{
|
||||
await HandleInvalidJsonPayloadAsync(context, httpContext, e);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Read files, if any.
|
||||
var files = ReadFilesAsync(context, request);
|
||||
|
||||
if (files.Any())
|
||||
{
|
||||
if (!ValidateFileSizes(context, httpContext, files))
|
||||
// Parse Non-Form content.
|
||||
try
|
||||
{
|
||||
await HandleFileSizeTooLargeAsync(context, httpContext);
|
||||
return;
|
||||
var content = await ParseContentAsync(context, request);
|
||||
ParsedContent.Set(context, content);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
await HandleInvalidJsonPayloadAsync(context, httpContext, e);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (!ValidateFileExtensionWhitelist(context, httpContext, files))
|
||||
{
|
||||
await HandleInvalidFileExtensionWhitelistAsync(context, httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidateFileExtensionBlacklist(context, httpContext, files))
|
||||
{
|
||||
await HandleInvalidFileExtensionBlacklistAsync(context, httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidateFileMimeTypes(context, httpContext, files))
|
||||
{
|
||||
await HandleInvalidFileMimeTypesAsync(context, httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
Files.Set(context, files.ToArray());
|
||||
}
|
||||
|
||||
// Complete.
|
||||
|
|
|
|||
28
src/modules/Elsa.Http/Extensions/StringValueExtensions.cs
Normal file
28
src/modules/Elsa.Http/Extensions/StringValueExtensions.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using Microsoft.Extensions.Primitives;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="StringValues"/> Enumerable dictonaries.
|
||||
/// </summary>
|
||||
public static class StringValueExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert the collection to the desired dictionary type.
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, object> ToObjectDictionary(this IEnumerable<KeyValuePair<string, StringValues>> collection)
|
||||
{
|
||||
return collection.ToDictionary<KeyValuePair<string, StringValues>, string, object>(
|
||||
item => item.Key,
|
||||
item => item.Value.Count <= 1 ?
|
||||
item.Value[0]!
|
||||
: item.Value.ToArray());
|
||||
}
|
||||
}
|
||||
|
|
@ -158,12 +158,10 @@ public class HttpFeature : FeatureBase
|
|||
.AddRequestHandler<ValidateWorkflowRequestHandler, ValidateWorkflowRequest, ValidateWorkflowResponse>()
|
||||
.AddNotificationHandler<UpdateRouteTable>()
|
||||
|
||||
// Content parsers.
|
||||
.AddScoped<IHttpContentParser, StringHttpContentParser>()
|
||||
.AddScoped<IHttpContentParser, JsonHttpContentParser>()
|
||||
.AddScoped<IHttpContentParser, XmlHttpContentParser>()
|
||||
.AddScoped<IHttpContentParser, FormHttpContentParser>()
|
||||
|
||||
// Content parsers.
|
||||
.AddSingleton<IHttpContentParser, JsonHttpContentParser>()
|
||||
.AddSingleton<IHttpContentParser, XmlHttpContentParser>()
|
||||
|
||||
// HTTP content factories.
|
||||
.AddScoped<IHttpContentFactory, TextContentFactory>()
|
||||
.AddScoped<IHttpContentFactory, JsonContentFactory>()
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
using System.Xml.Serialization;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Http.Contracts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Elsa.Http.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// Reads application/xml and text/xml content type streams.
|
||||
/// </summary>
|
||||
public class FormHttpContentParser : IHttpContentParser
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Priority => 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetSupportsContentType(string contentType) => contentType.StartsWith("application/x-www-form-urlencoded", StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<object> ReadAsync(Stream content, Type? returnType, CancellationToken cancellationToken)
|
||||
{
|
||||
using var reader = new FormReader(content);
|
||||
|
||||
return (await reader.ReadFormAsync(cancellationToken)).ToDictionary<KeyValuePair<string, StringValues>, string, object>(formField => formField.Key, formField => formField.Value[0]!); ;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Http.Contracts;
|
||||
|
||||
namespace Elsa.Http.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// Reads any content type streams as a string and attempts to convert the string to the specified return type.
|
||||
/// </summary>
|
||||
public class StringHttpContentParser : IHttpContentParser
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Priority => -10;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetSupportsContentType(string contentType) => !contentType.StartsWith("multipart/form-data");
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<object> ReadAsync(Stream content, Type? returnType, CancellationToken cancellationToken)
|
||||
{
|
||||
using var reader = new StreamReader(content, leaveOpen: true);
|
||||
var text = await reader.ReadToEndAsync();
|
||||
return returnType == null ? text : text.ConvertTo(returnType)!;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using Elsa.MassTransit.Implementations;
|
|||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Attributes;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.UIHints;
|
||||
using MassTransit;
|
||||
|
||||
namespace Elsa.MassTransit.Activities;
|
||||
|
|
@ -29,7 +30,10 @@ public class PublishMessage : CodeActivity
|
|||
/// <summary>
|
||||
/// The message to send. Must be a concrete implementation of the configured <see cref="MessageType"/>.
|
||||
/// </summary>
|
||||
[Input(Description = "The message to send. Must be a concrete implementation of the configured message type.")]
|
||||
[Input(
|
||||
Description = "The message to send. Must be a concrete implementation of the configured message type.",
|
||||
UIHint = InputUIHints.MultiLine
|
||||
)]
|
||||
public Input<object> Message { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ using Elsa.MassTransit.Options;
|
|||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Attributes;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.UIHints;
|
||||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Management;
|
||||
using Elsa.Workflows.Models;
|
||||
using Humanizer;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -17,40 +17,31 @@ namespace Elsa.MassTransit.Implementations;
|
|||
/// <summary>
|
||||
/// Provides activities to the system from the configured MassTransit message types.
|
||||
/// </summary>
|
||||
public class MassTransitActivityTypeProvider : IActivityProvider
|
||||
public class MassTransitActivityTypeProvider(IActivityFactory activityFactory, IOptions<MassTransitActivityOptions> options, IActivityDescriber activityDescriber) : IActivityProvider
|
||||
{
|
||||
private readonly IActivityFactory _activityFactory;
|
||||
private readonly MassTransitActivityOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public MassTransitActivityTypeProvider(IActivityFactory activityFactory, IOptions<MassTransitActivityOptions> options)
|
||||
{
|
||||
_activityFactory = activityFactory;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
|
||||
public async ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var messageTypes = _options.MessageTypes;
|
||||
var descriptors = CreateDescriptors(messageTypes).ToList();
|
||||
return new(descriptors);
|
||||
var messageTypes = options.Value.MessageTypes;
|
||||
var descriptors = await CreateDescriptorsAsync(messageTypes, cancellationToken);
|
||||
return descriptors.ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<ActivityDescriptor> CreateDescriptors(IEnumerable<Type> messageTypes)
|
||||
private async Task<IEnumerable<ActivityDescriptor>> CreateDescriptorsAsync(IEnumerable<Type> messageTypes, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var descriptors = new List<ActivityDescriptor>();
|
||||
foreach (var messageType in messageTypes)
|
||||
{
|
||||
yield return CreateMessageReceivedDescriptor(messageType);
|
||||
descriptors.Add(await CreateMessageReceivedDescriptor(messageType, cancellationToken));
|
||||
|
||||
if(messageType.IsClass)
|
||||
yield return CreatePublishMessageDescriptor(messageType);
|
||||
descriptors.Add(await CreatePublishMessageDescriptor(messageType, cancellationToken));
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private ActivityDescriptor CreateMessageReceivedDescriptor(Type messageType)
|
||||
private async Task<ActivityDescriptor> CreateMessageReceivedDescriptor(Type messageType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var activityAttr = messageType.GetCustomAttribute<ActivityAttribute>();
|
||||
var typeName = activityAttr?.Type ?? messageType.Name;
|
||||
|
|
@ -61,6 +52,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider
|
|||
var category = categoryAttr?.Category ?? activityAttr?.Category ?? "MassTransit";
|
||||
var descriptionAttr = messageType.GetCustomAttribute<DescriptionAttribute>();
|
||||
var description = descriptionAttr?.Description ?? activityAttr?.Description;
|
||||
|
||||
var outputDescriptor = await activityDescriber.DescribeOutputProperty<MessageReceived, object>(x => x.Result!, cancellationToken);
|
||||
var openOutputType = typeof(Output<>);
|
||||
var outputType = openOutputType.MakeGenericType(messageType);
|
||||
outputDescriptor.Type = outputType;
|
||||
|
||||
return new()
|
||||
{
|
||||
|
|
@ -73,17 +69,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider
|
|||
IsBrowsable = true,
|
||||
Outputs =
|
||||
{
|
||||
new OutputDescriptor
|
||||
{
|
||||
Description = "The received message",
|
||||
DisplayName = "Received Message",
|
||||
Name = nameof(MessageReceived.Result),
|
||||
Type = typeof(object)
|
||||
}
|
||||
outputDescriptor
|
||||
},
|
||||
Constructor = context =>
|
||||
{
|
||||
var activity = _activityFactory.Create<MessageReceived>(context);
|
||||
var activity = activityFactory.Create<MessageReceived>(context);
|
||||
activity.Type = fullTypeName;
|
||||
activity.MessageType = messageType;
|
||||
return activity;
|
||||
|
|
@ -91,7 +81,7 @@ public class MassTransitActivityTypeProvider : IActivityProvider
|
|||
};
|
||||
}
|
||||
|
||||
private ActivityDescriptor CreatePublishMessageDescriptor(Type messageType)
|
||||
private async Task<ActivityDescriptor> CreatePublishMessageDescriptor(Type messageType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var activityAttr = messageType.GetCustomAttribute<ActivityAttribute>();
|
||||
var typeName = activityAttr?.Type ?? messageType.Name;
|
||||
|
|
@ -104,6 +94,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider
|
|||
var descriptionAttr = messageType.GetCustomAttribute<DescriptionAttribute>();
|
||||
var description = descriptionAttr?.Description ?? activityAttr?.Description;
|
||||
|
||||
var messageInputDescriptor = await activityDescriber.DescribeInputPropertyAsync<PublishMessage, object>(x => x.Message, cancellationToken: cancellationToken);
|
||||
var openInputType = typeof(Input<>);
|
||||
var inputType = openInputType.MakeGenericType(messageType);
|
||||
messageInputDescriptor.Type = inputType;
|
||||
|
||||
return new()
|
||||
{
|
||||
TypeName = fullTypeName,
|
||||
|
|
@ -115,18 +110,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider
|
|||
IsBrowsable = true,
|
||||
Inputs =
|
||||
{
|
||||
new InputDescriptor
|
||||
{
|
||||
Description = "The message to publish.",
|
||||
UIHint = InputUIHints.MultiLine,
|
||||
DisplayName = "Message",
|
||||
Type = typeof(Input<object>),
|
||||
Name = nameof(PublishMessage.Message)
|
||||
}
|
||||
messageInputDescriptor
|
||||
},
|
||||
Constructor = context =>
|
||||
{
|
||||
var activity = _activityFactory.Create<PublishMessage>(context);
|
||||
var activity = activityFactory.Create<PublishMessage>(context);
|
||||
activity.Type = fullTypeName;
|
||||
activity.MessageType = messageType;
|
||||
return activity;
|
||||
|
|
|
|||
|
|
@ -3,16 +3,22 @@ using Elsa.ProtoActor.Extensions;
|
|||
using Elsa.ProtoActor.Mappers;
|
||||
using Elsa.ProtoActor.ProtoBuf;
|
||||
using Elsa.ProtoActor.Snapshots;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Options;
|
||||
using Elsa.Workflows.Runtime.Requests;
|
||||
using Elsa.Workflows.State;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Proto;
|
||||
using Proto.Cluster;
|
||||
using Proto.Persistence;
|
||||
using CancellationTokens = Elsa.Workflows.Models.CancellationTokens;
|
||||
using WorkflowStatus = Elsa.Workflows.WorkflowStatus;
|
||||
using WorkflowSubStatus = Elsa.Workflows.WorkflowSubStatus;
|
||||
|
||||
namespace Elsa.ProtoActor.Grains;
|
||||
|
||||
|
|
@ -38,6 +44,8 @@ internal class WorkflowInstance : WorkflowInstanceBase
|
|||
private IWorkflowHost _workflowHost = default!;
|
||||
private WorkflowState _workflowState = default!;
|
||||
|
||||
private readonly ICollection<CancellationTokenSource> _cancellationTokenSources = new List<CancellationTokenSource>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public WorkflowInstance(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
|
|
@ -139,6 +147,10 @@ internal class WorkflowInstance : WorkflowInstanceBase
|
|||
var versionOptions = VersionOptions.FromString(request.VersionOptions);
|
||||
var cancellationToken = Context.CancellationToken;
|
||||
|
||||
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_cancellationTokenSources.Add(cancellationTokenSource);
|
||||
cancellationToken = cancellationTokenSource.Token;
|
||||
|
||||
// Only need to reconstruct a workflow host if not already done so during CanStart.
|
||||
if (_workflowHost == null!)
|
||||
{
|
||||
|
|
@ -155,7 +167,9 @@ internal class WorkflowInstance : WorkflowInstanceBase
|
|||
CorrelationId = correlationId,
|
||||
Input = input,
|
||||
Properties = properties,
|
||||
TriggerActivityId = request.TriggerActivityId
|
||||
TriggerActivityId = request.TriggerActivityId,
|
||||
StatusUpdatedCallback = StatusUpdated,
|
||||
CancellationTokens = new CancellationTokens(cancellationToken)
|
||||
};
|
||||
|
||||
var task = _workflowHost.StartWorkflowAsync(startWorkflowOptions, cancellationToken);
|
||||
|
|
@ -187,6 +201,31 @@ internal class WorkflowInstance : WorkflowInstanceBase
|
|||
});
|
||||
}
|
||||
|
||||
private void StatusUpdated(WorkflowExecutionContext context)
|
||||
{
|
||||
_ = Task.Run(async () => await Update(context));
|
||||
}
|
||||
|
||||
private async Task Update(WorkflowExecutionContext context)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var extractor = scope.ServiceProvider.GetRequiredService<IWorkflowStateExtractor>();
|
||||
var bookmarkPersistor = scope.ServiceProvider.GetRequiredService<IBookmarksPersister>();
|
||||
var workflowState = extractor.Extract(context);
|
||||
var originalBookmarks = _workflowHost.WorkflowState.Bookmarks;
|
||||
|
||||
_workflowState = workflowState;
|
||||
|
||||
await SaveSnapshotAsync();
|
||||
SaveWorkflowInstance(workflowState);
|
||||
var newBookmarks = workflowState.Bookmarks;
|
||||
|
||||
var diff = Diff.For(originalBookmarks, newBookmarks);
|
||||
|
||||
var bookmarkRequest = new UpdateBookmarksRequest(workflowState.DefinitionId, diff, workflowState.CorrelationId);
|
||||
await bookmarkPersistor.PersistBookmarksAsync(bookmarkRequest);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task Stop()
|
||||
{
|
||||
|
|
@ -208,6 +247,10 @@ internal class WorkflowInstance : WorkflowInstanceBase
|
|||
var activityInstanceId = request.ActivityInstanceId.NullIfEmpty();
|
||||
var activityHash = request.ActivityHash.NullIfEmpty();
|
||||
var cancellationToken = Context.CancellationToken;
|
||||
|
||||
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_cancellationTokenSources.Add(cancellationTokenSource);
|
||||
cancellationToken = cancellationTokenSource.Token;
|
||||
|
||||
var resumeWorkflowHostOptions = new ResumeWorkflowHostOptions
|
||||
{
|
||||
|
|
@ -218,7 +261,8 @@ internal class WorkflowInstance : WorkflowInstanceBase
|
|||
ActivityInstanceId = activityInstanceId,
|
||||
ActivityHash = activityHash,
|
||||
Input = _input,
|
||||
Properties = _properties
|
||||
Properties = _properties,
|
||||
CancellationTokens = cancellationToken
|
||||
};
|
||||
|
||||
var definitionId = _definitionId;
|
||||
|
|
@ -260,6 +304,18 @@ internal class WorkflowInstance : WorkflowInstanceBase
|
|||
/// <inheritdoc />
|
||||
public override Task<WorkflowExecutionResponse> Resume(ResumeWorkflowRequest request) => Task.FromResult(new WorkflowExecutionResponse());
|
||||
|
||||
public override async Task Cancel()
|
||||
{
|
||||
if (_workflowState.Status != WorkflowStatus.Finished)
|
||||
{
|
||||
_workflowState.SubStatus = WorkflowSubStatus.Cancelled;
|
||||
_workflowState.Status = WorkflowStatus.Finished;
|
||||
}
|
||||
|
||||
foreach(var source in _cancellationTokenSources)
|
||||
source.Cancel();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ExportWorkflowStateResponse> ExportState(ExportWorkflowStateRequest request)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ service WorkflowInstance {
|
|||
rpc Start (StartWorkflowRequest) returns (WorkflowExecutionResponse);
|
||||
rpc Stop (Empty) returns (Empty);
|
||||
rpc Resume (ResumeWorkflowRequest) returns (WorkflowExecutionResponse);
|
||||
rpc Cancel (Empty) returns (Empty);
|
||||
rpc ExportState(ExportWorkflowStateRequest) returns (ExportWorkflowStateResponse);
|
||||
rpc ImportState(ImportWorkflowStateRequest) returns (ImportWorkflowStateResponse);
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ using Elsa.Workflows.State;
|
|||
using Proto.Cluster;
|
||||
using Bookmark = Elsa.Workflows.Models.Bookmark;
|
||||
using CountRunningWorkflowsRequest = Elsa.Workflows.Runtime.Requests.CountRunningWorkflowsRequest;
|
||||
using WorkflowStatus = Elsa.Workflows.WorkflowStatus;
|
||||
|
||||
namespace Elsa.ProtoActor.Services;
|
||||
|
||||
|
|
@ -246,6 +247,13 @@ internal class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
return result!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var client = _cluster.GetNamedWorkflowGrain(workflowInstanceId);
|
||||
await client.Cancel(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<WorkflowMatch>> FindWorkflowsAsync(WorkflowsFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
@ -296,7 +304,7 @@ internal class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
DefinitionId = request.DefinitionId,
|
||||
Version = request.Version,
|
||||
CorrelationId = request.CorrelationId,
|
||||
WorkflowStatus = Workflows.WorkflowStatus.Running
|
||||
WorkflowStatus = WorkflowStatus.Running
|
||||
};
|
||||
return await _workflowInstanceStore.CountAsync(filter, cancellationToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using Elsa.Features.Services;
|
||||
using Elsa.Webhooks.Features;
|
||||
|
||||
namespace Elsa.Webhooks.Extensions;
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Adds extensions to <see cref="IModule"/> that enables the <see cref="WebhooksFeature"/> feature.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
<Import Project="..\..\..\frameworks.props"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>3.0.0</Version>
|
||||
<Description>
|
||||
Provides API endpoints for client applications such as the Elsa Designer web component.
|
||||
</Description>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ internal class BulkDelete : ElsaEndpoint<Request, Response>
|
|||
|
||||
public override async Task<Response> ExecuteAsync(Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
var count = await _workflowDefinitionManager.BulkDeleteByDefinitionIdsAsync(request!.DefinitionIds, cancellationToken);
|
||||
var count = await _workflowDefinitionManager.BulkDeleteByDefinitionIdsAsync(request.DefinitionIds, cancellationToken);
|
||||
return new Response(count);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,30 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using Elsa.Abstractions;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
|
||||
namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.BulkCancel;
|
||||
|
||||
public class BulkCancel : ElsaEndpoint<Request, Response>
|
||||
{
|
||||
private readonly IWorkflowRuntime _workflowRuntime;
|
||||
|
||||
public BulkCancel(IWorkflowRuntime workflowRuntime)
|
||||
{
|
||||
_workflowRuntime = workflowRuntime;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/bulk-actions/cancel/workflow-instances/by-id");
|
||||
ConfigurePermissions("cancel:workflow-instances");
|
||||
}
|
||||
|
||||
|
||||
public override async Task<Response> ExecuteAsync(Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Implement workflow cancellation.
|
||||
var count = -1;
|
||||
var tasks = request.Ids.Select(id => _workflowRuntime.CancelWorkflowAsync(id, cancellationToken)).ToList();
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
var count = tasks.Count(t => t.IsCompletedSuccessfully);
|
||||
|
||||
return new(count);
|
||||
}
|
||||
|
||||
public record BulkCancelWorkflowInstancesRequest(ICollection<string> Ids);
|
||||
|
||||
public record BulkCancelWorkflowInstancesResponse([property: JsonPropertyName("cancelled")] int CancelledCount);
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Notifications;
|
||||
using Elsa.Workflows.Signals;
|
||||
|
||||
namespace Elsa.Workflows;
|
||||
|
||||
public partial class ActivityExecutionContext
|
||||
{
|
||||
private readonly CancellationTokenRegistration _cancellationRegistration;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly INotificationSender _publisher;
|
||||
|
||||
private void CancelActivity()
|
||||
{
|
||||
// If the activity is not running, do nothing.
|
||||
if (Status != ActivityStatus.Running && Status != ActivityStatus.Faulted)
|
||||
return;
|
||||
|
||||
_ = Task.Run(async () => await CancelActivityAsync());
|
||||
}
|
||||
|
||||
private async Task CancelActivityAsync()
|
||||
{
|
||||
// Select all child contexts.
|
||||
var childContexts = WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == this).ToList();
|
||||
|
||||
foreach (var childContext in childContexts)
|
||||
childContext._cancellationTokenSource.Cancel();
|
||||
|
||||
TransitionTo(ActivityStatus.Canceled);
|
||||
ClearBookmarks();
|
||||
ClearCompletionCallbacks();
|
||||
WorkflowExecutionContext.Bookmarks.RemoveWhere(x => x.ActivityNodeId == NodeId);
|
||||
|
||||
// Add an execution log entry.
|
||||
AddExecutionLogEntry("Canceled", payload: JournalData, includeActivityState: true);
|
||||
|
||||
_cancellationRegistration.Dispose();
|
||||
|
||||
await this.SendSignalAsync(new CancelSignal());
|
||||
await _publisher.SendAsync(new ActivityCancelled(this));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Workflows;
|
||||
|
||||
public partial class ActivityExecutionContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="WorkflowExecutionLogEntry"/> to the execution log of the current <see cref="Workflows.WorkflowExecutionContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="eventName">The name of the event.</param>
|
||||
/// <param name="message">The message of the event.</param>
|
||||
/// <param name="source">The source of the activity. For example, the source file name and line number in case of composite activities.</param>
|
||||
/// <param name="payload">Any contextual data related to this event.</param>
|
||||
/// <param name="includeActivityState">True to include activity state with this event, false otherwise.</param>
|
||||
/// <returns>Returns the created <see cref="WorkflowExecutionLogEntry"/>.</returns>
|
||||
public WorkflowExecutionLogEntry AddExecutionLogEntry(string eventName, string? message = default, string? source = default, object? payload = default, bool includeActivityState = false)
|
||||
{
|
||||
var activityState = includeActivityState ? ActivityState : default;
|
||||
|
||||
var logEntry = new WorkflowExecutionLogEntry(
|
||||
Id,
|
||||
ParentActivityExecutionContext?.Id,
|
||||
Activity.Id,
|
||||
Activity.Type,
|
||||
Activity.Version,
|
||||
Activity.Name,
|
||||
NodeId,
|
||||
activityState,
|
||||
_systemClock.UtcNow,
|
||||
WorkflowExecutionContext.ExecutionLogSequence++,
|
||||
eventName,
|
||||
message,
|
||||
source ?? Activity.GetSource(),
|
||||
payload);
|
||||
|
||||
WorkflowExecutionContext.ExecutionLog.Add(logEntry);
|
||||
return logEntry;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ using Elsa.Common.Contracts;
|
|||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Memory;
|
||||
using Elsa.Workflows.Models;
|
||||
|
|
@ -15,7 +16,7 @@ namespace Elsa.Workflows;
|
|||
/// <summary>
|
||||
/// Represents the context of an activity execution.
|
||||
/// </summary>
|
||||
public class ActivityExecutionContext : IExecutionContext
|
||||
public partial class ActivityExecutionContext : IExecutionContext
|
||||
{
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly List<Bookmark> _bookmarks = new();
|
||||
|
|
@ -47,6 +48,10 @@ public class ActivityExecutionContext : IExecutionContext
|
|||
Tag = tag;
|
||||
CancellationToken = cancellationToken;
|
||||
Id = id;
|
||||
_publisher = GetRequiredService<INotificationSender>();
|
||||
|
||||
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_cancellationRegistration = _cancellationTokenSource.Token.Register(CancelActivity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -123,8 +128,21 @@ public class ActivityExecutionContext : IExecutionContext
|
|||
/// <summary>
|
||||
/// The current status of the activity.
|
||||
/// </summary>
|
||||
public ActivityStatus Status { get; set; }
|
||||
public ActivityStatus Status { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current status of the activity.
|
||||
/// </summary>
|
||||
public void TransitionTo(ActivityStatus status)
|
||||
{
|
||||
Status = status;
|
||||
|
||||
if (Status is ActivityStatus.Completed
|
||||
or ActivityStatus.Canceled
|
||||
or ActivityStatus.Faulted)
|
||||
_cancellationRegistration.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception that occurred during the activity execution, if any.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
namespace Elsa.Workflows;
|
||||
|
||||
/// <summary>
|
||||
/// Provides context to the currently executing workflow.
|
||||
/// </summary>
|
||||
public partial class WorkflowExecutionContext
|
||||
{
|
||||
private ICollection<CancellationTokenSource> _cancellationTokenSources = new List<CancellationTokenSource>();
|
||||
private ICollection<CancellationTokenRegistration> _cancellationRegistrations = new List<CancellationTokenRegistration>();
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the workflow and all it's children.
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
foreach (var source in _cancellationTokenSources)
|
||||
source.Cancel();
|
||||
|
||||
_cancellationTokenSources.Clear();
|
||||
}
|
||||
|
||||
private void CancelWorkflow()
|
||||
{
|
||||
Bookmarks.Clear();
|
||||
_completionCallbackEntries.Clear();
|
||||
|
||||
if (Status != WorkflowStatus.Running && SubStatus != WorkflowSubStatus.Faulted)
|
||||
return;
|
||||
|
||||
AddExecutionLogEntry("Workflow cancelled");
|
||||
|
||||
TransitionTo(WorkflowSubStatus.Cancelled);
|
||||
|
||||
foreach (var registration in _cancellationRegistrations)
|
||||
registration.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Workflows;
|
||||
|
||||
public partial class WorkflowExecutionContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="WorkflowExecutionLogEntry"/> to the execution log of the current <see cref="Workflows.WorkflowExecutionContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="eventName">The name of the event.</param>
|
||||
/// <param name="message">The message of the event.</param>
|
||||
/// <param name="payload">Any contextual data related to this event.</param>
|
||||
/// <returns>Returns the created <see cref="WorkflowExecutionLogEntry"/>.</returns>
|
||||
public WorkflowExecutionLogEntry AddExecutionLogEntry(string eventName, string? message = default, object? payload = default)
|
||||
{
|
||||
var logEntry = new WorkflowExecutionLogEntry(
|
||||
Id,
|
||||
default,
|
||||
Workflow.Id,
|
||||
Workflow.Type,
|
||||
Workflow.Identity.Version,
|
||||
Workflow.Name,
|
||||
Workflow.Identity.Id,
|
||||
default,
|
||||
SystemClock.UtcNow,
|
||||
ExecutionLogSequence++,
|
||||
eventName,
|
||||
message,
|
||||
Workflow.GetSource(),
|
||||
payload);
|
||||
|
||||
ExecutionLog.Add(logEntry);
|
||||
return logEntry;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ public record ActivityCompletionCallbackEntry(ActivityExecutionContext Owner, Ac
|
|||
/// Provides context to the currently executing workflow.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class WorkflowExecutionContext : IExecutionContext
|
||||
public partial class WorkflowExecutionContext : IExecutionContext
|
||||
{
|
||||
private static readonly object ActivityOutputRegistryKey = new();
|
||||
private static readonly object LastActivityResultKey = new();
|
||||
|
|
@ -37,6 +37,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
private readonly IList<ActivityCompletionCallbackEntry> _completionCallbackEntries = new List<ActivityCompletionCallbackEntry>();
|
||||
private IList<ActivityExecutionContext> _activityExecutionContexts;
|
||||
private readonly IHasher _hasher;
|
||||
private readonly Action<WorkflowExecutionContext>? _statusUpdatedCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="WorkflowExecutionContext"/>.
|
||||
|
|
@ -51,6 +52,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
string? triggerActivityId,
|
||||
IEnumerable<ActivityIncident> incidents,
|
||||
DateTimeOffset createdAt,
|
||||
Action<WorkflowExecutionContext>? statusUpdatedCallback,
|
||||
CancellationTokens cancellationTokens)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
|
|
@ -70,6 +72,13 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
CreatedAt = createdAt;
|
||||
CancellationTokens = cancellationTokens;
|
||||
Incidents = incidents.ToList();
|
||||
|
||||
var appSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationTokens.ApplicationCancellationToken);
|
||||
_cancellationTokenSources.Add(appSource);
|
||||
var sysSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationTokens.SystemCancellationToken);
|
||||
_cancellationTokenSources.Add(sysSource);
|
||||
_cancellationRegistrations.Add(appSource.Token.Register(CancelWorkflow));
|
||||
_cancellationRegistrations.Add(sysSource.Token.Register(CancelWorkflow));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -84,6 +93,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
IDictionary<string, object>? properties = default,
|
||||
ExecuteActivityDelegate? executeDelegate = default,
|
||||
string? triggerActivityId = default,
|
||||
Action<WorkflowExecutionContext>? statusUpdatedCallback = default,
|
||||
CancellationTokens cancellationTokens = default)
|
||||
{
|
||||
var systemClock = serviceProvider.GetRequiredService<ISystemClock>();
|
||||
|
|
@ -99,6 +109,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
properties,
|
||||
executeDelegate,
|
||||
triggerActivityId,
|
||||
statusUpdatedCallback,
|
||||
cancellationTokens
|
||||
);
|
||||
}
|
||||
|
|
@ -115,6 +126,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
IDictionary<string, object>? properties = default,
|
||||
ExecuteActivityDelegate? executeDelegate = default,
|
||||
string? triggerActivityId = default,
|
||||
Action<WorkflowExecutionContext>? statusUpdatedCallback = default,
|
||||
CancellationTokens cancellationTokens = default)
|
||||
{
|
||||
var workflowExecutionContext = await CreateAsync(
|
||||
|
|
@ -128,6 +140,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
properties,
|
||||
executeDelegate,
|
||||
triggerActivityId,
|
||||
statusUpdatedCallback,
|
||||
cancellationTokens);
|
||||
|
||||
var workflowStateExtractor = serviceProvider.GetRequiredService<IWorkflowStateExtractor>();
|
||||
|
|
@ -150,6 +163,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
IDictionary<string, object>? properties = default,
|
||||
ExecuteActivityDelegate? executeDelegate = default,
|
||||
string? triggerActivityId = default,
|
||||
Action<WorkflowExecutionContext>? statusUpdatedCallback = default,
|
||||
CancellationTokens cancellationTokens = default)
|
||||
{
|
||||
// Setup a workflow execution context.
|
||||
|
|
@ -163,6 +177,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
triggerActivityId,
|
||||
incidents,
|
||||
createdAt,
|
||||
statusUpdatedCallback,
|
||||
cancellationTokens)
|
||||
{
|
||||
MemoryRegister = workflow.CreateRegister()
|
||||
|
|
@ -521,6 +536,20 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
throw new Exception($"Cannot transition from {SubStatus} to {subStatus}");
|
||||
|
||||
SubStatus = subStatus;
|
||||
|
||||
//For now only trigger on Cancelled, since the other statuses are handling via the host/runner
|
||||
if (SubStatus == WorkflowSubStatus.Cancelled
|
||||
&& _statusUpdatedCallback is not null)
|
||||
_statusUpdatedCallback(this);
|
||||
|
||||
if (Status == WorkflowStatus.Finished
|
||||
|| SubStatus == WorkflowSubStatus.Suspended)
|
||||
{
|
||||
foreach (var registration in _cancellationRegistrations)
|
||||
{
|
||||
registration.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Elsa.Common.Contracts;
|
||||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Expressions.Models;
|
||||
|
|
@ -74,45 +73,6 @@ public static class ActivityExecutionContextExtensions
|
|||
/// </summary>
|
||||
public static bool IsTriggerOfWorkflow(this ActivityExecutionContext context) => context.WorkflowExecutionContext.TriggerActivityId == context.Activity.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="WorkflowExecutionLogEntry"/> to the execution log of the current <see cref="WorkflowExecutionContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ActivityExecutionContext"/></param> being extended.
|
||||
/// <param name="eventName">The name of the event.</param>
|
||||
/// <param name="message">The message of the event.</param>
|
||||
/// <param name="source">The source of the activity. For example, the source file name and line number in case of composite activities.</param>
|
||||
/// <param name="payload">Any contextual data related to this event.</param>
|
||||
/// <param name="includeActivityState">True to include activity state with this event, false otherwise.</param>
|
||||
/// <returns>Returns the created <see cref="WorkflowExecutionLogEntry"/>.</returns>
|
||||
public static WorkflowExecutionLogEntry AddExecutionLogEntry(this ActivityExecutionContext context, string eventName, string? message = default, string? source = default, object? payload = default, bool includeActivityState = false)
|
||||
{
|
||||
var activity = context.Activity;
|
||||
var activityInstanceId = context.Id;
|
||||
var parentActivityInstanceId = context.ParentActivityExecutionContext?.Id;
|
||||
var workflowExecutionContext = context.WorkflowExecutionContext;
|
||||
var now = context.GetRequiredService<ISystemClock>().UtcNow;
|
||||
var activityState = includeActivityState ? context.ActivityState : default;
|
||||
|
||||
var logEntry = new WorkflowExecutionLogEntry(
|
||||
activityInstanceId,
|
||||
parentActivityInstanceId,
|
||||
activity.Id,
|
||||
activity.Type,
|
||||
activity.Version,
|
||||
activity.Name,
|
||||
context.NodeId,
|
||||
activityState,
|
||||
now,
|
||||
workflowExecutionContext.ExecutionLogSequence++,
|
||||
eventName,
|
||||
message,
|
||||
source ?? activity.GetSource(),
|
||||
payload);
|
||||
|
||||
workflowExecutionContext.ExecutionLog.Add(logEntry);
|
||||
return logEntry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a workflow variable by name and optionally sets the value.
|
||||
/// </summary>
|
||||
|
|
@ -477,7 +437,7 @@ public static class ActivityExecutionContextExtensions
|
|||
await childContext.CancelActivityAsync();
|
||||
|
||||
// Mark the activity as complete.
|
||||
context.Status = ActivityStatus.Completed;
|
||||
context.TransitionTo(ActivityStatus.Completed);
|
||||
|
||||
// Record the outcomes, if any.
|
||||
if (outcomes != null)
|
||||
|
|
@ -595,7 +555,7 @@ public static class ActivityExecutionContextExtensions
|
|||
await CancelActivityAsync(childContext);
|
||||
|
||||
var publisher = context.GetRequiredService<INotificationSender>();
|
||||
context.Status = ActivityStatus.Canceled;
|
||||
context.TransitionTo(ActivityStatus.Canceled);
|
||||
context.ClearBookmarks();
|
||||
context.ClearCompletionCallbacks();
|
||||
context.WorkflowExecutionContext.Bookmarks.RemoveWhere(x => x.ActivityNodeId == context.NodeId);
|
||||
|
|
|
|||
|
|
@ -292,7 +292,12 @@ public static class ExpressionExecutionContextExtensions
|
|||
select v;
|
||||
|
||||
var variable = q.FirstOrDefault();
|
||||
variable?.Set(context, value);
|
||||
|
||||
if(variable != null)
|
||||
variable.Set(context, value);
|
||||
|
||||
if (variable == null)
|
||||
CreateVariable(context, variableName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -125,36 +125,4 @@ public static class WorkflowExecutionContextExtensions
|
|||
/// Returns true if all activities have completed or canceled, false otherwise.
|
||||
/// </summary>
|
||||
public static bool AllActivitiesCompleted(this WorkflowExecutionContext workflowExecutionContext) => workflowExecutionContext.ActivityExecutionContexts.All(x => x.IsCompleted);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="WorkflowExecutionLogEntry"/> to the execution log of the current <see cref="WorkflowExecutionContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="WorkflowExecutionContext"/></param> being extended.
|
||||
/// <param name="eventName">The name of the event.</param>
|
||||
/// <param name="message">The message of the event.</param>
|
||||
/// <param name="payload">Any contextual data related to this event.</param>
|
||||
/// <returns>Returns the created <see cref="WorkflowExecutionLogEntry"/>.</returns>
|
||||
public static WorkflowExecutionLogEntry AddExecutionLogEntry(this WorkflowExecutionContext context, string eventName, string? message = default, object? payload = default)
|
||||
{
|
||||
var now = context.GetRequiredService<ISystemClock>().UtcNow;
|
||||
|
||||
var logEntry = new WorkflowExecutionLogEntry(
|
||||
context.Id,
|
||||
default,
|
||||
context.Workflow.Id,
|
||||
context.Workflow.Type,
|
||||
context.Workflow.Identity.Version,
|
||||
context.Workflow.Name,
|
||||
context.Workflow.Identity.Id,
|
||||
default,
|
||||
now,
|
||||
context.ExecutionLogSequence++,
|
||||
eventName,
|
||||
message,
|
||||
context.Workflow.GetSource(),
|
||||
payload);
|
||||
|
||||
context.ExecutionLog.Add(logEntry);
|
||||
return logEntry;
|
||||
}
|
||||
}
|
||||
|
|
@ -41,16 +41,24 @@ public class DefaultActivityInvokerMiddleware : IActivityExecutionMiddleware
|
|||
|
||||
// Evaluate input properties.
|
||||
await EvaluateInputPropertiesAsync(context);
|
||||
|
||||
|
||||
// Prevent the activity from being started if cancellation is requested.
|
||||
if (context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
context.TransitionTo(ActivityStatus.Canceled);
|
||||
context.AddExecutionLogEntry("Activity cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the activity can be executed.
|
||||
if (!await context.Activity.CanExecuteAsync(context))
|
||||
{
|
||||
context.Status = ActivityStatus.Pending;
|
||||
context.TransitionTo(ActivityStatus.Pending);
|
||||
context.AddExecutionLogEntry("Precondition Failed", "Cannot execute at this time");
|
||||
return;
|
||||
}
|
||||
|
||||
context.Status = ActivityStatus.Running;
|
||||
context.TransitionTo(ActivityStatus.Running);
|
||||
|
||||
// Execute activity.
|
||||
await ExecuteActivityAsync(context);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class ExceptionHandlingMiddleware : IActivityExecutionMiddleware
|
|||
{
|
||||
_logger.LogWarning(e, "An exception was caught from a downstream middleware component");
|
||||
context.Exception = e;
|
||||
context.Status = ActivityStatus.Faulted;
|
||||
context.TransitionTo(ActivityStatus.Faulted);
|
||||
|
||||
var activity = context.Activity;
|
||||
var exceptionState = ExceptionState.FromException(e);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ public class DefaultActivitySchedulerMiddleware : WorkflowExecutionMiddleware
|
|||
|
||||
while (scheduler.HasAny)
|
||||
{
|
||||
// Do not start a workflow if cancellation has been requested.
|
||||
if (context.CancellationTokens.ApplicationCancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
var currentWorkItem = scheduler.Take();
|
||||
await ExecuteWorkItemAsync(context, currentWorkItem);
|
||||
}
|
||||
|
|
@ -18,4 +18,5 @@ public class RunWorkflowOptions
|
|||
public IDictionary<string, object>? Properties { get; set; }
|
||||
public string? TriggerActivityId { get; set; }
|
||||
public CancellationTokens CancellationTokens { get; set; }
|
||||
public Action<WorkflowExecutionContext>? StatusUpdatedCallback { get; set; }
|
||||
}
|
||||
|
|
@ -36,8 +36,8 @@ public class JsonWorkflowStateSerializer : IWorkflowStateSerializer
|
|||
{
|
||||
var options = GetSerializerOptions();
|
||||
var serializingWorkflowState = new SerializingWorkflowState(options);
|
||||
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
|
||||
|
||||
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
|
||||
|
||||
return JsonSerializer.Serialize(workflowState, options);
|
||||
}
|
||||
|
||||
|
|
@ -46,8 +46,8 @@ public class JsonWorkflowStateSerializer : IWorkflowStateSerializer
|
|||
{
|
||||
var options = GetSerializerOptions();
|
||||
var serializingWorkflowState = new SerializingWorkflowState(options);
|
||||
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
|
||||
|
||||
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
|
||||
|
||||
return JsonSerializer.SerializeToUtf8Bytes(workflowState, options);
|
||||
}
|
||||
|
||||
|
|
@ -56,8 +56,8 @@ public class JsonWorkflowStateSerializer : IWorkflowStateSerializer
|
|||
{
|
||||
var options = GetSerializerOptions();
|
||||
var serializingWorkflowState = new SerializingWorkflowState(options);
|
||||
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
|
||||
|
||||
await _notificationSender.SendAsync(serializingWorkflowState, cancellationToken);
|
||||
|
||||
return JsonSerializer.SerializeToElement(workflowState, options);
|
||||
}
|
||||
|
||||
|
|
@ -110,8 +110,8 @@ public class JsonWorkflowStateSerializer : IWorkflowStateSerializer
|
|||
options.Converters.Add(JsonMetadataServices.TimeSpanConverter);
|
||||
options.Converters.Add(new PolymorphicObjectConverterFactory());
|
||||
options.Converters.Add(new TypeJsonConverter(_wellKnownTypeRegistry));
|
||||
options.Converters.Add(new VariableConverterFactory(_wellKnownTypeRegistry, _loggerFactory));
|
||||
|
||||
options.Converters.Add(new VariableConverterFactory(_wellKnownTypeRegistry, _loggerFactory));
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ public class VariablePersistenceManager : IVariablePersistenceManager
|
|||
|
||||
foreach (var context in contexts)
|
||||
{
|
||||
var variables = GetLocalVariables(context);
|
||||
var variables = GetLocalVariables(context).ToList();
|
||||
var storageDriverContext = new StorageDriverContext(context, cancellationToken);
|
||||
|
||||
foreach (var variable in variables)
|
||||
|
|
@ -89,7 +89,7 @@ public class VariablePersistenceManager : IVariablePersistenceManager
|
|||
public async Task DeleteVariablesAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var register = context.ExpressionExecutionContext.Memory;
|
||||
var variableList = GetLocalVariables(context);
|
||||
var variableList = GetLocalVariables(context).ToList();
|
||||
var cancellationToken = context.CancellationToken;
|
||||
var storageDriverContext = new StorageDriverContext(context, cancellationToken);
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ public class WorkflowRunner : IWorkflowRunner
|
|||
var properties = options?.Properties;
|
||||
var correlationId = options?.CorrelationId;
|
||||
var triggerActivityId = options?.TriggerActivityId;
|
||||
var statusUpdatedCallback = options?.StatusUpdatedCallback;
|
||||
var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(
|
||||
scope.ServiceProvider,
|
||||
workflow,
|
||||
|
|
@ -103,6 +104,7 @@ public class WorkflowRunner : IWorkflowRunner
|
|||
properties,
|
||||
default,
|
||||
triggerActivityId,
|
||||
statusUpdatedCallback,
|
||||
options?.CancellationTokens ?? cancellationToken);
|
||||
|
||||
// Schedule the first activity.
|
||||
|
|
@ -122,6 +124,7 @@ public class WorkflowRunner : IWorkflowRunner
|
|||
var properties = options?.Properties;
|
||||
var correlationId = options?.CorrelationId ?? workflowState.CorrelationId;
|
||||
var triggerActivityId = options?.TriggerActivityId;
|
||||
var statusUpdatedCallback = options?.StatusUpdatedCallback;
|
||||
var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(
|
||||
scope.ServiceProvider,
|
||||
workflow,
|
||||
|
|
@ -130,6 +133,7 @@ public class WorkflowRunner : IWorkflowRunner
|
|||
input, properties,
|
||||
default,
|
||||
triggerActivityId,
|
||||
statusUpdatedCallback,
|
||||
options?.CancellationTokens ?? cancellationToken);
|
||||
|
||||
var bookmarkId = options?.BookmarkId;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,31 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
|
|||
return state;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WorkflowExecutionContext Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state)
|
||||
{
|
||||
workflowExecutionContext.Id = state.Id;
|
||||
workflowExecutionContext.CorrelationId = state.CorrelationId;
|
||||
workflowExecutionContext.SubStatus = state.SubStatus;
|
||||
workflowExecutionContext.Bookmarks = state.Bookmarks;
|
||||
workflowExecutionContext.Output = state.Output;
|
||||
workflowExecutionContext.ExecutionLogSequence = state.ExecutionLogSequence;
|
||||
workflowExecutionContext.CreatedAt = state.CreatedAt;
|
||||
ApplyInput(state, workflowExecutionContext);
|
||||
ApplyProperties(state, workflowExecutionContext);
|
||||
ApplyActivityExecutionContexts(state, workflowExecutionContext);
|
||||
ApplyCompletionCallbacks(state, workflowExecutionContext);
|
||||
ApplyScheduledActivities(state, workflowExecutionContext);
|
||||
return workflowExecutionContext;
|
||||
}
|
||||
|
||||
private void ApplyInput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
// Only add input from state if the input doesn't already exist on the workflow execution context.
|
||||
foreach (var inputItem in state.Input)
|
||||
if (!workflowExecutionContext.Input.ContainsKey(inputItem.Key)) workflowExecutionContext.Input.Add(inputItem.Key, inputItem.Value);
|
||||
}
|
||||
|
||||
private IDictionary<string, object> GetPersistableInput(WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
// TODO: This is a temporary solution. We need to find a better way to handle this.
|
||||
|
|
@ -52,24 +77,6 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
|
|||
return filteredInput;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WorkflowExecutionContext Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state)
|
||||
{
|
||||
// Do not map input. We don't want to overwrite the input that was passed to the workflow.
|
||||
workflowExecutionContext.Id = state.Id;
|
||||
workflowExecutionContext.CorrelationId = state.CorrelationId;
|
||||
workflowExecutionContext.SubStatus = state.SubStatus;
|
||||
workflowExecutionContext.Bookmarks = state.Bookmarks;
|
||||
workflowExecutionContext.Output = state.Output;
|
||||
workflowExecutionContext.ExecutionLogSequence = state.ExecutionLogSequence;
|
||||
workflowExecutionContext.CreatedAt = state.CreatedAt;
|
||||
ApplyProperties(state, workflowExecutionContext);
|
||||
ApplyActivityExecutionContexts(state, workflowExecutionContext);
|
||||
ApplyCompletionCallbacks(state, workflowExecutionContext);
|
||||
ApplyScheduledActivities(state, workflowExecutionContext);
|
||||
return workflowExecutionContext;
|
||||
}
|
||||
|
||||
private void ExtractProperties(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
state.Properties = workflowExecutionContext.Properties;
|
||||
|
|
@ -127,7 +134,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
|
|||
activityExecutionContext.Id = activityExecutionContextState.Id;
|
||||
activityExecutionContext.Properties = properties;
|
||||
activityExecutionContext.ActivityState = activityExecutionContextState.ActivityState ?? new Dictionary<string, object>();
|
||||
activityExecutionContext.Status = activityExecutionContextState.Status;
|
||||
activityExecutionContext.TransitionTo(activityExecutionContextState.Status);
|
||||
activityExecutionContext.StartedAt = activityExecutionContextState.StartedAt;
|
||||
activityExecutionContext.CompletedAt = activityExecutionContextState.CompletedAt;
|
||||
activityExecutionContext.Tag = activityExecutionContextState.Tag;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime.Requests;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Contracts;
|
||||
|
||||
|
|
@ -11,5 +10,5 @@ public interface IBookmarksPersister
|
|||
/// <summary>
|
||||
/// Persists bookmarks and raises events.
|
||||
/// </summary>
|
||||
Task PersistBookmarksAsync(WorkflowExecutionContext context, Diff<Bookmark> diff);
|
||||
Task PersistBookmarksAsync(UpdateBookmarksRequest updateBookmarksRequest);
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
namespace Elsa.Workflows.Runtime.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Stores <see cref="WorkflowExecutionContext"/> records.
|
||||
/// </summary>
|
||||
public interface IWorkflowExecutionContextStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves a record of the <see cref="WorkflowExecutionContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="WorkflowExecutionContext"/> to save.</param>
|
||||
Task SaveAsync(WorkflowExecutionContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a <see cref="WorkflowExecutionContext"/> with the specified ID.
|
||||
/// </summary>
|
||||
/// <returns>The matching entity or null if no match was found.</returns>
|
||||
Task<WorkflowExecutionContext?> FindAsync(string workflowExecutionContextId);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the record of the <see cref="WorkflowExecutionContext"/> with the specified ID if it exists.
|
||||
/// </summary>
|
||||
Task DeleteAsync(string workflowExecutionContextId);
|
||||
}
|
||||
|
|
@ -60,6 +60,13 @@ public interface IWorkflowRuntime
|
|||
/// <param name="options">Options for executing the workflow.</param>
|
||||
Task<WorkflowExecutionResult> ExecuteWorkflowAsync(WorkflowMatch match, ExecuteWorkflowOptions? options = default);
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the execution of a workflow.
|
||||
/// </summary>
|
||||
/// <param name="workflowInstanceId">The ID of the workflow instance to cancel.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all the workflows that can be started or resumed based on a query model.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -73,6 +73,11 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
/// </summary>
|
||||
public Func<IServiceProvider, IWorkflowInboxMessageStore> WorkflowInboxStore { get; set; } = sp => sp.GetRequiredService<MemoryWorkflowInboxMessageStore>();
|
||||
|
||||
/// <summary>
|
||||
/// A factory that instantiates an <see cref="IWorkflowExecutionContextStore"/>.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IWorkflowExecutionContextStore> WorkflowExecutionContextStore { get; set; } = sp => sp.GetRequiredService<MemoryWorkflowExecutionContextStore>();
|
||||
|
||||
/// <summary>
|
||||
/// A factory that instantiates an <see cref="IDistributedLockProvider"/>.
|
||||
/// </summary>
|
||||
|
|
@ -157,6 +162,7 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
.AddScoped(WorkflowExecutionLogStore)
|
||||
.AddScoped(ActivityExecutionLogStore)
|
||||
.AddScoped(WorkflowInboxStore)
|
||||
.AddScoped(WorkflowExecutionContextStore)
|
||||
.AddSingleton(RunTaskDispatcher)
|
||||
.AddSingleton(BackgroundActivityScheduler)
|
||||
.AddScoped<IBookmarkManager, DefaultBookmarkManager>()
|
||||
|
|
@ -188,6 +194,7 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
.AddMemoryStore<WorkflowExecutionLogRecord, MemoryWorkflowExecutionLogStore>()
|
||||
.AddMemoryStore<ActivityExecutionRecord, MemoryActivityExecutionStore>()
|
||||
.AddMemoryStore<WorkflowInboxMessage, MemoryWorkflowInboxMessageStore>()
|
||||
.AddMemoryStore<WorkflowExecutionContext, MemoryWorkflowExecutionContextStore>()
|
||||
|
||||
// Distributed locking.
|
||||
.AddScoped(DistributedLockProvider)
|
||||
|
|
@ -207,7 +214,8 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
.AddNotificationHandler<DeleteActivityExecutionLogRecords>()
|
||||
.AddNotificationHandler<ReadWorkflowInboxMessage>()
|
||||
.AddNotificationHandler<DeliverWorkflowMessagesFromInbox>()
|
||||
.AddNotificationHandler<DeleteWorkflowExecutionLogRecords>()
|
||||
.AddNotificationHandler<DeleteWorkflowExecutionLogRecords>()
|
||||
.AddNotificationHandler<WorkflowExecutionContextNotificationsHandler>()
|
||||
|
||||
// Workflow activation strategies.
|
||||
.AddScoped<IWorkflowActivationStrategy, SingletonStrategy>()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Management.Notifications;
|
||||
using Elsa.Workflows.Notifications;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Deletes workflow execution log records in response to the <see cref="WorkflowInstancesDeleting"/> notification.
|
||||
/// </summary>
|
||||
internal class WorkflowExecutionContextNotificationsHandler :
|
||||
INotificationHandler<WorkflowExecuting>,
|
||||
INotificationHandler<WorkflowExecuted>
|
||||
{
|
||||
private readonly IWorkflowExecutionContextStore _store;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteWorkflowExecutionLogRecords"/> class.
|
||||
/// </summary>
|
||||
public WorkflowExecutionContextNotificationsHandler(IWorkflowExecutionContextStore store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(WorkflowExecuting notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return _store.SaveAsync(notification.WorkflowExecutionContext);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(WorkflowExecuted notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return _store.DeleteAsync(notification.WorkflowExecutionContext.Id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Pipelines.WorkflowExecution;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Requests;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Middleware.Workflows;
|
||||
|
||||
|
|
@ -24,6 +25,7 @@ public class PersistBookmarkMiddleware : WorkflowExecutionMiddleware
|
|||
await Next(context);
|
||||
var updatedBookmarks = context.Bookmarks.ToList();
|
||||
var diff = Diff.For(originalBookmarks, updatedBookmarks);
|
||||
await _bookmarksPersister.PersistBookmarksAsync(context, diff);
|
||||
var bookmarkRequest = new UpdateBookmarksRequest(context.Id, diff, context.CorrelationId);
|
||||
await _bookmarksPersister.PersistBookmarksAsync(bookmarkRequest);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,5 +7,5 @@ namespace Elsa.Workflows.Runtime.Notifications;
|
|||
/// </summary>
|
||||
/// <param name="WorkflowExecutionContext">The workflow execution context.</param>
|
||||
/// <param name="IndexedWorkflowBookmarks">The bookmarks that were added, removed, or unchanged.</param>
|
||||
public record WorkflowBookmarksIndexed(WorkflowExecutionContext WorkflowExecutionContext, IndexedWorkflowBookmarks IndexedWorkflowBookmarks) : INotification;
|
||||
public record WorkflowBookmarksIndexed(IndexedWorkflowBookmarks IndexedWorkflowBookmarks) : INotification;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ namespace Elsa.Workflows.Runtime.Notifications;
|
|||
/// </summary>
|
||||
/// <param name="Context">The workflow execution context.</param>
|
||||
/// <param name="Diff">The bookmarks that were added, removed, or unchanged.</param>
|
||||
public record WorkflowBookmarksPersisted(WorkflowExecutionContext Context, Diff<Bookmark> Diff) : INotification;
|
||||
public record WorkflowBookmarksPersisted(Diff<Bookmark> Diff) : INotification;
|
||||
|
|
@ -24,4 +24,9 @@ public class StartWorkflowHostOptions
|
|||
|
||||
/// <summary>Cancellation tokens that can be used to cancel the workflow instance without cancelling system-level operations.</summary>
|
||||
public CancellationTokens CancellationTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Callback method that will be called when the status of the workflow has been updated
|
||||
/// </summary>
|
||||
public Action<WorkflowExecutionContext>? StatusUpdatedCallback { get; set; }
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
using Elsa.Mediator;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Notifications;
|
||||
using Elsa.Workflows.Runtime.Requests;
|
||||
|
|
@ -11,25 +9,15 @@ namespace Elsa.Workflows.Runtime.Services;
|
|||
/// <inheritdoc />
|
||||
public class BookmarksPersister(IBookmarkUpdater bookmarkUpdater, INotificationSender notificationSender) : IBookmarksPersister
|
||||
{
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task PersistBookmarksAsync(WorkflowExecutionContext context, Diff<Bookmark> diff)
|
||||
public async Task PersistBookmarksAsync(UpdateBookmarksRequest updateBookmarksRequest)
|
||||
{
|
||||
var cancellationToken = context.CancellationTokens.SystemCancellationToken;
|
||||
var updateBookmarksContext = new UpdateBookmarksRequest(context.Id, diff, context.CorrelationId);
|
||||
await bookmarkUpdater.UpdateBookmarksAsync(updateBookmarksContext, cancellationToken);
|
||||
|
||||
await bookmarkUpdater.UpdateBookmarksAsync(updateBookmarksRequest);
|
||||
|
||||
// Publish domain event.
|
||||
await notificationSender.SendAsync(new WorkflowBookmarksIndexed(context, new IndexedWorkflowBookmarks(context.Id, diff.Added, diff.Removed, diff.Unchanged)), cancellationToken);
|
||||
|
||||
// Notify all interested activities that the bookmarks have been persisted.
|
||||
var activityExecutionContexts = context.ActivityExecutionContexts.Where(x => x.Activity is IBookmarksPersistedHandler && x.Bookmarks.Any()).ToList();
|
||||
|
||||
foreach (var activityExecutionContext in activityExecutionContexts)
|
||||
await ((IBookmarksPersistedHandler)activityExecutionContext.Activity).BookmarksPersistedAsync(activityExecutionContext);
|
||||
|
||||
await notificationSender.SendAsync(new WorkflowBookmarksIndexed(new IndexedWorkflowBookmarks(updateBookmarksRequest.WorkflowInstanceId, updateBookmarksRequest.Diff.Added, updateBookmarksRequest.Diff.Removed, updateBookmarksRequest.Diff.Unchanged)));
|
||||
|
||||
// Publish domain event.
|
||||
await notificationSender.SendAsync(new WorkflowBookmarksPersisted(context, diff), NotificationStrategy.Background, cancellationToken);
|
||||
await notificationSender.SendAsync(new WorkflowBookmarksPersisted(updateBookmarksRequest.Diff), NotificationStrategy.Background);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
|
|
@ -32,6 +33,10 @@ public class DefaultWorkflowRuntime : IWorkflowRuntime
|
|||
private readonly IWorkflowInstanceFactory _workflowInstanceFactory;
|
||||
private readonly WorkflowStateMapper _workflowStateMapper;
|
||||
private readonly IIdentityGenerator _identityGenerator;
|
||||
private readonly IWorkflowExecutionContextStore _workflowExecutionContextStore;
|
||||
private readonly IWorkflowStateExtractor _workflowStateExtractor;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IBookmarksPersister _bookmarksPersister;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
|
|
@ -47,7 +52,11 @@ public class DefaultWorkflowRuntime : IWorkflowRuntime
|
|||
IDistributedLockProvider distributedLockProvider,
|
||||
IWorkflowInstanceFactory workflowInstanceFactory,
|
||||
WorkflowStateMapper workflowStateMapper,
|
||||
IIdentityGenerator identityGenerator)
|
||||
IIdentityGenerator identityGenerator,
|
||||
IWorkflowExecutionContextStore workflowExecutionContextStore,
|
||||
IWorkflowStateExtractor workflowStateExtractor,
|
||||
IServiceProvider serviceProvider,
|
||||
IBookmarksPersister bookmarksPersister)
|
||||
{
|
||||
_workflowHostFactory = workflowHostFactory;
|
||||
_workflowDefinitionService = workflowDefinitionService;
|
||||
|
|
@ -60,6 +69,10 @@ public class DefaultWorkflowRuntime : IWorkflowRuntime
|
|||
_workflowInstanceFactory = workflowInstanceFactory;
|
||||
_workflowStateMapper = workflowStateMapper;
|
||||
_identityGenerator = identityGenerator;
|
||||
_workflowExecutionContextStore = workflowExecutionContextStore;
|
||||
_workflowStateExtractor = workflowStateExtractor;
|
||||
_serviceProvider = serviceProvider;
|
||||
_bookmarksPersister = bookmarksPersister;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -90,6 +103,67 @@ public class DefaultWorkflowRuntime : IWorkflowRuntime
|
|||
{
|
||||
return await StartWorkflowAsync(definitionId, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflowExecutionContext = await _workflowExecutionContextStore.FindAsync(workflowInstanceId);
|
||||
|
||||
if (workflowExecutionContext is null)
|
||||
{
|
||||
// The execution context is not running on this instance.
|
||||
// It might not be running on any instance, so check the db and update the record.
|
||||
// Use lock to prevent race conditions and other instances from updating the workflow context
|
||||
await using var cancelLock = await _distributedLockProvider.TryAcquireLockAsync($"{workflowInstanceId}-cancel");
|
||||
if (cancelLock == null)
|
||||
return;
|
||||
|
||||
var workflowInstance = await _workflowInstanceStore.FindAsync(workflowInstanceId, cancellationToken);
|
||||
if (workflowInstance is null
|
||||
|| workflowInstance.SubStatus == WorkflowSubStatus.Cancelled
|
||||
|| workflowInstance.SubStatus == WorkflowSubStatus.Faulted)
|
||||
return;
|
||||
|
||||
var workflowState = await ExportWorkflowStateAsync(workflowInstanceId, cancellationToken);
|
||||
|
||||
if (workflowState == null)
|
||||
throw new Exception("Workflow state not found");
|
||||
|
||||
var workflowDefinition = await _workflowDefinitionService.FindAsync(workflowState.DefinitionId, VersionOptions.SpecificVersion(workflowState.DefinitionVersion), cancellationToken);
|
||||
|
||||
if (workflowDefinition == null)
|
||||
throw new Exception("Workflow definition not found");
|
||||
|
||||
var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken);
|
||||
workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(_serviceProvider, workflow, workflowState, cancellationTokens: cancellationToken);
|
||||
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
await CancelWorkflowExecutionContextAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await using var mainCancelLock = await _distributedLockProvider.AcquireLockAsync($"{workflowInstanceId}-cancel", TimeSpan.FromMinutes(1));
|
||||
|
||||
await CancelWorkflowExecutionContextAsync();
|
||||
|
||||
async Task CancelWorkflowExecutionContextAsync()
|
||||
{
|
||||
var originalBookmarks = workflowExecutionContext.Bookmarks.ToList();
|
||||
|
||||
workflowExecutionContext.Cancel();
|
||||
|
||||
var newBookmarks = workflowExecutionContext.Bookmarks.ToList();
|
||||
var diff = Diff.For(originalBookmarks, newBookmarks);
|
||||
var bookmarkRequest = new UpdateBookmarksRequest(workflowExecutionContext.Id,
|
||||
diff,
|
||||
workflowExecutionContext.CorrelationId);
|
||||
await _bookmarksPersister.PersistBookmarksAsync(bookmarkRequest);
|
||||
|
||||
var instance = await _workflowInstanceManager.SaveAsync(workflowExecutionContext);
|
||||
await _workflowInstanceStore.SaveAsync(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICollection<WorkflowExecutionResult>> StartWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions options)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
using Elsa.Common.Services;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Stores;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Stores <see cref="WorkflowExecutionContext"/> in memory.
|
||||
/// </summary>
|
||||
public class MemoryWorkflowExecutionContextStore : IWorkflowExecutionContextStore
|
||||
{
|
||||
private readonly MemoryStore<WorkflowExecutionContext> _store;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryActivityExecutionStore"/> class.
|
||||
/// </summary>
|
||||
public MemoryWorkflowExecutionContextStore(MemoryStore<WorkflowExecutionContext> store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SaveAsync(WorkflowExecutionContext context)
|
||||
{
|
||||
_store.Save(context, x => x.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<WorkflowExecutionContext?> FindAsync(string workflowExecutionContextId)
|
||||
{
|
||||
var result = _store.Find((context) => context.Id == workflowExecutionContextId);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteAsync(string workflowExecutionContextId)
|
||||
{
|
||||
_store.Delete(workflowExecutionContextId);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +80,7 @@ public class WorkflowHost : IWorkflowHost
|
|||
Input = input,
|
||||
Properties = properties,
|
||||
TriggerActivityId = options?.TriggerActivityId,
|
||||
StatusUpdatedCallback = options?.StatusUpdatedCallback,
|
||||
CancellationTokens = options?.CancellationTokens ?? cancellationToken
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\bundles\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.CSharp\Elsa.CSharp.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Http\Elsa.Http.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Identity\Elsa.Identity.csproj" />
|
||||
|
|
|
|||
|
|
@ -28,16 +28,20 @@ builder.Services.AddElsa(elsa =>
|
|||
|
||||
// Add services for HTTP activities and workflow middleware.
|
||||
elsa.UseHttp();
|
||||
|
||||
// Use JavaScript.
|
||||
elsa.UseJavaScript(javaScript =>
|
||||
|
||||
// Use C#.
|
||||
elsa.UseCSharp(options =>
|
||||
{
|
||||
javaScript.AllowClrAccess = true;
|
||||
javaScript.ConfigureEngine(options =>
|
||||
{
|
||||
options.RegisterType<OrderCompleted>();
|
||||
options.RegisterType<OrderCreated>();
|
||||
});
|
||||
options.Assemblies.Add(typeof(OrderCreated).Assembly);
|
||||
options.Namespaces.Add(typeof(OrderCreated).Namespace!);
|
||||
});
|
||||
|
||||
// Use JavaScript.
|
||||
elsa.UseJavaScript(options =>
|
||||
{
|
||||
options.AllowClrAccess = true;
|
||||
options.RegisterType<OrderCreated>();
|
||||
options.RegisterType<OrderCompleted>();
|
||||
});
|
||||
|
||||
// Use C#.
|
||||
|
|
@ -56,7 +60,7 @@ builder.Services.AddElsa(elsa =>
|
|||
identity.UseAdminUserProvider();
|
||||
identity.TokenOptions = options =>
|
||||
{
|
||||
options.SigningKey = "secret-token-signing-key";
|
||||
options.SigningKey = "super-secret-and-securely-stored-token-signing-key";
|
||||
options.AccessTokenLifetime = TimeSpan.FromDays(1);
|
||||
};
|
||||
});
|
||||
|
|
@ -67,7 +71,7 @@ builder.Services.AddElsa(elsa =>
|
|||
// Configure MassTransit.
|
||||
elsa.UseMassTransit(massTransit =>
|
||||
{
|
||||
massTransit.UseRabbitMq(rabbitMqConnectionString);
|
||||
//massTransit.UseRabbitMq(rabbitMqConnectionString);
|
||||
massTransit.AddMessageType<OrderCompleted>();
|
||||
massTransit.AddMessageType<OrderCreated>();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using Elsa.EntityFrameworkCore.Modules.Management;
|
|||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Samples.AspNet.Onboarding.WorkflowServer.Workflows;
|
||||
using Elsa.Webhooks.Extensions;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Samples.AspNet.Webhooks.WorkflowServer.Workflows;
|
||||
using Elsa.Webhooks.Extensions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="Proto.Persistence.Sqlite" Version="1.4.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
@ -23,6 +24,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.JavaScript\Elsa.JavaScript.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.ProtoActor\Elsa.ProtoActor.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Scheduling\Elsa.Scheduling.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.WorkflowProviders.BlobStorage\Elsa.WorkflowProviders.BlobStorage.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.IntegrationTests.Scenarios.WorkflowCancellation.Workflows;
|
||||
using Elsa.Mediator.HostedServices;
|
||||
using Elsa.Mediator.Options;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.IntegrationTests.Scenarios.WorkflowCancellation;
|
||||
|
||||
public class DefaultRuntimeTests
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||
|
||||
private readonly IWorkflowRuntime _workflowRuntime;
|
||||
private readonly BackgroundCommandSenderHostedService _backgroundCommandSenderHostedService;
|
||||
private readonly BackgroundEventPublisherHostedService _backgroundEventPublisherHostedService;
|
||||
|
||||
public DefaultRuntimeTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_services = new TestApplicationBuilder(testOutputHelper)
|
||||
.WithCapturingTextWriter(_capturingTextWriter)
|
||||
.AddWorkflow<BulkSuspendedWorkflow>()
|
||||
.AddWorkflow<ResumeDispatchWorkflow>()
|
||||
.AddWorkflow<SimpleChildWorkflow>()
|
||||
.AddWorkflow<SimpleSuspendedWorkflow>()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services
|
||||
.AddSingleton(sp =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<MediatorOptions>>().Value;
|
||||
return ActivatorUtilities.CreateInstance<BackgroundCommandSenderHostedService>(sp, options.CommandWorkerCount);
|
||||
})
|
||||
.AddSingleton(sp =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<MediatorOptions>>().Value;
|
||||
return ActivatorUtilities.CreateInstance<BackgroundEventPublisherHostedService>(sp, options.NotificationWorkerCount);
|
||||
})
|
||||
;
|
||||
})
|
||||
.Build();
|
||||
|
||||
_backgroundCommandSenderHostedService = _services.GetRequiredService<BackgroundCommandSenderHostedService>();
|
||||
_backgroundEventPublisherHostedService = _services.GetRequiredService<BackgroundEventPublisherHostedService>();
|
||||
_workflowRuntime = _services.GetRequiredService<IWorkflowRuntime>();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Cancelling a suspended workflow")]
|
||||
public async Task SuspendedCancelTest()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
|
||||
const string workflowDefinitionId = nameof(SimpleSuspendedWorkflow);
|
||||
var workflowState = await _workflowRuntime.StartWorkflowAsync(workflowDefinitionId, new StartWorkflowRuntimeOptions());
|
||||
|
||||
Assert.Equal(WorkflowStatus.Running, workflowState.Status);
|
||||
Assert.Equal(WorkflowSubStatus.Suspended, workflowState.SubStatus);
|
||||
|
||||
await _workflowRuntime.CancelWorkflowAsync(workflowState.WorkflowInstanceId);
|
||||
var lastWorkflowState = await _workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
|
||||
|
||||
Assert.Equal(WorkflowStatus.Finished, lastWorkflowState!.Status);
|
||||
Assert.Equal(WorkflowSubStatus.Cancelled, lastWorkflowState.SubStatus);
|
||||
Assert.Empty(_capturingTextWriter.Lines);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Cancelling a running workflow",
|
||||
Skip= "Unpredictable result, need to create a dispatcher for tests that will run outside of the unit test")]
|
||||
public async Task RunningCancelTest()
|
||||
{
|
||||
await _backgroundCommandSenderHostedService.StartAsync(CancellationToken.None);
|
||||
await _backgroundEventPublisherHostedService.StartAsync(CancellationToken.None);
|
||||
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
|
||||
const string workflowDefinitionId = nameof(BulkSuspendedWorkflow);
|
||||
var workflowState = await _workflowRuntime.StartWorkflowAsync(workflowDefinitionId, new StartWorkflowRuntimeOptions());
|
||||
|
||||
var bookmarks = new Stack<Bookmark>(workflowState.Bookmarks);
|
||||
var resumeOptions = new ResumeWorkflowRuntimeOptions { BookmarkId = bookmarks.Pop().Id };
|
||||
var state = await _workflowRuntime.ResumeWorkflowAsync(workflowState.WorkflowInstanceId,resumeOptions);
|
||||
|
||||
await _workflowRuntime.CancelWorkflowAsync(workflowState.WorkflowInstanceId);
|
||||
var lastWorkflowState = await _workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
|
||||
|
||||
Assert.Equal(WorkflowStatus.Finished, lastWorkflowState!.Status);
|
||||
Assert.Equal(WorkflowSubStatus.Cancelled, lastWorkflowState.SubStatus);
|
||||
Assert.NotEmpty(_capturingTextWriter.Lines);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.IntegrationTests.Scenarios.WorkflowCancellation.Workflows;
|
||||
using Elsa.Mediator.HostedServices;
|
||||
using Elsa.Mediator.Options;
|
||||
using Elsa.ProtoActor.HostedServices;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Options;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Proto.Persistence.Sqlite;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using WorkflowStatus = Elsa.Workflows.WorkflowStatus;
|
||||
using WorkflowSubStatus = Elsa.Workflows.WorkflowSubStatus;
|
||||
|
||||
namespace Elsa.IntegrationTests.Scenarios.WorkflowCancellation;
|
||||
|
||||
public class ProtoActorTests
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||
|
||||
private readonly IWorkflowRuntime _workflowRuntime;
|
||||
private readonly WorkflowServerHost _workflowServerHost;
|
||||
private readonly BackgroundCommandSenderHostedService _backgroundCommandSenderHostedService;
|
||||
private readonly BackgroundEventPublisherHostedService _backgroundEventPublisherHostedService;
|
||||
|
||||
public ProtoActorTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_services = new TestApplicationBuilder(testOutputHelper)
|
||||
.WithCapturingTextWriter(_capturingTextWriter)
|
||||
.AddWorkflow<BulkSuspendedWorkflow>()
|
||||
.AddWorkflow<ResumeDispatchWorkflow>()
|
||||
.AddWorkflow<SimpleChildWorkflow>()
|
||||
.AddWorkflow<SimpleSuspendedWorkflow>()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services
|
||||
.AddSingleton(sp => ActivatorUtilities.CreateInstance<WorkflowServerHost>(sp));
|
||||
|
||||
services
|
||||
.AddSingleton(sp =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<MediatorOptions>>().Value;
|
||||
return ActivatorUtilities.CreateInstance<BackgroundCommandSenderHostedService>(sp,
|
||||
options.CommandWorkerCount);
|
||||
})
|
||||
.AddSingleton(sp =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<MediatorOptions>>().Value;
|
||||
return ActivatorUtilities.CreateInstance<BackgroundEventPublisherHostedService>(sp,
|
||||
options.NotificationWorkerCount);
|
||||
});
|
||||
}).ConfigureElsa(elsa => elsa.UseWorkflowRuntime(runtime => runtime.UseProtoActor(protoActor =>
|
||||
{
|
||||
protoActor.PersistenceProvider = _ =>
|
||||
new SqliteProvider(
|
||||
new SqliteConnectionStringBuilder("Data Source=elsa.sqlite.db;Cache=Shared;"));
|
||||
}
|
||||
)))
|
||||
.Build();
|
||||
|
||||
_backgroundCommandSenderHostedService = _services.GetRequiredService<BackgroundCommandSenderHostedService>();
|
||||
_backgroundEventPublisherHostedService = _services.GetRequiredService<BackgroundEventPublisherHostedService>();
|
||||
_workflowServerHost = _services.GetRequiredService<WorkflowServerHost>();
|
||||
_workflowRuntime = _services.GetRequiredService<IWorkflowRuntime>();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Cancelling a suspended workflow")]
|
||||
public async Task SuspendedCancelTest()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
await _workflowServerHost.StartAsync(default);
|
||||
const string workflowDefinitionId = nameof(SimpleSuspendedWorkflow);
|
||||
var workflowState =
|
||||
await _workflowRuntime.StartWorkflowAsync(workflowDefinitionId, new StartWorkflowRuntimeOptions());
|
||||
|
||||
Assert.Equal(WorkflowStatus.Running, workflowState.Status);
|
||||
Assert.Equal(WorkflowSubStatus.Suspended, workflowState.SubStatus);
|
||||
|
||||
await _workflowRuntime.CancelWorkflowAsync(workflowState.WorkflowInstanceId);
|
||||
|
||||
var lastWorkflowState = await _workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
|
||||
|
||||
Assert.Equal(WorkflowStatus.Finished, lastWorkflowState!.Status);
|
||||
Assert.Equal(WorkflowSubStatus.Cancelled, lastWorkflowState.SubStatus);
|
||||
Assert.Empty(_capturingTextWriter.Lines);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Cancelling a running workflow",
|
||||
Skip= "Unpredictable result, need to create a dispatcher for tests that will run outside of the unit test")]
|
||||
public async Task RunningCancelTest()
|
||||
{
|
||||
// Populate registries.
|
||||
await _services.PopulateRegistriesAsync();
|
||||
await _workflowServerHost.StartAsync(default);
|
||||
await _backgroundCommandSenderHostedService.StartAsync(CancellationToken.None);
|
||||
await _backgroundEventPublisherHostedService.StartAsync(CancellationToken.None);
|
||||
|
||||
const string workflowDefinitionId = nameof(BulkSuspendedWorkflow);
|
||||
var workflowState =
|
||||
await _workflowRuntime.StartWorkflowAsync(workflowDefinitionId, new StartWorkflowRuntimeOptions());
|
||||
|
||||
var bookmarks = new Stack<Bookmark>(workflowState.Bookmarks);
|
||||
var resumeOptions = new ResumeWorkflowRuntimeOptions { BookmarkId = bookmarks.Pop().Id };
|
||||
await _workflowRuntime.ResumeWorkflowAsync(workflowState.WorkflowInstanceId, resumeOptions);
|
||||
|
||||
await _workflowRuntime.CancelWorkflowAsync(workflowState.WorkflowInstanceId);
|
||||
var lastWorkflowState = await _workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
|
||||
|
||||
Assert.Equal(WorkflowStatus.Finished, lastWorkflowState!.Status);
|
||||
Assert.Equal(WorkflowSubStatus.Cancelled, lastWorkflowState.SubStatus);
|
||||
|
||||
Assert.NotEmpty(_capturingTextWriter.Lines);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Scheduling.Activities;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.IntegrationTests.Scenarios.WorkflowCancellation.Workflows;
|
||||
|
||||
public class BulkSuspendedWorkflow : WorkflowBase
|
||||
{
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
object[] items = Enumerable.Range(0,1500).Select(x => (object) x).ToArray();
|
||||
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new Start(),
|
||||
new Delay(TimeSpan.FromSeconds(10)),
|
||||
new BulkDispatchWorkflows
|
||||
{
|
||||
WorkflowDefinitionId = new Input<string>(nameof(SimpleChildWorkflow)),
|
||||
Items = new Input<ICollection<object>>(items)
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.IntegrationTests.Scenarios.WorkflowCancellation.Workflows;
|
||||
|
||||
public class ResumeDispatchWorkflow : WorkflowBase
|
||||
{
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new PublishEvent { EventName = new Input<string>("ResumeBlockDispatch") }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Contracts;
|
||||
|
||||
namespace Elsa.IntegrationTests.Scenarios.WorkflowCancellation.Workflows;
|
||||
|
||||
public class SimpleChildWorkflow : WorkflowBase
|
||||
{
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new WriteLine("Executed SimpleChildWorkflow")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.IntegrationTests.Scenarios.WorkflowCancellation.Workflows;
|
||||
|
||||
public class SimpleSuspendedWorkflow : WorkflowBase
|
||||
{
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new Start(),
|
||||
new Event("BlockingEvent"),
|
||||
new WriteLine("Workflow was not properly blocked")
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue