diff --git a/.github/workflows/publish-dashboard.yml b/.github/workflows/publish-dashboard.yml
deleted file mode 100644
index 922a5fd36..000000000
--- a/.github/workflows/publish-dashboard.yml
+++ /dev/null
@@ -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 }}
diff --git a/.github/workflows/publish-latest-dashboard-and-server-docker.yml b/.github/workflows/publish-latest-dashboard-and-server-docker.yml
new file mode 100644
index 000000000..cf70a6305
--- /dev/null
+++ b/.github/workflows/publish-latest-dashboard-and-server-docker.yml
@@ -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 }}
diff --git a/.github/workflows/publish-latest-dashboard-docker.yml b/.github/workflows/publish-latest-dashboard-docker.yml
new file mode 100644
index 000000000..ce702328d
--- /dev/null
+++ b/.github/workflows/publish-latest-dashboard-docker.yml
@@ -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 }}
diff --git a/.github/workflows/publish-latest-dashboard.yml b/.github/workflows/publish-latest-dashboard.yml
deleted file mode 100644
index 755982d6f..000000000
--- a/.github/workflows/publish-latest-dashboard.yml
+++ /dev/null
@@ -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 }}
diff --git a/.github/workflows/publish-latest-server-docker.yml b/.github/workflows/publish-latest-server-docker.yml
new file mode 100644
index 000000000..216a8e5e5
--- /dev/null
+++ b/.github/workflows/publish-latest-server-docker.yml
@@ -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 }}
diff --git a/Elsa.sln b/Elsa.sln
index 8cf20f074..f93616a0b 100644
--- a/Elsa.sln
+++ b/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}
diff --git a/Elsa.sln.DotSettings b/Elsa.sln.DotSettings
index 87c7f152c..24bbaf7f4 100644
--- a/Elsa.sln.DotSettings
+++ b/Elsa.sln.DotSettings
@@ -16,7 +16,10 @@
True
True
True
+ True
True
+ True
+
True
True
True
diff --git a/README.md b/README.md
index 049b84bec..eabba2930 100644
--- a/README.md
+++ b/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.
+## 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())
+ .BuildServiceProvider();
+
+ var workflowRunner = services.GetRequiredService();
+ await workflowRunner.BuildAndStartWorkflowAsync();
+ }
+ }
+}
+```
+
+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
diff --git a/appveyor.yml b/appveyor.yml
index 4fd502b9c..8c4aad64d 100644
--- a/appveyor.yml
+++ b/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: >-
diff --git a/build-and-push-dashboard-with-docker.ps1 b/build-and-push-dashboard-with-docker.ps1
new file mode 100644
index 000000000..38a965e35
--- /dev/null
+++ b/build-and-push-dashboard-with-docker.ps1
@@ -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
\ No newline at end of file
diff --git a/build-and-run-dashboard-monolith-with-docker.ps1 b/build-and-run-dashboard-monolith-with-docker.ps1
new file mode 100644
index 000000000..452c720c8
--- /dev/null
+++ b/build-and-run-dashboard-monolith-with-docker.ps1
@@ -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
\ No newline at end of file
diff --git a/build-and-run-dashboard-with-docker.ps1 b/build-and-run-dashboard-with-docker.ps1
new file mode 100644
index 000000000..5a4166da9
--- /dev/null
+++ b/build-and-run-dashboard-with-docker.ps1
@@ -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
\ No newline at end of file
diff --git a/build-and-run-server-with-docker.ps1 b/build-and-run-server-with-docker.ps1
new file mode 100644
index 000000000..26eb65692
--- /dev/null
+++ b/build-and-run-server-with-docker.ps1
@@ -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
\ No newline at end of file
diff --git a/build-assets-and-run-dashboard-monolith.ps1 b/build-assets-and-run-dashboard-monolith.ps1
new file mode 100644
index 000000000..37fab40f0
--- /dev/null
+++ b/build-assets-and-run-dashboard-monolith.ps1
@@ -0,0 +1,3 @@
+.\build-assets
+cd ../../../..
+.\run-dashboard-monolith
\ No newline at end of file
diff --git a/build-assets.ps1 b/build-assets.ps1
new file mode 100644
index 000000000..32502ecfa
--- /dev/null
+++ b/build-assets.ps1
@@ -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
\ No newline at end of file
diff --git a/doc/elsa-2-hello-world-http.gif b/doc/elsa-2-hello-world-http.gif
index 8364e3dec..ba589cabf 100644
Binary files a/doc/elsa-2-hello-world-http.gif and b/doc/elsa-2-hello-world-http.gif differ
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 302bbab1a..cf478d2ed 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -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"
\ No newline at end of file
+ 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"
\ No newline at end of file
diff --git a/docker/Dockerfile b/docker/Dockerfile
index f1423aa73..112164087 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -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
diff --git a/docker/Dockerfile-elsa-dashboard b/docker/Dockerfile-elsa-dashboard
new file mode 100644
index 000000000..3a7dd9eb7
--- /dev/null
+++ b/docker/Dockerfile-elsa-dashboard
@@ -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"]
diff --git a/docker/Dockerfile-with-npm b/docker/Dockerfile-elsa-dashboard-and-server
similarity index 96%
rename from docker/Dockerfile-with-npm
rename to docker/Dockerfile-elsa-dashboard-and-server
index 42b0ae407..7a8ec7113 100644
--- a/docker/Dockerfile-with-npm
+++ b/docker/Dockerfile-elsa-dashboard-and-server
@@ -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
diff --git a/docker/Dockerfile-elsa-server b/docker/Dockerfile-elsa-server
new file mode 100644
index 000000000..22e23c569
--- /dev/null
+++ b/docker/Dockerfile-elsa-server
@@ -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"]
diff --git a/docker/README.md b/docker/README.md
index 5155f80fc..7db896ff7 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -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
```
diff --git a/pack-local-feed.ps1 b/pack-local-feed.ps1
new file mode 100644
index 000000000..ee159b95d
--- /dev/null
+++ b/pack-local-feed.ps1
@@ -0,0 +1 @@
+dotnet pack -o C:\Projects\Elsa\feed --version-suffix "rc2-5" -p:PackageVersion=2.0.0-rc2-5
\ No newline at end of file
diff --git a/run-dashboard-monolith.ps1 b/run-dashboard-monolith.ps1
new file mode 100644
index 000000000..605e9aa9c
--- /dev/null
+++ b/run-dashboard-monolith.ps1
@@ -0,0 +1,2 @@
+cd ./src/samples/dashboard/aspnetcore/ElsaDashboard.Samples.AspNetCore.Monolith
+dotnet run
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Activities/AzureServiceBusMessageReceived/AzureServiceBusQueueMessageReceived.cs b/src/activities/Elsa.Activities.AzureServiceBus/Activities/AzureServiceBusMessageReceived/AzureServiceBusQueueMessageReceived.cs
index 4e6515230..e5ebb3a82 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Activities/AzureServiceBusMessageReceived/AzureServiceBusQueueMessageReceived.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Activities/AzureServiceBusMessageReceived/AzureServiceBusQueueMessageReceived.cs
@@ -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();
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Activities/AzureServiceBusTopicMessageReceived/AzureServiceBusTopicMessageReceived.cs b/src/activities/Elsa.Activities.AzureServiceBus/Activities/AzureServiceBusTopicMessageReceived/AzureServiceBusTopicMessageReceived.cs
index 67cf80e68..417d56a09 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Activities/AzureServiceBusTopicMessageReceived/AzureServiceBusTopicMessageReceived.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Activities/AzureServiceBusTopicMessageReceived/AzureServiceBusTopicMessageReceived.cs
@@ -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();
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Activities/SendAzureServiceBusMessage/SendAzureServiceBusQueueMessage.cs b/src/activities/Elsa.Activities.AzureServiceBus/Activities/SendAzureServiceBusMessage/SendAzureServiceBusQueueMessage.cs
index 8eebd471a..342ce1214 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Activities/SendAzureServiceBusMessage/SendAzureServiceBusQueueMessage.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Activities/SendAzureServiceBusMessage/SendAzureServiceBusQueueMessage.cs
@@ -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 OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Activities/SendAzureServiceBusMessage/SendAzureServiceBusTopicMessage.cs b/src/activities/Elsa.Activities.AzureServiceBus/Activities/SendAzureServiceBusMessage/SendAzureServiceBusTopicMessage.cs
index db6fcaa95..ec9b6d21e 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Activities/SendAzureServiceBusMessage/SendAzureServiceBusTopicMessage.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Activities/SendAzureServiceBusMessage/SendAzureServiceBusTopicMessage.cs
@@ -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 OnExecuteAsync(ActivityExecutionContext context)
{
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Bookmarks/QueueMessageReceivedBookmark.cs b/src/activities/Elsa.Activities.AzureServiceBus/Bookmarks/QueueMessageReceivedBookmark.cs
index 69e24b97b..5d5104a55 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Bookmarks/QueueMessageReceivedBookmark.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Bookmarks/QueueMessageReceivedBookmark.cs
@@ -23,14 +23,14 @@ namespace Elsa.Activities.AzureServiceBus.Bookmarks
public class QueueMessageReceivedBookmarkProvider : BookmarkProvider
{
- public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) =>
+ public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext 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
- }
+ })
};
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Bookmarks/TopicMessageReceivedBookmark.cs b/src/activities/Elsa.Activities.AzureServiceBus/Bookmarks/TopicMessageReceivedBookmark.cs
index 2bd88e493..087a46127 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Bookmarks/TopicMessageReceivedBookmark.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Bookmarks/TopicMessageReceivedBookmark.cs
@@ -25,15 +25,15 @@ namespace Elsa.Activities.AzureServiceBus.Bookmarks
public class TopicMessageReceivedBookmarkProvider : BookmarkProvider
{
- public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) =>
+ public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext 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
- }
+ })
};
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Consumers/RestartServiceBusQueuesConsumer.cs b/src/activities/Elsa.Activities.AzureServiceBus/Consumers/RestartServiceBusQueuesConsumer.cs
index 8f19112b1..ffa63c517 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Consumers/RestartServiceBusQueuesConsumer.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Consumers/RestartServiceBusQueuesConsumer.cs
@@ -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
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Elsa.Activities.AzureServiceBus.csproj b/src/activities/Elsa.Activities.AzureServiceBus/Elsa.Activities.AzureServiceBus.csproj
index 7c7e36e90..7aea2526b 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Elsa.Activities.AzureServiceBus.csproj
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Elsa.Activities.AzureServiceBus.csproj
@@ -1,7 +1,7 @@
-
-
+
+
netstandard2.1
@@ -13,10 +13,10 @@
-
+
-
+
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Extensions/ServiceCollectionExtensions.cs b/src/activities/Elsa.Activities.AzureServiceBus/Extensions/ServiceCollectionExtensions.cs
index cafa5476b..ff75f7c33 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Extensions/ServiceCollectionExtensions.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Extensions/ServiceCollectionExtensions.cs
@@ -38,10 +38,10 @@ namespace Elsa.Activities.AzureServiceBus.Extensions
.AddBookmarkProvider()
;
- options.AddConsumer();
- options.AddConsumer();
- options.AddConsumer();
- options.AddConsumer();
+ options.AddCompetingConsumer();
+ options.AddCompetingConsumer();
+ options.AddCompetingConsumer();
+ options.AddCompetingConsumer();
options
.AddActivity()
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusQueuesStarter.cs b/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusQueuesStarter.cs
index e16e04a27..3894484db 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusQueuesStarter.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusQueuesStarter.cs
@@ -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 _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())
{
- var queueName = await activity.GetPropertyValueAsync(x => x.QueueName, cancellationToken);
+ var queueName = await activity.EvaluatePropertyValueAsync(x => x.QueueName, cancellationToken);
if (string.IsNullOrWhiteSpace(queueName))
{
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusTopicsStarter.cs b/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusTopicsStarter.cs
index 012860a1f..cdbef3fdd 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusTopicsStarter.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusTopicsStarter.cs
@@ -95,8 +95,8 @@ namespace Elsa.Activities.AzureServiceBus.Services
foreach (var activity in workflowBlueprintWrapper.Filter())
{
- 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)!;
}
}
diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Services/WorkerBase.cs b/src/activities/Elsa.Activities.AzureServiceBus/Services/WorkerBase.cs
index 9eeac8426..a2802bf82 100644
--- a/src/activities/Elsa.Activities.AzureServiceBus/Services/WorkerBase.cs
+++ b/src/activities/Elsa.Activities.AzureServiceBus/Services/WorkerBase.cs
@@ -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);
}
}
diff --git a/src/activities/Elsa.Activities.BlobStorage/Activities/BlobExists.cs b/src/activities/Elsa.Activities.BlobStorage/Activities/BlobExists.cs
index c91d83e3d..f86f5d49f 100644
--- a/src/activities/Elsa.Activities.BlobStorage/Activities/BlobExists.cs
+++ b/src/activities/Elsa.Activities.BlobStorage/Activities/BlobExists.cs
@@ -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 OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.BlobStorage/Activities/DeleteBlob.cs b/src/activities/Elsa.Activities.BlobStorage/Activities/DeleteBlob.cs
index eb26ed737..01e8257a5 100644
--- a/src/activities/Elsa.Activities.BlobStorage/Activities/DeleteBlob.cs
+++ b/src/activities/Elsa.Activities.BlobStorage/Activities/DeleteBlob.cs
@@ -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 }
)]
diff --git a/src/activities/Elsa.Activities.BlobStorage/Activities/ReadBlob.cs b/src/activities/Elsa.Activities.BlobStorage/Activities/ReadBlob.cs
index a1805906a..c592f58ed 100644
--- a/src/activities/Elsa.Activities.BlobStorage/Activities/ReadBlob.cs
+++ b/src/activities/Elsa.Activities.BlobStorage/Activities/ReadBlob.cs
@@ -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 OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.BlobStorage/Activities/WriteBlob.cs b/src/activities/Elsa.Activities.BlobStorage/Activities/WriteBlob.cs
index 4e1cf39a4..72c9773af 100644
--- a/src/activities/Elsa.Activities.BlobStorage/Activities/WriteBlob.cs
+++ b/src/activities/Elsa.Activities.BlobStorage/Activities/WriteBlob.cs
@@ -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 OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.Conductor/Activities/EventReceived.cs b/src/activities/Elsa.Activities.Conductor/Activities/EventReceived.cs
new file mode 100644
index 000000000..cd74ad614
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Activities/EventReceived.cs
@@ -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 Outcomes { get; set; } = new HashSet();
+
+ [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()!;
+ var outcomes = eventModel.Outcomes;
+
+ if (outcomes?.Any() == false)
+ outcomes = new[] { OutcomeNames.Done };
+
+ Payload = eventModel.Payload;
+ return base.Outcomes(outcomes!);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Activities/RunTask.cs b/src/activities/Elsa.Activities.Conductor/Activities/RunTask.cs
new file mode 100644
index 000000000..111029c27
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Activities/RunTask.cs
@@ -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 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()!;
+ var outcomes = eventModel.Outcomes;
+
+ if (outcomes?.Any() == false)
+ outcomes = new[] { OutcomeNames.Done };
+
+ ResultPayload = eventModel.Payload;
+ return base.Outcomes(outcomes!);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Activities/SendCommand.cs b/src/activities/Elsa.Activities.Conductor/Activities/SendCommand.cs
new file mode 100644
index 000000000..723a63366
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Activities/SendCommand.cs
@@ -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 OnExecuteAsync(ActivityExecutionContext context)
+ {
+ await _eventPublisher.PublishAsync(new SendCommandModel(CommandName, Payload, context.WorkflowInstance.Id));
+ return Done();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Consumers/RunTaskConsumer.cs b/src/activities/Elsa.Activities.Conductor/Consumers/RunTaskConsumer.cs
new file mode 100644
index 000000000..1a5f1ccd8
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Consumers/RunTaskConsumer.cs
@@ -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
+ {
+ private readonly ApplicationTasksClient _applicationTasksClient;
+ public RunTaskConsumer(ApplicationTasksClient applicationTasksClient) => _applicationTasksClient = applicationTasksClient;
+ public async Task Handle(RunTaskModel message) => await _applicationTasksClient.RunTaskAsync(message);
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Consumers/SendCommandConsumer.cs b/src/activities/Elsa.Activities.Conductor/Consumers/SendCommandConsumer.cs
new file mode 100644
index 000000000..564063ff9
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Consumers/SendCommandConsumer.cs
@@ -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
+ {
+ private readonly ApplicationCommandsClient _applicationCommandsClient;
+ public SendCommandConsumer(ApplicationCommandsClient applicationCommandsClient) => _applicationCommandsClient = applicationCommandsClient;
+ public async Task Handle(SendCommandModel message) => await _applicationCommandsClient.SendCommandAsync(message);
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Elsa.Activities.Conductor.csproj b/src/activities/Elsa.Activities.Conductor/Elsa.Activities.Conductor.csproj
new file mode 100644
index 000000000..ffa682cee
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Elsa.Activities.Conductor.csproj
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ net5.0
+
+ 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.
+
+ elsa, workflows, conductor
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Events/Dispatch.cs b/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Events/Dispatch.cs
new file mode 100644
index 000000000..fd8d85336
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Events/Dispatch.cs
@@ -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 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Events/Execute.cs b/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Events/Execute.cs
new file mode 100644
index 000000000..1acd5bf7a
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Events/Execute.cs
@@ -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 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Tasks/Dispatch.cs b/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Tasks/Dispatch.cs
new file mode 100644
index 000000000..11eff0846
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Tasks/Dispatch.cs
@@ -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 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Tasks/Execute.cs b/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Tasks/Execute.cs
new file mode 100644
index 000000000..c1e1a560e
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Endpoints/Conductor/Tasks/Execute.cs
@@ -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 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Extensions/ElsaOptionsBuilderExtensions.cs b/src/activities/Elsa.Activities.Conductor/Extensions/ElsaOptionsBuilderExtensions.cs
new file mode 100644
index 000000000..745993ea7
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Extensions/ElsaOptionsBuilderExtensions.cs
@@ -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? configureOptions = default, Action? configureHttpClient = default)
+ {
+ var services = elsa.Services;
+
+ if (configureOptions != null)
+ services.Configure(configureOptions);
+
+ services.ConfigureCommandsHttpClient(configureHttpClient);
+ services.ConfigureTasksHttpClient(configureHttpClient);
+
+ services
+ .AddCommandsProvider()
+ .AddEventsProvider()
+ .AddTasksProvider()
+ .AddActivityTypeProvider()
+ .AddActivityTypeProvider()
+ .AddActivityTypeProvider()
+ .AddBookmarkProvider()
+ .AddBookmarkProvider()
+ .AddSingleton>>()
+ .AddSingleton>>()
+ .AddSingleton>>();
+
+ elsa
+ .AddActivitiesFrom()
+ .AddCompetingConsumer()
+ .AddCompetingConsumer();
+
+ return elsa;
+ }
+
+ public static IServiceCollection AddCommandsProvider(this IServiceCollection services) where T : class, ICommandsProvider => services.AddScoped();
+ public static IServiceCollection AddEventsProvider(this IServiceCollection services) where T : class, IEventsProvider => services.AddScoped();
+ public static IServiceCollection AddTasksProvider(this IServiceCollection services) where T : class, ITasksProvider => services.AddScoped();
+
+ private static void ConfigureCommandsHttpClient(this IServiceCollection services, Action? configureHttpClient = default) => services.ConfigureHttpClient(o => o.CommandsHookUrl, configureHttpClient);
+ private static void ConfigureTasksHttpClient(this IServiceCollection services, Action? configureHttpClient = default) => services.ConfigureHttpClient(o => o.CommandsHookUrl, configureHttpClient);
+
+ private static void ConfigureHttpClient(this IServiceCollection services, Func baseAddress, Action? configureHttpClient = default) where T : class
+ {
+ var httpClientBuilder = services.AddHttpClient((sp, httpClient) =>
+ {
+ var options = sp.GetRequiredService>().Value;
+ httpClient.BaseAddress = baseAddress(options);
+ });
+
+ if (configureHttpClient == null)
+ httpClientBuilder.AddTransientHttpErrorPolicy(x => x.WaitAndRetryAsync(10, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount))));
+ else
+ configureHttpClient(httpClientBuilder);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/FodyWeavers.xml b/src/activities/Elsa.Activities.Conductor/FodyWeavers.xml
new file mode 100644
index 000000000..00e1d9a1c
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/IsExternalInit.cs b/src/activities/Elsa.Activities.Conductor/IsExternalInit.cs
new file mode 100644
index 000000000..475dd51b6
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/IsExternalInit.cs
@@ -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
+{
+ ///
+ /// Reserved to be used by the compiler for tracking metadata.
+ /// This class should not be used by developers in source code.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal static class IsExternalInit
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Models/CommandDefinition.cs b/src/activities/Elsa.Activities.Conductor/Models/CommandDefinition.cs
new file mode 100644
index 000000000..4e41e48fa
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Models/CommandDefinition.cs
@@ -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; }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Models/EventDefinition.cs b/src/activities/Elsa.Activities.Conductor/Models/EventDefinition.cs
new file mode 100644
index 000000000..e44b40f42
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Models/EventDefinition.cs
@@ -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? Outcomes { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Models/EventModel.cs b/src/activities/Elsa.Activities.Conductor/Models/EventModel.cs
new file mode 100644
index 000000000..3f0fb6492
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Models/EventModel.cs
@@ -0,0 +1,6 @@
+using System.Collections.Generic;
+
+namespace Elsa.Activities.Conductor.Models
+{
+ public record EventModel(string EventName, ICollection? Outcomes, object? Payload, string? WorkflowInstanceId, string? CorrelationId);
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Models/RunTaskModel.cs b/src/activities/Elsa.Activities.Conductor/Models/RunTaskModel.cs
new file mode 100644
index 000000000..51742a257
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Models/RunTaskModel.cs
@@ -0,0 +1,4 @@
+namespace Elsa.Activities.Conductor.Models
+{
+ public record RunTaskModel(string Task, object? Payload, string WorkflowInstanceId);
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Models/SendCommandModel.cs b/src/activities/Elsa.Activities.Conductor/Models/SendCommandModel.cs
new file mode 100644
index 000000000..bf77e8441
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Models/SendCommandModel.cs
@@ -0,0 +1,4 @@
+namespace Elsa.Activities.Conductor.Models
+{
+ public record SendCommandModel(string Command, object? Payload, string WorkflowInstanceId);
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Models/TaskDefinition.cs b/src/activities/Elsa.Activities.Conductor/Models/TaskDefinition.cs
new file mode 100644
index 000000000..40c81b530
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Models/TaskDefinition.cs
@@ -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? Outcomes { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Models/TaskResultModel.cs b/src/activities/Elsa.Activities.Conductor/Models/TaskResultModel.cs
new file mode 100644
index 000000000..9524d6c4f
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Models/TaskResultModel.cs
@@ -0,0 +1,6 @@
+using System.Collections.Generic;
+
+namespace Elsa.Activities.Conductor.Models
+{
+ public record TaskResultModel(string TaskName, ICollection? Outcomes, object? Payload, string? WorkflowInstanceId, string? CorrelationId);
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Options/ConductorOptions.cs b/src/activities/Elsa.Activities.Conductor/Options/ConductorOptions.cs
new file mode 100644
index 000000000..dfd226100
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Options/ConductorOptions.cs
@@ -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);
+ }
+
+ ///
+ /// The URL to post commands to.
+ ///
+ public Uri CommandsHookUrl { get; set; } = default!;
+
+ ///
+ /// The URL to post tasks to.
+ ///
+ public Uri TasksHookUrl { get; set; } = default!;
+
+ ///
+ /// The serializer to use when serializing a command before sending to the application.
+ ///
+ public JsonSerializerSettings SerializerSettings { get; set; }
+
+ ///
+ /// A collection of commands that can be sent to the application.
+ ///
+ public ICollection Commands { get; set; } = new List();
+
+ ///
+ /// A collection of events that can be received from the application.
+ ///
+ public ICollection Events { get; set; } = new List();
+
+ ///
+ /// A collection of tasks that can be sent to the application.
+ ///
+ public ICollection Tasks { get; set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/CommandActivityTypeProvider.cs b/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/CommandActivityTypeProvider.cs
new file mode 100644
index 000000000..cedd1b0ce
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/CommandActivityTypeProvider.cs
@@ -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> _scopedCommandsProviders;
+
+ public CommandActivityTypeProvider(IDescribesActivityType describesActivityType, IActivityActivator activityActivator, Scoped> scopedCommandsProviders)
+ {
+ _describesActivityType = describesActivityType;
+ _activityActivator = activityActivator;
+ _scopedCommandsProviders = scopedCommandsProviders;
+ }
+
+ public async ValueTask> GetActivityTypesAsync(CancellationToken cancellationToken = default)
+ {
+ var commands = await GetCommandsAsync(cancellationToken);
+ var activityTypes = await GetActivityTypesAsync(commands, cancellationToken).ToListAsync(cancellationToken);
+ return activityTypes;
+ }
+
+ private async IAsyncEnumerable GetActivityTypesAsync(IEnumerable commands, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ foreach (var command in commands)
+ yield return await CreateActivityTypeAsync(command, cancellationToken);
+ }
+
+ private async Task CreateActivityTypeAsync(CommandDefinition command, CancellationToken cancellationToken)
+ {
+ var descriptor = await _describesActivityType.DescribeAsync(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(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> GetCommandsAsync(CancellationToken cancellationToken) =>
+ await _scopedCommandsProviders.UseServiceAsync(async commandProviders => await GetCommandsAsync(commandProviders, cancellationToken).ToListAsync(cancellationToken));
+
+ private static async IAsyncEnumerable GetCommandsAsync(IEnumerable commandProviders, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ foreach (var commandProvider in commandProviders)
+ {
+ var commands = await commandProvider.GetCommandsAsync(cancellationToken);
+
+ foreach (var command in commands)
+ yield return command;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/EventActivityTypeProvider.cs b/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/EventActivityTypeProvider.cs
new file mode 100644
index 000000000..8580b142b
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/EventActivityTypeProvider.cs
@@ -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> _scopedEventsProviders;
+
+ public EventActivityTypeProvider(IDescribesActivityType describesActivityType, IActivityActivator activityActivator, Scoped> scopedEventsProviders)
+ {
+ _describesActivityType = describesActivityType;
+ _activityActivator = activityActivator;
+ _scopedEventsProviders = scopedEventsProviders;
+ }
+
+ public async ValueTask> GetActivityTypesAsync(CancellationToken cancellationToken = default)
+ {
+ var events = await GetEventsAsync(cancellationToken);
+ return await GetActivityTypesAsync(events, cancellationToken).ToListAsync(cancellationToken);
+ }
+
+ private async IAsyncEnumerable GetActivityTypesAsync(IEnumerable events, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ foreach (var eventDefinition in events)
+ yield return await CreateActivityTypeAsync(eventDefinition, cancellationToken);
+ }
+
+ private async Task CreateActivityTypeAsync(EventDefinition eventDefinition, CancellationToken cancellationToken)
+ {
+ var descriptor = await _describesActivityType.DescribeAsync(cancellationToken);
+
+ descriptor.Type = eventDefinition.Name;
+ descriptor.DisplayName = eventDefinition.DisplayName ?? eventDefinition.Name;
+ descriptor.Description = eventDefinition.Description;
+ descriptor.InputProperties = Array.Empty();
+ 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(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> GetEventsAsync(CancellationToken cancellationToken) =>
+ await _scopedEventsProviders.UseServiceAsync(async eventProviders => await GetEventsAsync(eventProviders, cancellationToken).ToListAsync(cancellationToken));
+
+ private static async IAsyncEnumerable GetEventsAsync(IEnumerable eventProviders, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ foreach (var commandProvider in eventProviders)
+ {
+ var commands = await commandProvider.GetEventsAsync(cancellationToken);
+
+ foreach (var command in commands)
+ yield return command;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/TaskActivityTypeProvider.cs b/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/TaskActivityTypeProvider.cs
new file mode 100644
index 000000000..48800d98e
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Providers/ActivityTypes/TaskActivityTypeProvider.cs
@@ -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> _scopedTasksProviders;
+
+ public TaskActivityTypeProvider(IDescribesActivityType describesActivityType, IActivityActivator activityActivator, Scoped> scopedTasksProviders)
+ {
+ _describesActivityType = describesActivityType;
+ _activityActivator = activityActivator;
+ _scopedTasksProviders = scopedTasksProviders;
+ }
+
+ public async ValueTask> GetActivityTypesAsync(CancellationToken cancellationToken = default)
+ {
+ var events = await GetTasksAsync(cancellationToken);
+ return await GetActivityTypesAsync(events, cancellationToken).ToListAsync(cancellationToken);
+ }
+
+ private async IAsyncEnumerable GetActivityTypesAsync(IEnumerable events, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ foreach (var taskDefinition in events)
+ yield return await CreateActivityTypeAsync(taskDefinition, cancellationToken);
+ }
+
+ private async Task CreateActivityTypeAsync(TaskDefinition taskDefinition, CancellationToken cancellationToken)
+ {
+ var descriptor = await _describesActivityType.DescribeAsync(cancellationToken);
+
+ descriptor.Type = taskDefinition.Name;
+ descriptor.DisplayName = taskDefinition.DisplayName ?? taskDefinition.Name;
+ descriptor.Description = taskDefinition.Description;
+ descriptor.InputProperties = Array.Empty();
+ 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(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> GetTasksAsync(CancellationToken cancellationToken) =>
+ await _scopedTasksProviders.UseServiceAsync(async taskProviders => await GetTasksAsync(taskProviders, cancellationToken).ToListAsync(cancellationToken));
+
+ private static async IAsyncEnumerable GetTasksAsync(IEnumerable taskProviders, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ foreach (var commandProvider in taskProviders)
+ {
+ var commands = await commandProvider.GetTasksAsync(cancellationToken);
+
+ foreach (var command in commands)
+ yield return command;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Providers/Bookmarks/EventBookmarkProvider.cs b/src/activities/Elsa.Activities.Conductor/Providers/Bookmarks/EventBookmarkProvider.cs
new file mode 100644
index 000000000..a1b68a260
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Providers/Bookmarks/EventBookmarkProvider.cs
@@ -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
+ {
+ public override bool SupportsActivity(BookmarkProviderContext context) => context.ActivityType.Type == typeof(EventReceived);
+ public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) => await GetBookmarksInternalAsync(context, cancellationToken).ToListAsync(cancellationToken);
+
+ private async IAsyncEnumerable GetBookmarksInternalAsync(BookmarkProviderContext 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();
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Providers/Bookmarks/TaskBookmarkProvider.cs b/src/activities/Elsa.Activities.Conductor/Providers/Bookmarks/TaskBookmarkProvider.cs
new file mode 100644
index 000000000..ebcfa1120
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Providers/Bookmarks/TaskBookmarkProvider.cs
@@ -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
+ {
+ public override bool SupportsActivity(BookmarkProviderContext context) => context.ActivityType.Type == typeof(RunTask);
+ public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) => await GetBookmarksInternalAsync(context, cancellationToken).ToListAsync(cancellationToken);
+
+ private async IAsyncEnumerable GetBookmarksInternalAsync(BookmarkProviderContext 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();
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Providers/Commands/OptionsCommandsProvider.cs b/src/activities/Elsa.Activities.Conductor/Providers/Commands/OptionsCommandsProvider.cs
new file mode 100644
index 000000000..db93c05c6
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Providers/Commands/OptionsCommandsProvider.cs
@@ -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 options) => _options = options.Value;
+ public ValueTask> GetCommandsAsync(CancellationToken cancellationToken) => new(_options.Commands);
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Providers/Events/OptionsEventsProvider.cs b/src/activities/Elsa.Activities.Conductor/Providers/Events/OptionsEventsProvider.cs
new file mode 100644
index 000000000..8144fdc13
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Providers/Events/OptionsEventsProvider.cs
@@ -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 options) => _options = options.Value;
+ public ValueTask> GetEventsAsync(CancellationToken cancellationToken) => new(_options.Events);
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Providers/Tasks/OptionsTasksProvider.cs b/src/activities/Elsa.Activities.Conductor/Providers/Tasks/OptionsTasksProvider.cs
new file mode 100644
index 000000000..d59925f8b
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Providers/Tasks/OptionsTasksProvider.cs
@@ -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 options) => _options = options.Value;
+ public ValueTask> GetTasksAsync(CancellationToken cancellationToken) => new(_options.Tasks);
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Services/ApplicationCommandsClient.cs b/src/activities/Elsa.Activities.Conductor/Services/ApplicationCommandsClient.cs
new file mode 100644
index 000000000..6e1c7e414
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Services/ApplicationCommandsClient.cs
@@ -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 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.
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Services/ApplicationTasksClient.cs b/src/activities/Elsa.Activities.Conductor/Services/ApplicationTasksClient.cs
new file mode 100644
index 000000000..529a65f9d
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Services/ApplicationTasksClient.cs
@@ -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 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.
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Services/ICommandsProvider.cs b/src/activities/Elsa.Activities.Conductor/Services/ICommandsProvider.cs
new file mode 100644
index 000000000..7d4d4e4e6
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Services/ICommandsProvider.cs
@@ -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> GetCommandsAsync(CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Services/IEventsProvider.cs b/src/activities/Elsa.Activities.Conductor/Services/IEventsProvider.cs
new file mode 100644
index 000000000..089f6eecb
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Services/IEventsProvider.cs
@@ -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> GetEventsAsync(CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Conductor/Services/ITasksProvider.cs b/src/activities/Elsa.Activities.Conductor/Services/ITasksProvider.cs
new file mode 100644
index 000000000..20c94d8a7
--- /dev/null
+++ b/src/activities/Elsa.Activities.Conductor/Services/ITasksProvider.cs
@@ -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> GetTasksAsync(CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Console/Activities/WriteLine/WriteLine.cs b/src/activities/Elsa.Activities.Console/Activities/WriteLine/WriteLine.cs
index cb12b7e11..2f6416bbd 100644
--- a/src/activities/Elsa.Activities.Console/Activities/WriteLine/WriteLine.cs
+++ b/src/activities/Elsa.Activities.Console/Activities/WriteLine/WriteLine.cs
@@ -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;
diff --git a/src/activities/Elsa.Activities.Dropbox/Activities/SaveToDropbox.cs b/src/activities/Elsa.Activities.Dropbox/Activities/SaveToDropbox.cs
index 52f1a1832..ce44c92fc 100644
--- a/src/activities/Elsa.Activities.Dropbox/Activities/SaveToDropbox.cs
+++ b/src/activities/Elsa.Activities.Dropbox/Activities/SaveToDropbox.cs
@@ -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 OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.Email/Activities/SendEmail/SendEmail.cs b/src/activities/Elsa.Activities.Email/Activities/SendEmail/SendEmail.cs
index a3dc6a71f..d70fa5648 100644
--- a/src/activities/Elsa.Activities.Email/Activities/SendEmail/SendEmail.cs
+++ b/src/activities/Elsa.Activities.Email/Activities/SendEmail/SendEmail.cs
@@ -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 To { get; set; } = new List();
- [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 Cc { get; set; } = new List();
- [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 Bcc { get; set; } = new List();
- [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 OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.Email/Elsa.Activities.Email.csproj b/src/activities/Elsa.Activities.Email/Elsa.Activities.Email.csproj
index 88348392e..74ca560d6 100644
--- a/src/activities/Elsa.Activities.Email/Elsa.Activities.Email.csproj
+++ b/src/activities/Elsa.Activities.Email/Elsa.Activities.Email.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/src/activities/Elsa.Activities.Entity/Activities/EntityChanged/EntityChanged.cs b/src/activities/Elsa.Activities.Entity/Activities/EntityChanged/EntityChanged.cs
index 8988ccded..4c78dca1a 100644
--- a/src/activities/Elsa.Activities.Entity/Activities/EntityChanged/EntityChanged.cs
+++ b/src/activities/Elsa.Activities.Entity/Activities/EntityChanged/EntityChanged.cs
@@ -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 }
diff --git a/src/activities/Elsa.Activities.Entity/Bookmarks/EntityChangedBookmark.cs b/src/activities/Elsa.Activities.Entity/Bookmarks/EntityChangedBookmark.cs
index 510dca23a..c255d349b 100644
--- a/src/activities/Elsa.Activities.Entity/Bookmarks/EntityChangedBookmark.cs
+++ b/src/activities/Elsa.Activities.Entity/Bookmarks/EntityChangedBookmark.cs
@@ -23,15 +23,15 @@ namespace Elsa.Activities.Entity.Bookmarks
public class EntityChangedWorkflowTriggerProvider : BookmarkProvider
{
- public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) =>
+ public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext 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
- )
+ ))
};
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Entity/Extensions/WorkflowRunnerExtensions.cs b/src/activities/Elsa.Activities.Entity/Extensions/WorkflowRunnerExtensions.cs
index b7c7e1591..456365f6d 100644
--- a/src/activities/Elsa.Activities.Entity/Extensions/WorkflowRunnerExtensions.cs
+++ b/src/activities/Elsa.Activities.Entity/Extensions/WorkflowRunnerExtensions.cs
@@ -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);
}
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Activities/HttpEndpoint/HttpEndpoint.cs b/src/activities/Elsa.Activities.Http/Activities/HttpEndpoint/HttpEndpoint.cs
index 5e044cada..4dfca6c00 100644
--- a/src/activities/Elsa.Activities.Http/Activities/HttpEndpoint/HttpEndpoint.cs
+++ b/src/activities/Elsa.Activities.Http/Activities/HttpEndpoint/HttpEndpoint.cs
@@ -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 _parsers;
-
- public HttpEndpoint(
- IHttpContextAccessor httpContextAccessor,
- IEnumerable parsers)
- {
- _httpContextAccessor = httpContextAccessor;
- _parsers = parsers;
- }
-
///
/// The path that triggers this activity.
///
- [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; }
///
/// The HTTP methods that triggers this activity.
///
- [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.
///
- [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 to parse the received request content into if 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.
///
- [ActivityProperty(Category = PropertyCategories.Advanced)]
+ [ActivityInput(Category = PropertyCategories.Advanced)]
public Type? TargetType { get; set; }
- protected override async ValueTask 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 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 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()!;
+ 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();
- }
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Activities/Redirect/Redirect.cs b/src/activities/Elsa.Activities.Http/Activities/Redirect/Redirect.cs
index 432b0f35b..129ee2262 100644
--- a/src/activities/Elsa.Activities.Http/Activities/Redirect/Redirect.cs
+++ b/src/activities/Elsa.Activities.Http/Activities/Redirect/Redirect.cs
@@ -29,10 +29,10 @@ namespace Elsa.Activities.Http
private IStringLocalizer 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)
diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs
index 6e7ee6c10..c57f7e90c 100644
--- a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs
+++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs
@@ -42,14 +42,14 @@ namespace Elsa.Activities.Http
///
/// The URL to invoke.
///
- [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; }
///
/// The HTTP method to use.
///
- [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
///
/// The body to send along with the request.
///
- [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; }
///
/// The Content Type header to send along with the request body.
///
- [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; }
///
/// The headers to send along with the request.
///
- [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; }
///
/// A list of HTTP status codes this activity can handle.
///
- [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 SupportedStatusCodes { get; set; } = new HashSet(new[] { 200 });
+ public ICollection? SupportedStatusCodes { get; set; } = new HashSet(new[] { 200 });
protected override async ValueTask 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 { 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)
diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs
index 3b0f026c7..d76ef9511 100644
--- a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs
+++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs
@@ -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
{
diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs
index a82713aa3..3b3bb27a1 100644
--- a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs
+++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs
@@ -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
diff --git a/src/activities/Elsa.Activities.Http/Activities/WriteHttpResponse/WriteHttpResponse.cs b/src/activities/Elsa.Activities.Http/Activities/WriteHttpResponse/WriteHttpResponse.cs
index 38f5066cf..9d732c95a 100644
--- a/src/activities/Elsa.Activities.Http/Activities/WriteHttpResponse/WriteHttpResponse.cs
+++ b/src/activities/Elsa.Activities.Http/Activities/WriteHttpResponse/WriteHttpResponse.cs
@@ -34,35 +34,56 @@ namespace Elsa.Activities.Http
///
/// The HTTP status code to return.
///
- [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;
///
/// The content to send along with the response
///
- [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; }
///
/// The Content-Type header to send along with the response.
///
- [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";
+
+ ///
+ /// The character set to use when writing the response.
+ ///
+ [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";
///
/// The headers to send along with the response.
///
- [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 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;
diff --git a/src/activities/Elsa.Activities.Http/Bookmarks/HttpEndpointBookmark.cs b/src/activities/Elsa.Activities.Http/Bookmarks/HttpEndpointBookmark.cs
index 5975dc3db..48c6ba719 100644
--- a/src/activities/Elsa.Activities.Http/Bookmarks/HttpEndpointBookmark.cs
+++ b/src/activities/Elsa.Activities.Http/Bookmarks/HttpEndpointBookmark.cs
@@ -13,13 +13,13 @@ namespace Elsa.Activities.Http.Bookmarks
public class HttpEndpointBookmarkProvider : BookmarkProvider
{
- public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken)
+ public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext 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();
+ var methods = (await context.ReadActivityPropertyAsync(x => x.Methods, cancellationToken))?.Select(x => x.ToLowerInvariant()) ?? Enumerable.Empty();
- HttpEndpointBookmark CreateBookmark(string method) => new(path, method, correlationId);
+ BookmarkResult CreateBookmark(string method) => Result(new(path, method, correlationId));
return methods.Select(CreateBookmark);
}
diff --git a/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj b/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj
index 2f39f892c..520dac541 100644
--- a/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj
+++ b/src/activities/Elsa.Activities.Http/Elsa.Activities.Http.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/src/activities/Elsa.Activities.Http/Endpoints/Signals/DispatchEndpoint.cs b/src/activities/Elsa.Activities.Http/Endpoints/Signals/DispatchEndpoint.cs
index 56d4328b1..f67394460 100644
--- a/src/activities/Elsa.Activities.Http/Endpoints/Signals/DispatchEndpoint.cs
+++ b/src/activities/Elsa.Activities.Http/Endpoints/Signals/DispatchEndpoint.cs
@@ -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 Handle(string token, CancellationToken cancellationToken)
+ public async Task 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);
}
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Endpoints/Signals/TriggerEndpoint.cs b/src/activities/Elsa.Activities.Http/Endpoints/Signals/TriggerEndpoint.cs
index 869bb4e45..3f347fd63 100644
--- a/src/activities/Elsa.Activities.Http/Endpoints/Signals/TriggerEndpoint.cs
+++ b/src/activities/Elsa.Activities.Http/Endpoints/Signals/TriggerEndpoint.cs
@@ -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 Handle(string token, CancellationToken cancellationToken)
+ public async Task 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);
}
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Extensions/ActivityExecutionContextExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/ActivityExecutionContextExtensions.cs
index 579c76f5a..d0c9c7976 100644
--- a/src/activities/Elsa.Activities.Http/Extensions/ActivityExecutionContextExtensions.cs
+++ b/src/activities/Elsa.Activities.Http/Extensions/ActivityExecutionContextExtensions.cs
@@ -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();
- var token = tokenService.CreateToken(payload);
+ var token = context.GenerateSignalToken(signal);
var url = $"/signals/trigger/{token}";
var absoluteUrlProvider = context.GetService();
return absoluteUrlProvider.ToAbsoluteUrl(url).ToString();
diff --git a/src/activities/Elsa.Activities.Http/Extensions/ApplicationBuilderExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/ApplicationBuilderExtensions.cs
index e4ebda7c6..417fb4f27 100644
--- a/src/activities/Elsa.Activities.Http/Extensions/ApplicationBuilderExtensions.cs
+++ b/src/activities/Elsa.Activities.Http/Extensions/ApplicationBuilderExtensions.cs
@@ -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();
+ var options = app.ApplicationServices.GetRequiredService>().Value;
+ var basePath = options.BasePath;
+
+ return basePath != null
+ ? app.Map(basePath.Value, branch => branch.UseMiddleware())
+ : app.UseMiddleware();
}
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Extensions/HttpRequestExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/HttpRequestExtensions.cs
index 3f55f1346..876e06d9c 100644
--- a/src/activities/Elsa.Activities.Http/Extensions/HttpRequestExtensions.cs
+++ b/src/activities/Elsa.Activities.Http/Extensions/HttpRequestExtensions.cs
@@ -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;
+ }
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs
index 189e4ef97..e873f6deb 100644
--- a/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs
+++ b/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs
@@ -26,16 +26,13 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection AddHttpServices(this IServiceCollection services, Action? configureOptions = null)
{
- if (configureOptions != null)
- {
+ if (configureOptions != null)
services.Configure(configureOptions);
- }
services.TryAddSingleton();
services.AddHttpClient(nameof(SendHttpRequest));
services
- .AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
@@ -47,6 +44,7 @@ namespace Microsoft.Extensions.DependencyInjection
.AddHttpContextAccessor()
.AddNotificationHandlers(typeof(ConfigureJavaScriptEngine))
.AddLiquidFilter("signal_url")
+ .AddJavaScriptTypeDefinitionProvider()
.AddDataProtection();
return services;
diff --git a/src/activities/Elsa.Activities.Http/JavaScript/ConfigureJavaScriptEngine.cs b/src/activities/Elsa.Activities.Http/JavaScript/ConfigureJavaScriptEngine.cs
index d27069dda..cd241266c 100644
--- a/src/activities/Elsa.Activities.Http/JavaScript/ConfigureJavaScriptEngine.cs
+++ b/src/activities/Elsa.Activities.Http/JavaScript/ConfigureJavaScriptEngine.cs
@@ -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
+ public class ConfigureJavaScriptEngine : INotificationHandler, INotificationHandler
{
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;
+ }
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/JavaScript/HttpTypeDefinitionProvider.cs b/src/activities/Elsa.Activities.Http/JavaScript/HttpTypeDefinitionProvider.cs
new file mode 100644
index 000000000..7a21c79c0
--- /dev/null
+++ b/src/activities/Elsa.Activities.Http/JavaScript/HttpTypeDefinitionProvider.cs
@@ -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 CollectTypes(TypeDefinitionContext context)
+ {
+ return new[] { typeof(HttpRequestModel) };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Liquid/ConfigureLiquidEngine.cs b/src/activities/Elsa.Activities.Http/Liquid/ConfigureLiquidEngine.cs
index 80aa482d6..5e79d5b59 100644
--- a/src/activities/Elsa.Activities.Http/Liquid/ConfigureLiquidEngine.cs
+++ b/src/activities/Elsa.Activities.Http/Liquid/ConfigureLiquidEngine.cs
@@ -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();
options.MemberAccessStrategy.Register((_, name, _) =>
{
var request = _httpContextAccessor.HttpContext?.Request;
diff --git a/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs b/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs
new file mode 100644
index 000000000..19b53b3fa
--- /dev/null
+++ b/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs
@@ -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 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(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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs b/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs
deleted file mode 100644
index 3f5425d87..000000000
--- a/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs
+++ /dev/null
@@ -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);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Models/HttpRequestModel.cs b/src/activities/Elsa.Activities.Http/Models/HttpRequestModel.cs
index 6cad7c5d4..42ea11edb 100644
--- a/src/activities/Elsa.Activities.Http/Models/HttpRequestModel.cs
+++ b/src/activities/Elsa.Activities.Http/Models/HttpRequestModel.cs
@@ -3,13 +3,8 @@ using System.Collections.Generic;
namespace Elsa.Activities.Http.Models
{
- public class HttpRequestModel
+ public record HttpRequestModel(Uri Path, string Method, IDictionary QueryString, IDictionary Headers, object? Body = default)
{
- public Uri Path { get; set; } = default!;
- public string Method { get; set; } = default!;
- public IDictionary QueryString { get; set; } = default!;
- public IDictionary Headers { get; set; } = default!;
- public object? Body { get; set; }
- public T GetBody() => (T)Body!;
+ public T GetBody() => (T) Body!;
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Http/Options/HttpActivityOptions.cs b/src/activities/Elsa.Activities.Http/Options/HttpActivityOptions.cs
index 3e77dab1d..0a2df30c3 100644
--- a/src/activities/Elsa.Activities.Http/Options/HttpActivityOptions.cs
+++ b/src/activities/Elsa.Activities.Http/Options/HttpActivityOptions.cs
@@ -1,9 +1,18 @@
using System;
+using Microsoft.AspNetCore.Http;
namespace Elsa.Activities.Http.Options
{
public class HttpActivityOptions
{
+ ///
+ /// The base URL of the server. This should be set to the same value at which the Elsa Server is publicly available. It will be used when generating absolute URLs need to be generated by activities such as SendEmail.
+ ///
public Uri BaseUrl { get; set; } = default!;
+
+ ///
+ /// The root path at which HTTP activities can be invoked.
+ ///
+ public PathString? BasePath { get; set; }
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.MassTransit/Activities/CancelScheduledMassTransitMessage/CancelScheduledMassTransitMessage.cs b/src/activities/Elsa.Activities.MassTransit/Activities/CancelScheduledMassTransitMessage/CancelScheduledMassTransitMessage.cs
index 3473162b5..4286a5c94 100644
--- a/src/activities/Elsa.Activities.MassTransit/Activities/CancelScheduledMassTransitMessage/CancelScheduledMassTransitMessage.cs
+++ b/src/activities/Elsa.Activities.MassTransit/Activities/CancelScheduledMassTransitMessage/CancelScheduledMassTransitMessage.cs
@@ -25,7 +25,7 @@ namespace Elsa.Activities.MassTransit
_options = options.Value;
}
- [ActivityProperty(Hint = "Expression that returns the tokenId of a scheduled message to cancel.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "Expression that returns the tokenId of a scheduled message to cancel.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? TokenId { get; set; }
protected override bool OnCanExecute(ActivityExecutionContext context) => TokenId != null && _options.SchedulerAddress != null;
diff --git a/src/activities/Elsa.Activities.MassTransit/Activities/PublishMassTransitMessage/PublishMassTransitMessage.cs b/src/activities/Elsa.Activities.MassTransit/Activities/PublishMassTransitMessage/PublishMassTransitMessage.cs
index 13ee879d2..c5a212ca5 100644
--- a/src/activities/Elsa.Activities.MassTransit/Activities/PublishMassTransitMessage/PublishMassTransitMessage.cs
+++ b/src/activities/Elsa.Activities.MassTransit/Activities/PublishMassTransitMessage/PublishMassTransitMessage.cs
@@ -19,7 +19,7 @@ namespace Elsa.Activities.MassTransit
{
}
- [ActivityProperty(Hint = "An expression that evaluates to the event to publish.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "An expression that evaluates to the event to publish.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public object? Message { get; set; }
protected override bool OnCanExecute(ActivityExecutionContext context) => Message != null;
diff --git a/src/activities/Elsa.Activities.MassTransit/Activities/ReceiveMassTransitMessage/ReceiveMassTransitMessage.cs b/src/activities/Elsa.Activities.MassTransit/Activities/ReceiveMassTransitMessage/ReceiveMassTransitMessage.cs
index 213216a84..16b3b5f55 100644
--- a/src/activities/Elsa.Activities.MassTransit/Activities/ReceiveMassTransitMessage/ReceiveMassTransitMessage.cs
+++ b/src/activities/Elsa.Activities.MassTransit/Activities/ReceiveMassTransitMessage/ReceiveMassTransitMessage.cs
@@ -14,7 +14,7 @@ namespace Elsa.Activities.MassTransit
)]
public class ReceiveMassTransitMessage : Activity
{
- [ActivityProperty(Hint = "The assembly-qualified type name of the message to receive.")]
+ [ActivityInput(Hint = "The assembly-qualified type name of the message to receive.")]
public Type? MessageType { get; set; }
protected override bool OnCanExecute(ActivityExecutionContext context) => MessageType?.IsAssignableFrom(context.Input?.GetType()) ?? false;
diff --git a/src/activities/Elsa.Activities.MassTransit/Activities/ScheduleSendMassTransitMessage/ScheduleSendMassTransitMessage.cs b/src/activities/Elsa.Activities.MassTransit/Activities/ScheduleSendMassTransitMessage/ScheduleSendMassTransitMessage.cs
index f29d6b145..4fd8be822 100644
--- a/src/activities/Elsa.Activities.MassTransit/Activities/ScheduleSendMassTransitMessage/ScheduleSendMassTransitMessage.cs
+++ b/src/activities/Elsa.Activities.MassTransit/Activities/ScheduleSendMassTransitMessage/ScheduleSendMassTransitMessage.cs
@@ -31,13 +31,13 @@ namespace Elsa.Activities.MassTransit
_options = options.Value;
}
- [ActivityProperty(Hint = "An expression that evaluates to the message to be delivered.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "An expression that evaluates to the message to be delivered.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public object? Message { get; set; }
- [ActivityProperty(Hint = "The address of a specific endpoint to deliver the message to.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The address of a specific endpoint to deliver the message to.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public Uri EndpointAddress { get; set; } = default!;
- [ActivityProperty(Hint = "An expression that evaluates to the date and time to deliver the message.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "An expression that evaluates to the date and time to deliver the message.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public Instant ScheduledTime { get; set; }
protected override bool OnCanExecute(ActivityExecutionContext context) => Message != null && _options.SchedulerAddress != null;
diff --git a/src/activities/Elsa.Activities.MassTransit/Activities/SendMassTransitMessage/SendMassTransitMessage.cs b/src/activities/Elsa.Activities.MassTransit/Activities/SendMassTransitMessage/SendMassTransitMessage.cs
index 825cc732a..60787b130 100644
--- a/src/activities/Elsa.Activities.MassTransit/Activities/SendMassTransitMessage/SendMassTransitMessage.cs
+++ b/src/activities/Elsa.Activities.MassTransit/Activities/SendMassTransitMessage/SendMassTransitMessage.cs
@@ -20,10 +20,10 @@ namespace Elsa.Activities.MassTransit
{
}
- [ActivityProperty(Hint = "An expression that evaluates to the message to send.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "An expression that evaluates to the message to send.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public object? Message { get; set; }
- [ActivityProperty(Hint = "The address of a specific endpoint to send the message to.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The address of a specific endpoint to send the message to.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public Uri EndpointAddress { get; set; } = default!;
protected override bool OnCanExecute(ActivityExecutionContext context) => Message != null;
diff --git a/src/activities/Elsa.Activities.MassTransit/Bookmarks/MessageReceivedBookmark.cs b/src/activities/Elsa.Activities.MassTransit/Bookmarks/MessageReceivedBookmark.cs
index 3d44d2fa5..f0ad85f53 100644
--- a/src/activities/Elsa.Activities.MassTransit/Bookmarks/MessageReceivedBookmark.cs
+++ b/src/activities/Elsa.Activities.MassTransit/Bookmarks/MessageReceivedBookmark.cs
@@ -13,14 +13,14 @@ namespace Elsa.Activities.MassTransit.Bookmarks
public class MessageReceivedTriggerProvider : BookmarkProvider
{
- public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) =>
+ public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) =>
new[]
{
- new MessageReceivedBookmark
+ Result(new MessageReceivedBookmark
{
- MessageType = (await context.Activity.GetPropertyValueAsync(x => x.MessageType, cancellationToken))!.Name,
+ MessageType = (await context.ReadActivityPropertyAsync(x => x.MessageType, cancellationToken))!.Name,
CorrelationId = context.ActivityExecutionContext.WorkflowExecutionContext.CorrelationId
- }
+ })
};
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.MassTransit/Consumers/WorkflowConsumer.cs b/src/activities/Elsa.Activities.MassTransit/Consumers/WorkflowConsumer.cs
index d99845508..5bea60f09 100644
--- a/src/activities/Elsa.Activities.MassTransit/Consumers/WorkflowConsumer.cs
+++ b/src/activities/Elsa.Activities.MassTransit/Consumers/WorkflowConsumer.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Elsa.Activities.MassTransit.Bookmarks;
using Elsa.Activities.MassTransit.Consumers.MessageCorrelation;
-using Elsa.Dispatch;
using Elsa.Services;
using MassTransit;
using MediatR;
@@ -22,10 +21,12 @@ namespace Elsa.Activities.MassTransit.Consumers
};
private readonly IMediator _mediator;
+ private readonly IWorkflowLaunchpad _workflowLaunchpad;
- public WorkflowConsumer(IMediator mediator)
+ public WorkflowConsumer(IMediator mediator, IWorkflowLaunchpad workflowLaunchpad)
{
_mediator = mediator;
+ _workflowLaunchpad = workflowLaunchpad;
}
public async Task Consume(ConsumeContext context)
@@ -37,19 +38,20 @@ namespace Elsa.Activities.MassTransit.Consumers
if (item.TryGetCorrelationId(message, out correlationId))
break;
- var bookmark = new MessageReceivedBookmark {
+ var bookmark = new MessageReceivedBookmark
+ {
MessageType = message.GetType().Name,
CorrelationId = correlationId.ToString()
};
- var trigger = new MessageReceivedBookmark {
+ var trigger = new MessageReceivedBookmark
+ {
MessageType = message.GetType().Name
};
- await _mediator.Send(new TriggerWorkflowsRequest(
+ await _workflowLaunchpad.CollectAndExecuteWorkflowsAsync(new CollectWorkflowsContext(
nameof(ReceiveMassTransitMessage),
bookmark,
trigger,
- message,
correlationId.ToString()
));
}
diff --git a/src/activities/Elsa.Activities.MassTransit/Elsa.Activities.MassTransit.csproj b/src/activities/Elsa.Activities.MassTransit/Elsa.Activities.MassTransit.csproj
index d18b7201f..58ac60e03 100644
--- a/src/activities/Elsa.Activities.MassTransit/Elsa.Activities.MassTransit.csproj
+++ b/src/activities/Elsa.Activities.MassTransit/Elsa.Activities.MassTransit.csproj
@@ -16,10 +16,10 @@
-
-
-
-
+
+
+
+
diff --git a/src/activities/Elsa.Activities.MassTransit/Extensions/ServiceCollectionExtensions.cs b/src/activities/Elsa.Activities.MassTransit/Extensions/ServiceCollectionExtensions.cs
index b76d6d439..e2e2f3df7 100644
--- a/src/activities/Elsa.Activities.MassTransit/Extensions/ServiceCollectionExtensions.cs
+++ b/src/activities/Elsa.Activities.MassTransit/Extensions/ServiceCollectionExtensions.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Elsa.Activities.MassTransit.Bookmarks;
using Elsa.Activities.MassTransit.Consumers;
using Elsa.Activities.MassTransit.Options;
-using Elsa.Bookmarks;
using MassTransit;
using MassTransit.ConsumeConfigurators;
using Microsoft.Extensions.DependencyInjection;
diff --git a/src/activities/Elsa.Activities.Rebus/Activities/PublishRebusMessage.cs b/src/activities/Elsa.Activities.Rebus/Activities/PublishRebusMessage.cs
index 22b271f4e..d63e042cf 100644
--- a/src/activities/Elsa.Activities.Rebus/Activities/PublishRebusMessage.cs
+++ b/src/activities/Elsa.Activities.Rebus/Activities/PublishRebusMessage.cs
@@ -19,10 +19,10 @@ namespace Elsa.Activities.Rebus
_eventPublisher = eventPublisher;
}
- [ActivityProperty(Hint = "The message to publish.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The message to publish.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public object Message { get; set; } = default!;
- [ActivityProperty(Hint = "Optional headers to send along with the message.", UIHint = ActivityPropertyUIHints.Json)]
+ [ActivityInput(Hint = "Optional headers to send along with the message.", UIHint = ActivityInputUIHints.Json)]
public IDictionary? Headers { get; set; }
protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.Rebus/Activities/RebusMessageReceived.cs b/src/activities/Elsa.Activities.Rebus/Activities/RebusMessageReceived.cs
index 9df44e58b..2ff76e5bd 100644
--- a/src/activities/Elsa.Activities.Rebus/Activities/RebusMessageReceived.cs
+++ b/src/activities/Elsa.Activities.Rebus/Activities/RebusMessageReceived.cs
@@ -9,7 +9,7 @@ namespace Elsa.Activities.Rebus
[Trigger(Category = "Rebus", Description = "Triggered when a message is received.", Outcomes = new[] { OutcomeNames.Done })]
public class RebusMessageReceived : Activity
{
- [ActivityProperty(Hint = "The type of message to receive.")]
+ [ActivityInput(Hint = "The type of message to receive.")]
public Type MessageType { get; set; } = default!;
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.Rebus/Activities/SendRebusMessage.cs b/src/activities/Elsa.Activities.Rebus/Activities/SendRebusMessage.cs
index d3e6238ef..fbae23186 100644
--- a/src/activities/Elsa.Activities.Rebus/Activities/SendRebusMessage.cs
+++ b/src/activities/Elsa.Activities.Rebus/Activities/SendRebusMessage.cs
@@ -19,10 +19,10 @@ namespace Elsa.Activities.Rebus
_bus = bus;
}
- [ActivityProperty(Hint = "The message to send.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The message to send.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public object Message { get; set; } = default!;
- [ActivityProperty(Hint = "Optional headers to send along with the message.", UIHint = ActivityPropertyUIHints.Json)]
+ [ActivityInput(Hint = "Optional headers to send along with the message.", UIHint = ActivityInputUIHints.Json)]
public IDictionary? Headers { get; set; }
protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.Rebus/Bookmarks/MessageReceivedBookmark.cs b/src/activities/Elsa.Activities.Rebus/Bookmarks/MessageReceivedBookmark.cs
index b9e077d8f..00ea1adbb 100644
--- a/src/activities/Elsa.Activities.Rebus/Bookmarks/MessageReceivedBookmark.cs
+++ b/src/activities/Elsa.Activities.Rebus/Bookmarks/MessageReceivedBookmark.cs
@@ -13,14 +13,14 @@ namespace Elsa.Activities.Rebus.Bookmarks
public class MessageReceivedTriggerProvider : BookmarkProvider
{
- public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) =>
+ public override async ValueTask> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken) =>
new[]
{
- new MessageReceivedBookmark
+ Result(new MessageReceivedBookmark
{
- MessageType = (await context.Activity.GetPropertyValueAsync(x => x.MessageType, cancellationToken))!.Name,
+ MessageType = (await context.ReadActivityPropertyAsync(x => x.MessageType, cancellationToken))!.Name,
CorrelationId = context.ActivityExecutionContext.WorkflowExecutionContext.CorrelationId
- }
+ })
};
}
}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Rebus/Consumers/MessageConsumer.cs b/src/activities/Elsa.Activities.Rebus/Consumers/MessageConsumer.cs
index cda17f0ea..dad7ed4b9 100644
--- a/src/activities/Elsa.Activities.Rebus/Consumers/MessageConsumer.cs
+++ b/src/activities/Elsa.Activities.Rebus/Consumers/MessageConsumer.cs
@@ -1,7 +1,6 @@
using System.Threading.Tasks;
using Elsa.Activities.Rebus.Bookmarks;
-using Elsa.Dispatch;
-using MediatR;
+using Elsa.Services;
using Rebus.Extensions;
using Rebus.Handlers;
using Rebus.Messages;
@@ -12,23 +11,22 @@ namespace Elsa.Activities.Rebus.Consumers
public class MessageConsumer : IHandleMessages where T : notnull
{
// TODO: Design multi-tenancy.
- private const string TenantId = default;
-
- private readonly IMediator _mediator;
-
- public MessageConsumer(IMediator mediator)
+ private const string? TenantId = default;
+
+ private readonly IWorkflowLaunchpad _workflowLaunchpad;
+
+ public MessageConsumer(IWorkflowLaunchpad workflowLaunchpad)
{
- _mediator = mediator;
+ _workflowLaunchpad = workflowLaunchpad;
}
public async Task Handle(T message)
{
var correlationId = MessageContext.Current.TransportMessage.Headers.GetValueOrNull(Headers.CorrelationId);
- await _mediator.Send(new TriggerWorkflowsRequest(
+ await _workflowLaunchpad.CollectAndExecuteWorkflowsAsync(new CollectWorkflowsContext(
nameof(RebusMessageReceived),
- new MessageReceivedBookmark {MessageType = message.GetType().Name, CorrelationId = correlationId},
- new MessageReceivedBookmark {MessageType = message.GetType().Name},
- message,
+ new MessageReceivedBookmark { MessageType = message.GetType().Name, CorrelationId = correlationId },
+ new MessageReceivedBookmark { MessageType = message.GetType().Name },
correlationId,
default,
TenantId));
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/AnswerCall.cs b/src/activities/Elsa.Activities.Telnyx/Activities/AnswerCall.cs
index f16ee866c..d68ec8bd8 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/AnswerCall.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/AnswerCall.cs
@@ -28,40 +28,40 @@ namespace Elsa.Activities.Telnyx.Activities
_telnyxClient = telnyxClient;
}
- [ActivityProperty(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string CallControlId { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Label = "Billing Group ID",
Hint = "Use this field to set the Billing Group ID for the call. Must be a valid and existing Billing Group ID.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? BillingGroupId { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? ClientState { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? CommandId { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Webhook URL",
Hint = "Use this field to override the URL for which Telnyx will send subsequent webhooks to for this call.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? WebhookUrl { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Webhook URL Method",
Hint = "HTTP request type used for Webhook URL",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "GET", "POST" },
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs b/src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs
index ac06cf1ee..640f0bb15 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs
@@ -30,29 +30,29 @@ namespace Elsa.Activities.Telnyx.Activities
_telnyxClient = telnyxClient;
}
- [ActivityProperty(Label = "Call Control ID A", Hint = "Unique identifier and token for controlling the call.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "Call Control ID A", Hint = "Unique identifier and token for controlling the call.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? CallControlIdA { get; set; } = default!;
- [ActivityProperty(Label = "Call Control ID B", Hint = "The Call Control ID of the call you want to bridge with.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "Call Control ID B", Hint = "The Call Control ID of the call you want to bridge with.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string CallControlIdB { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? CommandId { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? ClientState { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Park After Unbridged",
Hint = "HTTP request type used for Webhook URL",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "", "self" },
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/CallRingGroup.cs b/src/activities/Elsa.Activities.Telnyx/Activities/CallRingGroup.cs
index 566db24cc..bb66be9a8 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/CallRingGroup.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/CallRingGroup.cs
@@ -37,21 +37,21 @@ namespace Elsa.Activities.Telnyx.Activities
_logger = logger;
}
- [ActivityProperty(UIHint = ActivityPropertyUIHints.MultiText, DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(UIHint = ActivityInputUIHints.MultiText, DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public IList Extensions
{
get => GetState>(() => new List());
set => SetState(value);
}
- [ActivityProperty(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string CallControlId
{
get => GetState()!;
set => SetState(value);
}
- [ActivityProperty(
+ [ActivityInput(
Label = "Call Control App ID",
Hint = "The ID of the Call Control App (formerly ID of the connection) to be used when dialing the destination.",
Category = PropertyCategories.Advanced,
@@ -63,14 +63,14 @@ namespace Elsa.Activities.Telnyx.Activities
set => SetState(value);
}
- [ActivityProperty(SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public RingGroupStrategy Strategy
{
get => GetState();
set => SetState(value);
}
- [ActivityProperty(
+ [ActivityInput(
Hint = "The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
@@ -80,7 +80,7 @@ namespace Elsa.Activities.Telnyx.Activities
set => SetState(value);
}
- [ActivityProperty(
+ [ActivityInput(
Hint =
"The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
@@ -91,7 +91,7 @@ namespace Elsa.Activities.Telnyx.Activities
set => SetState(value);
}
- [ActivityProperty(DefaultValueProvider = typeof(CallRingGroup), SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(DefaultValueProvider = typeof(CallRingGroup), SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public Duration RingTime
{
get => GetState(() => Duration.FromSeconds(20));
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/Dial.cs b/src/activities/Elsa.Activities.Telnyx/Activities/Dial.cs
index dec870a7d..337d792f8 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/Dial.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/Dial.cs
@@ -35,83 +35,83 @@ namespace Elsa.Activities.Telnyx.Activities
_telnyxOptions = telnyxOptions;
}
- [ActivityProperty(
+ [ActivityInput(
Label = "Call Control ID", Hint = "The ID of the Call Control App (formerly ID of the connection) to be used when dialing the destination.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string ConnectionId { get; set; } = default!;
- [ActivityProperty(Hint = "The DID or SIP URI to dial out and bridge to the given call.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The DID or SIP URI to dial out and bridge to the given call.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string To { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? From { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint =
"The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? FromDisplayName { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "Enables Answering Machine Detection.",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end" },
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? AnsweringMachineDetection { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Answering Machine Detection Configuration",
Hint = "Optional configuration parameters to modify answering machine detection performance.",
- UIHint = ActivityPropertyUIHints.Json,
+ UIHint = ActivityInputUIHints.Json,
Category = PropertyCategories.Advanced)]
public AnsweringMachineConfig? AnsweringMachineDetectionConfig { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? CommandId { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? ClientState { get; set; }
- [ActivityProperty(Label = "Custom Headers", Hint = "Custom headers to be added to the SIP INVITE.", Category = PropertyCategories.Advanced, UIHint = ActivityPropertyUIHints.Json)]
+ [ActivityInput(Label = "Custom Headers", Hint = "Custom headers to be added to the SIP INVITE.", Category = PropertyCategories.Advanced, UIHint = ActivityInputUIHints.Json)]
public IList? CustomHeaders { get; set; }
- [ActivityProperty(Label = "SIP Authentication Username", Hint = "SIP Authentication username used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "SIP Authentication Username", Hint = "SIP Authentication username used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? SipAuthUsername { get; set; }
- [ActivityProperty(Label = "SIP Authentication Password", Hint = "SIP Authentication password used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "SIP Authentication Password", Hint = "SIP Authentication password used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? SipAuthPassword { get; set; }
- [ActivityProperty(Label = "Time Limit", Hint = "Sets the maximum duration of a Call Control Leg in seconds.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "Time Limit", Hint = "Sets the maximum duration of a Call Control Leg in seconds.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public int? TimeLimitSecs { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Timeout",
Hint = "The number of seconds that Telnyx will wait for the call to be answered by the destination to which it is being transferred.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public int? TimeoutSecs { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Webhook URL",
Hint = "Use this field to override the URL for which Telnyx will send subsequent webhooks to for this call.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? WebhookUrl { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Webhook URL Method",
Hint = "HTTP request type used for Webhook URL",
- UIHint = ActivityPropertyUIHints.Dropdown, Options = new[] { "GET", "POST" },
+ UIHint = ActivityInputUIHints.Dropdown, Options = new[] { "GET", "POST" },
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? WebhookUrlMethod { get; set; }
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/GatherUsingAudio.cs b/src/activities/Elsa.Activities.Telnyx/Activities/GatherUsingAudio.cs
index 0caaa1fb0..2e29f2b32 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/GatherUsingAudio.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/GatherUsingAudio.cs
@@ -29,25 +29,25 @@ namespace Elsa.Activities.Telnyx.Activities
_telnyxClient = telnyxClient;
}
- [ActivityProperty(
+ [ActivityInput(
Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? CallControlId { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Label = "Audio URL",
Hint = "The URL of a file to be played back at the beginning of each prompt. The URL can point to either a WAV or MP3 file.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public Uri AudioUrl { get; set; } = default!;
- [ActivityProperty(Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.", Category = PropertyCategories.Advanced,
+ [ActivityInput(Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.", Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? ClientState { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
@@ -55,7 +55,7 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? CommandId { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Inter Digit Timeout",
Hint = "The number of milliseconds to wait for input between digits.",
Category = PropertyCategories.Advanced,
@@ -64,14 +64,14 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public int? InterDigitTimeoutMillis { get; set; } = 5000;
- [ActivityProperty(
+ [ActivityInput(
Label = "Invalid Audio Url",
Hint = "The URL of a file to play when digits don't match the Valid Digits setting or the number of digits is not between Min and Max. The URL can point to either a WAV or MP3 file.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public Uri? InvalidAudioUrl { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "A list of all digits accepted as valid.",
Category = PropertyCategories.Advanced,
DefaultValue = "0123456789#*",
@@ -79,19 +79,19 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? ValidDigits { get; set; } = "0123456789#*";
- [ActivityProperty(Hint = "The minimum number of digits to fetch. This parameter has a minimum value of 1.", DefaultValue = 1, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The minimum number of digits to fetch. This parameter has a minimum value of 1.", DefaultValue = 1, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public int? MinimumDigits { get; set; } = 1;
- [ActivityProperty(Hint = "The maximum number of digits to fetch. This parameter has a maximum value of 128.", DefaultValue = 128, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The maximum number of digits to fetch. This parameter has a maximum value of 128.", DefaultValue = 128, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public int? MaximumDigits { get; set; } = 128;
- [ActivityProperty(Hint = "The maximum number of times the file should be played if there is no input from the user on the call.", DefaultValue = 3, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The maximum number of times the file should be played if there is no input from the user on the call.", DefaultValue = 3, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public int? MaximumTries { get; set; } = 3;
- [ActivityProperty(Hint = "The digit used to terminate input if fewer than `maximum_digits` digits have been gathered.", DefaultValue = "#", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The digit used to terminate input if fewer than `maximum_digits` digits have been gathered.", DefaultValue = "#", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? TerminatingDigit { get; set; } = "#";
- [ActivityProperty(
+ [ActivityInput(
Label = "Timeout",
Hint = "The number of milliseconds to wait for a DTMF response after file playback ends before a replaying the sound file.",
Category = PropertyCategories.Advanced,
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/HangupCall.cs b/src/activities/Elsa.Activities.Telnyx/Activities/HangupCall.cs
index 36215778c..737f05a39 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/HangupCall.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/HangupCall.cs
@@ -28,17 +28,17 @@ namespace Elsa.Activities.Telnyx.Activities
_telnyxClient = telnyxClient;
}
- [ActivityProperty(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? CallControlId { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? ClientState { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/PlayAudio.cs b/src/activities/Elsa.Activities.Telnyx/Activities/PlayAudio.cs
index 82cea62c2..4a1c7c151 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/PlayAudio.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/PlayAudio.cs
@@ -25,7 +25,7 @@ namespace Elsa.Activities.Telnyx.Activities
private readonly ITelnyxClient _telnyxClient;
public PlayAudio(ITelnyxClient telnyxClient) => _telnyxClient = telnyxClient;
- [ActivityProperty(
+ [ActivityInput(
Label = "Call Control ID",
Hint = "Unique identifier and token for controlling the call",
Category = PropertyCategories.Advanced,
@@ -33,21 +33,21 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? CallControlId { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Label = "Audio URL",
Hint = "The URL of a file to be played back at the beginning of each prompt. The URL can point to either a WAV or MP3 file.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public Uri AudioUrl { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? ClientState { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
@@ -55,7 +55,7 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? CommandId { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "The number of times the audio file should be played. If supplied, the value must be an integer between 1 and 100, or the special string 'infinity' for an endless loop.",
DefaultValue = "1",
Category = PropertyCategories.Advanced,
@@ -63,7 +63,7 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? Loop { get; set; } = "1";
- [ActivityProperty(
+ [ActivityInput(
Hint = "When enabled, audio will be mixed on top of any other audio that is actively being played back. Note that `overlay: true` will only work if there is another audio file already being played on the call.",
DefaultValue = false,
Category = PropertyCategories.Advanced,
@@ -71,9 +71,9 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public bool Overlay { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "Specifies the leg or legs on which audio will be played. If supplied, the value must be either 'self', 'opposite' or 'both'.",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[]{ "", "self", "opposite", "both" },
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/ResolveExtension.cs b/src/activities/Elsa.Activities.Telnyx/Activities/ResolveExtension.cs
index 93a284223..912f64cf9 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/ResolveExtension.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/ResolveExtension.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
+using Elsa.Activities.Telnyx.Models;
using Elsa.Activities.Telnyx.Services;
using Elsa.ActivityResults;
using Elsa.Attributes;
@@ -22,13 +23,17 @@ namespace Elsa.Activities.Telnyx.Activities
public ResolveExtension(IExtensionProvider extensionProvider) => _extensionProvider = extensionProvider;
- [ActivityProperty(Hint = "The extension to resolve. If the extension could not be resolved, it is returned as-is.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The extension to resolve. If the extension could not be resolved, it is returned as-is.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string Extension { get; set; } = default!;
+ [ActivityOutput(Hint = "The actual number the extension resolved to.")]
+ public Extension? ResolvedExtension { get; set; }
+
protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context)
{
- var resolvedNumber = await _extensionProvider.GetAsync(Extension, context.CancellationToken);
- var result = resolvedNumber != null ? Outcome("Resolved", resolvedNumber) : Outcome("Unresolved", Extension);
+ var resolvedExtension = await _extensionProvider.GetAsync(Extension, context.CancellationToken);
+ ResolvedExtension = resolvedExtension;
+ var result = resolvedExtension != null ? Outcome("Resolved", resolvedExtension) : (IActivityExecutionResult)Outcome("Unresolved");
return Combine(result);
}
}
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/SpeakText.cs b/src/activities/Elsa.Activities.Telnyx/Activities/SpeakText.cs
index 95a39f6d9..4bea2a094 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/SpeakText.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/SpeakText.cs
@@ -24,7 +24,7 @@ namespace Elsa.Activities.Telnyx.Activities
private readonly ITelnyxClient _telnyxClient;
public SpeakText(ITelnyxClient telnyxClient) => _telnyxClient = telnyxClient;
- [ActivityProperty(
+ [ActivityInput(
Label = "Call Control ID",
Hint = "Unique identifier and token for controlling the call",
Category = PropertyCategories.Advanced,
@@ -32,50 +32,50 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? CallControlId { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "The language you want spoken.",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "en-US", "en-AU", "nl-NL", "es-ES", "ru-RU" },
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string Language { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "The gender of the voice used to speak back the text.",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "female", "male" },
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string Voice { get; set; } = default!;
- [ActivityProperty(Hint = "The text or SSML to be converted into speech. There is a 5,000 character limit.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "The text or SSML to be converted into speech. There is a 5,000 character limit.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string Payload { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "The type of the provided payload. The payload can either be plain text, or Speech Synthesis Markup Language (SSML).",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "", "text", "ssml" },
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? PayloadType { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "This parameter impacts speech quality, language options and payload types. When using `basic`, only the `en-US` language and payload type `text` are allowed.",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "", "basic", "premium" },
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? ServiceLevel { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? ClientState { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/StartRecording.cs b/src/activities/Elsa.Activities.Telnyx/Activities/StartRecording.cs
index f4eba772b..4a1765a1f 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/StartRecording.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/StartRecording.cs
@@ -24,7 +24,7 @@ namespace Elsa.Activities.Telnyx.Activities
private readonly ITelnyxClient _telnyxClient;
public StartRecording(ITelnyxClient telnyxClient) => _telnyxClient = telnyxClient;
- [ActivityProperty(
+ [ActivityInput(
Label = "Call Control ID",
Hint = "Unique identifier and token for controlling the call",
Category = PropertyCategories.Advanced,
@@ -32,30 +32,30 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? CallControlId { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "When 'dual', final audio file will be stereo recorded with the first leg on channel A, and the rest on channel B.",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "single", "dual" },
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string Channels { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "The audio file format used when storing the call recording. Can be either 'mp3' or 'wav'.",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "wav", "mp3" },
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string Format { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? ClientState { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
@@ -63,7 +63,7 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? CommandId { get; set; }
- [ActivityProperty(Hint = "If enabled, a beep sound will be played at the start of a recording.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Hint = "If enabled, a beep sound will be played at the start of a recording.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public bool? PlayBeep { get; set; }
protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context)
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/TransferCall.cs b/src/activities/Elsa.Activities.Telnyx/Activities/TransferCall.cs
index e2848e9f6..1b91761a7 100644
--- a/src/activities/Elsa.Activities.Telnyx/Activities/TransferCall.cs
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/TransferCall.cs
@@ -31,7 +31,7 @@ namespace Elsa.Activities.Telnyx.Activities
_telnyxClient = telnyxClient;
}
- [ActivityProperty(
+ [ActivityInput(
Label = "Call Control ID",
Hint = "Unique identifier and token for controlling the call.",
Category = PropertyCategories.Advanced,
@@ -39,40 +39,40 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? CallControlId { get; set; } = default!;
- [ActivityProperty(Label = "To", Hint = "The DID or SIP URI to dial out and bridge to the given call.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "To", Hint = "The DID or SIP URI to dial out and bridge to the given call.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string To { get; set; } = default!;
- [ActivityProperty(
+ [ActivityInput(
Hint = "The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? From { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint =
"The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? FromDisplayName { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Answering Machine Detection",
Hint = "Enables Answering Machine Detection.",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end" },
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? AnsweringMachineDetection { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Answering Machine Detection Configuration",
Hint = "Optional configuration parameters to modify answering machine detection performance.",
Category = PropertyCategories.Advanced,
- UIHint = ActivityPropertyUIHints.Json
+ UIHint = ActivityInputUIHints.Json
)]
public AnsweringMachineConfig? AnsweringMachineDetectionConfig { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
@@ -80,38 +80,38 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? CommandId { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Audio URL",
Hint = "Audio URL to be played back when the transfer destination answers before bridging the call. The URL can point to either a WAV or MP3 file.",
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public Uri? AudioUrl { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? ClientState { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Hint = "Use this field to add state to every subsequent webhook for the new leg. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? TargetLegClientState { get; set; }
- [ActivityProperty(Hint = "Custom headers to be added to the SIP INVITE.", Category = PropertyCategories.Advanced, UIHint = ActivityPropertyUIHints.Json)]
+ [ActivityInput(Hint = "Custom headers to be added to the SIP INVITE.", Category = PropertyCategories.Advanced, UIHint = ActivityInputUIHints.Json)]
public IList? CustomHeaders { get; set; }
- [ActivityProperty(Label = "SIP Authentication Username", Hint = "SIP Authentication username used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "SIP Authentication Username", Hint = "SIP Authentication username used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? SipAuthUsername { get; set; }
- [ActivityProperty(Label = "SIP Authentication Password", Hint = "SIP Authentication password used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "SIP Authentication Password", Hint = "SIP Authentication password used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string? SipAuthPassword { get; set; }
- [ActivityProperty(Label = "Time Limit", Hint = "Sets the maximum duration of a Call Control Leg in seconds.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
+ [ActivityInput(Label = "Time Limit", Hint = "Sets the maximum duration of a Call Control Leg in seconds.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public int? TimeLimitSecs { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Timeout",
Hint = "The number of seconds that Telnyx will wait for the call to be answered by the destination to which it is being transferred.",
Category = PropertyCategories.Advanced,
@@ -119,7 +119,7 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public int? TimeoutSecs { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Webhook URL",
Hint = "Use this field to override the URL for which Telnyx will send subsequent webhooks to for this call.",
Category = PropertyCategories.Advanced,
@@ -127,10 +127,10 @@ namespace Elsa.Activities.Telnyx.Activities
)]
public string? WebhookUrl { get; set; }
- [ActivityProperty(
+ [ActivityInput(
Label = "Webhook URL Method",
Hint = "HTTP request type used for Webhook URL",
- UIHint = ActivityPropertyUIHints.Dropdown,
+ UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "GET", "POST" },
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/Webhook.cs b/src/activities/Elsa.Activities.Telnyx/Activities/Webhook.cs
new file mode 100644
index 000000000..98251d009
--- /dev/null
+++ b/src/activities/Elsa.Activities.Telnyx/Activities/Webhook.cs
@@ -0,0 +1,44 @@
+using System.ComponentModel;
+using Elsa.Activities.Telnyx.Extensions;
+using Elsa.Activities.Telnyx.Webhooks.Models;
+using Elsa.Activities.Telnyx.Webhooks.Payloads.Abstract;
+using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
+using Elsa.ActivityResults;
+using Elsa.Attributes;
+using Elsa.Services;
+using Elsa.Services.Models;
+
+namespace Elsa.Activities.Telnyx.Activities
+{
+ [Browsable(false)]
+ [Trigger(Category = "Telnyx", Outcomes = new[]{ OutcomeNames.Done })]
+ public class Webhook : Activity
+ {
+ [ActivityOutput] public TelnyxWebhook? Model { get; set; }
+ [ActivityOutput] public Payload? Payload { get; set; }
+
+ protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) => context.WorkflowExecutionContext.IsFirstPass ? ExecuteInternal(context) : Suspend();
+ protected override IActivityExecutionResult OnResume(ActivityExecutionContext context) => ExecuteInternal(context);
+
+ private IActivityExecutionResult ExecuteInternal(ActivityExecutionContext context)
+ {
+ var webhookModel = (TelnyxWebhook) context.Input!;
+
+ if (webhookModel.Data.Payload is CallPayload callPayload)
+ {
+ context.WorkflowExecutionContext.CorrelationId ??= callPayload.CallSessionId;
+
+ if (!context.HasCallControlId())
+ context.SetCallControlId(callPayload.CallControlId);
+
+ if (callPayload is CallInitiatedPayload callInitiatedPayload)
+ if (!context.HasFromNumber())
+ context.SetFromNumber(callInitiatedPayload.To);
+ }
+
+ Model = webhookModel;
+ Payload = webhookModel.Data.Payload;
+ return Done();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/activities/Elsa.Activities.Telnyx/ActivityTypes/NotificationActivityTypeProvider.cs b/src/activities/Elsa.Activities.Telnyx/ActivityTypes/NotificationActivityTypeProvider.cs
index a5804bb15..a4a7db3c3 100644
--- a/src/activities/Elsa.Activities.Telnyx/ActivityTypes/NotificationActivityTypeProvider.cs
+++ b/src/activities/Elsa.Activities.Telnyx/ActivityTypes/NotificationActivityTypeProvider.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using Elsa.Activities.Telnyx.Activities;
using Elsa.Activities.Telnyx.Extensions;
using Elsa.Activities.Telnyx.Webhooks.Attributes;
using Elsa.Activities.Telnyx.Webhooks.Models;
@@ -19,98 +20,74 @@ namespace Elsa.Activities.Telnyx.ActivityTypes
{
internal class NotificationActivityTypeProvider : IActivityTypeProvider
{
- private readonly IWebhookFilterService _webhookFilterService;
+ private readonly IDescribesActivityType _describesActivityType;
+ private readonly IActivityActivator _activityActivator;
- public NotificationActivityTypeProvider(IWebhookFilterService webhookFilterService)
+ public NotificationActivityTypeProvider(IDescribesActivityType describesActivityType, IActivityActivator activityActivator)
{
- _webhookFilterService = webhookFilterService;
+ _describesActivityType = describesActivityType;
+ _activityActivator = activityActivator;
}
public const string NotificationAttribute = "TelnyxNotification";
public const string EventTypeAttribute = "EventType";
- public ValueTask> GetActivityTypesAsync(CancellationToken cancellationToken = default)
- {
- var activityTypes = GetActivityTypes();
- return new ValueTask>(activityTypes);
- }
-
- private IEnumerable GetActivityTypes()
+ public async ValueTask> GetActivityTypesAsync(CancellationToken cancellationToken = default)
{
var payloadTypes = GetType().Assembly.GetAllWithBaseClass().Where(x => x.GetCustomAttribute() != null).ToList();
- var activityTypes = payloadTypes.Select(CreateWebhookActivityType).ToList();
+ var activityTypes = (await Task.WhenAll(payloadTypes.Select(async x => await CreateWebhookActivityTypeAsync(x, cancellationToken)))).ToList();
// Add variations on the same webhooks. The webhook filters will conditionally select the appropriate one.
- activityTypes.Add(CreateWebhookActivityTypeVariation("BridgeCallInitiated", "Bridge Call Initiated", "Triggered when an incoming bridging call was received."));
- activityTypes.Add(CreateWebhookActivityTypeVariation("OriginatorCallHangup", "Originator Call Hangup", "Triggered when an incoming call was hangup by the originator."));
+ activityTypes.Add(await CreateWebhookActivityTypeVariationAsync("BridgeCallInitiated", "Bridge Call Initiated", "Triggered when an incoming bridging call was received.", cancellationToken));
+ activityTypes.Add(await CreateWebhookActivityTypeVariationAsync("OriginatorCallHangup", "Originator Call Hangup", "Triggered when an incoming call was hangup by the originator.", cancellationToken));
return activityTypes;
}
- private ActivityType CreateWebhookActivityTypeVariation(string activityType, string displayName, string description)
+ private async Task CreateWebhookActivityTypeVariationAsync(string activityType, string displayName, string description, CancellationToken cancellationToken)
{
var hangupWebhookAttribute = typeof(T).GetCustomAttribute()!;
- return CreateWebhookActivityType(new WebhookAttribute(hangupWebhookAttribute.EventType, activityType, displayName, description));
+ return await CreateWebhookActivityTypeAsync(typeof(T), new WebhookAttribute(hangupWebhookAttribute.EventType, activityType, displayName, description), cancellationToken);
}
- private static ActivityType CreateWebhookActivityType(Type payloadType)
+ private async Task CreateWebhookActivityTypeAsync(Type payloadType, CancellationToken cancellationToken)
{
var webhookAttribute = payloadType.GetCustomAttribute();
if (webhookAttribute == null)
throw new InvalidOperationException($"Make sure that the payload type is annotated with the ${nameof(WebhookAttribute)} attribute");
- return CreateWebhookActivityType(webhookAttribute);
+ return await CreateWebhookActivityTypeAsync(payloadType, webhookAttribute, cancellationToken);
}
- private static ActivityType CreateWebhookActivityType(WebhookAttribute webhookAttribute)
+ private async Task CreateWebhookActivityTypeAsync(Type payloadType, WebhookAttribute webhookAttribute, CancellationToken cancellationToken)
{
- var typeName = webhookAttribute.ActivityType;
- var displayName = webhookAttribute.DisplayName;
- var description = webhookAttribute.Description;
+ var descriptor = await _describesActivityType.DescribeAsync