Merge pull request #6291 from jdevillard/enh/Connections
FEAT : Connection/Secret provider/handler to define generic configuration for Activities
This commit is contained in:
commit
6f5b7e77d6
45
Elsa.sln
45
Elsa.sln
|
|
@ -403,6 +403,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "oracle-setup", "oracle-setu
|
|||
docker\oracle-setup\setup.sql = docker\oracle-setup\setup.sql
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "connections", "connections", "{9DF81143-2F39-4CFB-9DA3-902279010622}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Models", "src\modules\Elsa.Connections.Models\Elsa.Connections.Models.csproj", "{75E08B0E-A41A-425C-9DCA-155505CE41CD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Api", "src\modules\Elsa.Connections.Api\Elsa.Connections.Api.csproj", "{BD120D97-4862-469E-B2CF-4109F7F21C20}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Core", "src\modules\Elsa.Connections.Core\Elsa.Connections.Core.csproj", "{8A3443F3-D40E-44B9-9805-B4CB7A3F022C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Persistence", "src\modules\Elsa.Connections.Persistence\Elsa.Connections.Persistence.csproj", "{FA2B2B44-7E3E-444B-A157-7526B376E9CA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Persistence.EntityFrameworkCore", "src\modules\Elsa.Connections.Persistence.EntityFrameworkCore\Elsa.Connections.Persistence.EntityFrameworkCore.csproj", "{DF0CA7E2-F5DF-40ED-91B6-748767084FC7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite\Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.csproj", "{DB20B168-CD90-41F2-BE08-3D8C6F142459}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -859,6 +873,30 @@ Global
|
|||
{A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{75E08B0E-A41A-425C-9DCA-155505CE41CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{75E08B0E-A41A-425C-9DCA-155505CE41CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{75E08B0E-A41A-425C-9DCA-155505CE41CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{75E08B0E-A41A-425C-9DCA-155505CE41CD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BD120D97-4862-469E-B2CF-4109F7F21C20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BD120D97-4862-469E-B2CF-4109F7F21C20}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BD120D97-4862-469E-B2CF-4109F7F21C20}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BD120D97-4862-469E-B2CF-4109F7F21C20}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8A3443F3-D40E-44B9-9805-B4CB7A3F022C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8A3443F3-D40E-44B9-9805-B4CB7A3F022C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A3443F3-D40E-44B9-9805-B4CB7A3F022C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8A3443F3-D40E-44B9-9805-B4CB7A3F022C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FA2B2B44-7E3E-444B-A157-7526B376E9CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FA2B2B44-7E3E-444B-A157-7526B376E9CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FA2B2B44-7E3E-444B-A157-7526B376E9CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FA2B2B44-7E3E-444B-A157-7526B376E9CA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DF0CA7E2-F5DF-40ED-91B6-748767084FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DF0CA7E2-F5DF-40ED-91B6-748767084FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DF0CA7E2-F5DF-40ED-91B6-748767084FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DF0CA7E2-F5DF-40ED-91B6-748767084FC7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DB20B168-CD90-41F2-BE08-3D8C6F142459}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DB20B168-CD90-41F2-BE08-3D8C6F142459}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB20B168-CD90-41F2-BE08-3D8C6F142459}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB20B168-CD90-41F2-BE08-3D8C6F142459}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -1011,6 +1049,13 @@ Global
|
|||
{FA5E857F-B173-4B5D-8049-B817A210DEF5} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E}
|
||||
{A51F9683-DA9F-45E7-82DE-1E261ACD6D68} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E}
|
||||
{66E2E2CF-967F-4564-89E8-F46FA973C99B} = {986E5482-0482-448C-B9E4-EC67A9474B85}
|
||||
{9DF81143-2F39-4CFB-9DA3-902279010622} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{75E08B0E-A41A-425C-9DCA-155505CE41CD} = {9DF81143-2F39-4CFB-9DA3-902279010622}
|
||||
{BD120D97-4862-469E-B2CF-4109F7F21C20} = {9DF81143-2F39-4CFB-9DA3-902279010622}
|
||||
{8A3443F3-D40E-44B9-9805-B4CB7A3F022C} = {9DF81143-2F39-4CFB-9DA3-902279010622}
|
||||
{FA2B2B44-7E3E-444B-A157-7526B376E9CA} = {9DF81143-2F39-4CFB-9DA3-902279010622}
|
||||
{DF0CA7E2-F5DF-40ED-91B6-748767084FC7} = {9DF81143-2F39-4CFB-9DA3-902279010622}
|
||||
{DB20B168-CD90-41F2-BE08-3D8C6F142459} = {9DF81143-2F39-4CFB-9DA3-902279010622}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
<ProjectReference Include="..\..\modules\Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Agents.Persistence.EntityFrameworkCore.Sqlite\Elsa.Agents.Persistence.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Agents.Persistence.EntityFrameworkCore.SqlServer\Elsa.Agents.Persistence.EntityFrameworkCore.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Connections.Api\Elsa.Connections.Api.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Connections.Core\Elsa.Connections.Core.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite\Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Connections.Persistence.EntityFrameworkCore\Elsa.Connections.Persistence.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.SqlServer\Elsa.EntityFrameworkCore.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.JavaScript.Libraries\Elsa.JavaScript.Libraries.csproj" />
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.Data.Sqlite;
|
||||
using Proto.Persistence.Sqlite;
|
||||
using WebhooksCore.Options;
|
||||
using Elsa.Connections.Middleware;
|
||||
|
||||
const bool useMassTransit = true;
|
||||
const bool useProtoActor = true;
|
||||
|
|
@ -193,6 +194,11 @@ services
|
|||
if (distributedCachingTransport == DistributedCachingTransport.MassTransit) distributedCaching.UseMassTransit();
|
||||
});
|
||||
}
|
||||
|
||||
elsa.UseConnections(
|
||||
configure=> configure.AddConnectionsFrom<Program>());
|
||||
elsa.UseConnectionPersistence(ef=> ef.UseEntityFrameworkCore(f=>f.UseSqlite()));
|
||||
elsa.UseConnectionsApi();
|
||||
});
|
||||
|
||||
services.Configure<WebhookSinksOptions>(options => configuration.GetSection("Webhooks").Bind(options));
|
||||
|
|
|
|||
5
src/modules/Elsa.Connections.Api/Constants.cs
Normal file
5
src/modules/Elsa.Connections.Api/Constants.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
namespace Elsa.Connections.Api;
|
||||
public class Constants
|
||||
{
|
||||
public const string PermissionsNamespace = "connections";
|
||||
}
|
||||
14
src/modules/Elsa.Connections.Api/Elsa.Connections.Api.csproj
Normal file
14
src/modules/Elsa.Connections.Api/Elsa.Connections.Api.csproj
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Provides Activity Connection API endpoints</Description>
|
||||
<PackageTags>elsa module activity connection abstraction api</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\Elsa.Api.Common\Elsa.Api.Common.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Connections.Core\Elsa.Connections.Core.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Connections.Models\Elsa.Connections.Models.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Connections.Persistence\Elsa.Connections.Persistence.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.ActivityConnectionDescriptor.Get;
|
||||
|
||||
public class Endpoint(IConnectionDescriptorRegistry store) : ElsaEndpointWithoutRequest<IEnumerable<InputDescriptor>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/connection-configuration/input-descriptor/{ActivityType}");
|
||||
ConfigurePermissions($"{Constants.PermissionsNamespace}/descriptor:read");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
var type = Route<string>("ActivityType");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(type))
|
||||
{
|
||||
AddError("ActivityType is required");
|
||||
await SendErrorsAsync(cancellation: ct);
|
||||
return;
|
||||
}
|
||||
|
||||
var config = await store.GetConnectionDescriptorAsync(type, ct);
|
||||
await SendOkAsync(config, ct);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.ActivityConnectionDescriptor.List;
|
||||
|
||||
public class List(IConnectionDescriptorRegistry registry) : ElsaEndpointWithoutRequest<PagedListResponse<ConnectionDescriptor>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/connection-configuration/descriptors");
|
||||
ConfigurePermissions($"{Constants.PermissionsNamespace}/descriptor:read");
|
||||
}
|
||||
|
||||
public override Task<PagedListResponse<ConnectionDescriptor>> ExecuteAsync(CancellationToken ct)
|
||||
{
|
||||
var descriptors = registry.ListAll().ToList();
|
||||
return Task.FromResult(new PagedListResponse<ConnectionDescriptor>(Page.Of(descriptors, descriptors.Count)) );
|
||||
}
|
||||
}
|
||||
53
src/modules/Elsa.Connections.Api/Endpoints/Add/Endpoint.cs
Normal file
53
src/modules/Elsa.Connections.Api/Endpoints/Add/Endpoint.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
using Elsa.Connections.Persistence.Filters;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Connections.Persistence.Entities;
|
||||
using Elsa.Connections.Api.Extensions;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.Add;
|
||||
|
||||
public class Endpoint(IConnectionStore store, IIdentityGenerator identityGenerator) : ElsaEndpoint<ConnectionInputModel, ConnectionModel>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/connection-configuration");
|
||||
ConfigurePermissions($"{Constants.PermissionsNamespace}:write");
|
||||
}
|
||||
|
||||
public override async Task<ConnectionModel> ExecuteAsync(ConnectionInputModel model, CancellationToken ct)
|
||||
{
|
||||
var isNameUnique = await IsNameUniqueAsync(model.Name, ct);
|
||||
|
||||
if (!isNameUnique)
|
||||
{
|
||||
AddError("A connection already exists with the specified name");
|
||||
await SendErrorsAsync(cancellation: ct);
|
||||
return null!;
|
||||
}
|
||||
|
||||
var newEntity = new ConnectionDefinition
|
||||
{
|
||||
Id = identityGenerator.GenerateId(),
|
||||
Name = model.Name,
|
||||
Description = model.Description,
|
||||
ConnectionType = model.ConnectionType,
|
||||
ConnectionConfiguration = model.ConnectionConfiguration,
|
||||
};
|
||||
await store.AddAsync(newEntity, ct);
|
||||
await SendOkAsync(ct);
|
||||
|
||||
return newEntity.ToModel();
|
||||
}
|
||||
|
||||
private async Task<bool> IsNameUniqueAsync(string name, CancellationToken ct)
|
||||
{
|
||||
var filter = new ConnectionDefinitionFilter
|
||||
{
|
||||
Name = name
|
||||
};
|
||||
return await store.FindAsync(filter, ct) == null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.Delete;
|
||||
|
||||
public class Endpoint(IConnectionStore store) : ElsaEndpoint<Request>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Delete("/connection-configuration/{id}");
|
||||
ConfigurePermissions($"{Constants.PermissionsNamespace}:delete");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(Request req, CancellationToken ct)
|
||||
{
|
||||
var entity = await store.GetAsync(req.Id, ct);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
await store.DeleteAsync(entity, ct);
|
||||
await SendOkAsync(ct);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.Delete;
|
||||
|
||||
public class Request
|
||||
{
|
||||
[Required] public string Id { get; set; } = null!;
|
||||
}
|
||||
32
src/modules/Elsa.Connections.Api/Endpoints/Get/Endpoint.cs
Normal file
32
src/modules/Elsa.Connections.Api/Endpoints/Get/Endpoint.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
using Elsa.Connections.Api.Extensions;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.Get;
|
||||
|
||||
public class Endpoint(IConnectionStore store) : ElsaEndpoint<Request,ConnectionModel>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/connection-configuration/{id}");
|
||||
ConfigurePermissions($"{Constants.PermissionsNamespace}:read");
|
||||
}
|
||||
|
||||
public override async Task<ConnectionModel> ExecuteAsync(Request req, CancellationToken ct)
|
||||
{
|
||||
var entity = await store.FindAsync(
|
||||
new()
|
||||
{
|
||||
Id = req.Id
|
||||
}, ct);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return null!;
|
||||
}
|
||||
|
||||
return entity.ToModel();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.Get;
|
||||
|
||||
public class Request
|
||||
{
|
||||
[Required] public string Id { get; set; } = null!;
|
||||
}
|
||||
24
src/modules/Elsa.Connections.Api/Endpoints/List/Endpoint.cs
Normal file
24
src/modules/Elsa.Connections.Api/Endpoints/List/Endpoint.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Connections.Api.Extensions;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.List;
|
||||
|
||||
public class Endpoint(IConnectionStore store) : ElsaEndpointWithoutRequest<PagedListResponse<ConnectionModel>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/connection-configuration");
|
||||
ConfigurePermissions($"{Constants.PermissionsNamespace}:read");
|
||||
}
|
||||
|
||||
public override async Task<PagedListResponse<ConnectionModel>> ExecuteAsync(CancellationToken ct)
|
||||
{
|
||||
var entities = await store.ListAsync(ct);
|
||||
var models = entities.Select(x => x.ToModel()).ToList();
|
||||
return new(Page.Of(models, models.Count()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
using Elsa.Connections.Persistence.Filters;
|
||||
using Elsa.Connections.Api.Extensions;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.Update;
|
||||
|
||||
public class Endpoint(IConnectionStore store) : ElsaEndpoint<ConnectionInputModel, ConnectionModel>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Put("/connection-configuration/{id}");
|
||||
ConfigurePermissions($"{Constants.PermissionsNamespace}:write");
|
||||
}
|
||||
|
||||
public override async Task<ConnectionModel> ExecuteAsync(ConnectionInputModel model, CancellationToken ct)
|
||||
{
|
||||
var id = Route<string>("id")!;
|
||||
var entity = await store.GetAsync(id, ct);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return null!;
|
||||
}
|
||||
|
||||
var isNameDuplicate = await IsNameDuplicateAsync(model.Name, id, ct);
|
||||
|
||||
if (isNameDuplicate)
|
||||
{
|
||||
AddError("Another connection already exist with the specified name");
|
||||
await SendErrorsAsync(cancellation: ct);
|
||||
return entity.ToModel();
|
||||
}
|
||||
|
||||
entity.Name = model.Name;
|
||||
entity.Description = model.Description;
|
||||
entity.ConnectionConfiguration = model.ConnectionConfiguration;
|
||||
|
||||
await store.UpdateAsync(entity, ct);
|
||||
return entity.ToModel();
|
||||
}
|
||||
|
||||
private async Task<bool> IsNameDuplicateAsync(string name, string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var entities = await store.FindAsync(new()
|
||||
{
|
||||
NotId = id,
|
||||
Name = name
|
||||
}, cancellationToken);
|
||||
|
||||
return entities != null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Connections.Models;
|
||||
using Elsa.Connections.Persistence.Entities;
|
||||
|
||||
namespace Elsa.Connections.Api.Extensions;
|
||||
|
||||
public static class ConnectionDefinitionExtensions
|
||||
{
|
||||
public static ConnectionModel ToModel(this ConnectionDefinition entity)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = entity.Id,
|
||||
Name = entity.Name,
|
||||
Description = entity.Description,
|
||||
ConnectionType = entity.ConnectionType,
|
||||
ConnectionConfiguration = entity.ConnectionConfiguration,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Connections.Api.Features;
|
||||
using Elsa.Features.Services;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extends <see cref="IModule"/> with methods to Connection API endpoints.
|
||||
/// </summary>
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Installs the Connections API feature.
|
||||
/// </summary>
|
||||
public static IModule UseConnectionsApi(this IModule module, Action<ConnectionsApiFeature>? configure = null)
|
||||
{
|
||||
return module.Use(configure);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Connections.Persistence.Features;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Connections.Api.Features;
|
||||
|
||||
/// <summary>
|
||||
/// A feature that installs API endpoints to interact connections.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(ConnectionPersistenceFeature))]
|
||||
[UsedImplicitly]
|
||||
public class ConnectionsApiFeature(IModule module) : FeatureBase(module)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.AddFastEndpointsAssembly<ConnectionsApiFeature>();
|
||||
}
|
||||
}
|
||||
3
src/modules/Elsa.Connections.Api/FodyWeavers.xml
Normal file
3
src/modules/Elsa.Connections.Api/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace Elsa.Connections.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ConnectionActivityAttribute(string type) : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The TypeName of the connection
|
||||
/// </summary>
|
||||
public string Type { get; set; } = type;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace Elsa.Connections.Attributes;
|
||||
|
||||
public class ConnectionPropertyAttribute(string ns, string displayName, string? description = null) : Attribute
|
||||
{
|
||||
public string? Namespace { get; set; } = ns;
|
||||
public string? Description { get; set; } = description;
|
||||
public string? DisplayName { get; set; } = displayName;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
namespace Elsa.Connections.Attributes;
|
||||
|
||||
public class ConnectionTypeAttribute : Attribute
|
||||
{
|
||||
public ConnectionTypeAttribute(Type type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
/// <summary>
|
||||
/// The TypeName of the connection
|
||||
/// </summary>
|
||||
public Type Type { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
namespace Elsa.Connections.Attributes;
|
||||
|
||||
public class NoLogAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using Elsa.Connections.Models;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Connections.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Store all connection descriptors available to the system
|
||||
/// </summary>
|
||||
public interface IConnectionDescriptorRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a connection descriptor to the registry
|
||||
/// </summary>
|
||||
/// <param name="connectionType">The type of the connection </param>
|
||||
/// <param name="connectionDescriptor">The type desiptor of the connection</param>
|
||||
void Add(Type connectionType, ConnectionDescriptor connectionDescriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an activity descriptor from the registry.
|
||||
/// </summary>
|
||||
/// <param name="connectionType">The type of the connection.</param>
|
||||
/// <param name="descriptor"></param>
|
||||
void Remove(Type connectionType, ActivityDescriptor descriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all connection descriptors in the registry.
|
||||
/// </summary>
|
||||
/// <returns>All connection descriptors in the registry.</returns>
|
||||
IEnumerable<ConnectionDescriptor> ListAll();
|
||||
|
||||
Type? Get(string type);
|
||||
|
||||
/// <summary>
|
||||
/// Get the Input Descriptors for an connection
|
||||
/// </summary>
|
||||
/// <param name="activityType">The type of the connectn</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<InputDescriptor>> GetConnectionDescriptorAsync(string activityType, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Elsa.Connections.Contracts;
|
||||
|
||||
public interface IConnectionDescriptorRegistryEX
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Elsa.Connections.Contracts;
|
||||
interface IConnectionProperty
|
||||
{
|
||||
public string ConnectionName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public object Properties { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace Elsa.Connections.Converters;
|
||||
|
||||
public class Int32Converter : System.Text.Json.Serialization.JsonConverter<int>
|
||||
{
|
||||
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
var stringValue = reader.GetString();
|
||||
if (int.TryParse(stringValue, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
else if (reader.TokenType == JsonTokenType.Number)
|
||||
{
|
||||
return reader.GetInt32();
|
||||
}
|
||||
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteNumberValue(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Provides an connection framework to use connection abtraction in activities</Description>
|
||||
<PackageTags>elsa module connection abstratction</PackageTags>
|
||||
<RootNamespace>Elsa.Connections</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Common\Elsa.Common.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Connections.Models\Elsa.Connections.Models.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Connections.Persistence\Elsa.Connections.Persistence.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// ReSharper disable once CheckNamespace
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Connections.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static partial class LogConnectionExtensions
|
||||
{
|
||||
[LoggerMessage(
|
||||
Level = LogLevel.Warning,
|
||||
Message = "Connection {name} was not found"
|
||||
)]
|
||||
public static partial void LogConnectionNotFound(ILogger logger, string name);
|
||||
|
||||
[LoggerMessage(
|
||||
Level = LogLevel.Warning,
|
||||
Message = "Connection is null"
|
||||
)]
|
||||
public static partial void LogConnectionIsNull(ILogger logger);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using Elsa.Connections.Features;
|
||||
using Elsa.Connections.Middleware;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Pipelines.ActivityExecution;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extends <see cref="IModule"/> with methods to install Semantic Kernel API endpoints.
|
||||
/// </summary>
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Installs the Semantic Kernel API feature.
|
||||
/// </summary>
|
||||
public static IModule UseConnections(this IModule module, Action<ConnectionsFeatures>? configure = null)
|
||||
{
|
||||
return module.Use(configure);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds extension methods to <see cref="ConnectionMiddleware"/>.
|
||||
/// </summary>
|
||||
public static class ConnectionMiddlewareExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Installs the <see cref="ConnectionMiddleware"/> component in the activity execution pipeline.
|
||||
/// </summary>
|
||||
public static IActivityExecutionPipelineBuilder UseConnectionMiddleware(this IActivityExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.UseMiddleware<ConnectionMiddleware>();
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Filters;
|
||||
using Elsa.Connections.ServiceProvider;
|
||||
using Elsa.Connections.Services;
|
||||
using Elsa.Connections.UIHints;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Pipelines.ActivityExecution;
|
||||
using Elsa.Workflows.Features;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Elsa.Connections.Middleware;
|
||||
using Elsa.Connections.Models;
|
||||
|
||||
namespace Elsa.Connections.Features;
|
||||
|
||||
/// <summary>
|
||||
/// A feature that installs API endpoints to interact with skilled agents.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(WorkflowsFeature))]
|
||||
[UsedImplicitly]
|
||||
public class ConnectionsFeatures(IModule module) : FeatureBase(module)
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A set of connection types to make available to the system.
|
||||
/// </summary>
|
||||
public HashSet<Type> ConnectionTypes { get; } = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
// Obfuscate Connection Properties.
|
||||
Services.AddActivityStateFilter<PropertyAttributeFilter>();
|
||||
// Activity property options providers.
|
||||
Services.AddScoped<Elsa.Workflows.IPropertyUIHandler, ConnectionOptionsProvider>();
|
||||
|
||||
Services.AddSingleton<IConnectionDescriptorRegistry, ConnectionRegistry>();
|
||||
|
||||
//UIHints
|
||||
Services.AddScoped<IUIHintHandler, ConnectionDropDownUIHintHandler>();
|
||||
|
||||
Services.Configure<ConnectionOptions>(options =>
|
||||
{
|
||||
foreach (var connectionType in ConnectionTypes.Distinct())
|
||||
options.ConnectionTypes.Add(connectionType);
|
||||
});
|
||||
}
|
||||
public override void Configure()
|
||||
{
|
||||
//TODO: Need to insert the middleware just before the BackgroundActivityInvoker
|
||||
//How to be sure that it is inserted before?
|
||||
var workflowFeature = Module.Configure<WorkflowsFeature>()
|
||||
.WithDefaultActivityExecutionPipeline(pipeline => pipeline.Insert<ConnectionMiddleware>(3));
|
||||
base.Configure();
|
||||
}
|
||||
public ConnectionsFeatures AddConnectionsFrom<TMarker>()
|
||||
{
|
||||
var connectionTypes = typeof(TMarker).Assembly.GetExportedTypes()
|
||||
.Where(x => x.GetCustomAttribute<ConnectionPropertyAttribute>() != null )
|
||||
.ToList();
|
||||
|
||||
ConnectionTypes.AddRange(connectionTypes);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Workflows;
|
||||
|
||||
namespace Elsa.Connections.Filters;
|
||||
|
||||
public class PropertyAttributeFilter : ActivityStateFilterBase
|
||||
{
|
||||
protected override ActivityStateFilterResult OnExecute(ActivityStateFilterContext context)
|
||||
{
|
||||
var inputDescriptor = context.InputDescriptor;
|
||||
|
||||
if (Attribute.IsDefined(inputDescriptor.PropertyInfo, typeof(NoLogAttribute)))
|
||||
{
|
||||
var contextValue = context.Value.GetProperty("connectionName").GetString();
|
||||
|
||||
if (contextValue == null)
|
||||
return ActivityStateFilterResult.Pass();
|
||||
|
||||
var maskedValue = $"**** see connection information for {contextValue} ****";
|
||||
return Filtered(maskedValue);
|
||||
}
|
||||
|
||||
return ActivityStateFilterResult.Pass();
|
||||
}
|
||||
}
|
||||
3
src/modules/Elsa.Connections.Core/FodyWeavers.xml
Normal file
3
src/modules/Elsa.Connections.Core/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Pipelines.ActivityExecution;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Elsa.Connections.Core.Extensions;
|
||||
using Elsa.Connections.Converters;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
|
||||
namespace Elsa.Connections.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// An activity execution middleware component that injects connection properties from Connection Name.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ConnectionMiddleware(ActivityMiddlewareDelegate next
|
||||
, IConnectionStore connectionStore
|
||||
,ILogger<ConnectionMiddleware> logger)
|
||||
: IActivityExecutionMiddleware
|
||||
{
|
||||
private static JsonSerializerOptions? _serializerOptions;
|
||||
|
||||
private static JsonSerializerOptions SerializerOptions =>
|
||||
_serializerOptions ??= new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
}.WithConverters(new Int32Converter());
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask InvokeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var activityDescriptor = context.ActivityDescriptor;
|
||||
|
||||
if (activityDescriptor.Attributes.Any(attr => attr.GetType() == typeof(ConnectionActivityAttribute)))
|
||||
{
|
||||
var inputDescriptors = activityDescriptor.Inputs.Where(x => x.PropertyInfo?.PropertyType.GetGenericTypeDefinition() == typeof(ConnectionProperties<>)).ToList();
|
||||
|
||||
if (inputDescriptors.Count > 0)
|
||||
{
|
||||
var input = inputDescriptors[0];
|
||||
var propertyType = input.PropertyInfo?.PropertyType.GetGenericArguments()[0];
|
||||
|
||||
dynamic inputValue = input.ValueGetter(context.Activity)!;
|
||||
var connectionName = (string)inputValue.ConnectionName;
|
||||
|
||||
if (connectionName == null)
|
||||
LogConnectionExtensions.LogConnectionIsNull(logger);
|
||||
else
|
||||
{
|
||||
//Get connection from store, if exist,
|
||||
var connectionConfiguration = await connectionStore.FindAsync(new Persistence.Filters.ConnectionDefinitionFilter() { Name = connectionName });
|
||||
if (connectionConfiguration != null)
|
||||
{
|
||||
dynamic deserializedjson = JsonSerializer.Deserialize(connectionConfiguration.ConnectionConfiguration, propertyType, SerializerOptions);
|
||||
|
||||
inputValue.Properties = deserializedjson;
|
||||
|
||||
input.ValueSetter(context.Activity, inputValue);
|
||||
}
|
||||
else
|
||||
LogConnectionExtensions.LogConnectionNotFound(logger, connectionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace Elsa.Connections.Models;
|
||||
|
||||
public class ConnectionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of connection types that are available to the system.
|
||||
/// </summary>
|
||||
public HashSet<Type> ConnectionTypes { get; set; } = new();
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
using Elsa.Workflows.UIHints.Dropdown;
|
||||
|
||||
namespace Elsa.Connections.ServiceProvider;
|
||||
|
||||
public class ConnectionOptionsProvider(IConnectionStore store) : DropDownOptionsProviderBase
|
||||
{
|
||||
public new async ValueTask<IDictionary<string, object>> GetUIPropertiesAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = await base.GetUIPropertiesAsync(propertyInfo, context, cancellationToken);
|
||||
options.Add("Refresh", true);
|
||||
return options;
|
||||
}
|
||||
|
||||
protected override async ValueTask<ICollection<SelectListItem>> GetItemsAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken)
|
||||
{
|
||||
var connection = new List<SelectListItem>();
|
||||
var connectionType = propertyInfo.GetCustomAttribute<ConnectionTypeAttribute>()?.Type;
|
||||
|
||||
if (connectionType == null)
|
||||
return connection;
|
||||
|
||||
var connections = await store.FindManyAsync(new() { Type = connectionType.ToString() }, cancellationToken);
|
||||
|
||||
foreach(var conn in connections)
|
||||
connection.Add(new(conn.Name, conn.Name));
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
protected override IDictionary<string, object> GetUIPropertyAdditionalOptions()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["Refresh"] = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Connections.Services;
|
||||
|
||||
public class ConnectionRegistry(IOptions<ConnectionOptions> options, IActivityDescriber describer) : IConnectionDescriptorRegistry
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, ConnectionDescriptor> _connectionDescriptors = new();
|
||||
private readonly ConnectionOptions _options = options.Value;
|
||||
|
||||
public void Add(Type connectionType, ConnectionDescriptor connectionDescriptor)
|
||||
{
|
||||
var descriptor = DescribeConnection(connectionType);
|
||||
|
||||
_connectionDescriptors.TryAdd(connectionType, descriptor);
|
||||
}
|
||||
|
||||
public IEnumerable<ConnectionDescriptor> ListAll()
|
||||
{
|
||||
foreach (var connectionType in _options.ConnectionTypes)
|
||||
yield return DescribeConnection(connectionType);
|
||||
}
|
||||
|
||||
public void Remove(Type connectionType, ActivityDescriptor descriptor)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Type? Get(string type)
|
||||
{
|
||||
return _options.ConnectionTypes.FirstOrDefault(c => c.ToString() == type);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<InputDescriptor>> GetConnectionDescriptorAsync(string activityType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var propertyType = Get(activityType);
|
||||
|
||||
if (propertyType == null)
|
||||
return [];
|
||||
|
||||
var connectionInputProperty = describer.GetInputProperties(propertyType);
|
||||
var connectionInputDescriptor = await DescribeInputPropertiesAsync(connectionInputProperty, cancellationToken);
|
||||
|
||||
return connectionInputDescriptor;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync(IEnumerable<PropertyInfo> properties, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await Task.WhenAll(properties.Select(async x => await describer.DescribeInputPropertyAsync(x, cancellationToken)));
|
||||
}
|
||||
|
||||
private ConnectionDescriptor DescribeConnection(Type connectionType)
|
||||
{
|
||||
var connectionAttribute = connectionType.GetCustomAttribute<ConnectionPropertyAttribute>();
|
||||
|
||||
if (connectionAttribute == null)
|
||||
throw new ArgumentNullException($"{connectionType} is not a valid connection, make sure [ConnectionPropertyAttribute] is set ");
|
||||
|
||||
return new(
|
||||
connectionType.ToString(),
|
||||
connectionAttribute.Description,
|
||||
connectionAttribute.Namespace,
|
||||
connectionAttribute.DisplayName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.UIHints;
|
||||
using Elsa.Workflows.UIHints.Dropdown;
|
||||
|
||||
namespace Elsa.Connections.UIHints;
|
||||
|
||||
/// <summary>
|
||||
/// UIHint Handler that use DropDown for Connection Property
|
||||
/// </summary>
|
||||
public class ConnectionDropDownUIHintHandler : IUIHintHandler
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string UIHint => $"connection-{InputUIHints.DropDown}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<Type>> GetPropertyUIHandlersAsync(PropertyInfo propertyInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return new([typeof(StaticDropDownOptionsProvider)]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
namespace Elsa.Connections.Models;
|
||||
|
||||
public record ConnectionDescriptor(string Type, string? Description, string? Namespace, string? ProviderName);
|
||||
27
src/modules/Elsa.Connections.Models/ConnectionInputModel.cs
Normal file
27
src/modules/Elsa.Connections.Models/ConnectionInputModel.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Elsa.Connections.Models;
|
||||
|
||||
public class ConnectionInputModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Name of the Connection
|
||||
/// </summary>
|
||||
[Required] public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The Description of the Connection
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Configuration of the Connection in a Json Format
|
||||
/// </summary>
|
||||
public JsonObject ConnectionConfiguration { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The Connection Type Name
|
||||
/// </summary>
|
||||
[Required] public string ConnectionType { get; set; } = null!;
|
||||
}
|
||||
11
src/modules/Elsa.Connections.Models/ConnectionModel.cs
Normal file
11
src/modules/Elsa.Connections.Models/ConnectionModel.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Elsa.Connections.Models;
|
||||
|
||||
public class ConnectionModel : ConnectionInputModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Id of the Connection
|
||||
/// </summary>
|
||||
[Required] public string Id { get; set; } = null!;
|
||||
}
|
||||
20
src/modules/Elsa.Connections.Models/ConnectionProperties.cs
Normal file
20
src/modules/Elsa.Connections.Models/ConnectionProperties.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Elsa.Connections.Models;
|
||||
|
||||
public class ConnectionProperties<T> where T : class, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ConnectionProperties"/> class.
|
||||
/// </summary>
|
||||
[JsonConstructor]
|
||||
public ConnectionProperties()
|
||||
{
|
||||
}
|
||||
|
||||
public string? ConnectionName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public T Properties { get; set; } = new();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Provides models that can be shared between the API and API clients</Description>
|
||||
<PackageTags>elsa module connections api</PackageTags>
|
||||
<RootNamespace>Elsa.Connections</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
2
src/modules/Elsa.Connections.Models/FodyWeavers.xml
Normal file
2
src/modules/Elsa.Connections.Models/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
</Weavers>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Elsa.EntityFrameworkCore.Abstractions;
|
||||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class SqliteConnectionDbContextFactory : DesignTimeDbContextFactoryBase<ConnectionDbContext>
|
||||
{
|
||||
protected override void ConfigureBuilder(DbContextOptionsBuilder<ConnectionDbContext> builder, string connectionString)
|
||||
{
|
||||
builder.UseElsaSqlite(GetType().Assembly, connectionString);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Provides an EF Core migrations for SQLite for the Connection Persistence module.</Description>
|
||||
<PackageTags>elsa module persistence efcore entity framework core sqlite</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Connections.Persistence.EntityFrameworkCore\Elsa.Connections.Persistence.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// <auto-generated />
|
||||
using Elsa.Connections.Persistence.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.Migrations
|
||||
{
|
||||
[DbContext(typeof(ConnectionDbContext))]
|
||||
[Migration("20250212220543_V3_3")]
|
||||
partial class V3_3
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Elsa")
|
||||
.HasAnnotation("ProductVersion", "8.0.12");
|
||||
|
||||
modelBuilder.Entity("Elsa.Connections.Persistence.Entities.ConnectionDefinition", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConnectionConfiguration")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConnectionType")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TenantId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.HasDatabaseName("IX_ConnectionDefinition_Name");
|
||||
|
||||
b.HasIndex("TenantId")
|
||||
.HasDatabaseName("IX_ConnectionDefinition_TenantId");
|
||||
|
||||
b.ToTable("ConnectionDefinitions", "Elsa");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class V3_3 : Migration
|
||||
{
|
||||
private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema;
|
||||
|
||||
/// <inheritdoc />
|
||||
public V3_3(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema)
|
||||
{
|
||||
_schema = schema;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: _schema.Schema);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ConnectionDefinitions",
|
||||
schema: _schema.Schema,
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ConnectionConfiguration = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ConnectionType = table.Column<string>(type: "TEXT", nullable: false),
|
||||
TenantId = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ConnectionDefinitions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConnectionDefinition_Name",
|
||||
schema: _schema.Schema,
|
||||
table: "ConnectionDefinitions",
|
||||
column: "Name");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConnectionDefinition_TenantId",
|
||||
schema: _schema.Schema,
|
||||
table: "ConnectionDefinitions",
|
||||
column: "TenantId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ConnectionDefinitions",
|
||||
schema: _schema.Schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// <auto-generated />
|
||||
using Elsa.Connections.Persistence.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.Migrations
|
||||
{
|
||||
[DbContext(typeof(ConnectionDbContext))]
|
||||
partial class ConnectionDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Elsa")
|
||||
.HasAnnotation("ProductVersion", "8.0.12");
|
||||
|
||||
modelBuilder.Entity("Elsa.Connections.Persistence.Entities.ConnectionDefinition", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConnectionConfiguration")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConnectionType")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TenantId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.HasDatabaseName("IX_ConnectionDefinition_Name");
|
||||
|
||||
b.HasIndex("TenantId")
|
||||
.HasDatabaseName("IX_ConnectionDefinition_TenantId");
|
||||
|
||||
b.ToTable("ConnectionDefinitions", "Elsa");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Connections.Persistence.EntityFrameworkCore;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.EntityFrameworkCore.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extensions to configure EF Core to use Sqlite.
|
||||
/// </summary>
|
||||
public static class ConnectionsSqliteProvidersExtensions
|
||||
{
|
||||
private static Assembly Assembly => typeof(ConnectionsSqliteProvidersExtensions).Assembly;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the feature to use Sqlite.
|
||||
/// </summary>
|
||||
public static EFCoreConnectionPersistenceFeature UseSqlite(this EFCoreConnectionPersistenceFeature feature, string? connectionString = null, ElsaDbContextOptions? options = null)
|
||||
{
|
||||
feature.UseSqlite(Assembly, connectionString, options);
|
||||
return feature;
|
||||
}
|
||||
|
||||
public static EFCoreConnectionPersistenceFeature UseSqlite(this EFCoreConnectionPersistenceFeature feature, Func<IServiceProvider, string> connectionStringFunc, ElsaDbContextOptions? options = null)
|
||||
{
|
||||
feature.UseSqlite(Assembly, connectionStringFunc, options);
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_3 -c ConnectionDbContext -o Migrations"
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Connections.Persistence.Entities;
|
||||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Elsa.Connections.Persistence.EntityFrameworkCore;
|
||||
public class Configurations : IEntityTypeConfiguration<ConnectionDefinition>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ConnectionDefinition> builder)
|
||||
{
|
||||
builder.Property(x => x.ConnectionConfiguration).HasJsonValueConversion();
|
||||
builder.HasIndex(x => x.Name).HasDatabaseName($"IX_{nameof(ConnectionDefinition)}_{nameof(ConnectionDefinition.Name)}");
|
||||
builder.HasIndex(x => x.TenantId).HasDatabaseName($"IX_{nameof(ConnectionDefinition)}_{nameof(ConnectionDefinition.TenantId)}");
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Elsa.Connections.Models;
|
||||
using Elsa.EntityFrameworkCore;
|
||||
using Elsa.Connections.Persistence.Entities;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Elsa.Connections.Persistence.EntityFrameworkCore;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Db Context for the Connection Module
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ConnectionDbContext : ElsaDbContextBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ConnectionDbContext(DbContextOptions<ConnectionDbContext> options, IServiceProvider serviceProvider)
|
||||
: base(options, serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Connection DB set.
|
||||
/// </summary>
|
||||
public DbSet<ConnectionDefinition> ConnectionDefinitions { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
var configuration = new Configurations();
|
||||
modelBuilder.ApplyConfiguration<ConnectionDefinition>(configuration);
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using Elsa.Connections.Persistence.Contracts;
|
||||
using Elsa.Connections.Persistence.Entities;
|
||||
using Elsa.Connections.Persistence.Filters;
|
||||
using Elsa.EntityFrameworkCore;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Connections.Persistence.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// An EF Core implementation of <see cref="IConnectionStore"/>.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class EFCoreConnectionStore(EntityStore<ConnectionDbContext, ConnectionDefinition> store) : IConnectionStore
|
||||
{
|
||||
public Task AddAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return store.AddAsync(entity, cancellationToken);
|
||||
}
|
||||
|
||||
public Task UpdateAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return store.UpdateAsync(entity, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ConnectionDefinition?> GetAsync(string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var filter = new ConnectionDefinitionFilter
|
||||
{
|
||||
Id = id
|
||||
};
|
||||
|
||||
return FindAsync(filter, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ConnectionDefinition?> FindAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return store.FindAsync(filter.Apply, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ConnectionDefinition>> ListAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return store.ListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task DeleteAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return store.DeleteAsync(entity, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ConnectionDefinition>> FindManyAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return store.QueryAsync(filter.Apply, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Provides an Entity Framework Core provider for the persistence layer of the Connection module</Description>
|
||||
<PackageTags>elsa connection persistence efcore entity framework core</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Connections.Persistence\Elsa.Connections.Persistence.csproj" />
|
||||
<ProjectReference Include="..\Elsa.EntityFrameworkCore.Common\Elsa.EntityFrameworkCore.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Connections.Persistence.Features;
|
||||
using Elsa.Connections.Persistence.EntityFrameworkCore;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Agents;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extensions to the <see cref="ConnectionPersistenceFeature"/> feature.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the <see cref="ConnectionPersistenceFeature"/> to use EF Core persistence providers.
|
||||
/// </summary>
|
||||
public static ConnectionPersistenceFeature UseEntityFrameworkCore(this ConnectionPersistenceFeature feature, Action<EFCoreConnectionPersistenceFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Configure(configure);
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Elsa.Connections.Persistence.Entities;
|
||||
using Elsa.Connections.Persistence.Features;
|
||||
using Elsa.EntityFrameworkCore;
|
||||
using Elsa.EntityFrameworkCore.EntityHandlers;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Connections.Persistence.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the default workflow runtime to use EF Core persistence providers.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(ConnectionPersistenceFeature))]
|
||||
public class EFCoreConnectionPersistenceFeature(IModule module) : PersistenceFeatureBase<EFCoreConnectionPersistenceFeature, ConnectionDbContext>(module)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.Configure<ConnectionPersistenceFeature>(feature =>
|
||||
{
|
||||
feature
|
||||
.UseConnectionStore(sp=> sp.GetRequiredService<EFCoreConnectionStore>())
|
||||
;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
base.Apply();
|
||||
AddEntityStore<ConnectionDefinition, EFCoreConnectionStore>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Elsa.Connections.Persistence.Filters;
|
||||
using Elsa.Connections.Persistence.Entities;
|
||||
|
||||
namespace Elsa.Connections.Persistence.Contracts;
|
||||
|
||||
public interface IConnectionStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Get The Connection from the Store
|
||||
/// </summary>
|
||||
public Task<ConnectionDefinition?> GetAsync(string id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get All Connections from the store
|
||||
/// </summary>
|
||||
public Task<IEnumerable<ConnectionDefinition>> ListAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get All connection using Filter
|
||||
/// </summary>
|
||||
public Task<ConnectionDefinition?> FindAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get All connection using Filter
|
||||
/// </summary>
|
||||
public Task<IEnumerable<ConnectionDefinition>> FindManyAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new entity to the store
|
||||
/// </summary>
|
||||
public Task AddAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Update the entity to the store
|
||||
/// </summary>
|
||||
public Task UpdateAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Delete the entity from the store
|
||||
/// </summary>
|
||||
public Task DeleteAsync(ConnectionDefinition entity, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Provides Connections persistence services</Description>
|
||||
<PackageTags>elsa module connections persistence </PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Common\Elsa.Common.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Connections.Models\Elsa.Connections.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using Elsa.Common.Entities;
|
||||
|
||||
namespace Elsa.Connections.Persistence.Entities;
|
||||
|
||||
public class ConnectionDefinition : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// The Name of the Connection
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The Description of the Connection
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Configuration of the Connection in a Json Format
|
||||
/// </summary>
|
||||
public JsonObject ConnectionConfiguration { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The Connection Type Name
|
||||
/// </summary>
|
||||
public string ConnectionType { get; set; } = null!;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Connections.Persistence.Features;
|
||||
using Elsa.Features.Services;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extends <see cref="IModule"/> with methods to use Connection Persistence Feature.
|
||||
/// </summary>
|
||||
public static class ConnectionPersistenceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Installs the persistence feature for the Connection module.
|
||||
/// </summary>
|
||||
public static IModule UseConnectionPersistence(this IModule module, Action<ConnectionPersistenceFeature>? configure = null)
|
||||
{
|
||||
return module.Use(configure);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
using Elsa.Connections.Persistence.Services;
|
||||
using Elsa.Connections.Persistence.Entities;
|
||||
|
||||
namespace Elsa.Connections.Persistence.Features;
|
||||
|
||||
public class ConnectionPersistenceFeature(IModule module) : FeatureBase(module)
|
||||
{
|
||||
private Func<IServiceProvider, IConnectionStore> _connectionStoreFactory = sp => sp.GetRequiredService<InMemoryConnectionStore>();
|
||||
|
||||
public ConnectionPersistenceFeature UseConnectionStore(Func<IServiceProvider, IConnectionStore> factory)
|
||||
{
|
||||
_connectionStoreFactory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
{
|
||||
Services.AddScoped(_connectionStoreFactory);
|
||||
|
||||
Services
|
||||
.AddMemoryStore<ConnectionDefinition, InMemoryConnectionStore>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Connections.Persistence.Entities;
|
||||
|
||||
namespace Elsa.Connections.Persistence.Filters;
|
||||
public class ConnectionDefinitionFilter
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? NotId { get; set; }
|
||||
public string? Type { get; set; }
|
||||
|
||||
public IQueryable<ConnectionDefinition> Apply(IQueryable<ConnectionDefinition> queryable)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(Id)) queryable = queryable.Where(x=>x.Id == Id);
|
||||
if (!string.IsNullOrWhiteSpace(NotId)) queryable = queryable.Where(x => x.Id != NotId);
|
||||
if (!string.IsNullOrWhiteSpace(Name)) queryable = queryable.Where(x=> x.Name == Name);
|
||||
if(!string.IsNullOrWhiteSpace(Type)) queryable = queryable.Where(x=>x.ConnectionType == Type);
|
||||
return queryable;
|
||||
}
|
||||
}
|
||||
3
src/modules/Elsa.Connections.Persistence/FodyWeavers.xml
Normal file
3
src/modules/Elsa.Connections.Persistence/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using Elsa.Common.Services;
|
||||
using Elsa.Connections.Persistence.Contracts;
|
||||
using Elsa.Connections.Persistence.Filters;
|
||||
using Elsa.Connections.Persistence.Entities;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Connections.Persistence.Services;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class InMemoryConnectionStore(MemoryStore<ConnectionDefinition> memoryStore) : IConnectionStore
|
||||
{
|
||||
public Task AddAsync(ConnectionDefinition model, CancellationToken cancellationToken = default)
|
||||
{
|
||||
memoryStore.Add(model, x => x.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DeleteAsync(ConnectionDefinition model, CancellationToken cancellationToken = default)
|
||||
{
|
||||
memoryStore.Delete(model.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<ConnectionDefinition?> GetAsync(string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = memoryStore.Find(c => c.Id == id);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ConnectionDefinition>> ListAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var results = memoryStore.List();
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
public Task<ConnectionDefinition?> FindAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var entity = memoryStore.Query(filter.Apply).FirstOrDefault();
|
||||
return Task.FromResult(entity);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ConnectionDefinition>> FindManyAsync(ConnectionDefinitionFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var entities = memoryStore.Query(filter.Apply);
|
||||
return Task.FromResult(entities);
|
||||
}
|
||||
|
||||
public Task UpdateAsync(ConnectionDefinition model, CancellationToken cancellationToken = default)
|
||||
{
|
||||
memoryStore.Update(model, x => x.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using Elsa.Expressions.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.JavaScript.Extensions;
|
||||
using Elsa.JavaScript.Helpers;
|
||||
using Elsa.JavaScript.Notifications;
|
||||
using Elsa.JavaScript.Options;
|
||||
using Elsa.Mediator.Contracts;
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public static class DictionaryExtensions
|
|||
public static T? GetValueOrDefault<T>(this IDictionary<string, object> dictionary, string key, Func<T?> defaultValueFactory) => TryGetValue<T>(dictionary, key, out var value) ? value : defaultValueFactory();
|
||||
public static T? GetValueOrDefault<T>(this IDictionary<string, object> dictionary, IEnumerable<string> keys, Func<T?> defaultValueFactory) => TryGetValue<T>(dictionary, keys, out var value) ? value : defaultValueFactory();
|
||||
public static T? GetValueOrDefault<T>(this IDictionary<string, object> dictionary, string key) => GetValueOrDefault<T>(dictionary, key, () => default);
|
||||
public static object? GetValueOrDefault(this IDictionary<string, object> dictionary, string key) => GetValueOrDefault<object>(dictionary, key, () => default);
|
||||
public static object? GetValueOrDefault(this IDictionary<string, object> dictionary, string key) => GetValueOrDefault<object>(dictionary, key, () => null);
|
||||
|
||||
public static T GetOrAdd<TKey, T>(this IDictionary<TKey, T> dictionary, TKey key, Func<T> valueFactory)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Extensions;
|
||||
|
||||
namespace Elsa.Workflows.UIHints.Dropdown;
|
||||
|
||||
|
|
@ -19,7 +20,9 @@ public abstract class DropDownOptionsProviderBase : IPropertyUIHandler
|
|||
var options = new Dictionary<string, object>
|
||||
{
|
||||
[InputUIHints.DropDown] = props
|
||||
};
|
||||
};
|
||||
|
||||
options.AddRange(GetUIPropertyAdditionalOptions());
|
||||
|
||||
return options;
|
||||
}
|
||||
|
|
@ -27,5 +30,14 @@ public abstract class DropDownOptionsProviderBase : IPropertyUIHandler
|
|||
/// <summary>
|
||||
/// Implement this to provide items to the dropdown list.
|
||||
/// </summary>
|
||||
protected abstract ValueTask<ICollection<SelectListItem>> GetItemsAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken);
|
||||
protected abstract ValueTask<ICollection<SelectListItem>> GetItemsAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Override this to provide additional options to the list
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual IDictionary<string, object> GetUIPropertyAdditionalOptions()
|
||||
{
|
||||
return new Dictionary<String, object>();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue