Skip to content

Commit

Permalink
Merge branch 'v3' into elasticsearch
Browse files Browse the repository at this point in the history
# Conflicts:
#	Elsa.sln
  • Loading branch information
grkngrn committed Jan 5, 2023
2 parents fcfd092 + 3dd1175 commit 26d0586
Show file tree
Hide file tree
Showing 46 changed files with 822 additions and 9 deletions.
1 change: 1 addition & 0 deletions .github/workflows/packages.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Elsa 3 Prerelease
on:
workflow_dispatch:
push:
branches:
- v3
Expand Down
21 changes: 21 additions & 0 deletions Elsa.sln
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.RunTaskIntegra
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Elasticsearch", "src\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj", "{3246883E-2FA7-4B4A-BDC5-99039A2869BC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Webhooks", "src\modules\Elsa.Webhooks\Elsa.Webhooks.csproj", "{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Webhooks.WorkflowServer", "src\samples\aspnet\Elsa.Samples.Webhooks.WorkflowServer\Elsa.Samples.Webhooks.WorkflowServer.csproj", "{130A7A00-A9AF-4EA8-8107-BBEA07F166DF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Webhooks.ExternalApp", "src\samples\aspnet\Elsa.Samples.Webhooks.ExternalApp\Elsa.Samples.Webhooks.ExternalApp.csproj", "{036D287A-33E1-4B28-BE13-14AEA16BC91F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -368,6 +374,18 @@ Global
{3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Release|Any CPU.Build.0 = Release|Any CPU
{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Release|Any CPU.Build.0 = Release|Any CPU
{130A7A00-A9AF-4EA8-8107-BBEA07F166DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{130A7A00-A9AF-4EA8-8107-BBEA07F166DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{130A7A00-A9AF-4EA8-8107-BBEA07F166DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{130A7A00-A9AF-4EA8-8107-BBEA07F166DF}.Release|Any CPU.Build.0 = Release|Any CPU
{036D287A-33E1-4B28-BE13-14AEA16BC91F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{036D287A-33E1-4B28-BE13-14AEA16BC91F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{036D287A-33E1-4B28-BE13-14AEA16BC91F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{036D287A-33E1-4B28-BE13-14AEA16BC91F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
Expand Down Expand Up @@ -431,5 +449,8 @@ Global
{AA84DBF7-F70F-4673-8DC2-6EFBE3E9BF83} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{51050209-EC2F-4DC7-8F46-07E22B7811CD} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
{3246883E-2FA7-4B4A-BDC5-99039A2869BC} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{130A7A00-A9AF-4EA8-8107-BBEA07F166DF} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
{036D287A-33E1-4B28-BE13-14AEA16BC91F} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
Expand Down
6 changes: 0 additions & 6 deletions src/bundles/Elsa.WorkflowServer.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
using Elsa.Elasticsearch.Options;
using Elsa.EntityFrameworkCore.Extensions;
using Elsa.Extensions;
using Elsa.Identity;
using Elsa.Identity.Options;
using Elsa.Jobs.Activities.Services;
using Elsa.EntityFrameworkCore.Modules.ActivityDefinitions;
using Elsa.EntityFrameworkCore.Modules.Labels;
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.Requirements;
using Elsa.WorkflowServer.Web.Jobs;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Data.Sqlite;
using Proto.Persistence.Sqlite;

Expand All @@ -27,7 +24,6 @@
var identityTokenSection = identitySection.GetSection("Tokens");
identitySection.Bind(identityOptions);
identityTokenSection.Bind(identityTokenOptions);
var postgreSqlConnectionString = configuration.GetConnectionString("PostgreSql")!;
var elasticOptions = new ElasticsearchOptions();
configuration.GetSection(ElasticsearchOptions.Elasticsearch).Bind(elasticOptions);

Expand Down Expand Up @@ -70,8 +66,6 @@
services.AddHealthChecks();
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
services.AddHttpContextAccessor();
services.AddSingleton<IAuthorizationHandler, LocalHostRequirementHandler>();
services.AddAuthorization(options => options.AddPolicy(IdentityPolicyNames.SecurityRoot, policy => policy.AddRequirements(new LocalHostRequirement())));

// Configure middleware pipeline.
var app = builder.Build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Elsa.Common.Exceptions;

/// <summary>
/// Configuration is missing.
/// </summary>
public class MissingConfigurationException : Exception
{
/// <inheritdoc />
public MissingConfigurationException(string message) : base(message)
{
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Elsa.Features.Abstractions;
using Elsa.Features.Attributes;
using Elsa.Features.Services;
using Elsa.Requirements;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;

namespace Elsa.Identity.Features;
Expand All @@ -26,5 +28,7 @@ public override void Apply()
Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, identityOptions.ConfigureJwtBearerOptions);
}

Services.AddSingleton<IAuthorizationHandler, LocalHostRequirementHandler>();
Services.AddAuthorization(options => options.AddPolicy(IdentityPolicyNames.SecurityRoot, policy => policy.AddRequirements(new LocalHostRequirement()))); }
}
4 changes: 4 additions & 0 deletions src/modules/Elsa.Identity/Features/IdentityFeature.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Elsa.Common.Exceptions;
using Elsa.Common.Features;
using Elsa.Extensions;
using Elsa.Features.Abstractions;
Expand Down Expand Up @@ -59,6 +60,9 @@ public override void ConfigureHostedServices()
/// <inheritdoc />
public override void Apply()
{
if (string.IsNullOrWhiteSpace(TokenOptions.SigningKey))
throw new MissingConfigurationException("SigningKey is a required setting for the Identity feature.");

Services.Configure<IdentityTokenOptions>(options => options.CopyFrom(TokenOptions));

Services
Expand Down
9 changes: 9 additions & 0 deletions src/modules/Elsa.Webhooks/Commands/InvokeWebhook.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Elsa.Mediator.Services;
using Elsa.Webhooks.Models;

namespace Elsa.Webhooks.Commands;

/// <summary>
/// Represents a command to invoke all registered webhook endpoints.
/// </summary>
public record InvokeWebhook(WebhookRegistration WebhookRegistration, WebhookEvent WebhookEvent) : ICommand;
23 changes: 23 additions & 0 deletions src/modules/Elsa.Webhooks/Elsa.Webhooks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />

<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Description>
Provides a way to register webhook endpoints that should be invoked when certain event occur.
</Description>
<PackageTags>elsa, module, webhooks</PackageTags>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.1" />
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions src/modules/Elsa.Webhooks/Extensions/ModuleExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Elsa.Features.Services;
using Elsa.Webhooks.Features;

namespace Elsa.Webhooks.Extensions;

/// <summary>
/// Adds extensions to <see cref="IModule"/> that enables the <see cref="WebhooksFeature"/> feature.
/// </summary>
public static class ModuleExtensions
{
/// <summary>
/// Enables and configures the <see cref="WebhooksFeature"/> feature.
/// </summary>
public static IModule UseWebhooks(this IModule module, Action<WebhooksFeature>? configure = default)
{
module.Configure(configure);
return module;
}
}
77 changes: 77 additions & 0 deletions src/modules/Elsa.Webhooks/Features/WebhooksFeature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Elsa.Extensions;
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Elsa.Webhooks.Implementations;
using Elsa.Webhooks.Models;
using Elsa.Webhooks.Options;
using Elsa.Webhooks.Services;
using Microsoft.Extensions.DependencyInjection;
using Polly;

namespace Elsa.Webhooks.Features;

/// <summary>
/// Installs and configures services that let the user register webhook endpoints.
/// </summary>
public class WebhooksFeature : FeatureBase
{
/// <inheritdoc />
public WebhooksFeature(IModule module) : base(module)
{
}

/// <summary>
/// A delegate that resolves the <see cref="IWebhookDispatcher"/> to use.
/// </summary>
public Func<IServiceProvider, IWebhookDispatcher> WebhookDispatcher { get; set; } = sp => sp.GetRequiredService<BackgroundWebhookDispatcher>();

/// <summary>
/// A delegate that is invoked when configuring <see cref="Options.WebhookOptions"/>.
/// </summary>
public Action<WebhookOptions> WebhookOptions { get; set; } = _ => { };

/// <summary>
/// A delegate to configure the <see cref="System.Net.Http.HttpClient"/> used when invoking webhook endpoints.
/// </summary>
public Action<IServiceProvider, HttpClient> HttpClient { get; set; } = (_, _) => { };

/// <summary>
/// A delegate to configure the <see cref="IHttpClientBuilder"/>. For example, to configure Polly policies.
/// </summary>
public Action<IHttpClientBuilder> HttpClientBuilder { get; set; } = builder => builder.AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(3, retry => TimeSpan.FromSeconds(retry)));

/// <summary>
/// Registers the specified webhook with <see cref="Options.WebhookOptions"/>
/// </summary>
public WebhooksFeature RegisterWebhook(Uri endpoint) => RegisterWebhook(new WebhookRegistration(endpoint));

/// <summary>
/// Registers the specified webhook with <see cref="Options.WebhookOptions"/>
/// </summary>
public WebhooksFeature RegisterWebhook(WebhookRegistration registration) => RegisterWebhooks(registration);

/// <summary>
/// Registers the specified webhooks with <see cref="Options.WebhookOptions"/>
/// </summary>
public WebhooksFeature RegisterWebhooks(params WebhookRegistration[] registrations)
{
Services.Configure<WebhookOptions>(options => options.Endpoints.AddRange(registrations));
return this;
}

/// <inheritdoc />
public override void Apply()
{
Services
.AddHandlersFrom<WebhooksFeature>()
.AddSingleton<BackgroundWebhookDispatcher>()
.AddSingleton(WebhookDispatcher)
.AddSingleton<IWebhookRegistrationService, DefaultWebhookRegistrationService>()
.AddSingleton<IWebhookRegistrationProvider, OptionsWebhookRegistrationProvider>();

Services.Configure(WebhookOptions);

var httpClientBuilder = Services.AddHttpClient<IWebhookInvoker, HttpWebhookInvoker>(HttpClient);
HttpClientBuilder(httpClientBuilder);
}
}
3 changes: 3 additions & 0 deletions src/modules/Elsa.Webhooks/FodyWeavers.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
29 changes: 29 additions & 0 deletions src/modules/Elsa.Webhooks/Handlers/InvokeWebhookHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Elsa.Mediator.Models;
using Elsa.Mediator.Services;
using Elsa.Webhooks.Commands;
using Elsa.Webhooks.Services;

namespace Elsa.Webhooks.Handlers;

/// <summary>
/// Handles the <see cref="InvokeWebhook"/> command.
/// </summary>
public class InvokeWebhookHandler : ICommandHandler<InvokeWebhook>
{
private readonly IWebhookInvoker _webhookInvoker;

/// <summary>
/// Constructor.
/// </summary>
public InvokeWebhookHandler(IWebhookInvoker webhookInvoker)
{
_webhookInvoker = webhookInvoker;
}

/// <inheritdoc />
public async Task<Unit> HandleAsync(InvokeWebhook command, CancellationToken cancellationToken)
{
await _webhookInvoker.InvokeWebhookAsync(command.WebhookRegistration, command.WebhookEvent, cancellationToken);
return Unit.Instance;
}
}
35 changes: 35 additions & 0 deletions src/modules/Elsa.Webhooks/Handlers/RunTaskHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Elsa.Common.Services;
using Elsa.Mediator.Services;
using Elsa.Webhooks.Models;
using Elsa.Webhooks.Services;
using Elsa.Workflows.Runtime.Bookmarks;
using Elsa.Workflows.Runtime.Notifications;

namespace Elsa.Webhooks.Handlers;

/// <summary>
/// Handles the <see cref="RunTaskRequest"/> notification and asynchronously invokes all registered webhook endpoints.
/// </summary>
public class RunTaskHandler : INotificationHandler<RunTaskRequest>
{
private readonly IWebhookDispatcher _webhookDispatcher;
private readonly ISystemClock _systemClock;

/// <summary>
/// Constructor.
/// </summary>
public RunTaskHandler(IWebhookDispatcher webhookDispatcher, ISystemClock systemClock)
{
_webhookDispatcher = webhookDispatcher;
_systemClock = systemClock;
}

/// <inheritdoc />
public async Task HandleAsync(RunTaskRequest notification, CancellationToken cancellationToken)
{
var payload = new RunTaskWebhook(notification.TaskId, notification.TaskName, notification.TaskParams);
var now = _systemClock.UtcNow;
var webhookEvent = new WebhookEvent("RunTask", payload, now);
await _webhookDispatcher.DispatchAsync(webhookEvent, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Elsa.Mediator.Services;
using Elsa.Webhooks.Commands;
using Elsa.Webhooks.Models;
using Elsa.Webhooks.Services;

namespace Elsa.Webhooks.Implementations;

/// <summary>
/// Uses a background channel to asynchronously invoke each webhook url.
/// </summary>
public class BackgroundWebhookDispatcher : IWebhookDispatcher
{
private readonly IBackgroundCommandSender _backgroundCommandSender;
private readonly IWebhookRegistrationService _webhookRegistrationService;

/// <summary>
/// Constructor.
/// </summary>
public BackgroundWebhookDispatcher(IBackgroundCommandSender backgroundCommandSender, IWebhookRegistrationService webhookRegistrationService)
{
_backgroundCommandSender = backgroundCommandSender;
_webhookRegistrationService = webhookRegistrationService;
}

/// <inheritdoc />
public async Task DispatchAsync(WebhookEvent webhookEvent, CancellationToken cancellationToken = default)
{
var registrations = await _webhookRegistrationService.ListByEventTypeAsync(webhookEvent.EventType, cancellationToken);

foreach (var registration in registrations)
{
var notification = new InvokeWebhook(registration, webhookEvent);
await _backgroundCommandSender.SendAsync(notification, cancellationToken);
}
}
}
Loading

0 comments on commit 26d0586

Please sign in to comment.