diff --git a/Elsa.sln b/Elsa.sln index 141b2dd91c..11101db222 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -158,6 +158,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.ProtoActor.Cluster.Azu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AzureServiceBusActivities", "src\samples\aspnet\Elsa.Samples.AzureServiceBusActivities\Elsa.Samples.AzureServiceBusActivities.csproj", "{C25CFDCF-8E06-4DD8-A83E-FF7EF9FDA02E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.ElasticsearchStorage", "src\samples\aspnet\Elsa.Samples.ElasticsearchStorage\Elsa.Samples.ElasticsearchStorage.csproj", "{4A54379F-4775-41B6-9752-FF300288916A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -404,6 +406,10 @@ Global {C25CFDCF-8E06-4DD8-A83E-FF7EF9FDA02E}.Debug|Any CPU.Build.0 = Debug|Any CPU {C25CFDCF-8E06-4DD8-A83E-FF7EF9FDA02E}.Release|Any CPU.ActiveCfg = Release|Any CPU {C25CFDCF-8E06-4DD8-A83E-FF7EF9FDA02E}.Release|Any CPU.Build.0 = Release|Any CPU + {4A54379F-4775-41B6-9752-FF300288916A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A54379F-4775-41B6-9752-FF300288916A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A54379F-4775-41B6-9752-FF300288916A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A54379F-4775-41B6-9752-FF300288916A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} @@ -473,5 +479,6 @@ Global {A1A8AD89-C9C4-41BF-BBCB-EF0544A28BFB} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} {9F79BBEC-02CE-4F76-AED4-BC191DA3054B} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} {C25CFDCF-8E06-4DD8-A83E-FF7EF9FDA02E} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {4A54379F-4775-41B6-9752-FF300288916A} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} EndGlobalSection EndGlobal diff --git a/docker-compose.yml b/docker-compose.yml index 47950f7ffb..1187a3048b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,9 +56,40 @@ services: ports: - "15672:15672" - "5672:5672" + + elasticsearch: + image: "docker.elastic.co/elasticsearch/elasticsearch:8.5.0" + environment: + - xpack.security.enabled=false + - discovery.type=single-node + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + cap_add: + - IPC_LOCK + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + ports: + - "9200:9200" + - "9300:9300" + + kibana: + image: "docker.elastic.co/kibana/kibana:8.5.0" + environment: + - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 + ports: + - "5601:5601" + depends_on: + - elasticsearch networks: consul: volumes: - mongodb_data: \ No newline at end of file + mongodb_data: + elasticsearch-data: + driver: local \ No newline at end of file diff --git a/src/bundles/Elsa.WorkflowServer.Web/Program.cs b/src/bundles/Elsa.WorkflowServer.Web/Program.cs index c35635f3fb..be8a6a796a 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/Program.cs +++ b/src/bundles/Elsa.WorkflowServer.Web/Program.cs @@ -29,18 +29,13 @@ identity.TokenOptions = identityTokenOptions; }) .UseDefaultAuthentication() - .UseWorkflowManagement(management => - { - management.UseDefaultManagement(m => m.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))); - management.UseWorkflowInstances(w => w.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))); - }) + .UseWorkflowManagement(management => management.UseEntityFrameworkCore(m => m.UseSqlite(sqliteConnectionString))) .UseWorkflowRuntime(runtime => { runtime.UseProtoActor(proto => { proto.PersistenceProvider = _ => new SqliteProvider(new SqliteConnectionStringBuilder(sqliteConnectionString)); }); - runtime.UseDefaultRuntime(df => df.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))); runtime.UseExecutionLogRecords(e => e.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))); runtime.UseAsyncWorkflowStateExporter(); runtime.UseMassTransitDispatcher(); diff --git a/src/designer/elsa-workflows-designer/src/services/activity-name-formatter.ts b/src/designer/elsa-workflows-designer/src/services/activity-name-formatter.ts index 65a3f8d59d..960f638371 100644 --- a/src/designer/elsa-workflows-designer/src/services/activity-name-formatter.ts +++ b/src/designer/elsa-workflows-designer/src/services/activity-name-formatter.ts @@ -22,7 +22,7 @@ export class ActivityNameFormatter { public static readonly SnakeCaseStrategy: ActivityNameStrategy = context => snakeCase(ActivityNameFormatter.DefaultStrategy(context)); public static readonly KebabCaseStrategy: ActivityNameStrategy = context => kebabCase(ActivityNameFormatter.DefaultStrategy(context)); - public strategy: ActivityNameStrategy = ActivityNameFormatter.CamelCaseStrategy; + public strategy: ActivityNameStrategy = ActivityNameFormatter.PascalCaseStrategy; public format(context: ActivityNameFormatterContext): string { return this.strategy(context); diff --git a/src/modules/Elsa.Elasticsearch/Common/ElasticConfiguration.cs b/src/modules/Elsa.Elasticsearch/Common/ElasticConfiguration.cs deleted file mode 100644 index 657f9e52a5..0000000000 --- a/src/modules/Elsa.Elasticsearch/Common/ElasticConfiguration.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Elastic.Clients.Elasticsearch; -using Elsa.Elasticsearch.Services; - -namespace Elsa.Elasticsearch.Common; - -public abstract class ElasticConfiguration : IElasticConfiguration -{ - public abstract void Apply(ElasticsearchClientSettings settings, IDictionary indexConfig); -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Common/ElasticStore.cs b/src/modules/Elsa.Elasticsearch/Common/ElasticStore.cs index 3aaf35ff89..83ebf09c28 100644 --- a/src/modules/Elsa.Elasticsearch/Common/ElasticStore.cs +++ b/src/modules/Elsa.Elasticsearch/Common/ElasticStore.cs @@ -1,73 +1,94 @@ using System.Collections.ObjectModel; using Elastic.Clients.Elasticsearch; using Elsa.Common.Models; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Exception = System.Exception; namespace Elsa.Elasticsearch.Common; +/// +/// A thin wrapper around for easy re-usability. +/// +/// The document type. +[PublicAPI] public class ElasticStore where T : class { private readonly ElasticsearchClient _elasticClient; private readonly ILogger _logger; + /// + /// Constructor. + /// public ElasticStore(ElasticsearchClient elasticClient, ILogger> logger) { _elasticClient = elasticClient; _logger = logger; } + /// + /// Searches the index using the specified search descriptor. + /// public async Task> SearchAsync(Action> search, PageArgs? pageArgs, CancellationToken cancellationToken) { - if (pageArgs != default) + if (pageArgs?.Page != null && pageArgs?.PageSize != null) { search += s => s.From(pageArgs.Offset).Size(pageArgs.Limit); } var response = await _elasticClient.SearchAsync(search, cancellationToken); - - if (response.IsSuccess()) - return new Page(response.Hits.Select(hit => hit.Source).ToList()!, response.Total); - - _logger.LogError("Failed to search data in Elasticsearch: {message}", response.ElasticsearchServerError.ToString()); - return new Page(new Collection(), 0); + + if (!response.IsSuccess()) + throw new Exception(response.DebugInformation); + + return new Page(response.Hits.Select(hit => hit.Source).ToList()!, response.Total); } - public async Task SaveAsync(T model, CancellationToken cancellationToken) + /// + /// Stores the specified document in the index. + /// + public async Task SaveAsync(T document, CancellationToken cancellationToken) { - var response = await _elasticClient.IndexAsync(model, cancellationToken); + var response = await _elasticClient.IndexAsync(document, cancellationToken); - if (response.IsSuccess()) return; - - throw new Exception($"Failed to save data in Elasticsearch: {response.ElasticsearchServerError}"); + if (!response.IsSuccess()) + throw new Exception($"Failed to save data in Elasticsearch: {response.ElasticsearchServerError}"); } + /// + /// Stores the specified documents in the index. + /// public async Task SaveManyAsync(IEnumerable documents, CancellationToken cancellationToken) { var response = await _elasticClient.IndexManyAsync(documents, cancellationToken); - if (response.IsSuccess()) return; - - throw new Exception($"Failed to save data in Elasticsearch: {response.ElasticsearchServerError}"); + if (!response.IsSuccess()) + throw new Exception($"Failed to save data in Elasticsearch: {response.ElasticsearchServerError}"); } - public async Task DeleteManyAsync(IEnumerable list, CancellationToken cancellationToken) + /// + /// Deletes the specified set of documents from the index. + /// + public async Task DeleteManyAsync(IEnumerable documents, CancellationToken cancellationToken) { - var response = await _elasticClient.BulkAsync(b => b.DeleteMany(list), cancellationToken); - - if (response.IsSuccess()) return response.Items.Count(i => i.IsValid); - - _logger.LogError("Failed to bulk delete data in Elasticsearch: {message}", response.ElasticsearchServerError.ToString()); - return 0; + var response = await _elasticClient.BulkAsync(b => b.DeleteMany(documents), cancellationToken); + + if (!response.IsSuccess()) + throw new Exception(response.DebugInformation); + + return response.Items.Count(i => i.IsValid); } + /// + /// Deletes the documents matching the specified query. + /// public async Task DeleteByQueryAsync(Action> query, CancellationToken cancellationToken) { var response = await _elasticClient.DeleteByQueryAsync(Indices.All, query, cancellationToken); - - if (response.IsSuccess()) return response.Deleted ?? 0; - _logger.LogError("Failed to delete data in Elasticsearch: {message}", response.ElasticsearchServerError.ToString()); - return 0; + if (!response.IsSuccess()) + throw new Exception(response.DebugInformation); + + return response.Deleted ?? 0; } } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Common/IndexConfiguration.cs b/src/modules/Elsa.Elasticsearch/Common/IndexConfiguration.cs new file mode 100644 index 0000000000..8fab24dfeb --- /dev/null +++ b/src/modules/Elsa.Elasticsearch/Common/IndexConfiguration.cs @@ -0,0 +1,23 @@ +using Elastic.Clients.Elasticsearch; +using Elsa.Elasticsearch.Services; +using Elsa.Elasticsearch.Strategies; + +namespace Elsa.Elasticsearch.Common; + +/// +/// A convenience base class for document type configurations. +/// +public abstract class IndexConfiguration : IIndexConfiguration +{ + /// + public Type DocumentType => typeof(T); + + /// + public virtual IIndexNamingStrategy IndexNamingStrategy => new DefaultNaming(); + + /// + public abstract void ConfigureClientSettings(ElasticsearchClientSettings settings); + + /// + public virtual ValueTask ConfigureClientAsync(ElasticsearchClient client, CancellationToken cancellationToken) => ValueTask.CompletedTask; +} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Common/PersistenceFeatureBase.cs b/src/modules/Elsa.Elasticsearch/Common/PersistenceFeatureBase.cs index c6a608fbc4..ef1d267d35 100644 --- a/src/modules/Elsa.Elasticsearch/Common/PersistenceFeatureBase.cs +++ b/src/modules/Elsa.Elasticsearch/Common/PersistenceFeatureBase.cs @@ -1,19 +1,34 @@ +using Elsa.Elasticsearch.Services; using Elsa.Features.Abstractions; using Elsa.Features.Services; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Elasticsearch.Common; +/// +/// Base class for features that configure Elasticsearch persistence. +/// public abstract class ElasticPersistenceFeatureBase : FeatureBase { - public ElasticPersistenceFeatureBase(IModule module) : base(module) + /// + protected ElasticPersistenceFeatureBase(IModule module) : base(module) { } + /// + /// Registers an . + /// + /// The entity type of the store. + /// The type of the store. protected void AddStore() where TModel : class where TStore : class { Services .AddSingleton>() .AddSingleton(); } + + /// + /// Registers an . + /// + protected void AddIndexConfiguration(Func> configuration) => Services.AddSingleton(configuration); } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Common/Utils.cs b/src/modules/Elsa.Elasticsearch/Common/Utils.cs deleted file mode 100644 index 83212a523f..0000000000 --- a/src/modules/Elsa.Elasticsearch/Common/Utils.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Elsa.Elasticsearch.Services; - -namespace Elsa.Elasticsearch.Common; - -public static class Utils -{ - public static IEnumerable GetElasticConfigurationTypes() => - AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(s => s.GetTypes()) - .Where(p => - typeof(IElasticConfiguration).IsAssignableFrom(p) && - p is {IsClass: true, IsAbstract: false}); - - public static IEnumerable GetElasticDocumentTypes() => - GetElasticConfigurationTypes() - .Select(t => t.BaseType!.GenericTypeArguments.First()).ToList(); - - public static IDictionary ResolveIndexConfig( - IDictionary defaultConfig, - IDictionary? userDefinedConfig) - { - var types = GetElasticDocumentTypes(); - - return userDefinedConfig?.Select(kvp => - new KeyValuePair(types.First(t => t.Name == kvp.Key), kvp.Value)) - .ToDictionary(x => x.Key, x => x.Value) ?? defaultConfig; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Extensions/ElasticExtensions.cs b/src/modules/Elsa.Elasticsearch/Extensions/ElasticExtensions.cs index 94fdb2da81..1dc8595f59 100644 --- a/src/modules/Elsa.Elasticsearch/Extensions/ElasticExtensions.cs +++ b/src/modules/Elsa.Elasticsearch/Extensions/ElasticExtensions.cs @@ -1,59 +1,18 @@ using Elastic.Clients.Elasticsearch; -using Elastic.Clients.Elasticsearch.IndexManagement; using Elastic.Transport; -using Elsa.Elasticsearch.Common; -using Elsa.Elasticsearch.Models; using Elsa.Elasticsearch.Options; -using Elsa.Elasticsearch.Services; namespace Elsa.Elasticsearch.Extensions; -public static class ElasticExtensions +internal static class ElasticExtensions { public static ElasticsearchClientSettings ConfigureAuthentication(this ElasticsearchClientSettings settings, ElasticsearchOptions options) { if (!string.IsNullOrEmpty(options.ApiKey)) - { settings.Authentication(new ApiKey(options.ApiKey)); - } else if (!string.IsNullOrEmpty(options.Username) && !string.IsNullOrEmpty(options.Password)) - { settings.Authentication(new BasicAuthentication(options.Username, options.Password)); - } return settings; } - - public static ElasticsearchClientSettings ConfigureMapping(this ElasticsearchClientSettings settings, IDictionary indexConfig) - { - foreach (var config in Utils.GetElasticConfigurationTypes()) - { - var configInstance = (IElasticConfiguration)Activator.CreateInstance(config)!; - configInstance.Apply(settings, indexConfig); - } - - return settings; - } - - public static void ConfigureAliases(this ElasticsearchClient client, IDictionary aliasConfig, IndexRolloverStrategy strategy) - { - var namingStrategy = (IIndexNamingStrategy)Activator.CreateInstance(strategy.IndexNamingStrategy)!; - - foreach (var type in Utils.GetElasticDocumentTypes()) - { - var aliasName = aliasConfig[type]; - var indexName = namingStrategy.GenerateName(aliasName); - - var indexExists = client.Indices.Exists(indexName).Exists; - if (indexExists) continue; - - var response = client.Indices.Create(indexName, c => c - .Aliases(a => a.Add(aliasName, new Alias {IsWriteIndex = true}))); - - if (response.IsValidResponse) continue; - response.TryGetOriginalException(out var exception); - if(exception != null) - throw exception; - } - } } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Elasticsearch/Extensions/ModuleExtensions.cs index 59970da52b..15718ee904 100644 --- a/src/modules/Elsa.Elasticsearch/Extensions/ModuleExtensions.cs +++ b/src/modules/Elsa.Elasticsearch/Extensions/ModuleExtensions.cs @@ -1,11 +1,14 @@ -using Elsa.Elasticsearch.Common; using Elsa.Elasticsearch.Features; -using Elsa.Elasticsearch.Models; using Elsa.Elasticsearch.Options; using Elsa.Features.Services; +using JetBrains.Annotations; namespace Elsa.Elasticsearch.Extensions; +/// +/// Extends to configure the feature. +/// +[PublicAPI] public static class ModuleExtensions { /// @@ -13,18 +16,10 @@ public static class ModuleExtensions /// public static IModule UseElasticsearch( this IModule module, - ElasticsearchOptions options, - IndexRolloverStrategy? rolloverStrategy = default, - IDictionary? indexConfig = default, + Action options, Action? configure = default) { - configure += f => - { - f.Options = options; - f.IndexRolloverStrategy = rolloverStrategy; - f.IndexConfig = Utils.ResolveIndexConfig(f.IndexConfig, options.IndexConfig ?? indexConfig); - }; - + configure += f => f.Options += options; module.Configure(configure); return module; } diff --git a/src/modules/Elsa.Elasticsearch/Features/ElasticsearchFeature.cs b/src/modules/Elsa.Elasticsearch/Features/ElasticsearchFeature.cs index d01d551355..497bc56876 100644 --- a/src/modules/Elsa.Elasticsearch/Features/ElasticsearchFeature.cs +++ b/src/modules/Elsa.Elasticsearch/Features/ElasticsearchFeature.cs @@ -1,59 +1,59 @@ using Elastic.Clients.Elasticsearch; using Elsa.Elasticsearch.Extensions; using Elsa.Elasticsearch.HostedServices; -using Elsa.Elasticsearch.Models; +using Elsa.Elasticsearch.Modules.Management; +using Elsa.Elasticsearch.Modules.Runtime; using Elsa.Elasticsearch.Options; using Elsa.Elasticsearch.Services; using Elsa.Features.Abstractions; using Elsa.Features.Services; +using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Runtime.Entities; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Elsa.Elasticsearch.Features; +/// +/// Configures Elasticsearch. +/// public class ElasticsearchFeature : FeatureBase { + /// public ElasticsearchFeature(IModule module) : base(module) { } - - internal ElasticsearchOptions Options { get; set; } = new(); - internal IDictionary IndexConfig { get; set; } = IElasticConfiguration.GetDefaultIndexConfig(); - internal IndexRolloverStrategy? IndexRolloverStrategy { get; set; } + /// + /// A delegate that configures Elasticsearch. + /// + public Action Options { get; set; } = _ => { }; + + /// public override void ConfigureHostedServices() { - Module.ConfigureHostedService(-1); - - if (IndexRolloverStrategy != null) - { - Module.ConfigureHostedService(-1); - } + Module.ConfigureHostedService(-2); } + /// public override void Apply() { - var elasticClient = new ElasticsearchClient(GetSettings()); - Services.AddSingleton(elasticClient); - - if (IndexRolloverStrategy != null) - { - elasticClient.ConfigureAliases(IndexConfig, IndexRolloverStrategy); - - var namingInstance = - (IIndexNamingStrategy) Activator.CreateInstance(IndexRolloverStrategy.IndexNamingStrategy)!; - Services.AddSingleton(_ => namingInstance); - - var rolloverInstance = - (IIndexRolloverStrategy) Activator.CreateInstance(IndexRolloverStrategy.Value, elasticClient, - namingInstance)!; - Services.AddSingleton(_ => rolloverInstance); - } + Services.Configure(Options); + Services.AddSingleton(sp => new ElasticsearchClient(GetSettings(sp))); } - private ElasticsearchClientSettings GetSettings() + private static ElasticsearchClientSettings GetSettings(IServiceProvider serviceProvider) { - return new ElasticsearchClientSettings(new Uri(Options.Endpoint)) - .ConfigureAuthentication(Options) - .ConfigureMapping(IndexConfig); + var options = serviceProvider.GetRequiredService>().Value; + var configuration = serviceProvider.GetRequiredService(); + var configs = serviceProvider.GetServices(); + var url = configuration.GetConnectionString(options.Endpoint) ?? options.Endpoint; + var settings = new ElasticsearchClientSettings(new Uri(url)).ConfigureAuthentication(options); + + foreach (var config in configs) + config.ConfigureClientSettings(settings); + + return settings; } } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureAliasesHostedService.cs b/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureAliasesHostedService.cs new file mode 100644 index 0000000000..761e01baad --- /dev/null +++ b/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureAliasesHostedService.cs @@ -0,0 +1,62 @@ +using Elastic.Clients.Elasticsearch; +using Elastic.Clients.Elasticsearch.IndexManagement; +using Elsa.Elasticsearch.Options; +using Elsa.Elasticsearch.Services; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +namespace Elsa.Elasticsearch.HostedServices; + +/// +/// Configures aliases. +/// +public class ConfigureAliasesHostedService : IHostedService +{ + private readonly ElasticsearchClient _client; + private readonly ElasticsearchOptions _options; + private readonly IEnumerable _configurations; + + /// + /// Constructor. + /// + public ConfigureAliasesHostedService(ElasticsearchClient client, IOptions options, IEnumerable configurations) + { + _client = client; + _options = options.Value; + _configurations = configurations; + } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + await ConfigureClientAsync(cancellationToken); + } + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + private async Task ConfigureClientAsync(CancellationToken cancellationToken) + { + foreach (var configuration in _configurations) + await configuration.ConfigureClientAsync(_client, cancellationToken); + + foreach (var configuration in _configurations) + { + var alias = _options.GetIndexNameFor(configuration.DocumentType); + var indexName = configuration.IndexNamingStrategy.GenerateName(alias); + var indexExists = (await _client.Indices.ExistsAsync(indexName, cancellationToken)).Exists; + + if (indexExists) + continue; + + var response = await _client.Indices.CreateAsync(indexName, c => c + .Aliases(a => a.Add(alias, new Alias { IsWriteIndex = true })), cancellationToken); + + if (response.IsValidResponse) + continue; + + if (response.TryGetOriginalException(out var exception)) + throw exception!; + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureIndexRolloverHostedService.cs b/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureIndexRolloverHostedService.cs deleted file mode 100644 index 1071f6a073..0000000000 --- a/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureIndexRolloverHostedService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Elsa.Elasticsearch.Scheduling; -using Elsa.Jobs.Schedules; -using Elsa.Jobs.Services; -using Microsoft.Extensions.Hosting; - -namespace Elsa.Elasticsearch.HostedServices; - -public class ConfigureIndexRolloverHostedService : IHostedService -{ - private readonly IJobScheduler _jobScheduler; - - public ConfigureIndexRolloverHostedService(IJobScheduler jobScheduler) - { - _jobScheduler = jobScheduler; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - var job = new ConfigureIndexRolloverJob(); - var schedule = new CronSchedule - { - // At the beginning of every month - CronExpression = "0 0 1 * *" - }; - - await _jobScheduler.ScheduleAsync(job, GetType().Name, schedule, cancellationToken: cancellationToken); - } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureMappingHostedService.cs b/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureMappingHostedService.cs deleted file mode 100644 index e9e4ac8cd0..0000000000 --- a/src/modules/Elsa.Elasticsearch/HostedServices/ConfigureMappingHostedService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Elastic.Clients.Elasticsearch; -using Elsa.Workflows.Management.Entities; -using Microsoft.Extensions.Hosting; - -namespace Elsa.Elasticsearch.HostedServices; - -public class ConfigureMappingHostedService : IHostedService -{ - private readonly ElasticsearchClient _elasticClient; - - public ConfigureMappingHostedService(ElasticsearchClient elasticClient) - { - _elasticClient = elasticClient; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - await _elasticClient.Indices.CreateAsync( - descriptor => descriptor.Mappings(m => m - .Properties(p => p - .Flattened(d => d.WorkflowState.Properties))), - cancellationToken); - } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Implementations/IndexNamingStrategies/NamingWithYearAndMonth.cs b/src/modules/Elsa.Elasticsearch/Implementations/IndexNamingStrategies/NamingWithYearAndMonth.cs deleted file mode 100644 index ddd24ad914..0000000000 --- a/src/modules/Elsa.Elasticsearch/Implementations/IndexNamingStrategies/NamingWithYearAndMonth.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Elsa.Elasticsearch.Services; - -namespace Elsa.Elasticsearch.Implementations.IndexNamingStrategies; - -public class NamingWithYearAndMonth : IIndexNamingStrategy -{ - public string GenerateName(string aliasName) - { - var now = DateTime.Now; - var month = now.ToString("MM"); - var year = now.Year; - - return aliasName + "-" + year + "-" + month; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Implementations/RolloverStrategies/RolloverOnMonthlyBasis.cs b/src/modules/Elsa.Elasticsearch/Implementations/RolloverStrategies/RolloverOnMonthlyBasis.cs deleted file mode 100644 index 85a0316686..0000000000 --- a/src/modules/Elsa.Elasticsearch/Implementations/RolloverStrategies/RolloverOnMonthlyBasis.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Elastic.Clients.Elasticsearch; -using Elsa.Elasticsearch.Services; - -namespace Elsa.Elasticsearch.Implementations.RolloverStrategies; - -public class RolloverOnMonthlyBasis : IIndexRolloverStrategy -{ - private readonly ElasticsearchClient _client; - private readonly IIndexNamingStrategy _indexNamingStrategy; - - public RolloverOnMonthlyBasis(ElasticsearchClient client, IIndexNamingStrategy indexNamingStrategy) - { - _client = client; - _indexNamingStrategy = indexNamingStrategy; - } - - public async Task ApplyAsync(CancellationToken cancellationToken) - { - var getAliasResponse = await _client.Indices.GetAliasAsync(cancellationToken); - var aliases = getAliasResponse.Aliases.Values.SelectMany(s => s.Aliases.Select(a => a.Key)); - - foreach (var alias in aliases) - { - var newIndexName = _indexNamingStrategy.GenerateName(alias); - - var indexExists = (await _client.Indices.ExistsAsync(newIndexName, cancellationToken)).Exists; - if (indexExists) continue; - - await _client.Indices.RolloverAsync( - alias, - cfg => cfg.NewIndex(newIndexName), - cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Models/IndexNamingStrategy.cs b/src/modules/Elsa.Elasticsearch/Models/IndexNamingStrategy.cs deleted file mode 100644 index a699f486b4..0000000000 --- a/src/modules/Elsa.Elasticsearch/Models/IndexNamingStrategy.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Elsa.Elasticsearch.Implementations.IndexNamingStrategies; - -namespace Elsa.Elasticsearch.Models; - -public class IndexNamingStrategy -{ - private IndexNamingStrategy(Type value) { Value = value; } - - public Type Value { get; private set; } - - public static IndexNamingStrategy NamingWithYearAndMonth => new (typeof(NamingWithYearAndMonth)); - - public override string ToString() - { - return Value.Name; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Models/IndexRolloverStrategy.cs b/src/modules/Elsa.Elasticsearch/Models/IndexRolloverStrategy.cs deleted file mode 100644 index 4dd2148bb5..0000000000 --- a/src/modules/Elsa.Elasticsearch/Models/IndexRolloverStrategy.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection.Emit; -using Elsa.Elasticsearch.Implementations.IndexNamingStrategies; -using Elsa.Elasticsearch.Implementations.RolloverStrategies; -using Elsa.Elasticsearch.Services; - -namespace Elsa.Elasticsearch.Models; - -public class IndexRolloverStrategy -{ - private IndexRolloverStrategy(Type value, Type indexNamingStrategy) - { - Value = value; - IndexNamingStrategy = indexNamingStrategy; - } - - public Type Value { get; private set; } - public Type IndexNamingStrategy { get; private set; } - - public static IndexRolloverStrategy RolloverOnMonthlyBasis => new (typeof(RolloverOnMonthlyBasis),typeof(NamingWithYearAndMonth)); -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Modules/Management/ElasticWorkflowInstanceFeature.cs b/src/modules/Elsa.Elasticsearch/Modules/Management/ElasticWorkflowInstanceFeature.cs index 46177da58d..f7359191a0 100644 --- a/src/modules/Elsa.Elasticsearch/Modules/Management/ElasticWorkflowInstanceFeature.cs +++ b/src/modules/Elsa.Elasticsearch/Modules/Management/ElasticWorkflowInstanceFeature.cs @@ -1,4 +1,6 @@ using Elsa.Elasticsearch.Common; +using Elsa.Elasticsearch.Features; +using Elsa.Elasticsearch.Services; using Elsa.Features.Attributes; using Elsa.Features.Services; using Elsa.Workflows.Management.Entities; @@ -7,25 +9,38 @@ namespace Elsa.Elasticsearch.Modules.Management; +/// +/// Configures the feature with Elasticsearch. +/// [DependsOn(typeof(WorkflowManagementFeature))] +[DependsOn(typeof(ElasticsearchFeature))] public class ElasticWorkflowInstanceFeature : ElasticPersistenceFeatureBase { + /// public ElasticWorkflowInstanceFeature(IModule module) : base(module) { } + /// + /// A delegate that creates an instance of a concrete implementation if for . + /// + public Func> IndexConfiguration { get; set; } = sp => ActivatorUtilities.CreateInstance(sp); + + /// public override void Configure() { - Module.Configure(feature => + Module.Configure(feature => { feature.WorkflowInstanceStore = sp => sp.GetRequiredService(); }); } + /// public override void Apply() { base.Apply(); - + AddStore(); + AddIndexConfiguration(IndexConfiguration); } } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Modules/Management/Extensions.cs b/src/modules/Elsa.Elasticsearch/Modules/Management/Extensions.cs index 901ad4a3e2..93e1d58a36 100644 --- a/src/modules/Elsa.Elasticsearch/Modules/Management/Extensions.cs +++ b/src/modules/Elsa.Elasticsearch/Modules/Management/Extensions.cs @@ -1,13 +1,18 @@ using Elsa.Workflows.Management.Features; +using JetBrains.Annotations; namespace Elsa.Elasticsearch.Modules.Management; +/// +/// Extends the feature. +/// +[PublicAPI] public static class Extensions { /// - /// Configures the to use the . + /// Configures the to use the . /// - public static WorkflowInstanceFeature UseElasticsearch(this WorkflowInstanceFeature feature, Action? configure = default) + public static WorkflowInstancesFeature UseElasticsearch(this WorkflowInstancesFeature feature, Action? configure = default) { feature.Module.Configure(configure); return feature; diff --git a/src/modules/Elsa.Elasticsearch/Modules/Management/WorkflowInstanceConfiguration.cs b/src/modules/Elsa.Elasticsearch/Modules/Management/WorkflowInstanceConfiguration.cs index c87877e33a..a59f9f4fe5 100644 --- a/src/modules/Elsa.Elasticsearch/Modules/Management/WorkflowInstanceConfiguration.cs +++ b/src/modules/Elsa.Elasticsearch/Modules/Management/WorkflowInstanceConfiguration.cs @@ -1,15 +1,39 @@ using Elastic.Clients.Elasticsearch; using Elsa.Elasticsearch.Common; +using Elsa.Elasticsearch.Options; using Elsa.Workflows.Management.Entities; +using Microsoft.Extensions.Options; namespace Elsa.Elasticsearch.Modules.Management; -public class WorkflowInstanceConfiguration : ElasticConfiguration +/// +/// Configures Elasticsearch with mappings for . +/// +public class WorkflowInstanceConfiguration : IndexConfiguration { - public override void Apply(ElasticsearchClientSettings settings, IDictionary indexConfig) + private readonly ElasticsearchOptions _options; + + /// + public WorkflowInstanceConfiguration(IOptions options) + { + _options = options.Value; + } + + /// + public override void ConfigureClientSettings(ElasticsearchClientSettings settings) + { + var alias = _options.GetIndexNameFor(); + var indexName = IndexNamingStrategy.GenerateName(alias); + settings.DefaultMappingFor(m => m.IndexName(indexName)); + } + + /// + public override async ValueTask ConfigureClientAsync(ElasticsearchClient client, CancellationToken cancellationToken) { - settings - .DefaultMappingFor(m => m - .IndexName(indexConfig[typeof(WorkflowInstance)])); + await client.Indices.CreateAsync( + descriptor => descriptor.Mappings(m => m + .Properties(p => p + .Flattened(d => d.WorkflowState.Properties))), + cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Modules/Management/WorkflowInstanceStore.cs b/src/modules/Elsa.Elasticsearch/Modules/Management/WorkflowInstanceStore.cs index bdac193f54..9906f78166 100644 --- a/src/modules/Elsa.Elasticsearch/Modules/Management/WorkflowInstanceStore.cs +++ b/src/modules/Elsa.Elasticsearch/Modules/Management/WorkflowInstanceStore.cs @@ -9,15 +9,22 @@ namespace Elsa.Elasticsearch.Modules.Management; +/// +/// Stores and retrieves workflow instances from Elasticsearch. +/// public class ElasticWorkflowInstanceStore : IWorkflowInstanceStore { private readonly ElasticStore _store; + /// + /// Constructor. + /// public ElasticWorkflowInstanceStore(ElasticStore store) { _store = store; } + /// public async Task FindByIdAsync(string id, CancellationToken cancellationToken = default) { var instances = await _store @@ -30,12 +37,15 @@ public ElasticWorkflowInstanceStore(ElasticStore store) return instances.Items.MaxBy(x => x.LastExecutedAt); } + /// public async Task SaveAsync(WorkflowInstance record, CancellationToken cancellationToken = default) => await _store.SaveAsync(record, cancellationToken); + /// public async Task SaveManyAsync(IEnumerable records, CancellationToken cancellationToken = default) => await _store.SaveManyAsync(records, cancellationToken); + /// public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) { var result = await _store.DeleteByQueryAsync(desc => desc @@ -47,6 +57,7 @@ public async Task DeleteAsync(string id, CancellationToken cancellationTok return result > 0; } + /// public async Task DeleteManyAsync(IEnumerable ids, CancellationToken cancellationToken = default) { var result = await _store.DeleteByQueryAsync(desc => desc @@ -59,6 +70,7 @@ public async Task DeleteManyAsync(IEnumerable ids, CancellationToke return (int)result; } + /// public async Task DeleteManyByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default) => await _store.DeleteByQueryAsync(desc => desc .Query(q => q @@ -67,51 +79,52 @@ await _store.DeleteByQueryAsync(desc => desc .Value(definitionId))), cancellationToken); + /// public async Task> FindManyAsync(FindWorkflowInstancesArgs args, CancellationToken cancellationToken = default) { var sortDescriptor = new SortOptionsDescriptor(); - var query = new QueryDescriptor(); var (searchTerm, definitionId, version, correlationId, workflowStatus, workflowSubStatus, pageArgs, orderBy, orderDirection) = args; - if (!string.IsNullOrWhiteSpace(definitionId)) - query = query.Match(m => m.Field(f => f.DefinitionId).Query(definitionId)); - - if (version != null) - query = query.Match(m => m.Field(f => f.Version).Query(version.ToString()!)); - - if (!string.IsNullOrWhiteSpace(correlationId)) - query = query.Match(m => m.Field(f => f.CorrelationId).Query(correlationId)); - - if (workflowStatus != null) - query = query.Match(m => m.Field(f => f.Status).Query(workflowStatus.ToString()!)); - - if (workflowSubStatus != null) - query = query.Match(m => m.Field(f => f.SubStatus).Query(workflowSubStatus.ToString()!)); - - if(!string.IsNullOrWhiteSpace(searchTerm)) - { - query = query - .QueryString(c => c - .Query(searchTerm)); - } - sortDescriptor = orderBy switch { OrderBy.Finished => orderDirection == OrderDirection.Ascending - ? sortDescriptor.Field(f => f.FinishedAt, cfg => cfg.Order(SortOrder.Asc)) - : sortDescriptor.Field(f => f.FinishedAt, cfg => cfg.Order(SortOrder.Desc)), + ? sortDescriptor.Field(f => f.FinishedAt!, cfg => cfg.Order(SortOrder.Asc)) + : sortDescriptor.Field(f => f.FinishedAt!, cfg => cfg.Order(SortOrder.Desc)), OrderBy.LastExecuted => orderDirection == OrderDirection.Ascending - ? sortDescriptor.Field(f => f.LastExecutedAt, cfg => cfg.Order(SortOrder.Asc)) - : sortDescriptor.Field(f => f.LastExecutedAt, cfg => cfg.Order(SortOrder.Desc)), + ? sortDescriptor.Field(f => f.LastExecutedAt!, cfg => cfg.Order(SortOrder.Asc)) + : sortDescriptor.Field(f => f.LastExecutedAt!, cfg => cfg.Order(SortOrder.Desc)), OrderBy.Created => orderDirection == OrderDirection.Ascending ? sortDescriptor.Field(f => f.CreatedAt, cfg => cfg.Order(SortOrder.Asc)) : sortDescriptor.Field(f => f.CreatedAt, cfg => cfg.Order(SortOrder.Desc)), _ => sortDescriptor }; - var result = await _store.SearchAsync(s => s.Sort(sortDescriptor).Query(query), args.PageArgs, cancellationToken); + var result = await _store.SearchAsync(s => + { + if (!string.IsNullOrWhiteSpace(definitionId)) + s.Query(q => q.Match(m => m.Field(f => f.DefinitionId).Query(definitionId))); + + if (version != null) + s.Query(q => q.Match(m => m.Field(f => f.Version).Query(version.ToString()!))); + + if (!string.IsNullOrWhiteSpace(correlationId)) + s.Query(q => q.Match(m => m.Field(f => f.CorrelationId).Query(correlationId))); + + if (workflowStatus != null) + s.Query(q => q.Match(m => m.Field(f => f.Status).Query(workflowStatus.ToString()!))); + + if (workflowSubStatus != null) + s.Query(q => q.Match(m => m.Field(f => f.SubStatus).Query(workflowSubStatus.ToString()!))); + + if(!string.IsNullOrWhiteSpace(searchTerm)) + s.Query(q => q + .QueryString(c => c + .Query(searchTerm))); + + s.Sort(sortDescriptor); + }, args.PageArgs, cancellationToken); return new Page(result.Items.Select(WorkflowInstanceSummary.FromInstance).ToList(), result.TotalCount); } diff --git a/src/modules/Elsa.Elasticsearch/Modules/Runtime/ElasticExecutionLogRecordFeature.cs b/src/modules/Elsa.Elasticsearch/Modules/Runtime/ElasticExecutionLogRecordFeature.cs index 8f34bcad87..d6669d7ab7 100644 --- a/src/modules/Elsa.Elasticsearch/Modules/Runtime/ElasticExecutionLogRecordFeature.cs +++ b/src/modules/Elsa.Elasticsearch/Modules/Runtime/ElasticExecutionLogRecordFeature.cs @@ -3,17 +3,30 @@ using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Features; using Elsa.Elasticsearch.Common; +using Elsa.Elasticsearch.Features; +using Elsa.Elasticsearch.Services; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Elasticsearch.Modules.Runtime; +/// +/// Configures the feature with Elasticsearch persistence. +/// [DependsOn(typeof(WorkflowRuntimeFeature))] +[DependsOn(typeof(ElasticsearchFeature))] public class ElasticExecutionLogRecordFeature : ElasticPersistenceFeatureBase { + /// public ElasticExecutionLogRecordFeature(IModule module) : base(module) { } + /// + /// A delegate that creates an instance of a concrete implementation if for . + /// + public Func> IndexConfiguration { get; set; } = sp => ActivatorUtilities.CreateInstance(sp); + + /// public override void Configure() { Module.Configure(feature => @@ -22,10 +35,12 @@ public override void Configure() }); } + /// public override void Apply() { base.Apply(); AddStore(); + AddIndexConfiguration(IndexConfiguration); } } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Modules/Runtime/ExecutionLogConfiguration.cs b/src/modules/Elsa.Elasticsearch/Modules/Runtime/ExecutionLogConfiguration.cs index f9680d5fb1..f3e8dcc570 100644 --- a/src/modules/Elsa.Elasticsearch/Modules/Runtime/ExecutionLogConfiguration.cs +++ b/src/modules/Elsa.Elasticsearch/Modules/Runtime/ExecutionLogConfiguration.cs @@ -1,14 +1,23 @@ using Elastic.Clients.Elasticsearch; using Elsa.Elasticsearch.Common; +using Elsa.Elasticsearch.Options; using Elsa.Workflows.Runtime.Entities; +using Microsoft.Extensions.Options; namespace Elsa.Elasticsearch.Modules.Runtime; -public class ExecutionLogConfiguration : ElasticConfiguration +/// +/// Configures Elasticsearch with mappings for . +/// +public class ExecutionLogConfiguration : IndexConfiguration { - public override void Apply(ElasticsearchClientSettings settings, IDictionary indexConfig) - { - settings.DefaultMappingFor(m => - m.IndexName(indexConfig[typeof(WorkflowExecutionLogRecord)])); - } + private readonly ElasticsearchOptions _options; + + /// + public ExecutionLogConfiguration(IOptions options) => _options = options.Value; + + /// + public override void ConfigureClientSettings(ElasticsearchClientSettings settings) => + settings.DefaultMappingFor(m => m + .IndexName(_options.GetIndexNameFor())); } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Modules/Runtime/WorkflowExecutionLogStore.cs b/src/modules/Elsa.Elasticsearch/Modules/Runtime/WorkflowExecutionLogStore.cs index 6347d94930..14d608a9cb 100644 --- a/src/modules/Elsa.Elasticsearch/Modules/Runtime/WorkflowExecutionLogStore.cs +++ b/src/modules/Elsa.Elasticsearch/Modules/Runtime/WorkflowExecutionLogStore.cs @@ -5,21 +5,30 @@ namespace Elsa.Elasticsearch.Modules.Runtime; +/// +/// Store and retrieves objects from an Elasticsearch. +/// public class ElasticWorkflowExecutionLogStore : IWorkflowExecutionLogStore { private readonly ElasticStore _store; + /// + /// Constructor. + /// public ElasticWorkflowExecutionLogStore(ElasticStore store) { _store = store; } + /// public async Task SaveAsync(WorkflowExecutionLogRecord record, CancellationToken cancellationToken = default) => await _store.SaveAsync(record, cancellationToken); - + + /// public async Task SaveManyAsync(IEnumerable records, CancellationToken cancellationToken = default) => await _store.SaveManyAsync(records, cancellationToken); + /// public async Task> FindManyByWorkflowInstanceIdAsync(string workflowInstanceId, PageArgs? pageArgs = default, CancellationToken cancellationToken = default) => await _store .SearchAsync(desc => desc diff --git a/src/modules/Elsa.Elasticsearch/Options/ElasticsearchOptions.cs b/src/modules/Elsa.Elasticsearch/Options/ElasticsearchOptions.cs index a027b3212b..8b96a2c326 100644 --- a/src/modules/Elsa.Elasticsearch/Options/ElasticsearchOptions.cs +++ b/src/modules/Elsa.Elasticsearch/Options/ElasticsearchOptions.cs @@ -1,12 +1,51 @@ +using Humanizer; +using JetBrains.Annotations; + namespace Elsa.Elasticsearch.Options; +/// +/// Contains Elasticsearch settings. +/// +[PublicAPI] public class ElasticsearchOptions { - public const string Elasticsearch = "Elasticsearch"; + /// + /// The URL of the Elasticsearch server or the name of a connection string that in turn stores the URL. + /// + public string Endpoint { get; set; } = default!; + + /// + /// The username to use when connecting with the Elasticsearch server. + /// + public string? Username { get; set; } + + /// + /// The password to use when connecting with the Elasticsearch server. + /// + public string? Password { get; set; } + + /// + /// The API key to use when connecting with the Elasticsearch server. + /// + public string? ApiKey { get; set; } + + /// + /// The interval to attempt a rollover. + /// + public TimeSpan RolloverInterval { get; set; } = TimeSpan.FromDays(10); - public Dictionary? IndexConfig { get; set; } - public string Endpoint { get; set; } - public string Username { get; set; } - public string Password { get; set; } - public string ApiKey { get; set; } + /// + /// A map between type and index name to use. When no index name is configured for a given type, the name of the type is used. + /// + public IDictionary IndexNameMappings { get; set; } = new Dictionary(); + + /// + /// Returns an index name for the specified document type. If no mapping was found, the simple name of the type is used. + /// + public string GetIndexNameFor() => GetIndexNameFor(typeof(T)); + + /// + /// Returns an index name for the specified document type. If no mapping was found, the simple name of the type is used. + /// + public string GetIndexNameFor(Type documentType) => IndexNameMappings.TryGetValue(documentType, out var index) ? index : documentType.Name.Dasherize(); } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Scheduling/ConfigureIndexRolloverJob.cs b/src/modules/Elsa.Elasticsearch/Scheduling/ConfigureIndexRolloverJob.cs deleted file mode 100644 index 262552e911..0000000000 --- a/src/modules/Elsa.Elasticsearch/Scheduling/ConfigureIndexRolloverJob.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; -using Elsa.Elasticsearch.Services; -using Elsa.Jobs.Models; -using Microsoft.Extensions.DependencyInjection; -using Job = Elsa.Jobs.Abstractions.Job; - -namespace Elsa.Elasticsearch.Scheduling; - -public class ConfigureIndexRolloverJob : Job -{ - [JsonConstructor] - public ConfigureIndexRolloverJob() - { - } - - protected override async ValueTask ExecuteAsync(JobExecutionContext context) - { - var rolloverStrategy = context.ServiceProvider.GetRequiredService(); - await rolloverStrategy.ApplyAsync(context.CancellationToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Services/IElasticConfiguration.cs b/src/modules/Elsa.Elasticsearch/Services/IElasticConfiguration.cs deleted file mode 100644 index b523e69ed9..0000000000 --- a/src/modules/Elsa.Elasticsearch/Services/IElasticConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Elastic.Clients.Elasticsearch; -using Elsa.Elasticsearch.Common; - -namespace Elsa.Elasticsearch.Services; - -public interface IElasticConfiguration -{ - void Apply(ElasticsearchClientSettings settings, IDictionary indexConfig); - - public static IDictionary GetDefaultIndexConfig() - { - return Utils.GetElasticDocumentTypes().ToDictionary(type => type, type => type.Name.ToLower()); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Services/IIndexConfiguration.cs b/src/modules/Elsa.Elasticsearch/Services/IIndexConfiguration.cs new file mode 100644 index 0000000000..6288b6eff7 --- /dev/null +++ b/src/modules/Elsa.Elasticsearch/Services/IIndexConfiguration.cs @@ -0,0 +1,39 @@ +using Elastic.Clients.Elasticsearch; + +namespace Elsa.Elasticsearch.Services; + +/// +/// Implement this interface to get a chance to configure some aspect of Elasticsearch, such as index mappings. +/// +public interface IIndexConfiguration +{ + /// + /// The document type to configure. + /// + Type DocumentType { get; } + + /// + /// The index naming strategy to use for rollovers. + /// + IIndexNamingStrategy IndexNamingStrategy { get; } + + /// + /// Invoked by the system to configure . + /// + /// The settings to configure. + void ConfigureClientSettings(ElasticsearchClientSettings settings); + + /// + /// Invoked by the system to configure the . + /// + /// The to configure. + /// A cancellation token. + ValueTask ConfigureClientAsync(ElasticsearchClient client, CancellationToken cancellationToken); +} + +/// +/// Implement this interface to get a chance to configure some aspect of Elasticsearch, such as index mappings. +/// +public interface IIndexConfiguration : IIndexConfiguration +{ +} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Services/IIndexNamingStrategy.cs b/src/modules/Elsa.Elasticsearch/Services/IIndexNamingStrategy.cs index 08059dedee..165123ab2f 100644 --- a/src/modules/Elsa.Elasticsearch/Services/IIndexNamingStrategy.cs +++ b/src/modules/Elsa.Elasticsearch/Services/IIndexNamingStrategy.cs @@ -1,6 +1,12 @@ namespace Elsa.Elasticsearch.Services; +/// +/// Represents a naming strategy to use when creating an index name from an alias. +/// public interface IIndexNamingStrategy { + /// + /// Returns an index name from the specified alias. + /// string GenerateName(string aliasName); } \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Services/IIndexRolloverStrategy.cs b/src/modules/Elsa.Elasticsearch/Services/IIndexRolloverStrategy.cs deleted file mode 100644 index ad992df3ae..0000000000 --- a/src/modules/Elsa.Elasticsearch/Services/IIndexRolloverStrategy.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Elsa.Elasticsearch.Services; - -public interface IIndexRolloverStrategy -{ - Task ApplyAsync(CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Strategies/DefaultNaming.cs b/src/modules/Elsa.Elasticsearch/Strategies/DefaultNaming.cs new file mode 100644 index 0000000000..e321ddb7e8 --- /dev/null +++ b/src/modules/Elsa.Elasticsearch/Strategies/DefaultNaming.cs @@ -0,0 +1,13 @@ +using Elsa.Elasticsearch.Services; +using Humanizer; + +namespace Elsa.Elasticsearch.Strategies; + +/// +/// Returns the alias as the name for the index. +/// +public class DefaultNaming : IIndexNamingStrategy +{ + /// + public string GenerateName(string aliasName) => aliasName.Kebaberize(); +} \ No newline at end of file diff --git a/src/modules/Elsa.Elasticsearch/Strategies/YearAndMonthNaming.cs b/src/modules/Elsa.Elasticsearch/Strategies/YearAndMonthNaming.cs new file mode 100644 index 0000000000..01780ca3ec --- /dev/null +++ b/src/modules/Elsa.Elasticsearch/Strategies/YearAndMonthNaming.cs @@ -0,0 +1,33 @@ +using Elsa.Common.Services; +using Elsa.Elasticsearch.Services; +using Humanizer; +using JetBrains.Annotations; + +namespace Elsa.Elasticsearch.Strategies; + +/// +/// Returns an index name based on the specified alias, current year and month. +/// +[PublicAPI] +public class YearAndMonthNaming : IIndexNamingStrategy +{ + private readonly ISystemClock _systemClock; + + /// + /// Constructor. + /// + public YearAndMonthNaming(ISystemClock systemClock) + { + _systemClock = systemClock; + } + + /// + public string GenerateName(string aliasName) + { + var now = _systemClock.UtcNow; + var month = now.ToString("MM"); + var year = now.Year; + + return aliasName.Kebaberize() + "-" + year + "-" + month; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Modules/Management/Extensions.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Modules/Management/Extensions.cs index 8a2445388b..f2018fe998 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Modules/Management/Extensions.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Modules/Management/Extensions.cs @@ -1,12 +1,17 @@ using Elsa.EntityFrameworkCore.Modules.Management; using Elsa.EntityFrameworkCore.Modules.Runtime; +using JetBrains.Annotations; // ReSharper disable once CheckNamespace namespace Elsa.EntityFrameworkCore.Extensions; +/// +/// Extends EF Core features. +/// +[PublicAPI] public static partial class Extensions { - public static EFCoreDefaultManagementPersistenceFeature UsePostgreSql(this EFCoreDefaultManagementPersistenceFeature feature, string connectionString) + public static EFCoreWorkflowDefinitionPersistenceFeature UsePostgreSql(this EFCoreWorkflowDefinitionPersistenceFeature feature, string connectionString) { feature.DbContextOptionsBuilder = (_, db) => db.UseElsaPostgreSql(connectionString); return feature; @@ -17,4 +22,10 @@ public static EFCoreWorkflowInstancePersistenceFeature UsePostgreSql(this EFCore feature.DbContextOptionsBuilder = (_, db) => db.UseElsaPostgreSql(connectionString); return feature; } + + public static EFCoreWorkflowManagementPersistenceFeature UsePostgreSql(this EFCoreWorkflowManagementPersistenceFeature feature, string connectionString) + { + feature.DbContextOptionsBuilder = (_, db) => db.UseElsaPostgreSql(connectionString); + return feature; + } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Modules/Management/Extensions.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Modules/Management/Extensions.cs index 595756e8b5..0b165f2ea2 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Modules/Management/Extensions.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Modules/Management/Extensions.cs @@ -1,11 +1,25 @@ using Elsa.EntityFrameworkCore.Modules.Management; +using JetBrains.Annotations; // ReSharper disable once CheckNamespace namespace Elsa.EntityFrameworkCore.Extensions; +[PublicAPI] public static partial class Extensions { - public static EFCoreDefaultManagementPersistenceFeature UseSqlServer(this EFCoreDefaultManagementPersistenceFeature feature, string connectionString) + public static EFCoreWorkflowDefinitionPersistenceFeature UseSqlServer(this EFCoreWorkflowDefinitionPersistenceFeature feature, string connectionString) + { + feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(connectionString); + return feature; + } + + public static EFCoreWorkflowInstancePersistenceFeature UseSqlServer(this EFCoreWorkflowInstancePersistenceFeature feature, string connectionString) + { + feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(connectionString); + return feature; + } + + public static EFCoreWorkflowManagementPersistenceFeature UseSqlServer(this EFCoreWorkflowManagementPersistenceFeature feature, string connectionString) { feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(connectionString); return feature; diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Modules/Management/Extensions.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Modules/Management/Extensions.cs index 17acbb52e1..0357129511 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Modules/Management/Extensions.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Modules/Management/Extensions.cs @@ -1,12 +1,14 @@ using Elsa.EntityFrameworkCore.Modules.Management; using Elsa.EntityFrameworkCore.Sqlite; +using JetBrains.Annotations; // ReSharper disable once CheckNamespace namespace Elsa.EntityFrameworkCore.Extensions; +[PublicAPI] public static partial class Extensions { - public static EFCoreDefaultManagementPersistenceFeature UseSqlite(this EFCoreDefaultManagementPersistenceFeature feature, string connectionString = Constants.DefaultConnectionString) + public static EFCoreWorkflowDefinitionPersistenceFeature UseSqlite(this EFCoreWorkflowDefinitionPersistenceFeature feature, string connectionString = Constants.DefaultConnectionString) { feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlite(connectionString); return feature; @@ -17,4 +19,10 @@ public static EFCoreWorkflowInstancePersistenceFeature UseSqlite(this EFCoreWork feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlite(connectionString); return feature; } + + public static EFCoreWorkflowManagementPersistenceFeature UseSqlite(this EFCoreWorkflowManagementPersistenceFeature feature, string connectionString = Constants.DefaultConnectionString) + { + feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlite(connectionString); + return feature; + } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs b/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs index 2e3bd0754a..e23c058820 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs @@ -19,7 +19,7 @@ protected PersistenceFeatureBase(IModule module) : base(module) public override void ConfigureHostedServices() { if (RunMigrations) - Module.ConfigureHostedService>(-1); // Migrations need to run before other hosted services that depend on DB access. + Module.ConfigureHostedService>(-100); // Migrations need to run before other hosted services that depend on DB access. } public override void Apply() diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/EFCoreWorkflowManagementPersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/EFCoreWorkflowManagementPersistenceFeature.cs new file mode 100644 index 0000000000..0f84f93a61 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/EFCoreWorkflowManagementPersistenceFeature.cs @@ -0,0 +1,38 @@ +using Elsa.EntityFrameworkCore.Common; +using Elsa.Features.Attributes; +using Elsa.Features.Services; +using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Features; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.EntityFrameworkCore.Modules.Management; + +/// +/// Configures the and features with an Entity Framework Core persistence provider. +/// +[DependsOn(typeof(WorkflowManagementFeature))] +[PublicAPI] +public class EFCoreWorkflowManagementPersistenceFeature : PersistenceFeatureBase +{ + /// + public EFCoreWorkflowManagementPersistenceFeature(IModule module) : base(module) + { + } + + /// + public override void Configure() + { + Module.Configure(feature => feature.WorkflowInstanceStore = sp => sp.GetRequiredService()); + Module.Configure(feature => feature.WorkflowDefinitionStore = sp => sp.GetRequiredService()); + } + + /// + public override void Apply() + { + base.Apply(); + + AddStore(); + AddStore(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Extensions.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Extensions.cs index b9e49690f7..187887ff66 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Extensions.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Extensions.cs @@ -1,4 +1,5 @@ -using Elsa.Workflows.Management.Features; +using Elsa.Extensions; +using Elsa.Workflows.Management.Features; namespace Elsa.EntityFrameworkCore.Modules.Management; @@ -10,8 +11,7 @@ public static class WorkflowManagementFeatureExtensions /// /// Sets up the EF Core persistence provider. /// - public static DefaultManagementFeature UseEntityFrameworkCore(this DefaultManagementFeature feature, - Action? configure = default) + public static WorkflowDefinitionsFeature UseEntityFrameworkCore(this WorkflowDefinitionsFeature feature, Action? configure = default) { feature.Module.Configure(configure); return feature; @@ -20,7 +20,16 @@ public static DefaultManagementFeature UseEntityFrameworkCore(this DefaultManage /// /// Sets up the EF Core persistence provider. /// - public static WorkflowInstanceFeature UseEntityFrameworkCore(this WorkflowInstanceFeature feature, Action? configure = default) + public static WorkflowInstancesFeature UseEntityFrameworkCore(this WorkflowInstancesFeature feature, Action? configure = default) + { + feature.Module.Configure(configure); + return feature; + } + + /// + /// Sets up the EF Core persistence provider. + /// + public static WorkflowManagementFeature UseEntityFrameworkCore(this WorkflowManagementFeature feature, Action? configure = default) { feature.Module.Configure(configure); return feature; diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/DefaultManagementPersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionPersistenceFeature.cs similarity index 58% rename from src/modules/Elsa.EntityFrameworkCore/Modules/Management/DefaultManagementPersistenceFeature.cs rename to src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionPersistenceFeature.cs index 3f52ce5348..03a3555ad1 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/DefaultManagementPersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionPersistenceFeature.cs @@ -7,21 +7,27 @@ namespace Elsa.EntityFrameworkCore.Modules.Management; +/// +/// Configures the feature with an Entity Framework Core persistence provider. +/// [DependsOn(typeof(WorkflowManagementFeature))] -public class EFCoreDefaultManagementPersistenceFeature : PersistenceFeatureBase +public class EFCoreWorkflowDefinitionPersistenceFeature : PersistenceFeatureBase { - public EFCoreDefaultManagementPersistenceFeature(IModule module) : base(module) + /// + public EFCoreWorkflowDefinitionPersistenceFeature(IModule module) : base(module) { } + /// public override void Configure() { - Module.Configure(feature => + Module.Configure(feature => { feature.WorkflowDefinitionStore = sp => sp.GetRequiredService(); }); } + /// public override void Apply() { base.Apply(); diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowInstancePersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowInstancePersistenceFeature.cs index a310a51ed6..53b681f4c6 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowInstancePersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowInstancePersistenceFeature.cs @@ -7,21 +7,27 @@ namespace Elsa.EntityFrameworkCore.Modules.Management; +/// +/// Configures the feature with an Entity Framework Core persistence provider. +/// [DependsOn(typeof(WorkflowManagementFeature))] public class EFCoreWorkflowInstancePersistenceFeature : PersistenceFeatureBase { + /// public EFCoreWorkflowInstancePersistenceFeature(IModule module) : base(module) { } + /// public override void Configure() { - Module.Configure(feature => + Module.Configure(feature => { feature.WorkflowInstanceStore = sp => sp.GetRequiredService(); }); } + /// public override void Apply() { base.Apply(); diff --git a/src/modules/Elsa.ProtoActor.Cluster.AzureContainerApps/Elsa.ProtoActor.Cluster.AzureContainerApps.csproj b/src/modules/Elsa.ProtoActor.Cluster.AzureContainerApps/Elsa.ProtoActor.Cluster.AzureContainerApps.csproj index ef4b5de96b..6dadce6632 100644 --- a/src/modules/Elsa.ProtoActor.Cluster.AzureContainerApps/Elsa.ProtoActor.Cluster.AzureContainerApps.csproj +++ b/src/modules/Elsa.ProtoActor.Cluster.AzureContainerApps/Elsa.ProtoActor.Cluster.AzureContainerApps.csproj @@ -14,7 +14,6 @@ - diff --git a/src/modules/Elsa.Quartz/Extensions/DependencyInjectionExtensions.cs b/src/modules/Elsa.Quartz/Extensions/DependencyInjectionExtensions.cs index f52c184c36..dcb45e6635 100644 --- a/src/modules/Elsa.Quartz/Extensions/DependencyInjectionExtensions.cs +++ b/src/modules/Elsa.Quartz/Extensions/DependencyInjectionExtensions.cs @@ -1,4 +1,3 @@ -using Elsa.Elasticsearch.Scheduling; using Elsa.Quartz.Jobs; using Elsa.Scheduling.Jobs; using Quartz; @@ -7,13 +6,15 @@ // ReSharper disable once CheckNamespace namespace Elsa.Extensions; +/// +/// Extends . +/// public static class DependencyInjectionExtensions { public static IServiceCollectionQuartzConfigurator AddElsaJobs(this IServiceCollectionQuartzConfigurator quartz) { quartz.AddJob(); quartz.AddJob(); - quartz.AddJob(); return quartz; } diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs index f87a107e01..d7673d9c49 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs @@ -9,7 +9,7 @@ namespace Elsa.WorkflowContexts.Middleware; /// -/// Middleware that loads & save workflow context into the currently executing workflow using installed workflow context providers. +/// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers. /// public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMiddleware { diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs index c7dda7f3f5..d6369dfa7c 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs @@ -8,17 +8,15 @@ namespace Elsa.WorkflowContexts.Middleware; /// -/// Middleware that loads & save workflow context into the currently executing workflow using installed workflow context providers. +/// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers. /// public class WorkflowContextWorkflowExecutionMiddleware : WorkflowExecutionMiddleware { - private readonly IServiceProvider _serviceProvider; private readonly IServiceScopeFactory _serviceScopeFactory; /// - public WorkflowContextWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory) : base(next) + public WorkflowContextWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory) : base(next) { - _serviceProvider = serviceProvider; _serviceScopeFactory = serviceScopeFactory; } diff --git a/src/modules/Elsa.Workflows.Designer/package-lock.json b/src/modules/Elsa.Workflows.Designer/package-lock.json index 85fdfb0344..5154290274 100644 --- a/src/modules/Elsa.Workflows.Designer/package-lock.json +++ b/src/modules/Elsa.Workflows.Designer/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "@elsa-workflows/elsa-workflows-designer": "^3.0.2-preview.12", + "@elsa-workflows/elsa-workflows-designer": "^3.0.2-preview.26", "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.3", "monaco-editor": "^0.34.1", @@ -115,9 +115,9 @@ } }, "node_modules/@elsa-workflows/elsa-workflows-designer": { - "version": "3.0.2-preview.12", - "resolved": "https://registry.npmjs.org/@elsa-workflows/elsa-workflows-designer/-/elsa-workflows-designer-3.0.2-preview.12.tgz", - "integrity": "sha512-aE56WlY21pGpHrEjQoKp/wVIxbupw9DdG/ZeQdt/5RM6qc98a/FC1mzJkLN2jL7fpmEUBJDflX/iKTDpu6F8JA==", + "version": "3.0.2-preview.26", + "resolved": "https://registry.npmjs.org/@elsa-workflows/elsa-workflows-designer/-/elsa-workflows-designer-3.0.2-preview.26.tgz", + "integrity": "sha512-9V0CKiy/TqLSKON4sXmbKj86H7mP7Vlj0OiGe+AY0eS3phLRl9f5XsShbw88CnmeR2PZtEPasDFKqqu+9tY+zg==", "dev": true, "dependencies": { "@antv/layout": "^0.3.11", @@ -2409,9 +2409,9 @@ "dev": true }, "@elsa-workflows/elsa-workflows-designer": { - "version": "3.0.2-preview.12", - "resolved": "https://registry.npmjs.org/@elsa-workflows/elsa-workflows-designer/-/elsa-workflows-designer-3.0.2-preview.12.tgz", - "integrity": "sha512-aE56WlY21pGpHrEjQoKp/wVIxbupw9DdG/ZeQdt/5RM6qc98a/FC1mzJkLN2jL7fpmEUBJDflX/iKTDpu6F8JA==", + "version": "3.0.2-preview.26", + "resolved": "https://registry.npmjs.org/@elsa-workflows/elsa-workflows-designer/-/elsa-workflows-designer-3.0.2-preview.26.tgz", + "integrity": "sha512-9V0CKiy/TqLSKON4sXmbKj86H7mP7Vlj0OiGe+AY0eS3phLRl9f5XsShbw88CnmeR2PZtEPasDFKqqu+9tY+zg==", "dev": true, "requires": { "@antv/layout": "^0.3.11", diff --git a/src/modules/Elsa.Workflows.Designer/package.json b/src/modules/Elsa.Workflows.Designer/package.json index ace1f39918..3fbfa24d30 100644 --- a/src/modules/Elsa.Workflows.Designer/package.json +++ b/src/modules/Elsa.Workflows.Designer/package.json @@ -6,7 +6,7 @@ "build:debug": "cross-env webpack --mode=development --env configuration=Debug" }, "devDependencies": { - "@elsa-workflows/elsa-workflows-designer": "^3.0.2-preview.12", + "@elsa-workflows/elsa-workflows-designer": "^3.0.2-preview.26", "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.3", "monaco-editor": "^0.34.1", diff --git a/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs index f56fa7bf32..9c9eb98b47 100644 --- a/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs +++ b/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs @@ -27,7 +27,7 @@ public static IModule UseWorkflowManagement(this IModule module, Action /// Adds the default workflow management feature to the specified module. /// - public static WorkflowManagementFeature UseDefaultManagement(this WorkflowManagementFeature feature, Action? configure = default) + public static WorkflowManagementFeature UseWorkflowDefinitions(this WorkflowManagementFeature feature, Action? configure = default) { feature.Module.Configure(configure); return feature; @@ -36,7 +36,7 @@ public static WorkflowManagementFeature UseDefaultManagement(this WorkflowManage /// /// Adds the workflow instance feature to workflow management module. /// - public static WorkflowManagementFeature UseWorkflowInstances(this WorkflowManagementFeature feature, Action? configure = default) + public static WorkflowManagementFeature UseWorkflowInstances(this WorkflowManagementFeature feature, Action? configure = default) { feature.Module.Configure(configure); return feature; diff --git a/src/modules/Elsa.Workflows.Management/Extensions/TypeExtensions.cs b/src/modules/Elsa.Workflows.Management/Extensions/TypeExtensions.cs index dcc3a4d264..ea23cd362a 100644 --- a/src/modules/Elsa.Workflows.Management/Extensions/TypeExtensions.cs +++ b/src/modules/Elsa.Workflows.Management/Extensions/TypeExtensions.cs @@ -1,9 +1,23 @@ // ReSharper disable once CheckNamespace namespace Elsa.Extensions; +/// +/// Extends with additional methods. +/// public static class TypeExtensions { + /// + /// Returns true of the type is generic, false otherwise. + /// public static bool IsGenericType(this Type type, Type genericType) => type.IsGenericType && type.GetGenericTypeDefinition() == genericType; + + /// + /// Returns true of the type is nullable, false otherwise. + /// public static bool IsNullableType(this Type type) => type.IsGenericType(typeof(Nullable<>)); + + /// + /// Returns the wrapped type of the specified nullable type. + /// public static Type GetTypeOfNullable(this Type type) => type.GenericTypeArguments[0]; } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowDefinitionsFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowDefinitionsFeature.cs new file mode 100644 index 0000000000..f4a6a28928 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowDefinitionsFeature.cs @@ -0,0 +1,29 @@ +using Elsa.Features.Abstractions; +using Elsa.Features.Services; +using Elsa.Workflows.Management.Implementations; +using Elsa.Workflows.Management.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Workflows.Management.Features; + +/// +/// Configures workflow definition storage. +/// +public class WorkflowDefinitionsFeature : FeatureBase +{ + /// + public WorkflowDefinitionsFeature(IModule module) : base(module) + { + } + + /// + /// The factory to create new instances of . + /// + public Func WorkflowDefinitionStore { get; set; } = sp => sp.GetRequiredService(); + + /// + public override void Apply() + { + Services.AddSingleton(WorkflowDefinitionStore); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowInstanceFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowInstanceFeature.cs deleted file mode 100644 index 887d9c7898..0000000000 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowInstanceFeature.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Elsa.Features.Abstractions; -using Elsa.Features.Services; - -namespace Elsa.Workflows.Management.Features; - -public class WorkflowInstanceFeature : FeatureBase -{ - public WorkflowInstanceFeature(IModule module) : base(module) - { - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowInstancesFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowInstancesFeature.cs new file mode 100644 index 0000000000..ec29fe7042 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowInstancesFeature.cs @@ -0,0 +1,29 @@ +using Elsa.Features.Abstractions; +using Elsa.Features.Services; +using Elsa.Workflows.Management.Implementations; +using Elsa.Workflows.Management.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Workflows.Management.Features; + +/// +/// Enables storage of workflow instances. +/// +public class WorkflowInstancesFeature : FeatureBase +{ + /// + public WorkflowInstancesFeature(IModule module) : base(module) + { + } + + /// + /// The factory to create new instances of . + /// + public Func WorkflowInstanceStore { get; set; } = sp => sp.GetRequiredService(); + + /// + public override void Apply() + { + Services.AddSingleton(WorkflowInstanceStore); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs index dd771ba2d5..9fc1782a0e 100644 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs @@ -65,16 +65,6 @@ public WorkflowManagementFeature(IModule module) : base(module) new(typeof(TimeOnly), PrimitivesCategory, "Represents a time of day, as would be read from a clock, within the range 00:00:00 to 23:59:59.9999999.") }; - /// - /// The factory to create new instances of . - /// - public Func WorkflowDefinitionStore { get; set; } = sp => sp.GetRequiredService(); - - /// - /// The factory to create new instances of . - /// - public Func WorkflowInstanceStore { get; set; } = sp => sp.GetRequiredService(); - /// /// Adds the specified activity type to the system. /// @@ -131,8 +121,6 @@ public override void Apply() .AddMemoryStore() .AddMemoryStore() .AddActivityProvider() - .AddSingleton(WorkflowInstanceStore) - .AddSingleton(WorkflowDefinitionStore) .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/samples/aspnet/Elsa.Samples.AzureServiceBusActivities/Elsa.Samples.AzureServiceBusActivities.csproj b/src/samples/aspnet/Elsa.Samples.AzureServiceBusActivities/Elsa.Samples.AzureServiceBusActivities.csproj index 019051efa8..a87e3720ed 100644 --- a/src/samples/aspnet/Elsa.Samples.AzureServiceBusActivities/Elsa.Samples.AzureServiceBusActivities.csproj +++ b/src/samples/aspnet/Elsa.Samples.AzureServiceBusActivities/Elsa.Samples.AzureServiceBusActivities.csproj @@ -17,8 +17,4 @@ - - - - diff --git a/src/samples/aspnet/Elsa.Samples.AzureServiceBusActivities/Program.cs b/src/samples/aspnet/Elsa.Samples.AzureServiceBusActivities/Program.cs index 76fe4e63aa..5da8aa7a82 100644 --- a/src/samples/aspnet/Elsa.Samples.AzureServiceBusActivities/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.AzureServiceBusActivities/Program.cs @@ -12,8 +12,7 @@ // Configure management feature to use EF Core. elsa.UseWorkflowManagement(management => { - management.UseDefaultManagement(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())); - management.UseWorkflowInstances(w => w.UseEntityFrameworkCore(ef => ef.UseSqlite())); + management.UseEntityFrameworkCore(ef => ef.UseSqlite()); }); // Configure runtime feature to use EF Core. diff --git a/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Elsa.Samples.ElasticsearchStorage.csproj b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Elsa.Samples.ElasticsearchStorage.csproj new file mode 100644 index 0000000000..4ab04c8aee --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Elsa.Samples.ElasticsearchStorage.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Pages/Index.cshtml b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Pages/Index.cshtml new file mode 100644 index 0000000000..d76ef7cc96 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Pages/Index.cshtml @@ -0,0 +1,24 @@ +@page +@using Elsa.Workflows.Designer +@using Microsoft.AspNetCore.Mvc.TagHelpers +@{ + var serverUrl = Url.Content("elsa/api"); +} + + + + + + + Elsa Workflows 3.0 + + + + + + + + + + + \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Pages/_ViewImports.cshtml b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..fabca8c884 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Pages/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@namespace Elsa.Samples.AzureServiceBusActivities.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Program.cs b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Program.cs new file mode 100644 index 0000000000..9689f0d1e3 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Program.cs @@ -0,0 +1,71 @@ +using Elsa.Elasticsearch.Extensions; +using Elsa.Elasticsearch.Modules.Management; +using Elsa.Elasticsearch.Modules.Runtime; +using Elsa.EntityFrameworkCore.Extensions; +using Elsa.EntityFrameworkCore.Modules.Management; +using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); +var configuration = builder.Configuration; + +// Add services to the container. +builder.Services.AddElsa(elsa => +{ + // Configure management feature to use EF Core. + elsa.UseWorkflowManagement(management => + { + management.UseWorkflowDefinitions(d => d.UseEntityFrameworkCore(ef => ef.UseSqlite())); + management.UseWorkflowInstances(i => i.UseElasticsearch()); + }); + + // Configure Elasticsearch. + elsa.UseElasticsearch(options => configuration.GetSection("Elasticsearch").Bind(options)); + + // Configure runtime feature to use EF Core. + elsa.UseWorkflowRuntime(runtime => + { + runtime.UseDefaultRuntime(d => d.UseEntityFrameworkCore(ef => ef.UseSqlite())); + runtime.UseExecutionLogRecords(log => log.UseElasticsearch()); + runtime.UseAsyncWorkflowStateExporter(); + }); + + // Expose API endpoints. + elsa.UseWorkflowsApi(api => api.AddFastEndpointsAssembly()); + + // Add services for HTTP activities and workflow middleware. + elsa.UseHttp(); + + // Use JavaScript and Liquid. + elsa.UseJavaScript(); + elsa.UseLiquid(); + + // Configure identity so that we can create a default admin user. + elsa.UseIdentity(identity => + { + identity.IdentityOptions.CreateDefaultAdmin = builder.Environment.IsDevelopment(); + identity.TokenOptions.SigningKey = "secret-token-signing-key"; + identity.TokenOptions.Lifetime = TimeSpan.FromDays(1); + }); + + // Use default authentication (JWT). + elsa.UseDefaultAuthentication(); +}); + +builder.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin())); + +// Add Razor pages. +builder.Services.AddRazorPages(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +app.UseHttpsRedirection(); +app.UseCors(); +app.UseStaticFiles(); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseWorkflowsApi(); +app.UseWorkflows(); +app.MapRazorPages(); +app.Run(); \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Properties/launchSettings.json b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Properties/launchSettings.json new file mode 100644 index 0000000000..1aa401654a --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:24402", + "sslPort": 44305 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5125", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7105;http://localhost:5125", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/appsettings.json b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/appsettings.json new file mode 100644 index 0000000000..cd630d7b02 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.ElasticsearchStorage/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "Elasticsearch": "http://localhost:9200" + }, + "Elasticsearch": { + "Endpoint": "Elasticsearch" + } +} diff --git a/src/samples/aspnet/Elsa.Samples.MassTransitActivities/Program.cs b/src/samples/aspnet/Elsa.Samples.MassTransitActivities/Program.cs index 6be787affc..b896cd6b9d 100644 --- a/src/samples/aspnet/Elsa.Samples.MassTransitActivities/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.MassTransitActivities/Program.cs @@ -14,7 +14,7 @@ // Configure management feature to use EF Core. elsa.UseWorkflowManagement(management => { - management.UseDefaultManagement(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())); + management.UseWorkflowDefinitions(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())); management.UseWorkflowInstances(w => w.UseEntityFrameworkCore(ef => ef.UseSqlite())); }); diff --git a/src/samples/aspnet/Elsa.Samples.TelnyxIntegration/Program.cs b/src/samples/aspnet/Elsa.Samples.TelnyxIntegration/Program.cs index 6f299e6d1d..eee300b04d 100644 --- a/src/samples/aspnet/Elsa.Samples.TelnyxIntegration/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.TelnyxIntegration/Program.cs @@ -32,7 +32,7 @@ .UseWorkflowManagement(management => { management - .UseDefaultManagement(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())) + .UseWorkflowDefinitions(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())) .UseWorkflowInstances(w => w.UseEntityFrameworkCore(ef => ef.UseSqlite())) .AddActivity() .AddActivity() diff --git a/src/samples/aspnet/Elsa.Samples.WorkflowServer/Program.cs b/src/samples/aspnet/Elsa.Samples.WorkflowServer/Program.cs index 71dd1cf71e..64a9deacfb 100644 --- a/src/samples/aspnet/Elsa.Samples.WorkflowServer/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.WorkflowServer/Program.cs @@ -12,7 +12,7 @@ // Configure management feature to use EF Core. elsa.UseWorkflowManagement(management => { - management.UseDefaultManagement(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())); + management.UseWorkflowDefinitions(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())); management.UseWorkflowInstances(w => w.UseEntityFrameworkCore(ef => ef.UseSqlite())); }); diff --git a/src/samples/aspnet/Elsa.Samples.WorkflowServerAndDesigner/Program.cs b/src/samples/aspnet/Elsa.Samples.WorkflowServerAndDesigner/Program.cs index d9be3e1cb1..cc72b06516 100644 --- a/src/samples/aspnet/Elsa.Samples.WorkflowServerAndDesigner/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.WorkflowServerAndDesigner/Program.cs @@ -10,7 +10,7 @@ // Configure management feature to use EF Core. elsa.UseWorkflowManagement(management => { - management.UseDefaultManagement(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())); + management.UseWorkflowDefinitions(dm => dm.UseEntityFrameworkCore(ef => ef.UseSqlite())); management.UseWorkflowInstances(w => w.UseEntityFrameworkCore(ef => ef.UseSqlite())); });