diff --git a/Elsa.sln b/Elsa.sln index eb2e143bd..ebe7084ae 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -63,12 +63,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0A6747 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Testing.Shared", "test\shared\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj", "{E7C689EB-5AD6-4E91-A007-6116EEA702FC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DistributedLocking.SqlServer", "src\providers\Elsa.DistributedLocking.SqlServer\Elsa.DistributedLocking.SqlServer.csproj", "{150EF356-E8BC-4922-91C2-362017479409}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DistributedLocking.Redis", "src\providers\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj", "{EBD49CCA-5400-4C36-B5DF-7B28190AA323}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DistributedLocking.AzureBlob", "src\providers\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj", "{E20238CE-5C64-4F87-8753-3B4E1EC9A740}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "locking", "locking", "{7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Core.IntegrationTests", "test\integration\Elsa.Core.IntegrationTests\Elsa.Core.IntegrationTests.csproj", "{4CB34AB3-01C9-4911-8914-EABAA744EAD3}" @@ -177,6 +171,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.Entity", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.EntityChanged", "src\samples\console\Elsa.Samples.EntityChanged\Elsa.Samples.EntityChanged.csproj", "{A3149FE4-FD39-4DC2-AA9F-208C7216BC03}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.AzureBlob", "src\locking\Elsa.DistributedLocking.AzureBlob\Elsa.DistributedLocking.AzureBlob.csproj", "{C3842132-35BC-4F6D-85CC-7098887F4307}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.Redis", "src\locking\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj", "{C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.SqlServer", "src\locking\Elsa.DistributedLocking.SqlServer\Elsa.DistributedLocking.SqlServer.csproj", "{78F2A9DC-3092-4371-ABFE-87C5DA4DE351}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -239,18 +239,6 @@ Global {E7C689EB-5AD6-4E91-A007-6116EEA702FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {E7C689EB-5AD6-4E91-A007-6116EEA702FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {E7C689EB-5AD6-4E91-A007-6116EEA702FC}.Release|Any CPU.Build.0 = Release|Any CPU - {150EF356-E8BC-4922-91C2-362017479409}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {150EF356-E8BC-4922-91C2-362017479409}.Debug|Any CPU.Build.0 = Debug|Any CPU - {150EF356-E8BC-4922-91C2-362017479409}.Release|Any CPU.ActiveCfg = Release|Any CPU - {150EF356-E8BC-4922-91C2-362017479409}.Release|Any CPU.Build.0 = Release|Any CPU - {EBD49CCA-5400-4C36-B5DF-7B28190AA323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EBD49CCA-5400-4C36-B5DF-7B28190AA323}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EBD49CCA-5400-4C36-B5DF-7B28190AA323}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EBD49CCA-5400-4C36-B5DF-7B28190AA323}.Release|Any CPU.Build.0 = Release|Any CPU - {E20238CE-5C64-4F87-8753-3B4E1EC9A740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E20238CE-5C64-4F87-8753-3B4E1EC9A740}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E20238CE-5C64-4F87-8753-3B4E1EC9A740}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E20238CE-5C64-4F87-8753-3B4E1EC9A740}.Release|Any CPU.Build.0 = Release|Any CPU {4CB34AB3-01C9-4911-8914-EABAA744EAD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4CB34AB3-01C9-4911-8914-EABAA744EAD3}.Debug|Any CPU.Build.0 = Debug|Any CPU {4CB34AB3-01C9-4911-8914-EABAA744EAD3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -423,6 +411,18 @@ Global {A3149FE4-FD39-4DC2-AA9F-208C7216BC03}.Debug|Any CPU.Build.0 = Debug|Any CPU {A3149FE4-FD39-4DC2-AA9F-208C7216BC03}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3149FE4-FD39-4DC2-AA9F-208C7216BC03}.Release|Any CPU.Build.0 = Release|Any CPU + {C3842132-35BC-4F6D-85CC-7098887F4307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3842132-35BC-4F6D-85CC-7098887F4307}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3842132-35BC-4F6D-85CC-7098887F4307}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3842132-35BC-4F6D-85CC-7098887F4307}.Release|Any CPU.Build.0 = Release|Any CPU + {C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2}.Release|Any CPU.Build.0 = Release|Any CPU + {78F2A9DC-3092-4371-ABFE-87C5DA4DE351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78F2A9DC-3092-4371-ABFE-87C5DA4DE351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78F2A9DC-3092-4371-ABFE-87C5DA4DE351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78F2A9DC-3092-4371-ABFE-87C5DA4DE351}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -449,9 +449,6 @@ Global {0A3C36AB-2ACF-4175-A512-66B4C3DB9371} = {AB1AE008-6FD6-414C-8E88-D735F42E1FA6} {0A6747EA-7AC6-4B6F-BC0C-C40C8A80C44D} = {AB1AE008-6FD6-414C-8E88-D735F42E1FA6} {E7C689EB-5AD6-4E91-A007-6116EEA702FC} = {0A6747EA-7AC6-4B6F-BC0C-C40C8A80C44D} - {150EF356-E8BC-4922-91C2-362017479409} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8} - {EBD49CCA-5400-4C36-B5DF-7B28190AA323} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8} - {E20238CE-5C64-4F87-8753-3B4E1EC9A740} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8} {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925} {4CB34AB3-01C9-4911-8914-EABAA744EAD3} = {0A3C36AB-2ACF-4175-A512-66B4C3DB9371} {FC47A50F-03B2-47D8-AAFB-075314FBA186} = {AB1AE008-6FD6-414C-8E88-D735F42E1FA6} @@ -505,6 +502,9 @@ Global {0AA2A003-C79E-4B43-803E-E1150254D2B0} = {E02BD53A-5B08-47A0-920E-DD537A6AFAAA} {A0A8431F-3A2F-433A-BB0D-EB9627DFA739} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180} {A3149FE4-FD39-4DC2-AA9F-208C7216BC03} = {FC9F520F-BA51-4AD2-BFEE-EF787798E734} + {C3842132-35BC-4F6D-85CC-7098887F4307} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8} + {C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8} + {78F2A9DC-3092-4371-ABFE-87C5DA4DE351} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158} diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Services/MessageSenderFactory.cs b/src/activities/Elsa.Activities.AzureServiceBus/Services/MessageSenderFactory.cs index d281a3b77..110d5b7c6 100644 --- a/src/activities/Elsa.Activities.AzureServiceBus/Services/MessageSenderFactory.cs +++ b/src/activities/Elsa.Activities.AzureServiceBus/Services/MessageSenderFactory.cs @@ -25,11 +25,11 @@ namespace Elsa.Activities.AzureServiceBus.Services { await _semaphore.WaitAsync(cancellationToken); - if (_senders.TryGetValue(queueName, out var messageSender)) - return messageSender; - try { + if (_senders.TryGetValue(queueName, out var messageSender)) + return messageSender; + await _managementClient.EnsureQueueExistsAsync(queueName, cancellationToken); var newMessageSender = new MessageSender(_connection, queueName); _senders.Add(queueName, newMessageSender); diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Services/QueueWorker.cs b/src/activities/Elsa.Activities.AzureServiceBus/Services/QueueWorker.cs index 97fdc5d5d..86d71018a 100644 --- a/src/activities/Elsa.Activities.AzureServiceBus/Services/QueueWorker.cs +++ b/src/activities/Elsa.Activities.AzureServiceBus/Services/QueueWorker.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace Elsa.Activities.AzureServiceBus.Services { - public class QueueWorker + public class QueueWorker : IAsyncDisposable { private readonly IMessageReceiver _messageReceiver; private readonly IServiceProvider _serviceProvider; @@ -63,5 +63,10 @@ namespace Elsa.Activities.AzureServiceBus.Services return Task.CompletedTask; } + + public async ValueTask DisposeAsync() + { + await _messageReceiver.CloseAsync(); + } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Data/CollectionNames.cs b/src/core/Elsa.Abstractions/Data/CollectionNames.cs index 19e827dde..f36c4cffb 100644 --- a/src/core/Elsa.Abstractions/Data/CollectionNames.cs +++ b/src/core/Elsa.Abstractions/Data/CollectionNames.cs @@ -4,5 +4,6 @@ { public static string WorkflowDefinitions = "WorkflowDefinitions"; public static string WorkflowInstances = "WorkflowInstances"; + public static string WorkflowExecutionLog = "WorkflowExecutionLog"; } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Models/ExecutionLogEntry.cs b/src/core/Elsa.Abstractions/Models/ExecutionLogEntry.cs deleted file mode 100644 index 4644fc0a7..000000000 --- a/src/core/Elsa.Abstractions/Models/ExecutionLogEntry.cs +++ /dev/null @@ -1,20 +0,0 @@ -using NodaTime; - -namespace Elsa.Models -{ - public class ExecutionLogEntry - { - public ExecutionLogEntry() - { - } - - public ExecutionLogEntry(string activityId, Instant timestamp) - { - ActivityId = activityId; - Timestamp = timestamp; - } - - public string ActivityId { get; set; } = default!; - public Instant Timestamp { get; set; }= default!; - } -} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Models/WorkflowExecutionLogRecord.cs b/src/core/Elsa.Abstractions/Models/WorkflowExecutionLogRecord.cs new file mode 100644 index 000000000..4b2ea4804 --- /dev/null +++ b/src/core/Elsa.Abstractions/Models/WorkflowExecutionLogRecord.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json.Linq; +using NodaTime; + +namespace Elsa.Models +{ + public class WorkflowExecutionLogRecord : Entity, ITenantScope + { + public WorkflowExecutionLogRecord() + { + } + + public WorkflowExecutionLogRecord(string id, string? tenantId, string workflowInstanceId, string activityId, Instant timestamp, string? message, JObject? data = default) + { + Id = id; + TenantId = tenantId; + WorkflowInstanceId = workflowInstanceId; + ActivityId = activityId; + Timestamp = timestamp; + Message = message; + Data = data; + } + + public string? TenantId { get; } + public string WorkflowInstanceId { get; set; } = default!; + public string ActivityId { get; set; } = default!; + public Instant Timestamp { get; set; }= default!; + public string? Message { get; set; } + public JObject? Data { get; set; } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Persistence/IWorkflowExecutionLogStore.cs b/src/core/Elsa.Abstractions/Persistence/IWorkflowExecutionLogStore.cs new file mode 100644 index 000000000..a71df5af5 --- /dev/null +++ b/src/core/Elsa.Abstractions/Persistence/IWorkflowExecutionLogStore.cs @@ -0,0 +1,8 @@ +using Elsa.Models; + +namespace Elsa.Persistence +{ + public interface IWorkflowExecutionLogStore : IStore + { + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs index e66e5c26c..e7bd00c61 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs +++ b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs @@ -70,8 +70,7 @@ namespace Elsa.Services.Models } public bool DeleteCompletedInstances => WorkflowBlueprint.DeleteCompletedInstances; - //public ICollection ExecutionLog => WorkflowInstance.ExecutionLog; - public ICollection ExecutionLog => new List(); + public ICollection ExecutionLog => new List(); public WorkflowStatus Status => WorkflowInstance.WorkflowStatus; public bool HasBlockingActivities => WorkflowInstance.BlockingActivities.Any(); public object? WorkflowContext { get; set; } diff --git a/src/core/Elsa.Core/ActivityResults/GoBackResult.cs b/src/core/Elsa.Core/ActivityResults/GoBackResult.cs index 15b7f2059..cd89bcb29 100644 --- a/src/core/Elsa.Core/ActivityResults/GoBackResult.cs +++ b/src/core/Elsa.Core/ActivityResults/GoBackResult.cs @@ -25,7 +25,7 @@ namespace Elsa.ActivityResults if (previousEntry == null) return; - var activityId = previousEntry.ActivityId; + var activityId = previousEntry; workflowExecutionContext.ScheduleActivity(activityId, _input); } } diff --git a/src/core/Elsa.Core/ElsaOptions.cs b/src/core/Elsa.Core/ElsaOptions.cs index 91000fb08..ecdd6243e 100644 --- a/src/core/Elsa.Core/ElsaOptions.cs +++ b/src/core/Elsa.Core/ElsaOptions.cs @@ -25,6 +25,7 @@ namespace Elsa WorkflowDefinitionStoreFactory = sp => ActivatorUtilities.CreateInstance(sp); WorkflowInstanceStoreFactory = sp => ActivatorUtilities.CreateInstance(sp); + WorkflowExecutionLogStoreFactory = sp => ActivatorUtilities.CreateInstance(sp); StorageFactory = sp => Storage.Net.StorageFactory.Blobs.InMemory(); DistributedLockProviderFactory = sp => new DefaultLockProvider(); SignalFactory = sp => new Signal(); @@ -54,6 +55,7 @@ namespace Elsa internal Func StorageFactory { get; set; } internal Func WorkflowDefinitionStoreFactory { get; set; } internal Func WorkflowInstanceStoreFactory { get; set; } + internal Func WorkflowExecutionLogStoreFactory { get; set; } internal Func DistributedLockProviderFactory { get; private set; } internal Func SignalFactory { get; private set; } internal Func CreateJsonSerializer { get; private set; } @@ -92,6 +94,12 @@ namespace Elsa WorkflowInstanceStoreFactory = factory; return this; } + + public ElsaOptions UseWorkflowExecutionLogStore(Func factory) + { + WorkflowExecutionLogStoreFactory = factory; + return this; + } public ElsaOptions UseAutoMapper(Action addAutoMapper) { diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 911d87fdf..23ffd27fe 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -43,6 +43,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddSingleton(options) .AddScoped(options.WorkflowDefinitionStoreFactory) .AddScoped(options.WorkflowInstanceStoreFactory) + .AddScoped(options.WorkflowExecutionLogStoreFactory) .AddSingleton(options.DistributedLockProviderFactory) .AddSingleton(options.SignalFactory) .AddSingleton(options.StorageFactory); diff --git a/src/core/Elsa.Core/Handlers/AppendExecutionLog.cs b/src/core/Elsa.Core/Handlers/AppendExecutionLog.cs deleted file mode 100644 index dd4e31417..000000000 --- a/src/core/Elsa.Core/Handlers/AppendExecutionLog.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Elsa.Events; -using Elsa.Models; -using MediatR; -using NodaTime; - -namespace Elsa.Handlers -{ - public class AppendExecutionLog : INotificationHandler - { - private readonly IClock _clock; - public AppendExecutionLog(IClock clock) => _clock = clock; - - public Task Handle(ActivityExecuted notification, CancellationToken cancellationToken) - { - notification.WorkflowExecutionContext.ExecutionLog.Add(new ExecutionLogEntry(notification.Activity.Id, _clock.GetCurrentInstant())); - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/src/core/Elsa.Core/Handlers/PersistWorkflow.cs b/src/core/Elsa.Core/Handlers/PersistWorkflow.cs index d0db4dfae..ab4aea77b 100644 --- a/src/core/Elsa.Core/Handlers/PersistWorkflow.cs +++ b/src/core/Elsa.Core/Handlers/PersistWorkflow.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Elsa.Events; using Elsa.Models; using Elsa.Persistence; -using Elsa.Services; using Elsa.Services.Models; using MediatR; using Microsoft.Extensions.Logging; diff --git a/src/core/Elsa.Core/Handlers/WriteWorkflowExecutionLog.cs b/src/core/Elsa.Core/Handlers/WriteWorkflowExecutionLog.cs new file mode 100644 index 000000000..ed94f1d25 --- /dev/null +++ b/src/core/Elsa.Core/Handlers/WriteWorkflowExecutionLog.cs @@ -0,0 +1,51 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Events; +using Elsa.Models; +using Elsa.Persistence; +using Elsa.Services; +using MediatR; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NodaTime; + +namespace Elsa.Handlers +{ + public class WriteWorkflowExecutionLog : INotificationHandler + { + private readonly IWorkflowExecutionLogStore _store; + private readonly IIdGenerator _idGenerator; + private readonly IClock _clock; + private readonly JsonSerializer _jsonSerializer; + + public WriteWorkflowExecutionLog(IWorkflowExecutionLogStore store, IIdGenerator idGenerator, IClock clock, JsonSerializer jsonSerializer) + { + _store = store; + _idGenerator = idGenerator; + _clock = clock; + _jsonSerializer = jsonSerializer; + } + + public async Task Handle(ActivityExecuted notification, CancellationToken cancellationToken) + { + var workflowInstance = notification.WorkflowExecutionContext.WorkflowInstance; + var id = _idGenerator.Generate(); + var tenantId = workflowInstance.TenantId; + var workflowInstanceId = workflowInstance.Id; + var activityId = notification.Activity.Id; + var timeStamp = _clock.GetCurrentInstant(); + const string message = "Activity Executed"; + + var state = new + { + notification.ActivityExecutionContext.Output, + notification.ActivityExecutionContext.Data + }; + + var logData = JObject.FromObject(state, _jsonSerializer); + var record = new WorkflowExecutionLogRecord(id, tenantId, workflowInstanceId, activityId, timeStamp, message, logData); + + await _store.SaveAsync(record, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowDefinitionStore.cs b/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowDefinitionStore.cs index f9936083c..934f9ae0b 100644 --- a/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowDefinitionStore.cs +++ b/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowDefinitionStore.cs @@ -1,7 +1,3 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Elsa.Models; using Elsa.Services; @@ -12,23 +8,5 @@ namespace Elsa.Persistence.InMemory public InMemoryWorkflowDefinitionStore(IIdGenerator idGenerator) : base(idGenerator) { } - - public Task CountAsync(VersionOptions? version = null, CancellationToken cancellationToken = default) => Task.FromResult(Entities.Values.WithVersion(version).Count()); - - public Task GetAsync(string workflowDefinitionId, VersionOptions version, CancellationToken cancellationToken = default) => - Task.FromResult(Entities.Values.WithVersion(version).FirstOrDefault(x => x.Id == workflowDefinitionId)); - - public Task GetByVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default) => - Task.FromResult(Entities.Values.FirstOrDefault(x => x.DefinitionVersionId == workflowDefinitionVersionId)); - - public Task> ListAsync(int? skip = null, int? take = null, VersionOptions? version = null, CancellationToken cancellationToken = default) - { - var query = Entities.Values.WithVersion(); - - if (skip.HasValue) query = query.Skip(skip.Value); - if (take.HasValue) query = query.Take(take.Value); - - return Task.FromResult(query); - } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowExecutionLogStore.cs b/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowExecutionLogStore.cs new file mode 100644 index 000000000..98c26a9c2 --- /dev/null +++ b/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowExecutionLogStore.cs @@ -0,0 +1,12 @@ +using Elsa.Models; +using Elsa.Services; + +namespace Elsa.Persistence.InMemory +{ + public class InMemoryWorkflowExecutionLogStore : InMemoryStore, IWorkflowExecutionLogStore + { + public InMemoryWorkflowExecutionLogStore(IIdGenerator idGenerator) : base(idGenerator) + { + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowInstanceStore.cs b/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowInstanceStore.cs index 1ca45d822..701276ef4 100644 --- a/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowInstanceStore.cs +++ b/src/core/Elsa.Core/Persistence/InMemory/InMemoryWorkflowInstanceStore.cs @@ -1,7 +1,3 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Elsa.Models; using Elsa.Services; @@ -12,30 +8,5 @@ namespace Elsa.Persistence.InMemory public InMemoryWorkflowInstanceStore(IIdGenerator idGenerator) : base(idGenerator) { } - - public Task GetByCorrelationIdAsync(string correlationId, WorkflowStatus status, CancellationToken cancellationToken = default) => - Task.FromResult(Entities.Values.FirstOrDefault(instance => instance.CorrelationId == correlationId && instance.WorkflowStatus == status))!; - - public Task GetByIdAsync(string workflowInstanceId, CancellationToken cancellationToken = default) => - Task.FromResult(Entities.Values - .FirstOrDefault(instance => instance.Id == workflowInstanceId))!; - - public Task> ListAsync(int page, int pageSize, CancellationToken cancellationToken = default) => Task.FromResult(Entities.Values.Skip(page * pageSize).Take(pageSize)); - - public Task> ListByBlockingActivityTypeAsync(string activityType, CancellationToken cancellationToken = default) => - Task.FromResult(Entities.Values - .Where(instance => instance.BlockingActivities.Any(a => a.ActivityType == activityType))); - - public Task> ListByCorrelationIdAsync(string correlationId, WorkflowStatus status, CancellationToken cancellationToken = default) => - Task.FromResult(Entities.Values.Where(instance => instance.CorrelationId == correlationId && instance.WorkflowStatus == status)); - - public Task> ListByDefinitionAndStatusAsync(string workflowDefinitionId, string tenantId, WorkflowStatus workflowStatus, CancellationToken cancellationToken = default) => - Task.FromResult(Entities.Values - .Where(instance => instance.DefinitionId == workflowDefinitionId && instance.WorkflowStatus == workflowStatus && instance.TenantId == tenantId)); - - public Task> ListByDefinitionAsync(string workflowDefinitionId, string tenantId, CancellationToken cancellationToken = default) => - Task.FromResult(Entities.Values.Where(x => x.DefinitionId == workflowDefinitionId && x.TenantId == tenantId)); - - public Task> ListByStatusAsync(WorkflowStatus workflowStatus, CancellationToken cancellationToken = default) => Task.FromResult(Entities.Values.Where(instance => instance.WorkflowStatus == workflowStatus)); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/WorkflowRunner.cs b/src/core/Elsa.Core/Services/WorkflowRunner.cs index ccdaae727..22cda1f96 100644 --- a/src/core/Elsa.Core/Services/WorkflowRunner.cs +++ b/src/core/Elsa.Core/Services/WorkflowRunner.cs @@ -259,6 +259,7 @@ namespace Elsa.Services } catch (Exception e) { + _logger.LogError(e, e.Message); workflowExecutionContext.Fault(null, new LocalizedString(e.Message, e.Message)); } } @@ -279,6 +280,7 @@ namespace Elsa.Services await _mediator.Publish(new ActivityExecuting(activityExecutionContext), cancellationToken); await result.ExecuteAsync(activityExecutionContext, cancellationToken); workflowExecutionContext.WorkflowInstance.Output = activityExecutionContext.Output; + workflowExecutionContext.ExecutionLog.Add(activity.Id); await _mediator.Publish(new ActivityExecuted(activityExecutionContext), cancellationToken); activityOperation = Execute; diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/AzureBlobLockProvider.cs b/src/locking/Elsa.DistributedLocking.AzureBlob/AzureBlobLockProvider.cs similarity index 100% rename from src/providers/Elsa.DistributedLocking.AzureBlob/AzureBlobLockProvider.cs rename to src/locking/Elsa.DistributedLocking.AzureBlob/AzureBlobLockProvider.cs diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/Elsa.DistributedLocking.AzureBlob.csproj b/src/locking/Elsa.DistributedLocking.AzureBlob/Elsa.DistributedLocking.AzureBlob.csproj similarity index 100% rename from src/providers/Elsa.DistributedLocking.AzureBlob/Elsa.DistributedLocking.AzureBlob.csproj rename to src/locking/Elsa.DistributedLocking.AzureBlob/Elsa.DistributedLocking.AzureBlob.csproj diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/ElsaOptionsExtensions.cs b/src/locking/Elsa.DistributedLocking.AzureBlob/ElsaOptionsExtensions.cs similarity index 100% rename from src/providers/Elsa.DistributedLocking.AzureBlob/ElsaOptionsExtensions.cs rename to src/locking/Elsa.DistributedLocking.AzureBlob/ElsaOptionsExtensions.cs diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/FodyWeavers.xml b/src/locking/Elsa.DistributedLocking.AzureBlob/FodyWeavers.xml similarity index 100% rename from src/providers/Elsa.DistributedLocking.AzureBlob/FodyWeavers.xml rename to src/locking/Elsa.DistributedLocking.AzureBlob/FodyWeavers.xml diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/LockedBlob.cs b/src/locking/Elsa.DistributedLocking.AzureBlob/LockedBlob.cs similarity index 100% rename from src/providers/Elsa.DistributedLocking.AzureBlob/LockedBlob.cs rename to src/locking/Elsa.DistributedLocking.AzureBlob/LockedBlob.cs diff --git a/src/providers/Elsa.DistributedLocking.Redis/Elsa.DistributedLocking.Redis.csproj b/src/locking/Elsa.DistributedLocking.Redis/Elsa.DistributedLocking.Redis.csproj similarity index 100% rename from src/providers/Elsa.DistributedLocking.Redis/Elsa.DistributedLocking.Redis.csproj rename to src/locking/Elsa.DistributedLocking.Redis/Elsa.DistributedLocking.Redis.csproj diff --git a/src/providers/Elsa.DistributedLocking.Redis/ElsaOptionsExtensions.cs b/src/locking/Elsa.DistributedLocking.Redis/ElsaOptionsExtensions.cs similarity index 100% rename from src/providers/Elsa.DistributedLocking.Redis/ElsaOptionsExtensions.cs rename to src/locking/Elsa.DistributedLocking.Redis/ElsaOptionsExtensions.cs diff --git a/src/providers/Elsa.DistributedLocking.Redis/FodyWeavers.xml b/src/locking/Elsa.DistributedLocking.Redis/FodyWeavers.xml similarity index 100% rename from src/providers/Elsa.DistributedLocking.Redis/FodyWeavers.xml rename to src/locking/Elsa.DistributedLocking.Redis/FodyWeavers.xml diff --git a/src/providers/Elsa.DistributedLocking.Redis/RedisLockProvider.cs b/src/locking/Elsa.DistributedLocking.Redis/RedisLockProvider.cs similarity index 100% rename from src/providers/Elsa.DistributedLocking.Redis/RedisLockProvider.cs rename to src/locking/Elsa.DistributedLocking.Redis/RedisLockProvider.cs diff --git a/src/providers/Elsa.DistributedLocking.SqlServer/Elsa.DistributedLocking.SqlServer.csproj b/src/locking/Elsa.DistributedLocking.SqlServer/Elsa.DistributedLocking.SqlServer.csproj similarity index 100% rename from src/providers/Elsa.DistributedLocking.SqlServer/Elsa.DistributedLocking.SqlServer.csproj rename to src/locking/Elsa.DistributedLocking.SqlServer/Elsa.DistributedLocking.SqlServer.csproj diff --git a/src/providers/Elsa.DistributedLocking.SqlServer/ElsaOptionsExtensions.cs b/src/locking/Elsa.DistributedLocking.SqlServer/ElsaOptionsExtensions.cs similarity index 100% rename from src/providers/Elsa.DistributedLocking.SqlServer/ElsaOptionsExtensions.cs rename to src/locking/Elsa.DistributedLocking.SqlServer/ElsaOptionsExtensions.cs diff --git a/src/providers/Elsa.DistributedLocking.SqlServer/FodyWeavers.xml b/src/locking/Elsa.DistributedLocking.SqlServer/FodyWeavers.xml similarity index 100% rename from src/providers/Elsa.DistributedLocking.SqlServer/FodyWeavers.xml rename to src/locking/Elsa.DistributedLocking.SqlServer/FodyWeavers.xml diff --git a/src/providers/Elsa.DistributedLocking.SqlServer/SqlLockProvider.cs b/src/locking/Elsa.DistributedLocking.SqlServer/SqlLockProvider.cs similarity index 100% rename from src/providers/Elsa.DistributedLocking.SqlServer/SqlLockProvider.cs rename to src/locking/Elsa.DistributedLocking.SqlServer/SqlLockProvider.cs diff --git a/src/persistence/Elsa.Persistence.MongoDb/Extensions/ServiceCollectionExtensions.cs b/src/persistence/Elsa.Persistence.MongoDb/Extensions/ServiceCollectionExtensions.cs index 78e86d261..a137a58a3 100644 --- a/src/persistence/Elsa.Persistence.MongoDb/Extensions/ServiceCollectionExtensions.cs +++ b/src/persistence/Elsa.Persistence.MongoDb/Extensions/ServiceCollectionExtensions.cs @@ -32,14 +32,17 @@ namespace Elsa.Persistence.MongoDb.Extensions elsa.Services .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(sp => sp.GetRequiredService().WorkflowDefinitions) .AddSingleton(sp => sp.GetRequiredService().WorkflowInstances) + .AddSingleton(sp => sp.GetRequiredService().WorkflowExecutionLog) .AddStartupTask(); elsa .UseWorkflowDefinitionStore(sp => sp.GetRequiredService()) - .UseWorkflowInstanceStore(sp => sp.GetRequiredService()); + .UseWorkflowInstanceStore(sp => sp.GetRequiredService()) + .UseWorkflowExecutionLogStore(sp => sp.GetRequiredService()); DatabaseRegister.RegisterMapsAndSerializers(); } diff --git a/src/persistence/Elsa.Persistence.MongoDb/Services/DatabaseInitializer.cs b/src/persistence/Elsa.Persistence.MongoDb/Services/DatabaseInitializer.cs index 6692bd1b1..c9e5eb541 100644 --- a/src/persistence/Elsa.Persistence.MongoDb/Services/DatabaseInitializer.cs +++ b/src/persistence/Elsa.Persistence.MongoDb/Services/DatabaseInitializer.cs @@ -20,6 +20,7 @@ namespace Elsa.Persistence.MongoDb.Services { await CreateWorkflowInstancesIndexes(cancellationToken); await CreateWorkflowDefinitionsIndexes(cancellationToken); + await CreateWorkflowExecutionLogIndexes(cancellationToken); } private async Task CreateWorkflowInstancesIndexes(CancellationToken cancellationToken) @@ -40,6 +41,15 @@ namespace Elsa.Persistence.MongoDb.Services await CreateIndexesAsync(_mongoClient.WorkflowDefinitions, cancellationToken, tenantKeysDefinition, definitionVersionIdKeysDefinition, versionKeysDefinition, nameKeysDefinition); } + + private async Task CreateWorkflowExecutionLogIndexes(CancellationToken cancellationToken) + { + var tenantKeysDefinition = Builders.IndexKeys.Ascending(x => x.TenantId); + var workflowInstanceIdKeysDefinition = Builders.IndexKeys.Ascending(x => x.WorkflowInstanceId); + var timestampKeysDefinition = Builders.IndexKeys.Ascending(x => x.Timestamp); + + await CreateIndexesAsync(_mongoClient.WorkflowExecutionLog, cancellationToken, tenantKeysDefinition, workflowInstanceIdKeysDefinition, timestampKeysDefinition); + } private async Task CreateIndexesAsync(IMongoCollection collection, CancellationToken cancellationToken, params IndexKeysDefinition[] definitions) { diff --git a/src/persistence/Elsa.Persistence.MongoDb/Services/ElsaMongoDbClient.cs b/src/persistence/Elsa.Persistence.MongoDb/Services/ElsaMongoDbClient.cs index 89172c1d3..294acc514 100644 --- a/src/persistence/Elsa.Persistence.MongoDb/Services/ElsaMongoDbClient.cs +++ b/src/persistence/Elsa.Persistence.MongoDb/Services/ElsaMongoDbClient.cs @@ -18,5 +18,6 @@ namespace Elsa.Persistence.MongoDb.Services public IMongoCollection WorkflowDefinitions => _mongoDatabase.GetCollection(CollectionNames.WorkflowDefinitions); public IMongoCollection WorkflowInstances => _mongoDatabase.GetCollection(CollectionNames.WorkflowInstances); + public IMongoCollection WorkflowExecutionLog => _mongoDatabase.GetCollection(CollectionNames.WorkflowExecutionLog); } } diff --git a/src/persistence/Elsa.Persistence.MongoDb/Stores/MongoDbWorkflowExecutionLogStore.cs b/src/persistence/Elsa.Persistence.MongoDb/Stores/MongoDbWorkflowExecutionLogStore.cs new file mode 100644 index 000000000..002a10b29 --- /dev/null +++ b/src/persistence/Elsa.Persistence.MongoDb/Stores/MongoDbWorkflowExecutionLogStore.cs @@ -0,0 +1,13 @@ +using Elsa.Models; +using Elsa.Services; +using MongoDB.Driver; + +namespace Elsa.Persistence.MongoDb.Stores +{ + public class MongoDbWorkflowExecutionLogStore : MongoDbStore, IWorkflowExecutionLogStore + { + public MongoDbWorkflowExecutionLogStore(IMongoCollection collection, IIdGenerator idGenerator) : base(collection, idGenerator) + { + } + } +} \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowDefinitionDocument.cs b/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowDefinitionDocument.cs index 6e863af6b..a3d3281be 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowDefinitionDocument.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowDefinitionDocument.cs @@ -6,7 +6,6 @@ namespace Elsa.Persistence.YesSql.Documents public class WorkflowDefinitionDocument : YesSqlDocument { public string DefinitionId { get; set; } = default!; - public string DefinitionVersionId { get; set; } = default!; public string? TenantId { get; set; } public string? Name { get; set; } diff --git a/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowExecutionLogRecordDocument.cs b/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowExecutionLogRecordDocument.cs new file mode 100644 index 000000000..8c50bf61a --- /dev/null +++ b/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowExecutionLogRecordDocument.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json.Linq; +using NodaTime; + +namespace Elsa.Persistence.YesSql.Documents +{ + public class WorkflowExecutionLogRecordDocument : YesSqlDocument + { + public string RecordId { get; } = default!; + public string? TenantId { get; } + public string WorkflowInstanceId { get; set; } = default!; + public string ActivityId { get; set; } = default!; + public Instant Timestamp { get; set; }= default!; + public string? Message { get; set; } + public JObject? Data { get; set; } + } +} \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowInstanceDocument.cs b/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowInstanceDocument.cs index ef390deee..4280e638e 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowInstanceDocument.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowInstanceDocument.cs @@ -32,7 +32,7 @@ namespace Elsa.Persistence.YesSql.Documents set => _blockingActivities = new HashSet(value, BlockingActivityEqualityComparer.Instance); } - public ICollection ExecutionLog { get; set; } = new List(); + public ICollection ExecutionLog { get; set; } = new List(); public WorkflowFault? Fault { get; set; } public Stack ScheduledActivities { get; set; } = new(); public Stack PostScheduledActivities { get; set; } = new(); diff --git a/src/persistence/Elsa.Persistence.YesSql/Extensions/ServiceCollectionExtensions.cs b/src/persistence/Elsa.Persistence.YesSql/Extensions/ServiceCollectionExtensions.cs index 63fe191b0..17a4f3377 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Extensions/ServiceCollectionExtensions.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Extensions/ServiceCollectionExtensions.cs @@ -3,7 +3,6 @@ using System.Data; using System.Linq; using Elsa.Data; using Elsa.Persistence.YesSql.Data; -using Elsa.Persistence.YesSql.Documents; using Elsa.Persistence.YesSql.Indexes; using Elsa.Persistence.YesSql.Mapping; using Elsa.Persistence.YesSql.Services; @@ -11,7 +10,6 @@ using Elsa.Persistence.YesSql.Stores; using Elsa.Runtime; using Microsoft.Extensions.DependencyInjection; using YesSql; -using YesSql.Data; using YesSql.Indexes; using YesSql.Provider.Sqlite; @@ -27,6 +25,7 @@ namespace Elsa.Persistence.YesSql.Extensions elsa.Services .AddScoped() .AddScoped() + .AddScoped() .AddSingleton(sp => CreateStore(sp, configure)) .AddScoped(CreateSession) .AddScoped() @@ -35,11 +34,13 @@ namespace Elsa.Persistence.YesSql.Extensions .AddDataMigration() .AddAutoMapperProfile() .AddIndexProvider() - .AddIndexProvider(); + .AddIndexProvider() + .AddIndexProvider(); return elsa .UseWorkflowDefinitionStore(sp => sp.GetRequiredService()) - .UseWorkflowInstanceStore(sp => sp.GetRequiredService()); + .UseWorkflowInstanceStore(sp => sp.GetRequiredService()) + .UseWorkflowExecutionLogStore(sp => sp.GetRequiredService()); } public static IServiceCollection AddIndexProvider(this IServiceCollection services) where T : class, IIndexProvider => services.AddSingleton(); diff --git a/src/persistence/Elsa.Persistence.YesSql/Indexes/WorkflowDefinitionIndex.cs b/src/persistence/Elsa.Persistence.YesSql/Indexes/WorkflowDefinitionIndex.cs index 4018878af..0ba962d01 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Indexes/WorkflowDefinitionIndex.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Indexes/WorkflowDefinitionIndex.cs @@ -1,5 +1,4 @@ using Elsa.Data; -using Elsa.Models; using Elsa.Persistence.YesSql.Documents; using YesSql.Indexes; @@ -8,7 +7,7 @@ namespace Elsa.Persistence.YesSql.Indexes public class WorkflowDefinitionIndex : MapIndex { public string? TenantId { get; set; } - public string EntityId { get; set; } = default!; + public string DefinitionId { get; set; } = default!; public string DefinitionVersionId { get; set; } = default!; public int Version { get; set; } public bool IsLatest { get; set; } @@ -26,7 +25,7 @@ namespace Elsa.Persistence.YesSql.Indexes .Map( workflowDefinition => new WorkflowDefinitionIndex { - EntityId = workflowDefinition.DefinitionId, + DefinitionId = workflowDefinition.DefinitionId, TenantId = workflowDefinition.TenantId, DefinitionVersionId = workflowDefinition.DefinitionVersionId, Version = workflowDefinition.Version, diff --git a/src/persistence/Elsa.Persistence.YesSql/Indexes/WorkflowExecutionLogRecordIndex.cs b/src/persistence/Elsa.Persistence.YesSql/Indexes/WorkflowExecutionLogRecordIndex.cs new file mode 100644 index 000000000..1da44d023 --- /dev/null +++ b/src/persistence/Elsa.Persistence.YesSql/Indexes/WorkflowExecutionLogRecordIndex.cs @@ -0,0 +1,34 @@ +using System; +using Elsa.Data; +using Elsa.Persistence.YesSql.Documents; +using YesSql.Indexes; + +namespace Elsa.Persistence.YesSql.Indexes +{ + public class WorkflowExecutionLogRecordIndex : MapIndex + { + public string? TenantId { get; set; } + public string RecordId { get; set; } = default!; + public string WorkflowInstanceId { get; set; } = default!; + public DateTime Timestamp { get; set; } + } + + public class WorkflowExecutionLogRecordIndexProvider : IndexProvider + { + public WorkflowExecutionLogRecordIndexProvider() => CollectionName = CollectionNames.WorkflowExecutionLog; + + public override void Describe(DescribeContext context) + { + context.For() + .Map( + record => new WorkflowExecutionLogRecordIndex + { + RecordId = record.RecordId, + TenantId = record.TenantId, + WorkflowInstanceId = record.WorkflowInstanceId, + Timestamp = record.Timestamp.ToDateTimeUtc() + } + ); + } + } +} \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.YesSql/Mapping/AutoMapperProfile.cs b/src/persistence/Elsa.Persistence.YesSql/Mapping/AutoMapperProfile.cs index de48ac3f9..d26d6bf51 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Mapping/AutoMapperProfile.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Mapping/AutoMapperProfile.cs @@ -19,6 +19,12 @@ namespace Elsa.Persistence.YesSql.Mapping .ForMember(d => d.Id, d => d.Ignore()) .ReverseMap() .ForMember(d => d.Id, d => d.MapFrom(s => s.DefinitionId)); + + CreateMap() + .ForMember(d => d.RecordId, d => d.MapFrom(s => s.Id)) + .ForMember(d => d.Id, d => d.Ignore()) + .ReverseMap() + .ForMember(d => d.Id, d => d.MapFrom(s => s.RecordId)); } } } \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.YesSql/Migrations.cs b/src/persistence/Elsa.Persistence.YesSql/Migrations.cs index f0d108e33..24069567b 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Migrations.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Migrations.cs @@ -13,7 +13,7 @@ namespace Elsa.Persistence.YesSql SchemaBuilder.CreateMapIndexTable( table => table .Column(nameof(WorkflowDefinitionIndex.TenantId)) - .Column(nameof(WorkflowDefinitionIndex.EntityId)) + .Column(nameof(WorkflowDefinitionIndex.DefinitionId)) .Column(nameof(WorkflowDefinitionIndex.DefinitionVersionId)) .Column(nameof(WorkflowDefinitionIndex.Version)) .Column(nameof(WorkflowDefinitionIndex.IsLatest)) @@ -40,13 +40,21 @@ namespace Elsa.Persistence.YesSql SchemaBuilder.CreateMapIndexTable( table => table - .Column("TenantId") - .Column("ActivityId") - .Column("ActivityType") - .Column("CorrelationId") - .Column("WorkflowStatus", DbType.String) - .Column("CreatedAt", DbType.DateTimeOffset), + .Column(nameof(WorkflowInstanceBlockingActivitiesIndex.TenantId)) + .Column(nameof(WorkflowInstanceBlockingActivitiesIndex.ActivityId)) + .Column(nameof(WorkflowInstanceBlockingActivitiesIndex.ActivityType)) + .Column(nameof(WorkflowInstanceBlockingActivitiesIndex.CorrelationId)) + .Column(nameof(WorkflowInstanceBlockingActivitiesIndex.WorkflowStatus), DbType.String) + .Column(nameof(WorkflowInstanceBlockingActivitiesIndex.CreatedAt), DbType.DateTimeOffset), CollectionNames.WorkflowInstances); + + SchemaBuilder.CreateMapIndexTable( + table => table + .Column(nameof(WorkflowExecutionLogRecordIndex.RecordId)) + .Column(nameof(WorkflowExecutionLogRecordIndex.TenantId)) + .Column(nameof(WorkflowExecutionLogRecordIndex.WorkflowInstanceId)) + .Column(nameof(WorkflowExecutionLogRecordIndex.Timestamp), DbType.DateTimeOffset), + CollectionNames.WorkflowExecutionLog); return 1; } diff --git a/src/persistence/Elsa.Persistence.YesSql/Services/DatabaseInitializer.cs b/src/persistence/Elsa.Persistence.YesSql/Services/DatabaseInitializer.cs index a08b303c6..8287d81f9 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Services/DatabaseInitializer.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Services/DatabaseInitializer.cs @@ -1,9 +1,7 @@ using System.Threading; using System.Threading.Tasks; - using Elsa.Data; using Elsa.Services; - using YesSql; namespace Elsa.Persistence.YesSql.Services @@ -21,6 +19,7 @@ namespace Elsa.Persistence.YesSql.Services { await _store.InitializeCollectionAsync(CollectionNames.WorkflowDefinitions); await _store.InitializeCollectionAsync(CollectionNames.WorkflowInstances); + await _store.InitializeCollectionAsync(CollectionNames.WorkflowExecutionLog); } } } diff --git a/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlStore.cs b/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlStore.cs index a77125aab..c57b62d4f 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlStore.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlStore.cs @@ -16,6 +16,8 @@ namespace Elsa.Persistence.YesSql.Stores { public abstract class YesSqlStore : IStore where T : class, IEntity where TDocument : class { + private readonly SemaphoreSlim _semaphore = new(1); + public YesSqlStore(ISession session, IIdGenerator idGenerator, IMapper mapper, string collectionName) { IdGenerator = idGenerator; @@ -31,10 +33,19 @@ namespace Elsa.Persistence.YesSql.Stores public async Task SaveAsync(T entity, CancellationToken cancellationToken = default) { - var existingDocument = await FindDocumentAsync(entity, cancellationToken); - var document = Mapper.Map(entity, existingDocument); - Session.Save(document, CollectionName); - await Session.CommitAsync(); + await _semaphore.WaitAsync(cancellationToken); + + try + { + var existingDocument = await FindDocumentAsync(entity, cancellationToken); + var document = Mapper.Map(entity, existingDocument); + Session.Save(document, CollectionName); + await Session.CommitAsync(); + } + finally + { + _semaphore.Release(); + } } public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) @@ -68,14 +79,14 @@ namespace Elsa.Persistence.YesSql.Stores var documents = await Query(specification, orderBy, paging, cancellationToken).ListAsync(); return Map(documents); } - + protected virtual IQuery Query(ISpecification specification, IOrderBy? orderBy = default, IPaging? paging = default, CancellationToken cancellationToken = default) { var query = ToQuery(specification); - if (orderBy != null) + if (orderBy != null) query = OrderBy(query, orderBy, specification); - + if (paging != null) query = query.Skip(paging.Skip).Take(paging.Take); diff --git a/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlWorkflowDefinitionStore.cs b/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlWorkflowDefinitionStore.cs index 5948c166b..34363c448 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlWorkflowDefinitionStore.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlWorkflowDefinitionStore.cs @@ -17,12 +17,12 @@ namespace Elsa.Persistence.YesSql.Stores { } - protected override async Task FindDocumentAsync(WorkflowDefinition entity, CancellationToken cancellationToken) => await Query(x => x.EntityId == entity.Id).FirstOrDefaultAsync(); + protected override async Task FindDocumentAsync(WorkflowDefinition entity, CancellationToken cancellationToken) => await Query(x => x.DefinitionId == entity.Id).FirstOrDefaultAsync(); protected override IQuery MapSpecification(ISpecification specification) { if (specification is EntityIdSpecification entityIdSpecification) - return Query(x => x.EntityId == entityIdSpecification.Id); + return Query(x => x.DefinitionId == entityIdSpecification.Id); return AutoMapSpecification(specification); } diff --git a/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlWorkflowExecutionLogStore.cs b/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlWorkflowExecutionLogStore.cs new file mode 100644 index 000000000..f3783af66 --- /dev/null +++ b/src/persistence/Elsa.Persistence.YesSql/Stores/YesSqlWorkflowExecutionLogStore.cs @@ -0,0 +1,23 @@ +using System.Threading; +using System.Threading.Tasks; +using AutoMapper; +using Elsa.Data; +using Elsa.Models; +using Elsa.Persistence.Specifications; +using Elsa.Persistence.YesSql.Documents; +using Elsa.Persistence.YesSql.Indexes; +using YesSql; +using IIdGenerator = Elsa.Services.IIdGenerator; + +namespace Elsa.Persistence.YesSql.Stores +{ + public class YesSqlWorkflowExecutionLogStore : YesSqlStore, IWorkflowExecutionLogStore + { + public YesSqlWorkflowExecutionLogStore(ISession session, IIdGenerator idGenerator, IMapper mapper) : base(session, idGenerator, mapper, CollectionNames.WorkflowExecutionLog) + { + } + + protected override async Task FindDocumentAsync(WorkflowExecutionLogRecord entity, CancellationToken cancellationToken) => await Query(x => x.RecordId == entity.Id).FirstOrDefaultAsync(); + protected override IQuery MapSpecification(ISpecification specification) => AutoMapSpecification(specification); + } +} \ No newline at end of file diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/FodyWeavers.xsd b/src/providers/Elsa.DistributedLocking.AzureBlob/FodyWeavers.xsd deleted file mode 100644 index 3f3946e28..000000000 --- a/src/providers/Elsa.DistributedLocking.AzureBlob/FodyWeavers.xsd +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/src/providers/Elsa.DistributedLocking.Redis/FodyWeavers.xsd b/src/providers/Elsa.DistributedLocking.Redis/FodyWeavers.xsd deleted file mode 100644 index 3f3946e28..000000000 --- a/src/providers/Elsa.DistributedLocking.Redis/FodyWeavers.xsd +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/src/providers/Elsa.DistributedLocking.SqlServer/FodyWeavers.xsd b/src/providers/Elsa.DistributedLocking.SqlServer/FodyWeavers.xsd deleted file mode 100644 index 3f3946e28..000000000 --- a/src/providers/Elsa.DistributedLocking.SqlServer/FodyWeavers.xsd +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/Elsa.Samples.AzureServiceBusWorker.csproj b/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/Elsa.Samples.AzureServiceBusWorker.csproj index 94fafe95f..cdf4708ca 100644 --- a/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/Elsa.Samples.AzureServiceBusWorker.csproj +++ b/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/Elsa.Samples.AzureServiceBusWorker.csproj @@ -6,11 +6,12 @@ - + - - + + + diff --git a/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/Program.cs b/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/Program.cs index aeb61a74e..a83310935 100644 --- a/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/Program.cs +++ b/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/Program.cs @@ -1,5 +1,6 @@ using Elsa.Activities.AzureServiceBus.Extensions; using Elsa.Persistence.InMemory; +using Elsa.Persistence.YesSql.Extensions; using Elsa.Samples.AzureServiceBusWorker.Workflows; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -19,7 +20,7 @@ namespace Elsa.Samples.AzureServiceBusWorker .ConfigureServices((hostContext, services) => { services - .AddElsa() + .AddElsa(options => options.UseYesSqlPersistence()) .AddConsoleActivities() .AddTimerActivities() .AddAzureServiceBusActivities(options => options.ConnectionString = hostContext.Configuration.GetConnectionString("AzureServiceBus")) diff --git a/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/appsettings.json b/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/appsettings.json index 458e714ca..453f80009 100644 --- a/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/appsettings.json +++ b/src/samples/worker/Elsa.Samples.AzureServiceBusWorker/appsettings.json @@ -7,6 +7,6 @@ } }, "ConnectionStrings": { - "AzureServiceBus": "" + "AzureServiceBus": "Endpoint=sb://elsa-samples.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=MAwkyEFjQdDFX4gI6jM/nvBMwHcEZQ4RHQ2XC6fFB9Q=" } } diff --git a/src/samples/worker/Elsa.Samples.DistributedLock/Elsa.Samples.DistributedLock.csproj b/src/samples/worker/Elsa.Samples.DistributedLock/Elsa.Samples.DistributedLock.csproj index 5c4c3a7be..d2b78b64d 100644 --- a/src/samples/worker/Elsa.Samples.DistributedLock/Elsa.Samples.DistributedLock.csproj +++ b/src/samples/worker/Elsa.Samples.DistributedLock/Elsa.Samples.DistributedLock.csproj @@ -12,10 +12,10 @@ - - - + + + diff --git a/src/server/Elsa.Server.Api/Models/WorkflowInstance.cs b/src/server/Elsa.Server.Api/Models/WorkflowInstance.cs index 6af0f2c6b..f9a53c012 100644 --- a/src/server/Elsa.Server.Api/Models/WorkflowInstance.cs +++ b/src/server/Elsa.Server.Api/Models/WorkflowInstance.cs @@ -16,7 +16,7 @@ namespace Elsa.Server.Api.Models { Variables = new Variables(); Activities = new List(); - ExecutionLog = new List(); + ExecutionLog = new List(); ScheduledActivities = new Stack(); PostScheduledActivities = new Stack(); } @@ -42,7 +42,7 @@ namespace Elsa.Server.Api.Models set => _blockingActivities = new HashSet(value, BlockingActivityEqualityComparer.Instance); } - [DataMember(Order = 16)] public ICollection ExecutionLog { get; set; } + [DataMember(Order = 16)] public ICollection ExecutionLog { get; set; } [DataMember(Order = 17)] public WorkflowFault? Fault { get; set; } [DataMember(Order = 18)] public Stack ScheduledActivities { get; set; } [DataMember(Order = 19)] public Stack PostScheduledActivities { get; set; }