Add Elsa Connection Module (wip on persistence and naming)
This commit is contained in:
parent
e39f671bcc
commit
7ed2bc1c1f
12
src/modules/Elsa.Connections.Api/Elsa.Connections.Api.csproj
Normal file
12
src/modules/Elsa.Connections.Api/Elsa.Connections.Api.csproj
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<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.Persistence\Elsa.Connections.Persistence.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Nodes;
|
||||
using Elsa.Abstractions;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Models;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Attributes;
|
||||
using Elsa.Workflows.Models;
|
||||
using FastEndpoints;
|
||||
using Humanizer;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.ActivityConnectionDescriptor.List;
|
||||
|
||||
|
||||
public class List(IConnectionDescriptorRegistry registry) : ElsaEndpointWithoutRequest<PagedListResponse<ConnectionDescriptor>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/connection-configuration/descriptors");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override Task<PagedListResponse<ConnectionDescriptor>> ExecuteAsync(CancellationToken ct)
|
||||
{
|
||||
var descriptors = registry.ListAll().ToList();
|
||||
return Task.FromResult(new PagedListResponse<ConnectionDescriptor>(Page.Of(descriptors, descriptors.Count())) );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.ActivityConnectionDescriptor.List;
|
||||
|
||||
public class Get(IConnectionDescriptorRegistry store) : ElsaEndpointWithoutRequest<IEnumerable<InputDescriptor>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/connection-configuration/input-descriptor/{ActivityType}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
string type = Route<string>("ActivityType");
|
||||
var config = await store.GetConnectionDescriptor(type);
|
||||
|
||||
if (config == null)
|
||||
await SendNotFoundAsync();
|
||||
else
|
||||
await SendOkAsync(config);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Elsa.Connections.Api.Endpoints.ActivityConnectionDescriptor.List;
|
||||
|
||||
public class Request
|
||||
{
|
||||
public string ActivityType { get; set; }
|
||||
}
|
||||
40
src/modules/Elsa.Connections.Api/Endpoints/Add/Endpoint.cs
Normal file
40
src/modules/Elsa.Connections.Api/Endpoints/Add/Endpoint.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Models;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.Add;
|
||||
|
||||
public class Endpoint(IConnectionRepository store) : ElsaEndpoint<ConnectionConfigurationMetadataModel>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/connection-configuration");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task<object?> ExecuteAsync(ConnectionConfigurationMetadataModel model, CancellationToken ct)
|
||||
{
|
||||
await store.AddConnectionConfigurationAsync(model, ct);
|
||||
await SendOkAsync();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class EndpointUpdate(IConnectionRepository store) : ElsaEndpoint<ConnectionConfigurationMetadataModel>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Put("/connection-configuration/{id}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task<object?> ExecuteAsync(ConnectionConfigurationMetadataModel model, CancellationToken ct)
|
||||
{
|
||||
await store.UpdateConnectionAsync(model.Id , model, ct);
|
||||
await SendOkAsync();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Connections.Contracts;
|
||||
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.Delete;
|
||||
|
||||
public class Endpoint(IConnectionRepository store) : ElsaEndpointWithoutRequest
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Delete("/connection-configuration/{id}");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task<object?> HandleAsync(CancellationToken ct)
|
||||
{
|
||||
var configurationId = Route<string>("id");
|
||||
await store.DeleteConnectionConfigurationAsync(configurationId, ct);
|
||||
await SendOkAsync();
|
||||
|
||||
return 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 System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Nodes;
|
||||
using Elsa.Abstractions;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Connections.Api.Endpoints.List;
|
||||
|
||||
public class Endpoint(IConnectionRepository store) : ElsaEndpointWithoutRequest<PagedListResponse<ConnectionConfigurationMetadataModel>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/connection-configuration");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task<PagedListResponse<ConnectionConfigurationMetadataModel>> ExecuteAsync(CancellationToken ct)
|
||||
{
|
||||
var config = await store.GetConnectionsAsync();
|
||||
return new PagedListResponse<ConnectionConfigurationMetadataModel>(Page.Of(config, config.Count()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Semantic Kernel API feature.
|
||||
/// </summary>
|
||||
public static IModule UseConnectionsApi(this IModule module, Action<ConnectionsApiFeature>? configure = null)
|
||||
{
|
||||
return module.Use(configure);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using Elsa.Connections.Api.Services;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Connections.Persistence.Features;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
{
|
||||
|
||||
Services.AddSingleton<IConnectionRepository, InMemoryConnectionRepository>();
|
||||
|
||||
Services
|
||||
|
||||
.AddMemoryStore<ConnectionConfigurationMetadataModel, InMemoryConnectionRepository>();
|
||||
|
||||
}
|
||||
}
|
||||
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,45 @@
|
|||
using Elsa.Common.Services;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Models;
|
||||
|
||||
namespace Elsa.Connections.Api.Services;
|
||||
|
||||
public class InMemoryConnectionRepository(MemoryStore<ConnectionConfigurationMetadataModel> memoryStore) : IConnectionRepository
|
||||
{
|
||||
public Task AddConnectionConfigurationAsync(ConnectionConfigurationMetadataModel model, CancellationToken cancellationToken = default)
|
||||
{
|
||||
model.Id = Guid.NewGuid().ToString("n");
|
||||
memoryStore.Add(model, model =>model.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DeleteConnectionConfigurationAsync(string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
memoryStore.Delete(id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<ConnectionConfigurationMetadataModel> GetConnectionAsync(string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = memoryStore.Find(c => c.Name == name);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<ICollection<ConnectionConfigurationMetadataModel>> GetConnectionsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ICollection<ConnectionConfigurationMetadataModel> results = memoryStore.List().ToList() ;
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public Task<ICollection<ConnectionConfigurationMetadataModel>> GetConnectionsFromTypeAsync(string type, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ICollection<ConnectionConfigurationMetadataModel> items = memoryStore.FindMany(c => c.ConnectionType == type).ToList();
|
||||
return Task.FromResult(items);
|
||||
}
|
||||
|
||||
public Task UpdateConnectionAsync(string id, ConnectionConfigurationMetadataModel model, CancellationToken cancellationToken = default)
|
||||
{
|
||||
memoryStore.Update(model, model => model.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
namespace Elsa.Connections.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ConnectionActivityAttribute : Attribute
|
||||
{
|
||||
|
||||
public ConnectionActivityAttribute(string type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
/// <summary>
|
||||
/// The TypeName of the connection
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
namespace Elsa.Connections.Attributes;
|
||||
|
||||
public class ConnectionPropertyAttribute : Attribute
|
||||
{
|
||||
public ConnectionPropertyAttribute(string @namespace, string displayName, string? description= default)
|
||||
{
|
||||
Namespace = @namespace;
|
||||
DisplayName = displayName;
|
||||
Description = description;
|
||||
|
||||
}
|
||||
public string? Namespace { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? DisplayName { get; set; }
|
||||
}
|
||||
|
|
@ -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,39 @@
|
|||
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>
|
||||
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>> GetConnectionDescriptor(string activityType, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Elsa.Connections.Models;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Connections.Contracts;
|
||||
|
||||
public interface IConnectionDescriptorRegistryEX
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elsa.Connections.Contracts;
|
||||
interface IConnectionProperty
|
||||
{
|
||||
public string ConnectionName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public object Properties { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Elsa.Connections.Models;
|
||||
|
||||
namespace Elsa.Connections.Contracts;
|
||||
|
||||
public interface IConnectionRepository
|
||||
{
|
||||
public Task<ConnectionConfigurationMetadataModel> GetConnectionAsync(string name, CancellationToken cancellationToken = default);
|
||||
public Task<ICollection<ConnectionConfigurationMetadataModel>> GetConnectionsAsync(CancellationToken cancellationToken = default);
|
||||
public Task<ICollection<ConnectionConfigurationMetadataModel>> GetConnectionsFromTypeAsync(string type, CancellationToken cancellationToken = default);
|
||||
|
||||
public Task AddConnectionConfigurationAsync(ConnectionConfigurationMetadataModel model, CancellationToken cancellationToken = default);
|
||||
|
||||
public Task UpdateConnectionAsync(string name, ConnectionConfigurationMetadataModel model, CancellationToken cancellationToken = default);
|
||||
public Task DeleteConnectionConfigurationAsync(string id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<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.Workflows.Core\Elsa.Workflows.Core.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Connections.Features;
|
||||
using Elsa.Features.Services;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
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;
|
||||
|
||||
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, ConnexionDropDownUIHintHandler>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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,27 @@
|
|||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Workflows;
|
||||
|
||||
namespace Elsa.Connections.Filters;
|
||||
|
||||
public class PropertyAttributeFilter : ActivityStateFilterBase
|
||||
{
|
||||
protected override ActivityStateFilterResult OnExecute(ActivityStateFilterContext context)
|
||||
{
|
||||
var activityExecutionContext = context.ActivityExecutionContext;
|
||||
var activity = activityExecutionContext.Activity;
|
||||
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);
|
||||
}
|
||||
else
|
||||
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,101 @@
|
|||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Pipelines.ActivityExecution;
|
||||
using Elsa.Workflows.UIHints.Dropdown;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Connections.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Adds extension methods to <see cref="ExecutionLogMiddleware"/>.
|
||||
/// </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>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An activity execution middleware component that extracts execution details as <see cref="WorkflowExecutionLogEntry"/> objects.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ConnectionMiddleware(ActivityMiddlewareDelegate next, IConnectionRepository connectionStore) : IActivityExecutionMiddleware
|
||||
{
|
||||
/// <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<>));
|
||||
|
||||
if (inputDescriptors != null && inputDescriptors.Any())
|
||||
{
|
||||
var input = inputDescriptors.First();
|
||||
var propertyType = input?.PropertyInfo?.PropertyType.GetGenericArguments()[0];
|
||||
|
||||
dynamic inputValue = input.ValueGetter(context.Activity);
|
||||
|
||||
var connectionName = (string)inputValue?.ConnectionName;
|
||||
|
||||
//Get connection from store, if exist,
|
||||
var connectionConfiguration = await connectionStore.GetConnectionAsync(connectionName);
|
||||
if (connectionConfiguration != null)
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
options.Converters.Add(new Int32Converter());
|
||||
|
||||
dynamic deserializedjson =
|
||||
JsonSerializer.Deserialize(connectionConfiguration.ConnectionConfiguration, propertyType, options);
|
||||
|
||||
inputValue.Properties = deserializedjson;
|
||||
|
||||
input.ValueSetter(context.Activity, inputValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
|
||||
private static bool IsActivityBookmarked(ActivityExecutionContext context)
|
||||
{
|
||||
return context.WorkflowExecutionContext.Bookmarks.Any(b => b.ActivityNodeId.Equals(context.ActivityNode.NodeId));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
string stringValue = reader.GetString();
|
||||
if (int.TryParse(stringValue, out int 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 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Elsa.Connections.Models;
|
||||
|
||||
public class ConnectionConfigurationMetadataModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
[Required]
|
||||
public string? Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public JsonObject ConnectionConfiguration { get; set; }
|
||||
|
||||
[Required]
|
||||
public string ConnectionType { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
namespace Elsa.Connections.Models;
|
||||
|
||||
public record ConnectionDescriptor(string type, string description, string @namespace, string providerName);
|
||||
|
|
@ -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 T();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Workflows.UIHints.Dropdown;
|
||||
|
||||
namespace Elsa.Connections.ServiceProvider;
|
||||
|
||||
public class ConnectionOptionsProvider : DropDownOptionsProviderBase
|
||||
{
|
||||
private readonly IConnectionRepository _store;
|
||||
|
||||
public ConnectionOptionsProvider(IConnectionRepository store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
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.GetConnectionsFromTypeAsync(connectionType.ToString());
|
||||
|
||||
foreach(var conn in connections)
|
||||
connection.Add(new SelectListItem(conn.Name, conn.Name));
|
||||
|
||||
|
||||
|
||||
|
||||
//connection.Add(new SelectListItem("connection 2", "idConnection2"));
|
||||
//connection.Add(new SelectListItem("connection 3", "idConnection3"));
|
||||
return connection;
|
||||
//throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Connections.Attributes;
|
||||
using Elsa.Connections.Contracts;
|
||||
using Elsa.Connections.Features;
|
||||
using Elsa.Connections.Models;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Connections.Services;
|
||||
public class ConnectionRegistry : IConnectionDescriptorRegistry
|
||||
{
|
||||
|
||||
private readonly ConcurrentDictionary<Type, ConnectionDescriptor> _connectionDescriptors = new();
|
||||
private readonly ConnectionOptions _options;
|
||||
private readonly IActivityDescriber _describer;
|
||||
|
||||
public ConnectionRegistry(IOptions<ConnectionOptions> options, IActivityDescriber describer)
|
||||
{
|
||||
_options = options.Value;
|
||||
_describer = describer;
|
||||
}
|
||||
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)
|
||||
{
|
||||
var item = _options.ConnectionTypes.Where(c => c.ToString() == type).FirstOrDefault();
|
||||
return item;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<InputDescriptor>> GetConnectionDescriptor(string activityType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var propertyType2 = Type.GetType(activityType);
|
||||
|
||||
var propertyType = Get(activityType);
|
||||
|
||||
if (propertyType == null)
|
||||
return null;
|
||||
|
||||
var connectionInputProperty = _describer.GetInputProperties(propertyType);
|
||||
var connectionInputDescriptor = await DescribeInputPropertiesAsync(connectionInputProperty);
|
||||
|
||||
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 ConnectionDescriptor(
|
||||
connectionType.ToString(),
|
||||
connectionAttribute?.Description,
|
||||
connectionAttribute.Namespace,
|
||||
connectionAttribute.DisplayName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.UIHints;
|
||||
using Elsa.Workflows.UIHints.Dropdown;
|
||||
|
||||
namespace Elsa.Connections.UIHints;
|
||||
|
||||
public class ConnexionDropDownUIHintHandler : IUIHintHandler
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string UIHint => $"connexion-{InputUIHints.DropDown}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<Type>> GetPropertyUIHandlersAsync(PropertyInfo propertyInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return new(new[] { typeof(StaticDropDownOptionsProvider) });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Configs\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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.Core\Elsa.Connections.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
using Elsa.Common.Features;
|
||||
using Elsa.Connections.Features;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Connections.Persistence.Features;
|
||||
|
||||
[DependsOn(typeof(ConnectionsFeatures))]
|
||||
public class ConnectionPersistenceFeature(IModule module) : FeatureBase(module)
|
||||
{
|
||||
//private Func<IServiceProvider, IApiKeyStore> _apiKeyStoreFactory = sp => sp.GetRequiredService<MemoryApiKeyStore>();
|
||||
//private Func<IServiceProvider, IServiceStore> _serviceStoreFactory = sp => sp.GetRequiredService<MemoryServiceStore>();
|
||||
//private Func<IServiceProvider, IAgentStore> _agentStoreFactory = sp => sp.GetRequiredService<MemoryAgentStore>();
|
||||
|
||||
//public AgentPersistenceFeature UseApiKeyStore(Func<IServiceProvider, IApiKeyStore> factory)
|
||||
//{
|
||||
// _apiKeyStoreFactory = factory;
|
||||
// return this;
|
||||
//}
|
||||
|
||||
//public AgentPersistenceFeature UseServiceStore(Func<IServiceProvider, IServiceStore> factory)
|
||||
//{
|
||||
// _serviceStoreFactory = factory;
|
||||
// return this;
|
||||
//}
|
||||
|
||||
//public AgentPersistenceFeature UseAgentStore(Func<IServiceProvider, IAgentStore> factory)
|
||||
//{
|
||||
// _agentStoreFactory = factory;
|
||||
// return this;
|
||||
//}
|
||||
|
||||
//public override void Configure()
|
||||
//{
|
||||
// Module.UseAgents(agents => agents.UseKernelConfigProvider(sp => sp.GetRequiredService<StoreKernelConfigProvider>()));
|
||||
//}
|
||||
|
||||
//public override void Apply()
|
||||
//{
|
||||
// Services
|
||||
// .AddScoped(_apiKeyStoreFactory)
|
||||
// .AddScoped(_serviceStoreFactory)
|
||||
// .AddScoped(_agentStoreFactory);
|
||||
|
||||
// Services
|
||||
// .AddScoped<IAgentManager, AgentManager>();
|
||||
|
||||
// Services
|
||||
// .AddMemoryStore<ApiKeyDefinition, MemoryApiKeyStore>()
|
||||
// .AddMemoryStore<ServiceDefinition, MemoryServiceStore>()
|
||||
// .AddMemoryStore<AgentDefinition, MemoryAgentStore>();
|
||||
|
||||
// Services.AddScoped<StoreKernelConfigProvider>();
|
||||
//}
|
||||
}
|
||||
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>
|
||||
Loading…
Reference in a new issue