Merge branch 'efcore-reusability' into webhook/url-management
# Conflicts: # src/designer/elsa-workflows-studio/package-lock.json # src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-root/elsa-studio-root.tsx # src/samples/server/Elsa.Samples.Server.Host/Startup.cs
This commit is contained in:
commit
8456754e3c
74
.github/workflows/publish-dashboard.yml
vendored
74
.github/workflows/publish-dashboard.yml
vendored
|
|
@ -1,74 +0,0 @@
|
|||
name: Build elsa-dashboard:[tag] and :stable
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GitHub Packages
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install NPM dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
working-directory: |
|
||||
src/designer/elsa-workflows-studio
|
||||
src/designer/bindings/blazor/Elsa.Designer.Bindings.Blazor
|
||||
|
||||
- name: Build NPM packages for elsa-workflows-studio
|
||||
run: npm run build
|
||||
working-directory: src/designer/elsa-workflows-studio
|
||||
|
||||
- name: Build NPM packages for Elsa.Designer.Bindings.Blazor
|
||||
run: npm run build
|
||||
working-directory: src/designer/bindings/blazor/Elsa.Designer.Bindings.Blazor
|
||||
|
||||
- name: Generate Docker tag
|
||||
id: docker-tag
|
||||
uses: yuya-takeyama/docker-tag-from-github-ref-action@v1
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ghcr.io/elsa-workflows/elsa-core/elsa-dashboard:${{ steps.docker-tag.outputs.tag }}, ghcr.io/elsa-workflows/elsa-core/elsa-dashboard:stable
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
65
.github/workflows/publish-latest-dashboard-and-server-docker.yml
vendored
Normal file
65
.github/workflows/publish-latest-dashboard-and-server-docker.yml
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
name: Build elsa-dashboard-and-server:latest
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
#release:
|
||||
# types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GitHub Packages
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
elsaworkflows/elsa-dashboard-and-server
|
||||
flavor: |
|
||||
latest=true
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=sha
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASS }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./docker/Dockerfile-elsa-dashboard-and-server
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
62
.github/workflows/publish-latest-dashboard-docker.yml
vendored
Normal file
62
.github/workflows/publish-latest-dashboard-docker.yml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
name: Build elsa-dashboard:latest
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
#release:
|
||||
# types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GitHub Packages
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
elsaworkflows/elsa-dashboard
|
||||
flavor: |
|
||||
latest=true
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=sha
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASS }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./docker/Dockerfile-elsa-dashboard
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
73
.github/workflows/publish-latest-dashboard.yml
vendored
73
.github/workflows/publish-latest-dashboard.yml
vendored
|
|
@ -1,73 +0,0 @@
|
|||
name: Build elsa-dashboard:latest
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- feature/elsa-2.0
|
||||
#release:
|
||||
# types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GitHub Packages
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install NPM dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
with:
|
||||
useRollingCache: true
|
||||
working-directory: |
|
||||
src/designer/elsa-workflows-studio
|
||||
src/designer/bindings/aspnet/Elsa.Designer.Components.Web
|
||||
|
||||
- name: Build NPM packages for elsa-workflows-studio
|
||||
run: npm run build
|
||||
working-directory: src/designer/elsa-workflows-studio
|
||||
|
||||
- name: Build NPM packages for Elsa.Designer.Components.Web
|
||||
run: npm run build
|
||||
working-directory: src/designer/bindings/aspnet/Elsa.Designer.Components.Web
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ghcr.io/elsa-workflows/elsa-core/elsa-dashboard:latest
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
62
.github/workflows/publish-latest-server-docker.yml
vendored
Normal file
62
.github/workflows/publish-latest-server-docker.yml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
name: Build elsa-server:latest
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
#release:
|
||||
# types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GitHub Packages
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
elsaworkflows/elsa-server
|
||||
flavor: |
|
||||
latest=true
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=sha
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASS }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./docker/Dockerfile-elsa-server
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
47
Elsa.sln
47
Elsa.sln
|
|
@ -241,8 +241,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "locking", "locking", "{DBBD
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DistributedLocking.SqlServer", "src\locking\Elsa.DistributedLocking.SqlServer\Elsa.DistributedLocking.SqlServer.csproj", "{B09A6E42-EF42-4693-BA0E-5EE1D7F81FFD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.Redis", "src\locking\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj", "{7E5514AC-3633-4237-9490-0992417F2273}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.AzureBlob", "src\locking\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj", "{495BE954-E6EA-41A9-8054-A5CA5DD0924E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DistributedLocking.AzureBlob", "src\locking\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj", "{495BE954-E6EA-41A9-8054-A5CA5DD0924E}"
|
||||
|
|
@ -267,8 +265,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{D7F754
|
|||
ProjectSection(SolutionItems) = preProject
|
||||
docker\docker-compose.yaml = docker\docker-compose.yaml
|
||||
docker\Dockerfile = docker\Dockerfile
|
||||
docker\Dockerfile-with-npm = docker\Dockerfile-with-npm
|
||||
docker\README.md = docker\README.md
|
||||
docker\Dockerfile-elsa-dashboard = docker\Dockerfile-elsa-dashboard
|
||||
docker\Dockerfile-elsa-dashboard-and-server = docker\Dockerfile-elsa-dashboard-and-server
|
||||
EndProjectSection
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
docker\docker-compose.yaml = docker\docker-compose.yaml
|
||||
|
|
@ -333,6 +332,33 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "clients", "clients", "{A998
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Webhooks.Client", "src\activities\Elsa.Webhooks.Client\Elsa.Webhooks.Client.csproj", "{8DA03FFB-8AE7-440E-9CDB-1EA1E93A6B4C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnetcore", "aspnetcore", "{28C5C63F-2DCF-46BE-9576-1F50A7CFE8C4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElsaDashboard.Web", "src\dashboards\aspnetcore\ElsaDashboard\ElsaDashboard.Web.csproj", "{5F81C84A-4C04-48FF-981C-23E48A7ACA6C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{3BF2C5A9-AC69-4587-833F-C16CFC97E100}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\publish-latest-dashboard-docker.yml = .github\workflows\publish-latest-dashboard-docker.yml
|
||||
.github\workflows\publish-latest-server-docker.yml = .github\workflows\publish-latest-server-docker.yml
|
||||
.github\workflows\publish-latest-dashboard-and-server-docker.yml = .github\workflows\publish-latest-dashboard-and-server-docker.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.SignalApi", "src\samples\aspnet\Elsa.Samples.SignalApi\Elsa.Samples.SignalApi.csproj", "{3E2423CF-50E6-4D2B-8749-17B1EF540FE4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "caching", "caching", "{9937FB02-72D9-4FCD-B31E-8E60CFF1B37F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Caching.Rebus", "src\caching\Elsa.Caching.Rebus\Elsa.Caching.Rebus.csproj", "{61C16CA0-B190-4642-A81A-5C03705CA2C1}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "providers", "providers", "{EC5D4AFD-3F7F-4B51-9C38-F18C60618731}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Providers.Redis", "src\providers\Elsa.Providers.Redis\Elsa.Providers.Redis.csproj", "{9ACA06DA-9AFE-41A2-8109-EFED8B9B14A1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.Redis", "src\locking\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj", "{D1644D32-AA51-4D10-942E-F2722DB963B4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Conductor", "src\activities\Elsa.Activities.Conductor\Elsa.Activities.Conductor.csproj", "{E62077E1-59F4-4937-B45B-59F86417BCF8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleClientApp.Web", "src\samples\server\SampleClientApp.Web\SampleClientApp.Web.csproj", "{7756E22A-AD59-46C1-A019-1D28D7217347}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -671,10 +697,6 @@ Global
|
|||
{B09A6E42-EF42-4693-BA0E-5EE1D7F81FFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B09A6E42-EF42-4693-BA0E-5EE1D7F81FFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B09A6E42-EF42-4693-BA0E-5EE1D7F81FFD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7E5514AC-3633-4237-9490-0992417F2273}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7E5514AC-3633-4237-9490-0992417F2273}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7E5514AC-3633-4237-9490-0992417F2273}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7E5514AC-3633-4237-9490-0992417F2273}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{495BE954-E6EA-41A9-8054-A5CA5DD0924E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{495BE954-E6EA-41A9-8054-A5CA5DD0924E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{495BE954-E6EA-41A9-8054-A5CA5DD0924E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
@ -771,6 +793,14 @@ Global
|
|||
{8DA03FFB-8AE7-440E-9CDB-1EA1E93A6B4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8DA03FFB-8AE7-440E-9CDB-1EA1E93A6B4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8DA03FFB-8AE7-440E-9CDB-1EA1E93A6B4C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -885,7 +915,6 @@ Global
|
|||
{A80FC28D-D865-428D-AF12-7A393D407803} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{DBBD242E-4437-4BDB-919F-A70839BE75FA} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
|
||||
{B09A6E42-EF42-4693-BA0E-5EE1D7F81FFD} = {DBBD242E-4437-4BDB-919F-A70839BE75FA}
|
||||
{7E5514AC-3633-4237-9490-0992417F2273} = {DBBD242E-4437-4BDB-919F-A70839BE75FA}
|
||||
{495BE954-E6EA-41A9-8054-A5CA5DD0924E} = {DBBD242E-4437-4BDB-919F-A70839BE75FA}
|
||||
{D86B94DC-A53C-4A67-A820-828DD359C49B} = {4673732F-2853-47BD-91B8-C95C229D2C89}
|
||||
{C869CC72-9A98-4246-9A76-6A50F48AFD85} = {4673732F-2853-47BD-91B8-C95C229D2C89}
|
||||
|
|
@ -920,6 +949,8 @@ Global
|
|||
{A998F0D4-08D7-4B2A-AC17-D20E45BB40B3} = {9DBE0062-22F7-4E96-B3B0-B39D274C4A8D}
|
||||
{8DA03FFB-8AE7-440E-9CDB-1EA1E93A6B4C} = {A998F0D4-08D7-4B2A-AC17-D20E45BB40B3}
|
||||
{2B67E954-3B04-402D-A9A7-AAAB1D6C3215} = {9DBE0062-22F7-4E96-B3B0-B39D274C4A8D}
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347} = {BBCFC623-2113-464D-8A1F-3CCDABF1241C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Interruptor/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=localizer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Materializer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=resumable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Resumer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Startable/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Telnyx/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Trippable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unbridge/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
|
|||
98
README.md
98
README.md
|
|
@ -11,18 +11,110 @@
|
|||
[](https://ci.appveyor.com/project/sfmskywalker/elsa)
|
||||
[](https://discord.gg/hhChk5H472)
|
||||
[]( http://stackoverflow.com/questions/tagged/elsa-workflows )
|
||||
[](https://github.com/elsa-workflows/elsa-core/actions/workflows/publish-latest-dashboard.yml)
|
||||
[](https://github.com/elsa-workflows/elsa-core/actions/workflows/publish-latest-dashboard-and-server-docker.yml)
|
||||
[](https://hub.docker.com/repository/docker/elsaworkflows/elsa-dashboard-and-server)
|
||||
|
||||
Elsa Core is a workflows library that enables workflow execution in any .NET Core application.
|
||||
Workflows can be defined not only using code but also visually and of course JSON.
|
||||
Workflows can be defined using code and using the visual workflow designer.
|
||||
|
||||
<p align="center">
|
||||
<img src="./doc/elsa-2-hello-world-http.gif" alt="Elsa 2 Preview">
|
||||
</p>
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation can be found [here](https://elsa-workflows.github.io/elsa-core/docs/next/quickstarts/quickstarts-console-hello-world).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Working on it.
|
||||
```bash
|
||||
dotnet new console -n "MyConsoleApp"
|
||||
|
||||
cd MyConsoleApp
|
||||
|
||||
dotnet add package Elsa --prerelease
|
||||
dotnet add package Elsa.Activities.Console --prerelease
|
||||
```
|
||||
|
||||
Create a new file called `HelloWorldWorkflow.cs` and add the following:
|
||||
|
||||
```csharp
|
||||
using Elsa.Activities.Console;
|
||||
using Elsa.Builders;
|
||||
|
||||
namespace MyConsoleApp
|
||||
{
|
||||
public class HelloWorld : IWorkflow
|
||||
{
|
||||
public void Build(IWorkflowBuilder builder) => builder.WriteLine("Hello World!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Modify `Program.cs` as follows:
|
||||
|
||||
```csharp
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace MyConsoleApp
|
||||
{
|
||||
class Program
|
||||
{
|
||||
private static async Task Main()
|
||||
{
|
||||
var services = new ServiceCollection()
|
||||
.AddElsa(options => options
|
||||
.AddConsoleActivities()
|
||||
.AddWorkflow<HelloWorld>())
|
||||
.BuildServiceProvider();
|
||||
|
||||
var workflowRunner = services.GetRequiredService<IBuildsAndStartsWorkflow>();
|
||||
await workflowRunner.BuildAndStartWorkflowAsync<HelloWorld>();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run the program:
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```bash
|
||||
Hello World!
|
||||
```
|
||||
|
||||
Check out the [Quickstart guides](https://elsa-workflows.github.io/elsa-core/docs/next/quickstarts/quickstarts-console-hello-world) for more examples, including how to setup the Elsa Dashboard to create and manage visual workflows.
|
||||
|
||||
## Docker
|
||||
|
||||
A quick and easy way to give Elsa a spin is to run the following Docker command:
|
||||
|
||||
```bash
|
||||
docker run -t -i -e ELSA__SERVER__BASEURL='http://localhost:13000' -p 13000:80 elsaworkflows/elsa-dashboard-and-server:latest
|
||||
```
|
||||
|
||||
Then navigate to http://localhost:13000.
|
||||
|
||||
## Building From Source
|
||||
|
||||
When you clone the repo, the solution file to open is `Elsa.sln` which should build with no issues.
|
||||
|
||||
### Elsa Dashboard & Client Assets
|
||||
If you want to run the sample project `ElsaDashboard.Samples.AspNetCore.Monolith.csproj`, you should build the client assets first.
|
||||
|
||||
The easiest way to do that is by running the `.\build-assets.ps1` file in the root of the repo (where this README.md is as well).
|
||||
Alternatively, you might run `.\build-assets-and-run-dashboard-monolith.ps1` that will first build the client assets and then run the dashboard application to give Elsa a quick whirl.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
Another quick way to try out Elsa is to run `build-and-run-dashboard-monolith-with-docker.ps1`, which will use Docker Compose to build an image and start a container.
|
||||
When the container starts, you can reach the Elsa Dashboard at http://localhost:6868
|
||||
|
||||
## Roadmap
|
||||
|
||||
|
|
|
|||
32
appveyor.yml
32
appveyor.yml
|
|
@ -11,7 +11,7 @@ environment:
|
|||
sonarcloud_api_key:
|
||||
secure: qaE9kOVkGGFM7ZMf3YeliZxDU9udTCy6W5xXHJ9T+tXv5qXPMYlIdR7hyNgxW04A
|
||||
|
||||
version: 2.0.0-rc1.{build}
|
||||
version: 2.1.0-preview.{build}
|
||||
|
||||
init:
|
||||
- cmd: git config --global core.autocrlf true
|
||||
|
|
@ -51,25 +51,25 @@ install:
|
|||
|
||||
cd ..\..\..\..
|
||||
|
||||
# dotnet tool install --global dotnet-sonarscanner
|
||||
dotnet tool install --global dotnet-sonarscanner
|
||||
|
||||
before_build:
|
||||
- cmd: >-
|
||||
docker-compose -f docker-compose.yaml up -d mongodb postgres
|
||||
|
||||
# - ps: >-
|
||||
# if(-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
|
||||
# & dotnet-sonarscanner begin
|
||||
# /k:$env:sonarcloud_project_name
|
||||
# /v:AppVeyor_build_$env:APPVEYOR_BUILD_VERSION
|
||||
# /o:$env:sonarcloud_org_name
|
||||
# /s:$env:APPVEYOR_BUILD_FOLDER\.SonarQube.Analysis.xml
|
||||
# /d:sonar.host.url="https://sonarcloud.io"
|
||||
# /d:sonar.login=$env:sonarcloud_api_key
|
||||
# /d:sonar.cs.xunit.reportsPaths=$env:APPVEYOR_BUILD_FOLDER\**\TestResults\TestResults.xml
|
||||
# /d:sonar.cs.opencover.reportsPaths=$env:APPVEYOR_BUILD_FOLDER\**\TestResults\*\coverage.opencover.xml
|
||||
# /d:sonar.branch.name=$env:APPVEYOR_REPO_BRANCH
|
||||
# }
|
||||
|
||||
- ps: >-
|
||||
if(-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
|
||||
& dotnet-sonarscanner begin
|
||||
/k:$env:sonarcloud_project_name
|
||||
/v:AppVeyor_build_$env:APPVEYOR_BUILD_VERSION
|
||||
/o:$env:sonarcloud_org_name
|
||||
/s:$env:APPVEYOR_BUILD_FOLDER\.SonarQube.Analysis.xml
|
||||
/d:sonar.host.url="https://sonarcloud.io"
|
||||
/d:sonar.login=$env:sonarcloud_api_key
|
||||
/d:sonar.cs.xunit.reportsPaths=$env:APPVEYOR_BUILD_FOLDER\**\TestResults\TestResults.xml
|
||||
/d:sonar.cs.opencover.reportsPaths=$env:APPVEYOR_BUILD_FOLDER\**\TestResults\*\coverage.opencover.xml
|
||||
/d:sonar.branch.name=$env:APPVEYOR_REPO_BRANCH
|
||||
}
|
||||
|
||||
build_script:
|
||||
- ps: >-
|
||||
|
|
|
|||
2
build-and-push-dashboard-with-docker.ps1
Normal file
2
build-and-push-dashboard-with-docker.ps1
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
docker build -t elsa-dashboard:latest -t elsaworkflows/elsa-dashboard:latest -f ./docker/Dockerfile-elsa-dashboard .
|
||||
docker push elsaworkflows/elsa-dashboard:latest
|
||||
2
build-and-run-dashboard-monolith-with-docker.ps1
Normal file
2
build-and-run-dashboard-monolith-with-docker.ps1
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
docker build -t elsa-dashboard-and-server:local -f ./docker/Dockerfile-elsa-dashboard-and-server .
|
||||
docker run -t -i -e ASPNETCORE_ENVIRONMENT='Development' -e ELSA__SERVER__BASEURL=http://localhost:14000 -p 14000:80 elsa-dashboard-and-server:local
|
||||
2
build-and-run-dashboard-with-docker.ps1
Normal file
2
build-and-run-dashboard-with-docker.ps1
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
docker build -t elsa-dashboard:local -f ./docker/Dockerfile-elsa-dashboard .
|
||||
docker run -t -i -e Elsa__Server__BaseAddress='http://localhost:11000' -e ASPNETCORE_ENVIRONMENT='Development' -p 13000:80 elsa-dashboard:latest
|
||||
2
build-and-run-server-with-docker.ps1
Normal file
2
build-and-run-server-with-docker.ps1
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
docker build -t elsa-server:local -f ./docker/Dockerfile-elsa-server .
|
||||
docker run -t -i -e ASPNETCORE_ENVIRONMENT='Development' -p 11000:80 elsa-server:local
|
||||
3
build-assets-and-run-dashboard-monolith.ps1
Normal file
3
build-assets-and-run-dashboard-monolith.ps1
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.\build-assets
|
||||
cd ../../../..
|
||||
.\run-dashboard-monolith
|
||||
11
build-assets.ps1
Normal file
11
build-assets.ps1
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
cd ./src/designer/elsa-workflows-studio
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
cd ../bindings/aspnet/Elsa.Designer.Components.Web
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
cd ../../../../dashboards/blazor/ElsaDashboard.Application
|
||||
npm install
|
||||
npm run build
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 MiB After Width: | Height: | Size: 2.5 MiB |
|
|
@ -1,37 +1,38 @@
|
|||
version: '3.7'
|
||||
|
||||
services:
|
||||
|
||||
mongodb:
|
||||
image: mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
|
||||
postgres:
|
||||
image: postgres:11
|
||||
environment:
|
||||
- POSTGRES_USER=root
|
||||
- POSTGRES_PASSWORD=Password12!
|
||||
- POSTGRES_DB=yessql
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
azureblobstorage:
|
||||
image: mcr.microsoft.com/azure-blob-storage
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
rabbitmq:
|
||||
image: "rabbitmq:3-management"
|
||||
ports:
|
||||
- "15672:15672"
|
||||
- "5672:5672"
|
||||
|
||||
smtp4dev:
|
||||
image: rnwood/smtp4dev:linux-amd64-3.1.0-ci0856
|
||||
ports:
|
||||
- "3000:80"
|
||||
- "2525:25"
|
||||
mongodb:
|
||||
image: mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
|
||||
postgres:
|
||||
image: postgres:13.3-alpine
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=root
|
||||
- POSTGRES_PASSWORD=Password12!
|
||||
- POSTGRES_DB=elsa
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
azureblobstorage:
|
||||
image: mcr.microsoft.com/azure-blob-storage
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
rabbitmq:
|
||||
image: "rabbitmq:3-management"
|
||||
ports:
|
||||
- "15672:15672"
|
||||
- "5672:5672"
|
||||
|
||||
smtp4dev:
|
||||
image: rnwood/smtp4dev:linux-amd64-3.1.0-ci0856
|
||||
ports:
|
||||
- "3000:80"
|
||||
- "2525:25"
|
||||
|
|
@ -12,13 +12,11 @@ COPY src/. ./src
|
|||
COPY *.props ./
|
||||
COPY ./Nuget.Config ./
|
||||
|
||||
|
||||
# build and publish (UseAppHost=false created platform independent binaries)
|
||||
WORKDIR /source/src/samples/dashboard/aspnetcore/ElsaDashboard.Samples.AspNetCore.Monolith
|
||||
RUN dotnet build "ElsaDashboard.Samples.AspNetCore.Monolith.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish "ElsaDashboard.Samples.AspNetCore.Monolith.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore
|
||||
|
||||
|
||||
# move binaries into smaller base image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
|
||||
WORKDIR /app
|
||||
|
|
|
|||
40
docker/Dockerfile-elsa-dashboard
Normal file
40
docker/Dockerfile-elsa-dashboard
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
FROM node:15-alpine as client-build
|
||||
|
||||
WORKDIR /app
|
||||
COPY /src .
|
||||
|
||||
WORKDIR /app/designer/elsa-workflows-studio
|
||||
RUN npm install --force
|
||||
RUN npm run build
|
||||
|
||||
WORKDIR /app/designer/bindings/aspnet/Elsa.Designer.Components.Web
|
||||
RUN npm install --force
|
||||
RUN npm run build
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim-amd64 AS build
|
||||
WORKDIR /source
|
||||
|
||||
# restore packages
|
||||
COPY ./src/dashboards/aspnetcore/ElsaDashboard/ElsaDashboard.Web.csproj ./src/dashboards/aspnetcore/ElsaDashboard/ElsaDashboard.Web.csproj
|
||||
RUN dotnet restore "./src/dashboards/aspnetcore/ElsaDashboard/ElsaDashboard.Web.csproj"
|
||||
|
||||
# copy required source code
|
||||
COPY src/. ./src
|
||||
COPY *.props ./
|
||||
COPY ./Nuget.Config ./
|
||||
COPY --from=client-build /app/designer/elsa-workflows-studio ./src/designer/elsa-workflows-studio
|
||||
COPY --from=client-build /app/designer/bindings/aspnet/Elsa.Designer.Components.Web ./src/designer/bindings/aspnet/Elsa.Designer.Components.Web
|
||||
|
||||
# build and publish (UseAppHost=false created platform independent binaries)
|
||||
WORKDIR /source/src/dashboards/aspnetcore/ElsaDashboard
|
||||
RUN dotnet build "ElsaDashboard.Web.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish "ElsaDashboard.Web.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore
|
||||
|
||||
# move binaries into smaller base image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish ./
|
||||
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 443/tcp
|
||||
ENTRYPOINT ["dotnet", "ElsaDashboard.Web.dll"]
|
||||
|
|
@ -11,7 +11,6 @@ WORKDIR /app/designer/bindings/aspnet/Elsa.Designer.Components.Web
|
|||
RUN npm install --force
|
||||
RUN npm run build
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim-amd64 AS build
|
||||
WORKDIR /source
|
||||
|
||||
|
|
@ -26,13 +25,11 @@ COPY ./Nuget.Config ./
|
|||
COPY --from=client-build /app/designer/elsa-workflows-studio ./src/designer/elsa-workflows-studio
|
||||
COPY --from=client-build /app/designer/bindings/aspnet/Elsa.Designer.Components.Web ./src/designer/bindings/aspnet/Elsa.Designer.Components.Web
|
||||
|
||||
|
||||
# build and publish (UseAppHost=false created platform independent binaries)
|
||||
# build and publish (UseAppHost=false creates platform independent binaries)
|
||||
WORKDIR /source/src/samples/dashboard/aspnetcore/ElsaDashboard.Samples.AspNetCore.Monolith
|
||||
RUN dotnet build "ElsaDashboard.Samples.AspNetCore.Monolith.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish "ElsaDashboard.Samples.AspNetCore.Monolith.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore
|
||||
|
||||
|
||||
# move binaries into smaller base image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
|
||||
WORKDIR /app
|
||||
30
docker/Dockerfile-elsa-server
Normal file
30
docker/Dockerfile-elsa-server
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FROM node:15-alpine as client-build
|
||||
|
||||
WORKDIR /app
|
||||
COPY /src .
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim-amd64 AS build
|
||||
WORKDIR /source
|
||||
|
||||
# restore packages
|
||||
COPY ./src/samples/server/Elsa.Samples.Server.Host/Elsa.Samples.Server.Host.csproj ./src/samples/server/Elsa.Samples.Server.Host/Elsa.Samples.Server.Host.csproj
|
||||
RUN dotnet restore "./src/samples/server/Elsa.Samples.Server.Host/Elsa.Samples.Server.Host.csproj"
|
||||
|
||||
# copy required source code
|
||||
COPY src/. ./src
|
||||
COPY *.props ./
|
||||
COPY ./Nuget.Config ./
|
||||
|
||||
# build and publish (UseAppHost=false creates platform independent binaries)
|
||||
WORKDIR /source/src/samples/server/Elsa.Samples.Server.Host
|
||||
RUN dotnet build "Elsa.Samples.Server.Host.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish "Elsa.Samples.Server.Host.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore
|
||||
|
||||
# move binaries into smaller base image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish ./
|
||||
|
||||
EXPOSE 80/tcp
|
||||
EXPOSE 443/tcp
|
||||
ENTRYPOINT ["dotnet", "Elsa.Samples.Server.Host.dll"]
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
Clone the repository and open a command line in the project root folder. Then you can run the two docker commands below to build and start the container. Alternatively you can use the provided docker-compose file in the docker folder that does it for you via `docker-compuse up -d`. The docker-compose file will also include a fake smtp server to test email delivery.
|
||||
|
||||
```
|
||||
$ docker build -t elsadashboard:local -f ./docker/Dockerfile-with-npm .
|
||||
$ docker build -t elsadashboardwithserver:local -f ./docker/Dockerfile-with-npm .
|
||||
$ docker run -t -i -e Elsa__Server__BaseAddress='http://localhost:6868' -e ASPNETCORE_ENVIRONMENT='Development' -p 6868:80 elsadashboard:latest
|
||||
```
|
||||
|
||||
|
|
|
|||
1
pack-local-feed.ps1
Normal file
1
pack-local-feed.ps1
Normal file
|
|
@ -0,0 +1 @@
|
|||
dotnet pack -o C:\Projects\Elsa\feed --version-suffix "rc2-5" -p:PackageVersion=2.0.0-rc2-5
|
||||
2
run-dashboard-monolith.ps1
Normal file
2
run-dashboard-monolith.ps1
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
cd ./src/samples/dashboard/aspnetcore/ElsaDashboard.Samples.AspNetCore.Monolith
|
||||
dotnet run
|
||||
|
|
@ -20,10 +20,10 @@ namespace Elsa.Activities.AzureServiceBus
|
|||
_serializer = serializer;
|
||||
}
|
||||
|
||||
[ActivityProperty(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string QueueName { get; set; } = default!;
|
||||
|
||||
[ActivityProperty]
|
||||
[ActivityInput]
|
||||
public Type MessageType { get; set; } = default!;
|
||||
|
||||
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) => context.WorkflowExecutionContext.IsFirstPass ? ExecuteInternal(context) : Suspend();
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ namespace Elsa.Activities.AzureServiceBus
|
|||
_serializer = serializer;
|
||||
}
|
||||
|
||||
[ActivityProperty(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string TopicName { get; set; } = default!;
|
||||
|
||||
[ActivityProperty(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string SubscriptionName { get; set; } = default!;
|
||||
|
||||
[ActivityProperty]
|
||||
[ActivityInput]
|
||||
public Type MessageType { get; set; } = default!;
|
||||
|
||||
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) => context.WorkflowExecutionContext.IsFirstPass ? ExecuteInternal(context) : Suspend();
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ namespace Elsa.Activities.AzureServiceBus
|
|||
_serializer = serializer;
|
||||
}
|
||||
|
||||
[ActivityProperty(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string QueueName { get; set; } = default!;
|
||||
|
||||
[ActivityProperty(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid, SyntaxNames.Json })]
|
||||
[ActivityInput(SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid, SyntaxNames.Json })]
|
||||
public object Message { get; set; } = default!;
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ namespace Elsa.Activities.AzureServiceBus
|
|||
_serializer = serializer;
|
||||
}
|
||||
|
||||
[ActivityProperty] public string TopicName { get; set; } = default!;
|
||||
[ActivityProperty] public object Message { get; set; } = default!;
|
||||
[ActivityInput] public string TopicName { get; set; } = default!;
|
||||
[ActivityInput] public object Message { get; set; } = default!;
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ namespace Elsa.Activities.AzureServiceBus.Bookmarks
|
|||
|
||||
public class QueueMessageReceivedBookmarkProvider : BookmarkProvider<QueueMessageReceivedBookmark, AzureServiceBusQueueMessageReceived>
|
||||
{
|
||||
public override async ValueTask<IEnumerable<IBookmark>> GetBookmarksAsync(BookmarkProviderContext<AzureServiceBusQueueMessageReceived> context, CancellationToken cancellationToken) =>
|
||||
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext<AzureServiceBusQueueMessageReceived> context, CancellationToken cancellationToken) =>
|
||||
new[]
|
||||
{
|
||||
new QueueMessageReceivedBookmark
|
||||
Result(new QueueMessageReceivedBookmark
|
||||
{
|
||||
QueueName = (await context.Activity.GetPropertyValueAsync(x => x.QueueName, cancellationToken))!,
|
||||
QueueName = (await context.ReadActivityPropertyAsync(x => x.QueueName, cancellationToken))!,
|
||||
CorrelationId = context.ActivityExecutionContext.WorkflowExecutionContext.CorrelationId
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -25,15 +25,15 @@ namespace Elsa.Activities.AzureServiceBus.Bookmarks
|
|||
|
||||
public class TopicMessageReceivedBookmarkProvider : BookmarkProvider<TopicMessageReceivedBookmark, AzureServiceBusTopicMessageReceived>
|
||||
{
|
||||
public override async ValueTask<IEnumerable<IBookmark>> GetBookmarksAsync(BookmarkProviderContext<AzureServiceBusTopicMessageReceived> context, CancellationToken cancellationToken) =>
|
||||
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext<AzureServiceBusTopicMessageReceived> context, CancellationToken cancellationToken) =>
|
||||
new[]
|
||||
{
|
||||
new TopicMessageReceivedBookmark
|
||||
Result(new TopicMessageReceivedBookmark
|
||||
{
|
||||
TopicName = (await context.Activity.GetPropertyValueAsync(x => x.TopicName, cancellationToken))!,
|
||||
SubscriptionName = (await context.Activity.GetPropertyValueAsync(x => x.SubscriptionName, cancellationToken))!,
|
||||
TopicName = (await context.ReadActivityPropertyAsync(x => x.TopicName, cancellationToken))!,
|
||||
SubscriptionName = (await context.ReadActivityPropertyAsync(x => x.SubscriptionName, cancellationToken))!,
|
||||
CorrelationId = context.ActivityExecutionContext.WorkflowExecutionContext.CorrelationId
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.AzureServiceBus.Services;
|
||||
using Elsa.Events;
|
||||
using Elsa.Triggers;
|
||||
using Rebus.Handlers;
|
||||
|
||||
namespace Elsa.Activities.AzureServiceBus.Consumers
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props"/>
|
||||
<Import Project="..\..\..\configureawait.props"/>
|
||||
<Import Project="..\..\..\common.props" />
|
||||
<Import Project="..\..\..\configureawait.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
|
|
@ -13,10 +13,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="5.1.2"/>
|
||||
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="5.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj"/>
|
||||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ namespace Elsa.Activities.AzureServiceBus.Extensions
|
|||
.AddBookmarkProvider<TopicMessageReceivedBookmarkProvider>()
|
||||
;
|
||||
|
||||
options.AddConsumer<RestartServiceBusQueuesConsumer, WorkflowDefinitionPublished>();
|
||||
options.AddConsumer<RestartServiceBusQueuesConsumer, WorkflowDefinitionRetracted>();
|
||||
options.AddConsumer<RestartServiceBusTopicsConsumer, WorkflowDefinitionPublished>();
|
||||
options.AddConsumer<RestartServiceBusTopicsConsumer, WorkflowDefinitionRetracted>();
|
||||
options.AddCompetingConsumer<RestartServiceBusQueuesConsumer, WorkflowDefinitionPublished>();
|
||||
options.AddCompetingConsumer<RestartServiceBusQueuesConsumer, WorkflowDefinitionRetracted>();
|
||||
options.AddCompetingConsumer<RestartServiceBusTopicsConsumer, WorkflowDefinitionPublished>();
|
||||
options.AddCompetingConsumer<RestartServiceBusTopicsConsumer, WorkflowDefinitionRetracted>();
|
||||
|
||||
options
|
||||
.AddActivity<AzureServiceBusQueueMessageReceived>()
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ using System.Linq;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Persistence.Specifications.WorkflowInstances;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -22,6 +19,7 @@ namespace Elsa.Activities.AzureServiceBus.Services
|
|||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ICollection<QueueWorker> _workers;
|
||||
private readonly SemaphoreSlim _semaphore = new(1);
|
||||
|
||||
public ServiceBusQueuesStarter(
|
||||
IQueueMessageReceiverClientFactory messageReceiverClientFactory,
|
||||
|
|
@ -38,11 +36,20 @@ namespace Elsa.Activities.AzureServiceBus.Services
|
|||
|
||||
public async Task CreateWorkersAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await DisposeExistingWorkersAsync();
|
||||
var queueNames = (await GetQueueNamesAsync(cancellationToken).ToListAsync(cancellationToken)).Distinct();
|
||||
await _semaphore.WaitAsync(cancellationToken);
|
||||
|
||||
foreach (var queueName in queueNames)
|
||||
await CreateAndAddWorkerAsync(queueName, cancellationToken);
|
||||
try
|
||||
{
|
||||
await DisposeExistingWorkersAsync();
|
||||
var queueNames = (await GetQueueNamesAsync(cancellationToken).ToListAsync(cancellationToken)).Distinct();
|
||||
|
||||
foreach (var queueName in queueNames)
|
||||
await CreateAndAddWorkerAsync(queueName, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateAndAddWorkerAsync(string queueName, CancellationToken cancellationToken)
|
||||
|
|
@ -89,7 +96,7 @@ namespace Elsa.Activities.AzureServiceBus.Services
|
|||
|
||||
foreach (var activity in workflowBlueprintWrapper.Filter<AzureServiceBusQueueMessageReceived>())
|
||||
{
|
||||
var queueName = await activity.GetPropertyValueAsync(x => x.QueueName, cancellationToken);
|
||||
var queueName = await activity.EvaluatePropertyValueAsync(x => x.QueueName, cancellationToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(queueName))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ namespace Elsa.Activities.AzureServiceBus.Services
|
|||
|
||||
foreach (var activity in workflowBlueprintWrapper.Filter<AzureServiceBusTopicMessageReceived>())
|
||||
{
|
||||
var topicName = await activity.GetPropertyValueAsync(x => x.TopicName, cancellationToken);
|
||||
var subscriptionName = await activity.GetPropertyValueAsync(x => x.SubscriptionName, cancellationToken);
|
||||
var topicName = await activity.EvaluatePropertyValueAsync(x => x.TopicName, cancellationToken);
|
||||
var subscriptionName = await activity.EvaluatePropertyValueAsync(x => x.SubscriptionName, cancellationToken);
|
||||
yield return (topicName, subscriptionName)!;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ namespace Elsa.Activities.AzureServiceBus.Services
|
|||
private async Task OnMessageReceived(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("Message received with ID {MessageId}", message.MessageId);
|
||||
await TriggerWorkflowsAsync(message, cancellationToken);
|
||||
await TriggerWorkflowsAsync(message, CancellationToken.None);
|
||||
await ReceiverClient.CompleteAsync(message.SystemProperties.LockToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Elsa.Activities.BlobStorage
|
|||
private readonly IBlobStorage _storage;
|
||||
public BlobExists(IBlobStorage storage) => _storage = storage;
|
||||
|
||||
[ActivityProperty(Hint = "The ID of the blob.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The ID of the blob.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string BlobId { get; set; } = default!;
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ namespace Elsa.Activities.BlobStorage
|
|||
private readonly IBlobStorage _storage;
|
||||
public DeleteBlob(IBlobStorage storage) => _storage = storage;
|
||||
|
||||
[ActivityProperty(
|
||||
[ActivityInput(
|
||||
Hint = "The IDs of the blobs.",
|
||||
UIHint = ActivityPropertyUIHints.MultiText,
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ namespace Elsa.Activities.BlobStorage
|
|||
private readonly IBlobStorage _storage;
|
||||
public ReadBlob(IBlobStorage storage) => _storage = storage;
|
||||
|
||||
[ActivityProperty(Hint = "The Id assigned to the blob.")]
|
||||
[ActivityInput(Hint = "The Id assigned to the blob.")]
|
||||
[Required]
|
||||
public string BlobId { get; set; } = default!;
|
||||
|
||||
[ActivityProperty(Hint = "If set, the output of this activity is written to the specified file. Otherwise, the bytes of the blob will be set as the activity output.")]
|
||||
[ActivityInput(Hint = "If set, the output of this activity is written to the specified file. Otherwise, the bytes of the blob will be set as the activity output.")]
|
||||
public string? DestinationFilePath { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ namespace Elsa.Activities.BlobStorage
|
|||
|
||||
private readonly IBlobStorage _storage;
|
||||
|
||||
[ActivityProperty(Hint = "The ID to be assigned to the blob.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The ID to be assigned to the blob.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[Required]
|
||||
public string BlobId { get; set; } = default!;
|
||||
|
||||
[ActivityProperty(Hint = "The bytes to write.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript }, DefaultSyntax = SyntaxNames.JavaScript)]
|
||||
[ActivityInput(Hint = "The bytes to write.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript }, DefaultSyntax = SyntaxNames.JavaScript)]
|
||||
public byte[]? Bytes { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Design;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Conductor
|
||||
{
|
||||
[Trigger(
|
||||
Category = "Conductor",
|
||||
Description = "Waits for an event sent from your application."
|
||||
)]
|
||||
public class EventReceived : Activity
|
||||
{
|
||||
[ActivityInput(
|
||||
Label = "Event",
|
||||
Hint = "The event to wait for.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string EventName { get; set; } = default!;
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Enter one or more possible outcomes for this event.",
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Json }
|
||||
)]
|
||||
public ISet<string> Outcomes { get; set; } = new HashSet<string>();
|
||||
|
||||
[ActivityOutput(Hint = "Any input that was sent along with the event from your application.")]
|
||||
public object? Payload { get; set; }
|
||||
|
||||
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) => context.IsFirstPass ? OnExecuteInternal(context) : Suspend();
|
||||
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context) => OnExecuteInternal(context);
|
||||
|
||||
private IActivityExecutionResult OnExecuteInternal(ActivityExecutionContext context)
|
||||
{
|
||||
var eventModel = context.GetInput<EventModel>()!;
|
||||
var outcomes = eventModel.Outcomes;
|
||||
|
||||
if (outcomes?.Any() == false)
|
||||
outcomes = new[] { OutcomeNames.Done };
|
||||
|
||||
Payload = eventModel.Payload;
|
||||
return base.Outcomes(outcomes!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Conductor
|
||||
{
|
||||
[Job(
|
||||
Category = "Conductor",
|
||||
Description = "Sends a task to your application and waits for the application to report the task as completed or cancelled."
|
||||
)]
|
||||
public class RunTask : Activity
|
||||
{
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
public RunTask(IEventPublisher eventPublisher)
|
||||
{
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Run Task",
|
||||
Hint = "The task to run.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string TaskName { get; set; } = default!;
|
||||
|
||||
[ActivityOutput(Hint = "Any input to send along with the task to your application.")]
|
||||
public object? Payload { get; set; }
|
||||
|
||||
[ActivityOutput(Hint = "Any input that was received along with the task completion.")]
|
||||
public object? ResultPayload { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await _eventPublisher.PublishAsync(new RunTaskModel(TaskName, Payload, context.WorkflowInstance.Id));
|
||||
return Suspend();
|
||||
}
|
||||
|
||||
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context)
|
||||
{
|
||||
var eventModel = context.GetInput<TaskResultModel>()!;
|
||||
var outcomes = eventModel.Outcomes;
|
||||
|
||||
if (outcomes?.Any() == false)
|
||||
outcomes = new[] { OutcomeNames.Done };
|
||||
|
||||
ResultPayload = eventModel.Payload;
|
||||
return base.Outcomes(outcomes!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Design;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Conductor
|
||||
{
|
||||
[Action(
|
||||
Category = "Conductor",
|
||||
Description = "Sends a command to your application.",
|
||||
Outcomes = new[] { OutcomeNames.Done }
|
||||
)]
|
||||
public class SendCommand : Activity
|
||||
{
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
public SendCommand(IEventPublisher eventPublisher)
|
||||
{
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Command",
|
||||
Hint = "The command to send.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string CommandName { get; set; } = default!;
|
||||
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.MultiLine,
|
||||
Hint = "Optional data to send to your application.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid, SyntaxNames.Json })]
|
||||
public object? Payload { get; set; } = default!;
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await _eventPublisher.PublishAsync(new SendCommandModel(CommandName, Payload, context.WorkflowInstance.Id));
|
||||
return Done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Rebus.Handlers;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Consumers
|
||||
{
|
||||
public class RunTaskConsumer : IHandleMessages<RunTaskModel>
|
||||
{
|
||||
private readonly ApplicationTasksClient _applicationTasksClient;
|
||||
public RunTaskConsumer(ApplicationTasksClient applicationTasksClient) => _applicationTasksClient = applicationTasksClient;
|
||||
public async Task Handle(RunTaskModel message) => await _applicationTasksClient.RunTaskAsync(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Rebus.Handlers;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Consumers
|
||||
{
|
||||
public class SendCommandConsumer : IHandleMessages<SendCommandModel>
|
||||
{
|
||||
private readonly ApplicationCommandsClient _applicationCommandsClient;
|
||||
public SendCommandConsumer(ApplicationCommandsClient applicationCommandsClient) => _applicationCommandsClient = applicationCommandsClient;
|
||||
public async Task Handle(SendCommandModel message) => await _applicationCommandsClient.SendCommandAsync(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props" />
|
||||
<Import Project="..\..\..\configureawait.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Description>
|
||||
Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application.
|
||||
This package provides activities to implement conductor-style workflows where Elsa sends commands to your application, which responds asynchronously with events.
|
||||
</Description>
|
||||
<PackageTags>elsa, workflows, conductor</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Providers.Bookmarks;
|
||||
using Elsa.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Endpoints.Conductor.Events
|
||||
{
|
||||
[ApiController]
|
||||
[Route("conductor/events/{eventName}/dispatch")]
|
||||
[Produces("application/json")]
|
||||
public class Dispatch : ControllerBase
|
||||
{
|
||||
private readonly IWorkflowLaunchpad _workflowLaunchpad;
|
||||
public Dispatch(IWorkflowLaunchpad workflowLaunchpad) => _workflowLaunchpad = workflowLaunchpad;
|
||||
|
||||
[HttpGet, HttpPost]
|
||||
public async Task<IActionResult> Handle(string eventName, EventModel model)
|
||||
{
|
||||
var bookmark = new EventBookmark(eventName.ToLowerInvariant());
|
||||
var trigger = new EventBookmark(eventName.ToLowerInvariant());
|
||||
var context = new CollectWorkflowsContext(nameof(EventReceived), bookmark, trigger, model.CorrelationId, model.WorkflowInstanceId);
|
||||
var pendingWorkflows = await _workflowLaunchpad.CollectAndDispatchWorkflowsAsync(context, model);
|
||||
|
||||
return Accepted(pendingWorkflows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Providers.Bookmarks;
|
||||
using Elsa.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Endpoints.Conductor.Events
|
||||
{
|
||||
[ApiController]
|
||||
[Route("conductor/events/{eventName}/execute")]
|
||||
[Produces("application/json")]
|
||||
public class Execute : ControllerBase
|
||||
{
|
||||
private readonly IWorkflowLaunchpad _workflowLaunchpad;
|
||||
public Execute(IWorkflowLaunchpad workflowLaunchpad) => _workflowLaunchpad = workflowLaunchpad;
|
||||
|
||||
[HttpGet, HttpPost]
|
||||
public async Task<IActionResult> Handle(string eventName, EventModel model)
|
||||
{
|
||||
var bookmark = new EventBookmark(eventName);
|
||||
var trigger = new EventBookmark(eventName);
|
||||
var context = new CollectWorkflowsContext(nameof(EventReceived), bookmark, trigger, model.CorrelationId, model.WorkflowInstanceId);
|
||||
var pendingWorkflows = await _workflowLaunchpad.CollectAndExecuteWorkflowsAsync(context, model);
|
||||
|
||||
return Accepted(pendingWorkflows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Providers.Bookmarks;
|
||||
using Elsa.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Endpoints.Conductor.Tasks
|
||||
{
|
||||
[ApiController]
|
||||
[Route("conductor/tasks/{taskName}/dispatch")]
|
||||
[Produces("application/json")]
|
||||
public class Dispatch : ControllerBase
|
||||
{
|
||||
private readonly IWorkflowLaunchpad _workflowLaunchpad;
|
||||
public Dispatch(IWorkflowLaunchpad workflowLaunchpad) => _workflowLaunchpad = workflowLaunchpad;
|
||||
|
||||
[HttpGet, HttpPost]
|
||||
public async Task<IActionResult> Handle(string taskName, TaskResultModel model)
|
||||
{
|
||||
var bookmark = new TaskBookmark(taskName.ToLowerInvariant());
|
||||
var trigger = new TaskBookmark(taskName.ToLowerInvariant());
|
||||
var context = new CollectWorkflowsContext(nameof(RunTask), bookmark, trigger, model.CorrelationId, model.WorkflowInstanceId);
|
||||
var pendingWorkflows = await _workflowLaunchpad.CollectAndDispatchWorkflowsAsync(context, model);
|
||||
|
||||
return Accepted(pendingWorkflows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Providers.Bookmarks;
|
||||
using Elsa.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Endpoints.Conductor.Tasks
|
||||
{
|
||||
[ApiController]
|
||||
[Route("conductor/tasks/{taskName}/execute")]
|
||||
[Produces("application/json")]
|
||||
public class Execute : ControllerBase
|
||||
{
|
||||
private readonly IWorkflowLaunchpad _workflowLaunchpad;
|
||||
public Execute(IWorkflowLaunchpad workflowLaunchpad) => _workflowLaunchpad = workflowLaunchpad;
|
||||
|
||||
[HttpGet, HttpPost]
|
||||
public async Task<IActionResult> Handle(string taskName, TaskResultModel model)
|
||||
{
|
||||
var bookmark = new TaskBookmark(taskName.ToLowerInvariant());
|
||||
var trigger = new TaskBookmark(taskName.ToLowerInvariant());
|
||||
var context = new CollectWorkflowsContext(nameof(RunTask), bookmark, trigger, model.CorrelationId, model.WorkflowInstanceId);
|
||||
var pendingWorkflows = await _workflowLaunchpad.CollectAndExecuteWorkflowsAsync(context, model);
|
||||
|
||||
return Accepted(pendingWorkflows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Activities.Conductor.Consumers;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Options;
|
||||
using Elsa.Activities.Conductor.Providers.ActivityTypes;
|
||||
using Elsa.Activities.Conductor.Providers.Bookmarks;
|
||||
using Elsa.Activities.Conductor.Providers.Commands;
|
||||
using Elsa.Activities.Conductor.Providers.Events;
|
||||
using Elsa.Activities.Conductor.Providers.Tasks;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Elsa.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Polly;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Extensions
|
||||
{
|
||||
public static class ElsaOptionsBuilderExtensions
|
||||
{
|
||||
public static ElsaOptionsBuilder AddConductorActivities(this ElsaOptionsBuilder elsa, Action<ConductorOptions>? configureOptions = default, Action<IHttpClientBuilder>? configureHttpClient = default)
|
||||
{
|
||||
var services = elsa.Services;
|
||||
|
||||
if (configureOptions != null)
|
||||
services.Configure(configureOptions);
|
||||
|
||||
services.ConfigureCommandsHttpClient(configureHttpClient);
|
||||
services.ConfigureTasksHttpClient(configureHttpClient);
|
||||
|
||||
services
|
||||
.AddCommandsProvider<OptionsCommandsProvider>()
|
||||
.AddEventsProvider<OptionsEventsProvider>()
|
||||
.AddTasksProvider<OptionsTasksProvider>()
|
||||
.AddActivityTypeProvider<CommandActivityTypeProvider>()
|
||||
.AddActivityTypeProvider<EventActivityTypeProvider>()
|
||||
.AddActivityTypeProvider<TaskActivityTypeProvider>()
|
||||
.AddBookmarkProvider<EventBookmarkProvider>()
|
||||
.AddBookmarkProvider<TaskBookmarkProvider>()
|
||||
.AddSingleton<Scoped<IEnumerable<ICommandsProvider>>>()
|
||||
.AddSingleton<Scoped<IEnumerable<IEventsProvider>>>()
|
||||
.AddSingleton<Scoped<IEnumerable<ITasksProvider>>>();
|
||||
|
||||
elsa
|
||||
.AddActivitiesFrom<SendCommand>()
|
||||
.AddCompetingConsumer<SendCommandConsumer, SendCommandModel>()
|
||||
.AddCompetingConsumer<RunTaskConsumer, RunTaskModel>();
|
||||
|
||||
return elsa;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddCommandsProvider<T>(this IServiceCollection services) where T : class, ICommandsProvider => services.AddScoped<ICommandsProvider, T>();
|
||||
public static IServiceCollection AddEventsProvider<T>(this IServiceCollection services) where T : class, IEventsProvider => services.AddScoped<IEventsProvider, T>();
|
||||
public static IServiceCollection AddTasksProvider<T>(this IServiceCollection services) where T : class, ITasksProvider => services.AddScoped<ITasksProvider, T>();
|
||||
|
||||
private static void ConfigureCommandsHttpClient(this IServiceCollection services, Action<IHttpClientBuilder>? configureHttpClient = default) => services.ConfigureHttpClient<ApplicationCommandsClient>(o => o.CommandsHookUrl, configureHttpClient);
|
||||
private static void ConfigureTasksHttpClient(this IServiceCollection services, Action<IHttpClientBuilder>? configureHttpClient = default) => services.ConfigureHttpClient<ApplicationTasksClient>(o => o.CommandsHookUrl, configureHttpClient);
|
||||
|
||||
private static void ConfigureHttpClient<T>(this IServiceCollection services, Func<ConductorOptions, Uri> baseAddress, Action<IHttpClientBuilder>? configureHttpClient = default) where T : class
|
||||
{
|
||||
var httpClientBuilder = services.AddHttpClient<T>((sp, httpClient) =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<ConductorOptions>>().Value;
|
||||
httpClient.BaseAddress = baseAddress(options);
|
||||
});
|
||||
|
||||
if (configureHttpClient == null)
|
||||
httpClientBuilder.AddTransientHttpErrorPolicy(x => x.WaitAndRetryAsync(10, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount))));
|
||||
else
|
||||
configureHttpClient(httpClientBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/activities/Elsa.Activities.Conductor/FodyWeavers.xml
Normal file
3
src/activities/Elsa.Activities.Conductor/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
17
src/activities/Elsa.Activities.Conductor/IsExternalInit.cs
Normal file
17
src/activities/Elsa.Activities.Conductor/IsExternalInit.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved to be used by the compiler for tracking metadata.
|
||||
/// This class should not be used by developers in source code.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class IsExternalInit
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Models
|
||||
{
|
||||
public class CommandDefinition : Entity
|
||||
{
|
||||
public string Name { get; set; } = default!;
|
||||
public string? DisplayName { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Models
|
||||
{
|
||||
public class EventDefinition : Entity
|
||||
{
|
||||
public string Name { get; set; } = default!;
|
||||
public string? DisplayName { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public ICollection<string>? Outcomes { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Models
|
||||
{
|
||||
public record EventModel(string EventName, ICollection<string>? Outcomes, object? Payload, string? WorkflowInstanceId, string? CorrelationId);
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Elsa.Activities.Conductor.Models
|
||||
{
|
||||
public record RunTaskModel(string Task, object? Payload, string WorkflowInstanceId);
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Elsa.Activities.Conductor.Models
|
||||
{
|
||||
public record SendCommandModel(string Command, object? Payload, string WorkflowInstanceId);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Models
|
||||
{
|
||||
public class TaskDefinition : Entity
|
||||
{
|
||||
public string Name { get; set; } = default!;
|
||||
public string? DisplayName { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public ICollection<string>? Outcomes { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Models
|
||||
{
|
||||
public record TaskResultModel(string TaskName, ICollection<string>? Outcomes, object? Payload, string? WorkflowInstanceId, string? CorrelationId);
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Newtonsoft.Json;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.JsonNet;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Options
|
||||
{
|
||||
public class ConductorOptions
|
||||
{
|
||||
public ConductorOptions()
|
||||
{
|
||||
SerializerSettings = new JsonSerializerSettings();
|
||||
SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The URL to post commands to.
|
||||
/// </summary>
|
||||
public Uri CommandsHookUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The URL to post tasks to.
|
||||
/// </summary>
|
||||
public Uri TasksHookUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The serializer to use when serializing a command before sending to the application.
|
||||
/// </summary>
|
||||
public JsonSerializerSettings SerializerSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A collection of commands that can be sent to the application.
|
||||
/// </summary>
|
||||
public ICollection<CommandDefinition> Commands { get; set; } = new List<CommandDefinition>();
|
||||
|
||||
/// <summary>
|
||||
/// A collection of events that can be received from the application.
|
||||
/// </summary>
|
||||
public ICollection<EventDefinition> Events { get; set; } = new List<EventDefinition>();
|
||||
|
||||
/// <summary>
|
||||
/// A collection of tasks that can be sent to the application.
|
||||
/// </summary>
|
||||
public ICollection<TaskDefinition> Tasks { get; set; } = new List<TaskDefinition>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Elsa.Metadata;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Providers.ActivityTypes
|
||||
{
|
||||
public class CommandActivityTypeProvider : IActivityTypeProvider
|
||||
{
|
||||
private readonly IDescribesActivityType _describesActivityType;
|
||||
private readonly IActivityActivator _activityActivator;
|
||||
private readonly Scoped<IEnumerable<ICommandsProvider>> _scopedCommandsProviders;
|
||||
|
||||
public CommandActivityTypeProvider(IDescribesActivityType describesActivityType, IActivityActivator activityActivator, Scoped<IEnumerable<ICommandsProvider>> scopedCommandsProviders)
|
||||
{
|
||||
_describesActivityType = describesActivityType;
|
||||
_activityActivator = activityActivator;
|
||||
_scopedCommandsProviders = scopedCommandsProviders;
|
||||
}
|
||||
|
||||
public async ValueTask<IEnumerable<ActivityType>> GetActivityTypesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var commands = await GetCommandsAsync(cancellationToken);
|
||||
var activityTypes = await GetActivityTypesAsync(commands, cancellationToken).ToListAsync(cancellationToken);
|
||||
return activityTypes;
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<ActivityType> GetActivityTypesAsync(IEnumerable<CommandDefinition> commands, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var command in commands)
|
||||
yield return await CreateActivityTypeAsync(command, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<ActivityType> CreateActivityTypeAsync(CommandDefinition command, CancellationToken cancellationToken)
|
||||
{
|
||||
var descriptor = await _describesActivityType.DescribeAsync<SendCommand>(cancellationToken);
|
||||
|
||||
descriptor.Type = command.Name;
|
||||
descriptor.DisplayName = command.DisplayName ?? command.Name;
|
||||
descriptor.Description = command.Description;
|
||||
descriptor.InputProperties = descriptor.InputProperties.Where(x => x.Name != nameof(SendCommand.CommandName)).ToArray();
|
||||
|
||||
return new ActivityType
|
||||
{
|
||||
Type = typeof(SendCommand),
|
||||
TypeName = descriptor.Type,
|
||||
DisplayName = descriptor.DisplayName,
|
||||
Describe = () => descriptor,
|
||||
Description = descriptor.Description,
|
||||
ActivateAsync = async context =>
|
||||
{
|
||||
var activity = await _activityActivator.ActivateActivityAsync<SendCommand>(context, cancellationToken);
|
||||
activity.CommandName = command.Name;
|
||||
return activity;
|
||||
},
|
||||
CanExecuteAsync = async (context, instance) => await instance.CanExecuteAsync(context),
|
||||
ExecuteAsync = async (context, instance) => await instance.ExecuteAsync(context),
|
||||
ResumeAsync = async (context, instance) => await instance.ResumeAsync(context)
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<CommandDefinition>> GetCommandsAsync(CancellationToken cancellationToken) =>
|
||||
await _scopedCommandsProviders.UseServiceAsync(async commandProviders => await GetCommandsAsync(commandProviders, cancellationToken).ToListAsync(cancellationToken));
|
||||
|
||||
private static async IAsyncEnumerable<CommandDefinition> GetCommandsAsync(IEnumerable<ICommandsProvider> commandProviders, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var commandProvider in commandProviders)
|
||||
{
|
||||
var commands = await commandProvider.GetCommandsAsync(cancellationToken);
|
||||
|
||||
foreach (var command in commands)
|
||||
yield return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Elsa.Metadata;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Providers.ActivityTypes
|
||||
{
|
||||
public class EventActivityTypeProvider : IActivityTypeProvider
|
||||
{
|
||||
private readonly IDescribesActivityType _describesActivityType;
|
||||
private readonly IActivityActivator _activityActivator;
|
||||
private readonly Scoped<IEnumerable<IEventsProvider>> _scopedEventsProviders;
|
||||
|
||||
public EventActivityTypeProvider(IDescribesActivityType describesActivityType, IActivityActivator activityActivator, Scoped<IEnumerable<IEventsProvider>> scopedEventsProviders)
|
||||
{
|
||||
_describesActivityType = describesActivityType;
|
||||
_activityActivator = activityActivator;
|
||||
_scopedEventsProviders = scopedEventsProviders;
|
||||
}
|
||||
|
||||
public async ValueTask<IEnumerable<ActivityType>> GetActivityTypesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var events = await GetEventsAsync(cancellationToken);
|
||||
return await GetActivityTypesAsync(events, cancellationToken).ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<ActivityType> GetActivityTypesAsync(IEnumerable<EventDefinition> events, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var eventDefinition in events)
|
||||
yield return await CreateActivityTypeAsync(eventDefinition, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<ActivityType> CreateActivityTypeAsync(EventDefinition eventDefinition, CancellationToken cancellationToken)
|
||||
{
|
||||
var descriptor = await _describesActivityType.DescribeAsync<EventReceived>(cancellationToken);
|
||||
|
||||
descriptor.Type = eventDefinition.Name;
|
||||
descriptor.DisplayName = eventDefinition.DisplayName ?? eventDefinition.Name;
|
||||
descriptor.Description = eventDefinition.Description;
|
||||
descriptor.InputProperties = Array.Empty<ActivityInputDescriptor>();
|
||||
descriptor.Outcomes = eventDefinition.Outcomes?.ToArray() ?? new[] { OutcomeNames.Done };
|
||||
|
||||
return new ActivityType
|
||||
{
|
||||
Type = typeof(EventReceived),
|
||||
TypeName = descriptor.Type,
|
||||
DisplayName = descriptor.DisplayName,
|
||||
Describe = () => descriptor,
|
||||
Description = descriptor.Description,
|
||||
ActivateAsync = async context =>
|
||||
{
|
||||
var activity = await _activityActivator.ActivateActivityAsync<EventReceived>(context, cancellationToken);
|
||||
activity.EventName = eventDefinition.Name;
|
||||
return activity;
|
||||
},
|
||||
CanExecuteAsync = async (context, instance) => await instance.CanExecuteAsync(context),
|
||||
ExecuteAsync = async (context, instance) => await instance.ExecuteAsync(context),
|
||||
ResumeAsync = async (context, instance) => await instance.ResumeAsync(context)
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<EventDefinition>> GetEventsAsync(CancellationToken cancellationToken) =>
|
||||
await _scopedEventsProviders.UseServiceAsync(async eventProviders => await GetEventsAsync(eventProviders, cancellationToken).ToListAsync(cancellationToken));
|
||||
|
||||
private static async IAsyncEnumerable<EventDefinition> GetEventsAsync(IEnumerable<IEventsProvider> eventProviders, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var commandProvider in eventProviders)
|
||||
{
|
||||
var commands = await commandProvider.GetEventsAsync(cancellationToken);
|
||||
|
||||
foreach (var command in commands)
|
||||
yield return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Elsa.Metadata;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Providers.ActivityTypes
|
||||
{
|
||||
public class TaskActivityTypeProvider : IActivityTypeProvider
|
||||
{
|
||||
private readonly IDescribesActivityType _describesActivityType;
|
||||
private readonly IActivityActivator _activityActivator;
|
||||
private readonly Scoped<IEnumerable<ITasksProvider>> _scopedTasksProviders;
|
||||
|
||||
public TaskActivityTypeProvider(IDescribesActivityType describesActivityType, IActivityActivator activityActivator, Scoped<IEnumerable<ITasksProvider>> scopedTasksProviders)
|
||||
{
|
||||
_describesActivityType = describesActivityType;
|
||||
_activityActivator = activityActivator;
|
||||
_scopedTasksProviders = scopedTasksProviders;
|
||||
}
|
||||
|
||||
public async ValueTask<IEnumerable<ActivityType>> GetActivityTypesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var events = await GetTasksAsync(cancellationToken);
|
||||
return await GetActivityTypesAsync(events, cancellationToken).ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<ActivityType> GetActivityTypesAsync(IEnumerable<TaskDefinition> events, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var taskDefinition in events)
|
||||
yield return await CreateActivityTypeAsync(taskDefinition, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<ActivityType> CreateActivityTypeAsync(TaskDefinition taskDefinition, CancellationToken cancellationToken)
|
||||
{
|
||||
var descriptor = await _describesActivityType.DescribeAsync<RunTask>(cancellationToken);
|
||||
|
||||
descriptor.Type = taskDefinition.Name;
|
||||
descriptor.DisplayName = taskDefinition.DisplayName ?? taskDefinition.Name;
|
||||
descriptor.Description = taskDefinition.Description;
|
||||
descriptor.InputProperties = Array.Empty<ActivityInputDescriptor>();
|
||||
descriptor.Outcomes = taskDefinition.Outcomes?.ToArray() ?? new[] { OutcomeNames.Done };
|
||||
|
||||
return new ActivityType
|
||||
{
|
||||
Type = typeof(RunTask),
|
||||
TypeName = descriptor.Type,
|
||||
DisplayName = descriptor.DisplayName,
|
||||
Describe = () => descriptor,
|
||||
Description = descriptor.Description,
|
||||
ActivateAsync = async context =>
|
||||
{
|
||||
var activity = await _activityActivator.ActivateActivityAsync<RunTask>(context, cancellationToken);
|
||||
activity.TaskName = taskDefinition.Name;
|
||||
return activity;
|
||||
},
|
||||
CanExecuteAsync = async (context, instance) => await instance.CanExecuteAsync(context),
|
||||
ExecuteAsync = async (context, instance) => await instance.ExecuteAsync(context),
|
||||
ResumeAsync = async (context, instance) => await instance.ResumeAsync(context)
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<TaskDefinition>> GetTasksAsync(CancellationToken cancellationToken) =>
|
||||
await _scopedTasksProviders.UseServiceAsync(async taskProviders => await GetTasksAsync(taskProviders, cancellationToken).ToListAsync(cancellationToken));
|
||||
|
||||
private static async IAsyncEnumerable<TaskDefinition> GetTasksAsync(IEnumerable<ITasksProvider> taskProviders, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var commandProvider in taskProviders)
|
||||
{
|
||||
var commands = await commandProvider.GetTasksAsync(cancellationToken);
|
||||
|
||||
foreach (var command in commands)
|
||||
yield return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Bookmarks;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Providers.Bookmarks
|
||||
{
|
||||
public record EventBookmark(string EventName) : IBookmark
|
||||
{
|
||||
}
|
||||
|
||||
public class EventBookmarkProvider : BookmarkProvider<EventBookmark, EventReceived>
|
||||
{
|
||||
public override bool SupportsActivity(BookmarkProviderContext<EventReceived> context) => context.ActivityType.Type == typeof(EventReceived);
|
||||
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext<EventReceived> context, CancellationToken cancellationToken) => await GetBookmarksInternalAsync(context, cancellationToken).ToListAsync(cancellationToken);
|
||||
|
||||
private async IAsyncEnumerable<BookmarkResult> GetBookmarksInternalAsync(BookmarkProviderContext<EventReceived> context, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var eventName = ToLower(await context.ReadActivityPropertyAsync(x => x.EventName, cancellationToken))!;
|
||||
yield return Result(new EventBookmark(eventName), nameof(EventReceived));
|
||||
}
|
||||
|
||||
private static string? ToLower(string? s) => s?.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Bookmarks;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Providers.Bookmarks
|
||||
{
|
||||
public record TaskBookmark(string TaskName) : IBookmark
|
||||
{
|
||||
}
|
||||
|
||||
public class TaskBookmarkProvider : BookmarkProvider<TaskBookmark, RunTask>
|
||||
{
|
||||
public override bool SupportsActivity(BookmarkProviderContext<RunTask> context) => context.ActivityType.Type == typeof(RunTask);
|
||||
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext<RunTask> context, CancellationToken cancellationToken) => await GetBookmarksInternalAsync(context, cancellationToken).ToListAsync(cancellationToken);
|
||||
|
||||
private async IAsyncEnumerable<BookmarkResult> GetBookmarksInternalAsync(BookmarkProviderContext<RunTask> context, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var taskName = ToLower(await context.ReadActivityPropertyAsync(x => x.TaskName, cancellationToken))!;
|
||||
yield return Result(new TaskBookmark(taskName), nameof(RunTask));
|
||||
}
|
||||
|
||||
private static string? ToLower(string? s) => s?.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Options;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Providers.Commands
|
||||
{
|
||||
public class OptionsCommandsProvider : ICommandsProvider
|
||||
{
|
||||
private readonly ConductorOptions _options;
|
||||
public OptionsCommandsProvider(IOptions<ConductorOptions> options) => _options = options.Value;
|
||||
public ValueTask<IEnumerable<CommandDefinition>> GetCommandsAsync(CancellationToken cancellationToken) => new(_options.Commands);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Options;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Providers.Events
|
||||
{
|
||||
public class OptionsEventsProvider : IEventsProvider
|
||||
{
|
||||
private readonly ConductorOptions _options;
|
||||
public OptionsEventsProvider(IOptions<ConductorOptions> options) => _options = options.Value;
|
||||
public ValueTask<IEnumerable<EventDefinition>> GetEventsAsync(CancellationToken cancellationToken) => new(_options.Events);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Options;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Providers.Tasks
|
||||
{
|
||||
public class OptionsTasksProvider : ITasksProvider
|
||||
{
|
||||
private readonly ConductorOptions _options;
|
||||
public OptionsTasksProvider(IOptions<ConductorOptions> options) => _options = options.Value;
|
||||
public ValueTask<IEnumerable<TaskDefinition>> GetTasksAsync(CancellationToken cancellationToken) => new(_options.Tasks);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Services
|
||||
{
|
||||
public class ApplicationCommandsClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ConductorOptions _options;
|
||||
|
||||
public ApplicationCommandsClient(HttpClient httpClient, IOptions<ConductorOptions> options)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public async Task SendCommandAsync(SendCommandModel command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(command, _options.SerializerSettings);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("", content, cancellationToken);
|
||||
|
||||
// TODO: Handle response. If it contains instructions to resume the workflow, we can do so immediately.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Services
|
||||
{
|
||||
public class ApplicationTasksClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ConductorOptions _options;
|
||||
|
||||
public ApplicationTasksClient(HttpClient httpClient, IOptions<ConductorOptions> options)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public async Task RunTaskAsync(RunTaskModel task, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(task, _options.SerializerSettings);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("", content, cancellationToken);
|
||||
|
||||
// TODO: Handle response. If it contains instructions to resume the workflow, we can do so immediately.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Services
|
||||
{
|
||||
public interface ICommandsProvider
|
||||
{
|
||||
ValueTask<IEnumerable<CommandDefinition>> GetCommandsAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Services
|
||||
{
|
||||
public interface IEventsProvider
|
||||
{
|
||||
ValueTask<IEnumerable<EventDefinition>> GetEventsAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Services
|
||||
{
|
||||
public interface ITasksProvider
|
||||
{
|
||||
ValueTask<IEnumerable<TaskDefinition>> GetTasksAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ namespace Elsa.Activities.Console
|
|||
_output = output;
|
||||
}
|
||||
|
||||
[ActivityProperty(Hint = "The text to write.", SupportedSyntaxes = new[]{ SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The text to write.", SupportedSyntaxes = new[]{ SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? Text { get; set; }
|
||||
|
||||
private readonly TextWriter _output;
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ namespace Elsa.Activities.Dropbox.Activities
|
|||
_filesApi = filesApi;
|
||||
}
|
||||
|
||||
[ActivityProperty(Hint = "The file to store.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The file to store.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public byte[] FileData { get; set; } = default!;
|
||||
|
||||
[ActivityProperty(Hint = "The path to which the file should be saved.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The path to which the file should be saved.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string Path { get; set; } = default!;
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
|
|||
|
|
@ -28,22 +28,32 @@ namespace Elsa.Activities.Email
|
|||
_options = options.Value;
|
||||
}
|
||||
|
||||
[ActivityProperty(Hint = "The sender's email address.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The sender's email address.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? From { get; set; }
|
||||
|
||||
[ActivityProperty(Hint = "The recipients email addresses.", UIHint = ActivityPropertyUIHints.MultiText, DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript })]
|
||||
[ActivityInput(Hint = "The recipients email addresses.", UIHint = ActivityInputUIHints.MultiText, DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript })]
|
||||
public ICollection<string> To { get; set; } = new List<string>();
|
||||
|
||||
[ActivityProperty(Hint = "The cc recipients email addresses. (Optional)", UIHint = ActivityPropertyUIHints.MultiText, DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript })]
|
||||
[ActivityInput(
|
||||
Hint = "The cc recipients email addresses.",
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript },
|
||||
Category = "More")]
|
||||
public ICollection<string> Cc { get; set; } = new List<string>();
|
||||
|
||||
[ActivityProperty(Hint = "The Bcc recipients email addresses. (Optional)", UIHint = ActivityPropertyUIHints.MultiText, DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript })]
|
||||
[ActivityInput(
|
||||
Hint = "The Bcc recipients email addresses.",
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript },
|
||||
Category = "More")]
|
||||
public ICollection<string> Bcc { get; set; } = new List<string>();
|
||||
|
||||
[ActivityProperty(Hint = "The subject of the email message.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The subject of the email message.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? Subject { get; set; }
|
||||
|
||||
[ActivityProperty(Hint = "The body of the email message.", UIHint = ActivityPropertyUIHints.MultiLine, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The body of the email message.", UIHint = ActivityInputUIHints.MultiLine, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? Body { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MailKit" Version="2.11.1" />
|
||||
<PackageReference Include="MailKit" Version="2.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@ namespace Elsa.Activities.Entity
|
|||
)]
|
||||
public class EntityChanged : Activity
|
||||
{
|
||||
[ActivityProperty(UIHint = ActivityPropertyUIHints.SingleLine, Hint = "The Entity Name to observe. Matches any entity if no value is specified.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(UIHint = ActivityInputUIHints.SingleLine, Hint = "The Entity Name to observe. Matches any entity if no value is specified.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? EntityName { get; set; }
|
||||
|
||||
[ActivityProperty(
|
||||
UIHint = ActivityPropertyUIHints.Dropdown,
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Hint = "The Entity Changed Action to observe. Matches any action if no value is specified.",
|
||||
Options = new[] { "Added", "Updated", "Deleted" },
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
|
|
|
|||
|
|
@ -23,15 +23,15 @@ namespace Elsa.Activities.Entity.Bookmarks
|
|||
|
||||
public class EntityChangedWorkflowTriggerProvider : BookmarkProvider<EntityChangedBookmark, EntityChanged>
|
||||
{
|
||||
public override async ValueTask<IEnumerable<IBookmark>> GetBookmarksAsync(BookmarkProviderContext<EntityChanged> context, CancellationToken cancellationToken) =>
|
||||
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext<EntityChanged> context, CancellationToken cancellationToken) =>
|
||||
new[]
|
||||
{
|
||||
new EntityChangedBookmark(
|
||||
entityName: await context.Activity.GetPropertyValueAsync(x => x.EntityName, cancellationToken),
|
||||
action: await context.Activity.GetPropertyValueAsync(x => x.Action, cancellationToken),
|
||||
Result(new EntityChangedBookmark(
|
||||
entityName: await context.ReadActivityPropertyAsync(x => x.EntityName, cancellationToken),
|
||||
action: await context.ReadActivityPropertyAsync(x => x.Action, cancellationToken),
|
||||
contextId: context.ActivityExecutionContext.WorkflowExecutionContext.WorkflowInstance.ContextId,
|
||||
correlationId: context.ActivityExecutionContext.WorkflowExecutionContext.WorkflowInstance.CorrelationId
|
||||
)
|
||||
))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ namespace Elsa.Activities.Entity.Extensions
|
|||
public static class WorkflowRunnerExtensions
|
||||
{
|
||||
// TODO: Design multi-tenancy
|
||||
private const string TenantId = default;
|
||||
private const string? TenantId = default;
|
||||
|
||||
public static async Task TriggerEntityChangedWorkflowsAsync(
|
||||
this IWorkflowDispatcher workflowDispatcher,
|
||||
|
|
@ -37,7 +37,7 @@ namespace Elsa.Activities.Entity.Extensions
|
|||
correlationId
|
||||
);
|
||||
|
||||
await workflowDispatcher.DispatchAsync(new TriggerWorkflowsRequest(activityType, bookmark, trigger, input, correlationId, contextId, TenantId), cancellationToken);
|
||||
await workflowDispatcher.DispatchAsync(new TriggerWorkflowsRequest(activityType, bookmark, trigger, input, correlationId, default, contextId, TenantId), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Models;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Design;
|
||||
|
|
@ -24,28 +20,17 @@ namespace Elsa.Activities.Http
|
|||
)]
|
||||
public class HttpEndpoint : Activity
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IEnumerable<IHttpRequestBodyParser> _parsers;
|
||||
|
||||
public HttpEndpoint(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IEnumerable<IHttpRequestBodyParser> parsers)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_parsers = parsers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The path that triggers this activity.
|
||||
/// </summary>
|
||||
[ActivityProperty(Hint = "The relative path that triggers this activity.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The relative path that triggers this activity.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public PathString Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP methods that triggers this activity.
|
||||
/// </summary>
|
||||
[ActivityProperty(
|
||||
UIHint = ActivityPropertyUIHints.CheckList,
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.CheckList,
|
||||
Hint = "The HTTP methods that trigger this activity.",
|
||||
Options = new[] { "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD" },
|
||||
DefaultValue = new[] { "GET" },
|
||||
|
|
@ -57,7 +42,7 @@ namespace Elsa.Activities.Http
|
|||
/// A value indicating whether the HTTP request content body should be read and stored as part of the HTTP request model.
|
||||
/// The stored format depends on the content-type header.
|
||||
/// </summary>
|
||||
[ActivityProperty(
|
||||
[ActivityInput(
|
||||
Hint = "A value indicating whether the HTTP request content body should be read and stored as part of the HTTP request model. The stored format depends on the content-type header.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public bool ReadContent { get; set; }
|
||||
|
|
@ -66,39 +51,20 @@ namespace Elsa.Activities.Http
|
|||
/// The <see cref="Type"/> to parse the received request content into if <seealso cref="ReadContent"/> is set to true.
|
||||
/// If not set, the content will be parse into a default type, depending on the parser associated with the received content-type header.
|
||||
/// </summary>
|
||||
[ActivityProperty(Category = PropertyCategories.Advanced)]
|
||||
[ActivityInput(Category = PropertyCategories.Advanced)]
|
||||
public Type? TargetType { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context) => context.WorkflowExecutionContext.IsFirstPass ? await ExecuteInternalAsync(context.CancellationToken) : Suspend();
|
||||
[ActivityOutput(Hint = "The received HTTP request.")]
|
||||
public HttpRequestModel? Request { get; set; }
|
||||
|
||||
protected override ValueTask<IActivityExecutionResult> OnResumeAsync(ActivityExecutionContext context) => ExecuteInternalAsync(context.CancellationToken);
|
||||
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) => context.WorkflowExecutionContext.IsFirstPass ? ExecuteInternal(context) : Suspend();
|
||||
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context) => ExecuteInternal(context);
|
||||
|
||||
private async ValueTask<IActivityExecutionResult> ExecuteInternalAsync(CancellationToken cancellationToken)
|
||||
private IActivityExecutionResult ExecuteInternal(ActivityExecutionContext context)
|
||||
{
|
||||
var request = _httpContextAccessor.HttpContext.Request;
|
||||
|
||||
var model = new HttpRequestModel
|
||||
{
|
||||
Path = new Uri(request.Path.ToString(), UriKind.Relative),
|
||||
QueryString = request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()),
|
||||
Headers = request.Headers.ToDictionary(x => x.Key, x => x.Value.ToString()),
|
||||
Method = request.Method
|
||||
};
|
||||
|
||||
if (ReadContent)
|
||||
{
|
||||
var parser = SelectContentParser(request.ContentType);
|
||||
model.Body = await parser.ParseAsync(request, TargetType, cancellationToken);
|
||||
}
|
||||
|
||||
var model = context.GetInput<HttpRequestModel>()!;
|
||||
Request = model;
|
||||
return Done(model);
|
||||
}
|
||||
|
||||
private IHttpRequestBodyParser SelectContentParser(string contentType)
|
||||
{
|
||||
var simpleContentType = contentType?.Split(';').First();
|
||||
var formatters = _parsers.OrderByDescending(x => x.Priority).ToList();
|
||||
return formatters.FirstOrDefault(x => x.SupportedContentTypes.Contains(simpleContentType, StringComparer.OrdinalIgnoreCase)) ?? formatters.Last();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,10 +29,10 @@ namespace Elsa.Activities.Http
|
|||
|
||||
private IStringLocalizer<Redirect> T { get; }
|
||||
|
||||
[ActivityProperty(Hint = "The URL to redirect to (HTTP 302).", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The URL to redirect to (HTTP 302).", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public Uri Location { get; set; } = default!;
|
||||
|
||||
[ActivityProperty(Hint = "Whether or not the redirect is permanent (HTTP 301).", SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "Whether or not the redirect is permanent (HTTP 301).", SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public bool Permanent { get; set; }
|
||||
|
||||
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
|
||||
|
|
|
|||
|
|
@ -42,14 +42,14 @@ namespace Elsa.Activities.Http
|
|||
/// <summary>
|
||||
/// The URL to invoke.
|
||||
/// </summary>
|
||||
[ActivityProperty(Hint = "The URL to send the HTTP request to.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The URL to send the HTTP request to.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public Uri? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP method to use.
|
||||
/// </summary>
|
||||
[ActivityProperty(
|
||||
UIHint = ActivityPropertyUIHints.Dropdown,
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Hint = "The HTTP method to use when making the request.",
|
||||
Options = new[] { "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD" },
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
|
|
@ -59,42 +59,42 @@ namespace Elsa.Activities.Http
|
|||
/// <summary>
|
||||
/// The body to send along with the request.
|
||||
/// </summary>
|
||||
[ActivityProperty(Hint = "The HTTP content to send along with the request.", UIHint = ActivityPropertyUIHints.MultiLine, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The HTTP content to send along with the request.", UIHint = ActivityInputUIHints.MultiLine, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Content Type header to send along with the request body.
|
||||
/// </summary>
|
||||
[ActivityProperty(
|
||||
UIHint = ActivityPropertyUIHints.Dropdown,
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Hint = "The content type to send with the request.",
|
||||
Options = new[] { "text/plain", "text/html", "application/json", "application/xml", "application/x-www-form-urlencoded" },
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string? ContentType { get; set; }
|
||||
|
||||
[ActivityProperty(Hint = "The Authorization header value to send.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The Authorization header value to send.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? Authorization { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The headers to send along with the request.
|
||||
/// </summary>
|
||||
[ActivityProperty(Hint = "Additional headers to send along with the request.", UIHint = ActivityPropertyUIHints.Json)]
|
||||
[ActivityInput(Hint = "Additional headers to send along with the request.", UIHint = ActivityInputUIHints.Json)]
|
||||
public HttpRequestHeaders RequestHeaders { get; set; } = new();
|
||||
|
||||
[ActivityProperty(Hint = "Read the content of the response.", SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "Read the content of the response.", SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public bool ReadContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of HTTP status codes this activity can handle.
|
||||
/// </summary>
|
||||
[ActivityProperty(
|
||||
[ActivityInput(
|
||||
Hint = "A list of possible HTTP status codes to handle.",
|
||||
UIHint = ActivityPropertyUIHints.MultiText,
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public ICollection<int> SupportedStatusCodes { get; set; } = new HashSet<int>(new[] { 200 });
|
||||
public ICollection<int>? SupportedStatusCodes { get; set; } = new HashSet<int>(new[] { 200 });
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
|
|
@ -120,13 +120,13 @@ namespace Elsa.Activities.Http
|
|||
|
||||
var statusCode = (int) response.StatusCode;
|
||||
var statusOutcome = statusCode.ToString();
|
||||
var isSupportedStatusCode = SupportedStatusCodes.Contains(statusCode);
|
||||
var isSupportedStatusCode = SupportedStatusCodes?.Contains(statusCode) == true;
|
||||
var outcomes = new List<string> { OutcomeNames.Done, statusOutcome };
|
||||
|
||||
if (!isSupportedStatusCode)
|
||||
outcomes.Add("UnSupportedStatusCode");
|
||||
|
||||
return Combine(Outcomes(outcomes), Output(responseModel));
|
||||
return Combine(Output(responseModel), Outcomes(outcomes));
|
||||
}
|
||||
|
||||
private IHttpResponseBodyParser SelectContentParser(string contentType)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@ using Elsa.Builders;
|
|||
using Elsa.Services.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elsa.Activities.Http
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ using Elsa.Builders;
|
|||
using Elsa.Services.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elsa.Activities.Http
|
||||
|
|
|
|||
|
|
@ -34,35 +34,56 @@ namespace Elsa.Activities.Http
|
|||
/// <summary>
|
||||
/// The HTTP status code to return.
|
||||
/// </summary>
|
||||
[ActivityProperty(
|
||||
UIHint = ActivityPropertyUIHints.Dropdown,
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Hint = "The HTTP status code to write.",
|
||||
Options = new[] { HttpStatusCode.OK, HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.NoContent, HttpStatusCode.Redirect, HttpStatusCode.BadRequest, HttpStatusCode.NotFound, HttpStatusCode.Conflict },
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid },
|
||||
DefaultValue = HttpStatusCode.OK,
|
||||
Category = PropertyCategories.Advanced
|
||||
)]
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK;
|
||||
|
||||
/// <summary>
|
||||
/// The content to send along with the response
|
||||
/// </summary>
|
||||
[ActivityProperty(Hint = "The HTTP content to write.", UIHint = ActivityPropertyUIHints.MultiLine, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Hint = "The HTTP content to write.", UIHint = ActivityInputUIHints.MultiLine, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Content-Type header to send along with the response.
|
||||
/// </summary>
|
||||
[ActivityProperty(
|
||||
UIHint = ActivityPropertyUIHints.Dropdown,
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Hint = "The HTTP content type header to write.",
|
||||
Options = new[] { "text/plain", "text/html", "application/json", "application/xml" },
|
||||
DefaultValue = "text/plain",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string? ContentType { get; set; }
|
||||
public string? ContentType { get; set; } = "text/plain";
|
||||
|
||||
/// <summary>
|
||||
/// The character set to use when writing the response.
|
||||
/// </summary>
|
||||
[ActivityInput(
|
||||
Hint = "The character set to use when writing the response.",
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Options = new[] { "utf-8", "ASCII", "ANSI", "ISO-8859-1" },
|
||||
DefaultValue = "utf-8",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid },
|
||||
Category = PropertyCategories.Advanced)]
|
||||
public string CharSet { get; set; } = "utf-8";
|
||||
|
||||
/// <summary>
|
||||
/// The headers to send along with the response.
|
||||
/// </summary>
|
||||
[ActivityProperty(Hint = "Additional headers to write.", UIHint = ActivityPropertyUIHints.Json)]
|
||||
[ActivityInput(
|
||||
Hint = "Additional headers to write.",
|
||||
UIHint = ActivityInputUIHints.MultiLine,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
SupportedSyntaxes = new[]{ SyntaxNames.JavaScript, SyntaxNames.Liquid, SyntaxNames.Json },
|
||||
Category = PropertyCategories.Advanced
|
||||
)]
|
||||
public HttpResponseHeaders? ResponseHeaders { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
@ -74,7 +95,7 @@ namespace Elsa.Activities.Http
|
|||
return Fault(T["Response has already started"]!);
|
||||
|
||||
response.StatusCode = (int) StatusCode;
|
||||
response.ContentType = ContentType;
|
||||
response.ContentType = $"{ContentType};charset={CharSet}";
|
||||
|
||||
var headers = ResponseHeaders;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ namespace Elsa.Activities.Http.Bookmarks
|
|||
|
||||
public class HttpEndpointBookmarkProvider : BookmarkProvider<HttpEndpointBookmark, HttpEndpoint>
|
||||
{
|
||||
public override async ValueTask<IEnumerable<IBookmark>> GetBookmarksAsync(BookmarkProviderContext<HttpEndpoint> context, CancellationToken cancellationToken)
|
||||
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext<HttpEndpoint> context, CancellationToken cancellationToken)
|
||||
{
|
||||
var path = ToLower(await context.Activity.GetPropertyValueAsync(x => x.Path, cancellationToken))!;
|
||||
var path = ToLower(await context.ReadActivityPropertyAsync(x => x.Path, cancellationToken))!;
|
||||
var correlationId = ToLower(context.ActivityExecutionContext.WorkflowExecutionContext.CorrelationId);
|
||||
var methods = (await context.Activity.GetPropertyValueAsync(x => x.Methods, cancellationToken))?.Select(x => x.ToLowerInvariant()) ?? Enumerable.Empty<string>();
|
||||
var methods = (await context.ReadActivityPropertyAsync(x => x.Methods, cancellationToken))?.Select(x => x.ToLowerInvariant()) ?? Enumerable.Empty<string>();
|
||||
|
||||
HttpEndpointBookmark CreateBookmark(string method) => new(path, method, correlationId);
|
||||
BookmarkResult CreateBookmark(string method) => Result(new(path, method, correlationId));
|
||||
return methods.Select(CreateBookmark);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="5.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Models;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Elsa.Activities.Signaling.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Open.Linq.AsyncExtensions;
|
||||
|
||||
namespace Elsa.Activities.Http.Endpoints.Signals
|
||||
{
|
||||
|
|
@ -12,23 +10,18 @@ namespace Elsa.Activities.Http.Endpoints.Signals
|
|||
[Produces("application/json")]
|
||||
public class DispatchEndpoint : ControllerBase
|
||||
{
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly ISignaler _signaler;
|
||||
|
||||
public DispatchEndpoint(ITokenService tokenService, ISignaler signaler)
|
||||
public DispatchEndpoint(ISignaler signaler)
|
||||
{
|
||||
_tokenService = tokenService;
|
||||
_signaler = signaler;
|
||||
}
|
||||
|
||||
[HttpGet, HttpPost]
|
||||
public async Task<IActionResult> Handle(string token, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Handle(string token)
|
||||
{
|
||||
if (!_tokenService.TryDecryptToken(token, out Signal signal))
|
||||
return NotFound();
|
||||
|
||||
await _signaler.DispatchSignalAsync(signal.Name, null, signal.WorkflowInstanceId, cancellationToken);
|
||||
return Accepted();
|
||||
var pendingWorkflows = await _signaler.DispatchSignalTokenAsync(token).ToList();
|
||||
return Accepted(pendingWorkflows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Models;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Elsa.Activities.Signaling.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Open.Linq.AsyncExtensions;
|
||||
|
||||
namespace Elsa.Activities.Http.Endpoints.Signals
|
||||
{
|
||||
|
|
@ -12,26 +10,17 @@ namespace Elsa.Activities.Http.Endpoints.Signals
|
|||
[Produces("application/json")]
|
||||
public class TriggerEndpoint : ControllerBase
|
||||
{
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly ISignaler _signaler;
|
||||
public TriggerEndpoint(ISignaler signaler) => _signaler = signaler;
|
||||
|
||||
public TriggerEndpoint(ITokenService tokenService, ISignaler signaler)
|
||||
{
|
||||
_tokenService = tokenService;
|
||||
_signaler = signaler;
|
||||
}
|
||||
|
||||
[HttpGet, HttpPost]
|
||||
public async Task<IActionResult> Handle(string token, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Handle(string token)
|
||||
{
|
||||
if (!_tokenService.TryDecryptToken(token, out Signal signal))
|
||||
return NotFound();
|
||||
var result = await _signaler.TriggerSignalTokenAsync(token).ToList();
|
||||
|
||||
await _signaler.TriggerSignalAsync(signal.Name, null, signal.WorkflowInstanceId, cancellationToken);
|
||||
|
||||
return HttpContext.Response.HasStarted
|
||||
? new EmptyResult()
|
||||
: Accepted();
|
||||
: Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
using Elsa.Activities.Http.Models;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Elsa.Activities.Signaling.Extensions;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Activities.Http.Extensions
|
||||
|
|
@ -8,12 +8,7 @@ namespace Elsa.Activities.Http.Extensions
|
|||
{
|
||||
public static string GenerateSignalUrl(this ActivityExecutionContext context, string signal)
|
||||
{
|
||||
var workflowInstanceId =
|
||||
context.WorkflowExecutionContext.WorkflowInstance.Id;
|
||||
|
||||
var payload = new Signal(signal, workflowInstanceId);
|
||||
var tokenService = context.GetService<ITokenService>();
|
||||
var token = tokenService.CreateToken(payload);
|
||||
var token = context.GenerateSignalToken(signal);
|
||||
var url = $"/signals/trigger/{token}";
|
||||
var absoluteUrlProvider = context.GetService<IAbsoluteUrlProvider>();
|
||||
return absoluteUrlProvider.ToAbsoluteUrl(url).ToString();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
using Elsa.Activities.Http.Middleware;
|
||||
using Elsa.Activities.Http.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
|
|
@ -7,8 +10,12 @@ namespace Microsoft.AspNetCore.Builder
|
|||
{
|
||||
public static IApplicationBuilder UseHttpActivities(this IApplicationBuilder app)
|
||||
{
|
||||
return app
|
||||
.UseMiddleware<HttpEndpointMiddleware>();
|
||||
var options = app.ApplicationServices.GetRequiredService<IOptions<HttpActivityOptions>>().Value;
|
||||
var basePath = options.BasePath;
|
||||
|
||||
return basePath != null
|
||||
? app.Map(basePath.Value, branch => branch.UseMiddleware<HttpEndpointMiddleware>())
|
||||
: app.UseMiddleware<HttpEndpointMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,5 +54,16 @@ namespace Elsa.Activities.Http.Extensions
|
|||
correlationId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool GetUseDispatch(this HttpRequest request)
|
||||
{
|
||||
if (request.Query.ContainsKey("x-dispatch"))
|
||||
return true;
|
||||
|
||||
if (request.Headers.ContainsKey("X-Dispatch"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,16 +26,13 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
public static IServiceCollection AddHttpServices(this IServiceCollection services, Action<HttpActivityOptions>? configureOptions = null)
|
||||
{
|
||||
if (configureOptions != null)
|
||||
{
|
||||
if (configureOptions != null)
|
||||
services.Configure(configureOptions);
|
||||
}
|
||||
|
||||
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.AddHttpClient(nameof(SendHttpRequest));
|
||||
|
||||
services
|
||||
.AddSingleton<ITokenService, TokenService>()
|
||||
.AddSingleton<IHttpRequestBodyParser, DefaultHttpRequestBodyParser>()
|
||||
.AddSingleton<IHttpRequestBodyParser, JsonHttpRequestBodyParser>()
|
||||
.AddSingleton<IHttpRequestBodyParser, FormHttpRequestBodyParser>()
|
||||
|
|
@ -47,6 +44,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
.AddHttpContextAccessor()
|
||||
.AddNotificationHandlers(typeof(ConfigureJavaScriptEngine))
|
||||
.AddLiquidFilter<SignalUrlFilter>("signal_url")
|
||||
.AddJavaScriptTypeDefinitionProvider<HttpTypeDefinitionProvider>()
|
||||
.AddDataProtection();
|
||||
|
||||
return services;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Extensions;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Elsa.Scripting.JavaScript.Events;
|
||||
using Elsa.Scripting.JavaScript.Messages;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Activities.Http.JavaScript
|
||||
{
|
||||
public class ConfigureJavaScriptEngine : INotificationHandler<EvaluatingJavaScriptExpression>
|
||||
public class ConfigureJavaScriptEngine : INotificationHandler<EvaluatingJavaScriptExpression>, INotificationHandler<RenderingTypeScriptDefinitions>
|
||||
{
|
||||
private readonly IAbsoluteUrlProvider _absoluteUrlProvider;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
|
@ -42,5 +43,16 @@ namespace Elsa.Activities.Http.JavaScript
|
|||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Handle(RenderingTypeScriptDefinitions notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var output = notification.Output;
|
||||
|
||||
output.AppendLine("declare function queryString(name: string): string;");
|
||||
output.AppendLine("declare function absoluteUrl(url: string): string;");
|
||||
output.AppendLine("declare function signalUrl(signal: string): string;");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Activities.Http.Models;
|
||||
using Elsa.Scripting.JavaScript.Services;
|
||||
|
||||
namespace Elsa.Activities.Http.JavaScript
|
||||
{
|
||||
public class HttpTypeDefinitionProvider : TypeDefinitionProvider
|
||||
{
|
||||
public override IEnumerable<Type> CollectTypes(TypeDefinitionContext context)
|
||||
{
|
||||
return new[] { typeof(HttpRequestModel) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Models;
|
||||
using Elsa.Scripting.Liquid.Helpers;
|
||||
using Elsa.Scripting.Liquid.Messages;
|
||||
using Fluid;
|
||||
|
|
@ -7,7 +8,6 @@ using Fluid.Values;
|
|||
using MediatR;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Elsa.Activities.Http.Liquid
|
||||
{
|
||||
|
|
@ -29,6 +29,7 @@ namespace Elsa.Activities.Http.Liquid
|
|||
|
||||
options.Scope.SetValue("Request", new ObjectValue(new LiquidRequestAccessor()));
|
||||
|
||||
options.MemberAccessStrategy.Register<HttpResponseModel>();
|
||||
options.MemberAccessStrategy.Register<LiquidRequestAccessor, FluidValue>((_, name, _) =>
|
||||
{
|
||||
var request = _httpContextAccessor.HttpContext?.Request;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Bookmarks;
|
||||
using Elsa.Activities.Http.Extensions;
|
||||
using Elsa.Activities.Http.Models;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Persistence.Specifications.WorkflowInstances;
|
||||
using Elsa.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Open.Linq.AsyncExtensions;
|
||||
|
||||
namespace Elsa.Activities.Http.Middleware
|
||||
{
|
||||
public class HttpEndpointMiddleware
|
||||
{
|
||||
// TODO: Design multi-tenancy.
|
||||
private const string? TenantId = default;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public HttpEndpointMiddleware(RequestDelegate next) => _next = next;
|
||||
|
||||
public async Task InvokeAsync(
|
||||
HttpContext httpContext,
|
||||
IWorkflowLaunchpad workflowLaunchpad,
|
||||
IWorkflowInstanceStore workflowInstanceStore,
|
||||
IWorkflowRegistry workflowRegistry,
|
||||
IWorkflowBlueprintReflector workflowBlueprintReflector,
|
||||
IEnumerable<IHttpRequestBodyParser> contentParsers)
|
||||
{
|
||||
var cancellationToken = CancellationToken.None; // Prevent half-way request abortion (which also happens when WriteHttpResponse writes to the response).
|
||||
var path = httpContext.Request.Path.Value.ToLowerInvariant();
|
||||
var method = httpContext.Request.Method!.ToLowerInvariant();
|
||||
var request = httpContext.Request;
|
||||
request.TryGetCorrelationId(out var correlationId);
|
||||
var useDispatch = httpContext.Request.GetUseDispatch();
|
||||
|
||||
const string activityType = nameof(HttpEndpoint);
|
||||
var trigger = new HttpEndpointBookmark(path, method, null);
|
||||
var bookmark = new HttpEndpointBookmark(path, method, correlationId?.ToLowerInvariant());
|
||||
var collectWorkflowsContext = new CollectWorkflowsContext(activityType, bookmark, trigger, correlationId, default, default, TenantId);
|
||||
var pendingWorkflows = await workflowLaunchpad.CollectWorkflowsAsync(collectWorkflowsContext, cancellationToken).ToList();
|
||||
var pendingWorkflowInstanceIds = pendingWorkflows.Select(x => x.WorkflowInstanceId).Distinct();
|
||||
var pendingWorkflowInstances = (await workflowInstanceStore.FindManyAsync(new WorkflowInstanceIdsSpecification(pendingWorkflowInstanceIds), cancellationToken: cancellationToken)).ToDictionary(x => x.Id);
|
||||
var workflowDefinitionIds = pendingWorkflowInstances.Values.Select(x => x.DefinitionId).Distinct().ToHashSet();
|
||||
var workflowBlueprints = (await workflowRegistry.FindManyAsync(x => x.IsPublished && workflowDefinitionIds.Contains(x.Id), cancellationToken)).ToDictionary(x => x.Id);
|
||||
var serviceProvider = httpContext.RequestServices;
|
||||
var workflowBlueprintWrappers = (await Task.WhenAll(workflowBlueprints.Values.Select(async x => await workflowBlueprintReflector.ReflectAsync(serviceProvider, x, cancellationToken)))).ToDictionary(x => x.WorkflowBlueprint.Id);
|
||||
|
||||
var commonInputModel = new HttpRequestModel(
|
||||
new Uri(request.Path.ToString(), UriKind.Relative),
|
||||
request.Method,
|
||||
request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()),
|
||||
request.Headers.ToDictionary(x => x.Key, x => x.Value.ToString())
|
||||
);
|
||||
|
||||
var orderedContentParsers = contentParsers.OrderByDescending(x => x.Priority).ToList();
|
||||
var simpleContentType = request.ContentType?.Split(';').First();
|
||||
var contentParser = orderedContentParsers.FirstOrDefault(x => x.SupportedContentTypes.Contains(simpleContentType, StringComparer.OrdinalIgnoreCase)) ?? orderedContentParsers.Last();
|
||||
|
||||
foreach (var pendingWorkflow in pendingWorkflows)
|
||||
{
|
||||
var pendingWorkflowInstance = pendingWorkflowInstances[pendingWorkflow.WorkflowInstanceId];
|
||||
var workflowBlueprintWrapper = workflowBlueprintWrappers[pendingWorkflowInstance.DefinitionId];
|
||||
var activityWrapper = workflowBlueprintWrapper.GetActivity<HttpEndpoint>(pendingWorkflow.ActivityId!);
|
||||
var readContent = await activityWrapper!.EvaluatePropertyValueAsync(x => x.ReadContent, cancellationToken);
|
||||
var inputModel = commonInputModel;
|
||||
|
||||
if (readContent)
|
||||
{
|
||||
var targetType = await activityWrapper.EvaluatePropertyValueAsync(x => x.TargetType, cancellationToken);
|
||||
inputModel = inputModel with { Body = await contentParser.ParseAsync(request, targetType, cancellationToken) };
|
||||
}
|
||||
|
||||
if (useDispatch)
|
||||
await workflowLaunchpad.DispatchPendingWorkflowAsync(pendingWorkflow, inputModel, cancellationToken);
|
||||
else
|
||||
await workflowLaunchpad.ExecutePendingWorkflowsAsync(pendingWorkflows, inputModel, cancellationToken);
|
||||
}
|
||||
|
||||
if (pendingWorkflows.Count > 0)
|
||||
{
|
||||
if (useDispatch)
|
||||
{
|
||||
httpContext.Response.ContentType = "application/json";
|
||||
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(pendingWorkflows), cancellationToken);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await _next(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Bookmarks;
|
||||
using Elsa.Activities.Http.Extensions;
|
||||
using Elsa.Dispatch;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Activities.Http.Middleware
|
||||
{
|
||||
public class HttpEndpointMiddleware
|
||||
{
|
||||
// TODO: Design multi-tenancy.
|
||||
private const string? TenantId = default;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public HttpEndpointMiddleware(RequestDelegate next) => _next = next;
|
||||
|
||||
public async Task InvokeAsync(HttpContext httpContext, IMediator mediator)
|
||||
{
|
||||
var path = httpContext.Request.Path.Value.ToLowerInvariant();
|
||||
var method = httpContext.Request.Method!.ToLowerInvariant();
|
||||
var cancellationToken = httpContext.RequestAborted;
|
||||
httpContext.Request.TryGetCorrelationId(out var correlationId);
|
||||
|
||||
const string activityType = nameof(HttpEndpoint);
|
||||
var trigger = new HttpEndpointBookmark(path, method, null);
|
||||
var bookmark = new HttpEndpointBookmark(path, method, correlationId?.ToLowerInvariant());
|
||||
var workflowInstanceCount = await mediator.Send(new TriggerWorkflowsRequest(activityType, bookmark, trigger, default, correlationId, default, TenantId), cancellationToken);
|
||||
|
||||
if (workflowInstanceCount == 0)
|
||||
{
|
||||
await _next(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue