Add support for webhook triggers

This commit is contained in:
Sipke Schoorstra 2021-06-09 18:08:22 +02:00
parent 73e67025e7
commit 9a01aefe17
11 changed files with 166 additions and 35 deletions

View file

@ -233,13 +233,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "blazor", "blazor", "{D86B94
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnetcore", "aspnetcore", "{C869CC72-9A98-4246-9A76-6A50F48AFD85}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaDashboard.Samples.Monolith", "src\samples\dashboard\blazor\ElsaDashboard.Samples.Monolith\ElsaDashboard.Samples.Monolith.csproj", "{C0ECCB6E-B6FA-413E-B717-648E9CB71A97}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaDashboard.Samples.Server", "src\samples\dashboard\blazor\ElsaDashboard.Samples.Server\ElsaDashboard.Samples.Server.csproj", "{9B56C09A-E4D5-4FDD-AD41-EDD1F52B7B9B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaDashboard.Samples.WebAssembly", "src\samples\dashboard\blazor\ElsaDashboard.Samples.WebAssembly\ElsaDashboard.Samples.WebAssembly.csproj", "{84876EBC-8109-475E-8C4E-A5CB714DB429}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaDashboard.Samples.AspNetCore.Monolith", "src\samples\dashboard\aspnetcore\ElsaDashboard.Samples.AspNetCore.Monolith\ElsaDashboard.Samples.AspNetCore.Monolith.csproj", "{F1EF5639-021A-4D22-9D4A-73D57C88EBFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElsaDashboard.Samples.AspNetCore.Monolith", "src\samples\dashboard\aspnetcore\ElsaDashboard.Samples.AspNetCore.Monolith\ElsaDashboard.Samples.AspNetCore.Monolith.csproj", "{F1EF5639-021A-4D22-9D4A-73D57C88EBFA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.Persistence.YesSql", "src\samples\persistence\Elsa.Samples.Persistence.YesSql\Elsa.Samples.Persistence.YesSql.csproj", "{3A30EFFE-32D8-40B8-9C62-427037BB5424}"
@ -263,8 +258,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.CustomActivity
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnetcore", "aspnetcore", "{28C5C63F-2DCF-46BE-9576-1F50A7CFE8C4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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-and-server-docker.yml = .github\workflows\publish-latest-dashboard-and-server-docker.yml

View file

@ -16,8 +16,7 @@ namespace Elsa.Activities.Http.Bookmarks
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext<HttpEndpoint> context, CancellationToken cancellationToken)
{
var path = ToLower(await context.ReadActivityPropertyAsync(x => x.Path, cancellationToken))!;
var correlationId = ToLower(context.ActivityExecutionContext.WorkflowExecutionContext.CorrelationId);
var methods = (await context.ReadActivityPropertyAsync(x => x.Methods, cancellationToken))?.Select(x => x.ToLowerInvariant()) ?? Enumerable.Empty<string>();
var methods = (await context.ReadActivityPropertyAsync(x => x.Methods, cancellationToken))?.Select(ToLower) ?? Enumerable.Empty<string>();
BookmarkResult CreateBookmark(string method) => Result(new(path, method));
return methods.Select(CreateBookmark);

View file

@ -67,7 +67,7 @@ namespace Elsa.Activities.Http.Middleware
{
var pendingWorkflowInstance = pendingWorkflowInstances[pendingWorkflow.WorkflowInstanceId];
var workflowBlueprintWrapper = workflowBlueprintWrappers[pendingWorkflowInstance.DefinitionId];
var activityWrapper = workflowBlueprintWrapper.GetActivity<HttpEndpoint>(pendingWorkflow.ActivityId!);
var activityWrapper = workflowBlueprintWrapper.GetUnfilteredActivity<HttpEndpoint>(pendingWorkflow.ActivityId!);
var readContent = await activityWrapper!.EvaluatePropertyValueAsync(x => x.ReadContent, cancellationToken);
var inputModel = commonInputModel;

View file

@ -16,6 +16,7 @@ namespace Elsa.Activities.Webhooks.ActivityTypes
{
public class WebhookActivityTypeProvider : IActivityTypeProvider
{
public const string WebhookMarkerAttribute = "WebhookMarker";
private const string WebhookActivityCategory = "Webhooks";
private readonly IWebhookDefinitionStore _webhookDefinitionStore;
@ -59,17 +60,17 @@ namespace Elsa.Activities.Webhooks.ActivityTypes
InputProperties = new[]
{
new ActivityInputDescriptor(
nameof(HttpEndpoint.Methods),
nameof(HttpEndpoint.Methods),
typeof(HashSet<string>),
ActivityInputUIHints.Dropdown,
ActivityInputUIHints.CheckList,
"Request Method",
"Specify what request method this webhook should handle. Leave empty to handle both GET and POST requests",
new HashSet<string> { "", "GET", "POST" },
"Webhooks",
new[] { "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD" },
default,
0,
"POST",
SyntaxNames.Literal,
new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })
SyntaxNames.Json,
new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid })
}
};
@ -78,18 +79,22 @@ namespace Elsa.Activities.Webhooks.ActivityTypes
var activity = await _activityActivator.ActivateActivityAsync<HttpEndpoint>(context);
activity.Path = webhook.Path;
activity.ReadContent = true;
activity.TargetType = webhook.PayloadTypeName is not null and not "" ? Type.GetType(webhook.PayloadTypeName) : throw new Exception($"Type {webhook.PayloadTypeName} not found");
return activity;
activity.TargetType = !string.IsNullOrWhiteSpace(webhook.PayloadTypeName) ? Type.GetType(webhook.PayloadTypeName) ?? throw new Exception($"Type {webhook.PayloadTypeName} not found") : null;
return activity;
}
return new ActivityType
{
TypeName = webhook.Name,
Type = typeof(HttpEndpoint),
Description = webhook.Description is not null and not "" ? webhook.Description : $"A webhook at {webhook.Path}",
Description = !string.IsNullOrWhiteSpace(webhook.Description) ? webhook.Description : $"A webhook at {webhook.Path}",
DisplayName = webhook.Name,
ActivateAsync = ActivateActivityAsync,
Attributes = new Dictionary<string, object>
{
[WebhookMarkerAttribute] = true,
["Path"] = webhook.Path,
},
Describe = () => descriptor
};
}

View file

@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Http;
using Elsa.Activities.Http.Bookmarks;
using Elsa.Activities.Webhooks.ActivityTypes;
using Elsa.Bookmarks;
using Microsoft.AspNetCore.Http;
namespace Elsa.Activities.Webhooks.Bookmarks
{
public record WebhookBookmark(string WebhookActivityTypeName) : IBookmark;
public class WebhookBookmarkProvider : BookmarkProvider<HttpEndpointBookmark>
{
public override bool SupportsActivity(BookmarkProviderContext context)
{
var activityType = context.ActivityType;
return activityType.Attributes.ContainsKey(WebhookActivityTypeProvider.WebhookMarkerAttribute);
}
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken)
{
var path = ToLower((string)context.ActivityType.Attributes["Path"])!;
var methods = (await context.ReadActivityPropertyAsync<HttpEndpoint, HashSet<string>>(x => x.Methods, cancellationToken))?.Select(ToLower) ?? Enumerable.Empty<string>();
BookmarkResult CreateBookmark(string method) => Result(new(path, method), nameof(HttpEndpoint));
return methods.Select(CreateBookmark);
}
private static string? ToLower(string? s) => s?.ToLowerInvariant();
}
}

View file

@ -1,5 +1,6 @@
using System;
using Elsa.Activities.Webhooks.ActivityTypes;
using Elsa.Activities.Webhooks.Bookmarks;
using Elsa.Activities.Webhooks.Persistence.Decorators;
using Elsa.Activities.Webhooks.Services;
using Elsa.Services;
@ -22,11 +23,11 @@ namespace Elsa.Activities.Webhooks.Extensions
configure?.Invoke(webhookOptionsBuilder);
// Services.
services
.AddScoped<IActivityTypeProvider, WebhookActivityTypeProvider>()
.AddScoped(sp => webhookOptionsBuilder.WebhookOptions.WebhookDefinitionStoreFactory(sp))
.AddScoped<IWebhookPublisher, WebhookPublisher>();
.AddScoped<IWebhookPublisher, WebhookPublisher>()
.AddBookmarkProvider<WebhookBookmarkProvider>();
services.Decorate<IWebhookDefinitionStore, InitializingWebhookDefinitionStore>();
services.Decorate<IWebhookDefinitionStore, EventPublishingWebhookDefinitionStore>();

View file

@ -20,17 +20,8 @@ namespace Elsa.Bookmarks
public ActivityType ActivityType { get; }
public BookmarkIndexingMode Mode { get; }
public IActivityBlueprintWrapper<TActivity> GetActivity<TActivity>() where TActivity : IActivity => new ActivityBlueprintWrapper<TActivity>(ActivityExecutionContext);
}
public class BookmarkProviderContext<TActivity> : BookmarkProviderContext where TActivity: IActivity
{
public BookmarkProviderContext(ActivityExecutionContext activityExecutionContext, ActivityType activityType, BookmarkIndexingMode mode) : base(activityExecutionContext, activityType, mode)
{
}
public IActivityBlueprintWrapper<TActivity> Activity => GetActivity<TActivity>();
public async ValueTask<T?> ReadActivityPropertyAsync<T>(Expression<Func<TActivity, T>> propertyExpression, CancellationToken cancellationToken = default)
public async ValueTask<T?> ReadActivityPropertyAsync<TActivity, T>(Expression<Func<TActivity, T>> propertyExpression, CancellationToken cancellationToken = default) where TActivity : IActivity
{
var activityBlueprint = GetActivity<TActivity>();
@ -40,4 +31,15 @@ namespace Elsa.Bookmarks
return activityBlueprint.GetPropertyValue(propertyExpression);
}
}
public class BookmarkProviderContext<TActivity> : BookmarkProviderContext where TActivity: IActivity
{
public BookmarkProviderContext(ActivityExecutionContext activityExecutionContext, ActivityType activityType, BookmarkIndexingMode mode) : base(activityExecutionContext, activityType, mode)
{
}
public IActivityBlueprintWrapper<TActivity> Activity => GetActivity<TActivity>();
public ValueTask<T?> ReadActivityPropertyAsync<T>(Expression<Func<TActivity, T>> propertyExpression, CancellationToken cancellationToken = default) => base.ReadActivityPropertyAsync(propertyExpression, cancellationToken);
}
}

View file

@ -20,7 +20,11 @@ namespace Elsa
}
public static IActivityBlueprintWrapper<TActivity>? FindActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, Func<IActivityBlueprintWrapper<TActivity>, bool>? predicate = default) where TActivity : IActivity => workflowBlueprintWrapper.Filter(predicate).FirstOrDefault();
public static IActivityBlueprintWrapper<TActivity>? GetActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, string id) where TActivity : IActivity => workflowBlueprintWrapper.FindActivity<TActivity>(x => x.ActivityBlueprint.Id == id)!;
public static IActivityBlueprintWrapper<TActivity> GetActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, string id) where TActivity : IActivity => workflowBlueprintWrapper.FindActivity<TActivity>(x => x.ActivityBlueprint.Id == id)!;
public static IActivityBlueprintWrapper? GetActivity(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, string id) => workflowBlueprintWrapper.Activities.FirstOrDefault(x => x.ActivityBlueprint.Id == id);
public static IActivityBlueprintWrapper<TActivity>? FindUnfilteredActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, Func<IActivityBlueprintWrapper<TActivity>, bool> predicate) where TActivity : IActivity => workflowBlueprintWrapper.Activities.Select(x => x.As<TActivity>()).Where(predicate).FirstOrDefault();
public static IActivityBlueprintWrapper<TActivity> GetUnfilteredActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, string id) where TActivity : IActivity => workflowBlueprintWrapper.FindUnfilteredActivity<TActivity>(x => x.ActivityBlueprint.Id == id)!;
}
}

View file

@ -87,7 +87,7 @@ namespace Elsa.Triggers
{
var bookmarkResults = (await provider.GetBookmarksAsync(context, cancellationToken)).ToList();
return bookmarkResults
.Select(x => new WorkflowTrigger(workflowBlueprint, activityBlueprint.Id, activityBlueprint.Type, _bookmarkHasher.Hash(x.Bookmark), x.Bookmark))
.Select(x => new WorkflowTrigger(workflowBlueprint, activityBlueprint.Id, x.ActivityTypeName ?? activityBlueprint.Type, _bookmarkHasher.Hash(x.Bookmark), x.Bookmark))
.ToList();
}
}

View file

@ -1118,6 +1118,12 @@ select {
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.elsa-space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.elsa-space-y-8 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
@ -2544,6 +2550,14 @@ select {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.elsa-grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.elsa-col-span-4 {
grid-column: span 4 / span 4;
}
.elsa-transform {
--tw-translate-x: 0;
--tw-translate-y: 0;
@ -3212,6 +3226,10 @@ svg.jtk-connector.jtk-hover path {
border-color: rgba(229, 231, 235, var(--tw-divide-opacity));
}
.sm\:elsa-rounded-md {
border-radius: 0.375rem;
}
.sm\:elsa-rounded-lg {
border-radius: 0.5rem;
}
@ -4357,11 +4375,26 @@ svg.jtk-connector.jtk-hover path {
max-width: 65ch;
}
.lg\:elsa-py-0 {
padding-top: 0px;
padding-bottom: 0px;
}
.lg\:elsa-px-0 {
padding-left: 0px;
padding-right: 0px;
}
.lg\:elsa-px-8 {
padding-left: 2rem;
padding-right: 2rem;
}
.lg\:elsa-py-12 {
padding-top: 3rem;
padding-bottom: 3rem;
}
.lg\:elsa-pl-2 {
padding-left: 0.5rem;
}
@ -4374,13 +4407,26 @@ svg.jtk-connector.jtk-hover path {
gap: 2rem;
}
.lg\:elsa-gap-x-5 {
-moz-column-gap: 1.25rem;
column-gap: 1.25rem;
}
.lg\:elsa-grid-cols-12 {
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.lg\:elsa-col-span-2 {
grid-column: span 2 / span 2;
}
.lg\:elsa-col-span-6 {
grid-column: span 6 / span 6;
}
.lg\:elsa-col-span-9 {
grid-column: span 9 / span 9;
}
}
@media (min-width: 1280px) {

View file

@ -1118,6 +1118,12 @@ select {
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.elsa-space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.elsa-space-y-8 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
@ -2544,6 +2550,14 @@ select {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.elsa-grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.elsa-col-span-4 {
grid-column: span 4 / span 4;
}
.elsa-transform {
--tw-translate-x: 0;
--tw-translate-y: 0;
@ -3212,6 +3226,10 @@ svg.jtk-connector.jtk-hover path {
border-color: rgba(229, 231, 235, var(--tw-divide-opacity));
}
.sm\:elsa-rounded-md {
border-radius: 0.375rem;
}
.sm\:elsa-rounded-lg {
border-radius: 0.5rem;
}
@ -4357,11 +4375,26 @@ svg.jtk-connector.jtk-hover path {
max-width: 65ch;
}
.lg\:elsa-py-0 {
padding-top: 0px;
padding-bottom: 0px;
}
.lg\:elsa-px-0 {
padding-left: 0px;
padding-right: 0px;
}
.lg\:elsa-px-8 {
padding-left: 2rem;
padding-right: 2rem;
}
.lg\:elsa-py-12 {
padding-top: 3rem;
padding-bottom: 3rem;
}
.lg\:elsa-pl-2 {
padding-left: 0.5rem;
}
@ -4374,13 +4407,26 @@ svg.jtk-connector.jtk-hover path {
gap: 2rem;
}
.lg\:elsa-gap-x-5 {
-moz-column-gap: 1.25rem;
column-gap: 1.25rem;
}
.lg\:elsa-grid-cols-12 {
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.lg\:elsa-col-span-2 {
grid-column: span 2 / span 2;
}
.lg\:elsa-col-span-6 {
grid-column: span 6 / span 6;
}
.lg\:elsa-col-span-9 {
grid-column: span 9 / span 9;
}
}
@media (min-width: 1280px) {