From 02d8cd071bdfc7c5474fb5217b0bbf27b06cd19a Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 6 Jan 2019 16:52:04 +0100 Subject: [PATCH] Initial views and controllers displaying workflow instances --- Elsa.sln | 7 ++ .../Drivers/ReadLineDriver.cs | 9 ++- .../Elsa.Activities.Console.csproj | 4 - .../Elsa.Abstractions.csproj | 1 + .../Extensions/WorkflowExtensions.cs | 12 ++- src/core/Elsa.Abstractions/Models/Workflow.cs | 6 ++ .../Models/WorkflowExecutionContext.cs | 12 ++- src/core/Elsa.Core/Elsa.Core.csproj | 1 + .../Extensions/ServiceCollectionExtensions.cs | 2 + src/core/Elsa.Core/Handlers/ActivityDriver.cs | 9 ++- .../Handlers/UnknownActivityDriver.cs | 10 ++- .../Elsa.Core/Results/FaultWorkflowResult.cs | 9 ++- .../Elsa.Core/Results/FinishWorkflowResult.cs | 10 ++- src/core/Elsa.Core/Results/HaltResult.cs | 10 ++- .../Tokenizers/WorkflowTokenizer.cs | 14 +++- src/core/Elsa.Core/WorkflowInvoker.cs | 15 +++- .../Elsa.Persistence.Abstractions.csproj | 6 +- .../IWorkflowSpecificationVisitor.cs | 4 + .../Specifications/Primitives/And.cs | 2 +- .../Specifications/Primitives/Or.cs | 2 +- .../WorkflowIsBlockedOnActivity.cs | 3 +- .../Specifications/WorkflowIsFaulted.cs | 22 ++++++ .../Specifications/WorkflowIsFinished.cs | 22 ++++++ .../Specifications/WorkflowIsHalted.cs | 22 ++++++ .../Specifications/WorkflowIsInstanceOf.cs | 21 ++++++ .../WorkflowStartsWithActivity.cs | 10 ++- .../Elsa.Persistence.FileSystem.csproj | 2 +- .../FileSystemWorkflowStore.cs | 11 ++- .../InMemoryWorkflowStore.cs | 11 ++- .../Controllers/WorkflowsController.cs | 68 +++++++++++++++-- .../Elsa.Web.Management.csproj | 18 ++--- .../ViewModels/WorkflowInstancesViewModel.cs | 11 +++ .../ViewModels/WorkflowSummaryViewModel.cs | 12 +++ .../Views/Workflows/Definitions.cshtml | 73 +++++++++++++++++++ .../Views/Workflows/Details.cshtml | 2 + .../Views/Workflows/Edit.cshtml | 2 +- .../Views/Workflows/Index.cshtml | 48 ------------ .../Views/Workflows/Instances.cshtml | 50 +++++++++++++ .../Views/_ViewImports.cshtml | 2 + .../Services/FileSystemWorkflowStore.cs | 26 +++---- .../Startup.cs | 2 +- .../Elsa.Web.BootstrapTheme.csproj | 23 +++--- .../Workflows/Index.cshtml | 55 -------------- .../Views/Shared/Sidebar.cshtml | 2 +- 44 files changed, 478 insertions(+), 185 deletions(-) create mode 100644 src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsFaulted.cs create mode 100644 src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsFinished.cs create mode 100644 src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsHalted.cs create mode 100644 src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsInstanceOf.cs create mode 100644 src/web/modules/Elsa.Web.Management/ViewModels/WorkflowInstancesViewModel.cs create mode 100644 src/web/modules/Elsa.Web.Management/ViewModels/WorkflowSummaryViewModel.cs create mode 100644 src/web/modules/Elsa.Web.Management/Views/Workflows/Definitions.cshtml create mode 100644 src/web/modules/Elsa.Web.Management/Views/Workflows/Details.cshtml delete mode 100644 src/web/modules/Elsa.Web.Management/Views/Workflows/Index.cshtml create mode 100644 src/web/modules/Elsa.Web.Management/Views/Workflows/Instances.cshtml delete mode 100644 src/web/themes/Elsa.Web.BootstrapTheme/Views/Elsa.Web.Management/Workflows/Index.cshtml diff --git a/Elsa.sln b/Elsa.sln index ed149bd47..b16716eea 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -61,6 +61,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Web.BootstrapTheme", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleDashboard.Web", "src\samples\SampleDashboard.Web\SampleDashboard.Web.csproj", "{27BD3AE7-8D3B-4D19-A8E3-4DC6922C2213}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.InMemory", "src\core\Elsa.Persistence.InMemory\Elsa.Persistence.InMemory.csproj", "{089E23E7-0B8D-4BB6-949D-EB669C116D9E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -143,6 +145,10 @@ Global {27BD3AE7-8D3B-4D19-A8E3-4DC6922C2213}.Debug|Any CPU.Build.0 = Debug|Any CPU {27BD3AE7-8D3B-4D19-A8E3-4DC6922C2213}.Release|Any CPU.ActiveCfg = Release|Any CPU {27BD3AE7-8D3B-4D19-A8E3-4DC6922C2213}.Release|Any CPU.Build.0 = Release|Any CPU + {089E23E7-0B8D-4BB6-949D-EB669C116D9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {089E23E7-0B8D-4BB6-949D-EB669C116D9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {089E23E7-0B8D-4BB6-949D-EB669C116D9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {089E23E7-0B8D-4BB6-949D-EB669C116D9E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -171,6 +177,7 @@ Global {E04F98D6-F6B3-475F-BCAA-F0D586EE3965} = {B33F1927-FF0A-4826-80C6-F480E9C43892} {B5E36E84-1978-43C7-8859-F5184C9A657C} = {B33F1927-FF0A-4826-80C6-F480E9C43892} {27BD3AE7-8D3B-4D19-A8E3-4DC6922C2213} = {C55090FE-8506-4FDA-928F-5ACBB9CBE019} + {089E23E7-0B8D-4BB6-949D-EB669C116D9E} = {35F44BE9-13D0-417C-A01A-F0787BEE6DC3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158} diff --git a/src/activities/Elsa.Activities.Console/Drivers/ReadLineDriver.cs b/src/activities/Elsa.Activities.Console/Drivers/ReadLineDriver.cs index 0e24da34b..7658782ed 100644 --- a/src/activities/Elsa.Activities.Console/Drivers/ReadLineDriver.cs +++ b/src/activities/Elsa.Activities.Console/Drivers/ReadLineDriver.cs @@ -6,18 +6,21 @@ using Elsa.Handlers; using Elsa.Models; using Elsa.Results; using Microsoft.Extensions.Localization; +using NodaTime; namespace Elsa.Activities.Console.Drivers { public class ReadLineDriver : ActivityDriver { + private readonly IClock clock; private readonly TextReader input; - public ReadLineDriver() + public ReadLineDriver(IClock clock) { + this.clock = clock; } - public ReadLineDriver(TextReader input) : this() + public ReadLineDriver(IClock clock, TextReader input) : this(clock) { this.input = input; } @@ -25,7 +28,7 @@ namespace Elsa.Activities.Console.Drivers protected override async Task OnExecuteAsync(ReadLine activity, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken) { if (input == null) - return Halt(); + return Halt(clock.GetCurrentInstant()); var value = await input.ReadLineAsync(); workflowContext.SetLastResult(value); diff --git a/src/activities/Elsa.Activities.Console/Elsa.Activities.Console.csproj b/src/activities/Elsa.Activities.Console/Elsa.Activities.Console.csproj index 95647353d..8af5fb098 100644 --- a/src/activities/Elsa.Activities.Console/Elsa.Activities.Console.csproj +++ b/src/activities/Elsa.Activities.Console/Elsa.Activities.Console.csproj @@ -16,8 +16,4 @@ - - - - diff --git a/src/core/Elsa.Abstractions/Elsa.Abstractions.csproj b/src/core/Elsa.Abstractions/Elsa.Abstractions.csproj index 47fc4df6b..0783a78c9 100644 --- a/src/core/Elsa.Abstractions/Elsa.Abstractions.csproj +++ b/src/core/Elsa.Abstractions/Elsa.Abstractions.csproj @@ -18,6 +18,7 @@ + diff --git a/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs b/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs index 34689540f..d05318bd0 100644 --- a/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs +++ b/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs @@ -9,10 +9,20 @@ namespace Elsa.Extensions public static bool IsDefinition(this Workflow workflow) => workflow.Metadata.ParentId == null; public static bool IsInstance(this Workflow workflow) => !workflow.IsDefinition(); + public static bool IsHalted(this Workflow workflow) => + workflow.Status == WorkflowStatus.Halted; + + public static bool IsFaulted(this Workflow workflow) => + workflow.Status == WorkflowStatus.Aborted || + workflow.Status == WorkflowStatus.Faulted; + + public static bool IsFinished(this Workflow workflow) => + workflow.Status == WorkflowStatus.Finished; + public static IEnumerable GetStartActivities(this Workflow workflow) { var query = - from activity in workflow.Activities + from activity in workflow.Activities where !workflow.Connections.Select(x => x.Target.Activity).Contains(activity) select activity; diff --git a/src/core/Elsa.Abstractions/Models/Workflow.cs b/src/core/Elsa.Abstractions/Models/Workflow.cs index a744ada4e..3d8753a79 100644 --- a/src/core/Elsa.Abstractions/Models/Workflow.cs +++ b/src/core/Elsa.Abstractions/Models/Workflow.cs @@ -2,6 +2,7 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using NodaTime; namespace Elsa.Models { @@ -25,6 +26,11 @@ namespace Elsa.Models [JsonConverter(typeof(StringEnumConverter))] public WorkflowStatus Status { get; set; } + public Instant CreatedAt { get; set; } + public Instant? StartedAt { get; set; } + public Instant? HaltedAt { get; set; } + public Instant? FinishedAt { get; set; } + public IList Activities { get; set; } = new List(); public IList Connections { get; set; } = new List(); public Stack Scopes { get; set; } diff --git a/src/core/Elsa.Abstractions/Models/WorkflowExecutionContext.cs b/src/core/Elsa.Abstractions/Models/WorkflowExecutionContext.cs index db9ac5032..dfe633950 100644 --- a/src/core/Elsa.Abstractions/Models/WorkflowExecutionContext.cs +++ b/src/core/Elsa.Abstractions/Models/WorkflowExecutionContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NodaTime; namespace Elsa.Models { @@ -56,10 +57,11 @@ namespace Elsa.Models CurrentScope.LastResult = value; } - public void Fault(Exception exception, IActivity activity) => Fault(exception.Message, activity); + public void Fault(Exception exception, IActivity activity, Instant instant) => Fault(exception.Message, activity, instant); - public void Fault(string errorMessage, IActivity activity) + public void Fault(string errorMessage, IActivity activity, Instant instant) { + Workflow.FinishedAt = instant; Workflow.Fault = new WorkflowFault { Message = errorMessage, @@ -67,7 +69,7 @@ namespace Elsa.Models }; } - public void Halt() + public void Halt(Instant instant) { var activity = CurrentActivity; if (!Workflow.BlockingActivities.Contains(activity)) @@ -75,11 +77,13 @@ namespace Elsa.Models Workflow.BlockingActivities.Add(activity); } + Workflow.HaltedAt = instant; Workflow.Status = WorkflowStatus.Halted; } - public void Finish() + public void Finish(Instant instant) { + Workflow.FinishedAt = instant; Workflow.Status = WorkflowStatus.Finished; } diff --git a/src/core/Elsa.Core/Elsa.Core.csproj b/src/core/Elsa.Core/Elsa.Core.csproj index 3dd28d9cd..393d1c520 100644 --- a/src/core/Elsa.Core/Elsa.Core.csproj +++ b/src/core/Elsa.Core/Elsa.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs index e04fa3447..00753ad5b 100644 --- a/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Elsa.Serialization.Formatters; using Elsa.Serialization.Tokenizers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using NodaTime; namespace Elsa.Extensions { @@ -13,6 +14,7 @@ namespace Elsa.Extensions public static IServiceCollection AddWorkflowsCore(this IServiceCollection services) { services.TryAddSingleton(); + services.TryAddSingleton(SystemClock.Instance); services.TryAddScoped(); services.TryAddScoped(); services.AddScoped(); diff --git a/src/core/Elsa.Core/Handlers/ActivityDriver.cs b/src/core/Elsa.Core/Handlers/ActivityDriver.cs index 96b54193b..745cc11c3 100644 --- a/src/core/Elsa.Core/Handlers/ActivityDriver.cs +++ b/src/core/Elsa.Core/Handlers/ActivityDriver.cs @@ -1,18 +1,19 @@ using System; using System.Collections.Generic; using Elsa.Results; +using NodaTime; namespace Elsa.Handlers { public abstract class ActivityDriver : ActivityDriverBase where T : IActivity { - protected HaltResult Halt() => new HaltResult(); + protected HaltResult Halt(Instant instant) => new HaltResult(instant); protected TriggerEndpointsResult TriggerEndpoints(IEnumerable names) => new TriggerEndpointsResult(names); protected TriggerEndpointsResult TriggerEndpoint(string name) => TriggerEndpoints(new[] { name }); protected ScheduleActivityResult ScheduleActivity(IActivity activity) => new ScheduleActivityResult(activity); protected ReturnValueResult SetReturnValue(object value) => new ReturnValueResult(value); - protected FinishWorkflowResult Finish() => new FinishWorkflowResult(); - protected FaultWorkflowResult Fault(string errorMessage) => new FaultWorkflowResult(errorMessage); - protected FaultWorkflowResult Fault(Exception exception) => new FaultWorkflowResult(exception); + protected FinishWorkflowResult Finish(Instant instant) => new FinishWorkflowResult(instant); + protected FaultWorkflowResult Fault(string errorMessage, Instant instant) => new FaultWorkflowResult(errorMessage, instant); + protected FaultWorkflowResult Fault(Exception exception, Instant instant) => new FaultWorkflowResult(exception, instant); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Handlers/UnknownActivityDriver.cs b/src/core/Elsa.Core/Handlers/UnknownActivityDriver.cs index cd42ef9a6..4b1417d42 100644 --- a/src/core/Elsa.Core/Handlers/UnknownActivityDriver.cs +++ b/src/core/Elsa.Core/Handlers/UnknownActivityDriver.cs @@ -1,14 +1,22 @@ using Elsa.Activities; using Elsa.Models; using Elsa.Results; +using NodaTime; namespace Elsa.Handlers { public class UnknownActivityDriver : ActivityDriver { + private readonly IClock clock; + + public UnknownActivityDriver(IClock clock) + { + this.clock = clock; + } + protected override ActivityExecutionResult OnExecute(UnknownActivity activity, WorkflowExecutionContext workflowContext) { - return Fault($"Unknown activity: {activity.Name}, ID: {activity.Id}"); + return Fault($"Unknown activity: {activity.Name}, ID: {activity.Id}", clock.GetCurrentInstant()); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Results/FaultWorkflowResult.cs b/src/core/Elsa.Core/Results/FaultWorkflowResult.cs index d2a35f9b0..11df456fd 100644 --- a/src/core/Elsa.Core/Results/FaultWorkflowResult.cs +++ b/src/core/Elsa.Core/Results/FaultWorkflowResult.cs @@ -1,25 +1,28 @@ using System; using Elsa.Models; +using NodaTime; namespace Elsa.Results { public class FaultWorkflowResult : ActivityExecutionResult { private readonly string errorMessage; + private readonly Instant instant; - public FaultWorkflowResult(Exception exception) : this(exception.Message) + public FaultWorkflowResult(Exception exception, Instant instant) : this(exception.Message, instant) { } - public FaultWorkflowResult(string errorMessage) + public FaultWorkflowResult(string errorMessage, Instant instant) { this.errorMessage = errorMessage; + this.instant = instant; } protected override void Execute(IWorkflowInvoker invoker, WorkflowExecutionContext workflowContext) { var activity = workflowContext.CurrentActivity; - workflowContext.Fault(errorMessage, activity); + workflowContext.Fault(errorMessage, activity, instant); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Results/FinishWorkflowResult.cs b/src/core/Elsa.Core/Results/FinishWorkflowResult.cs index 1ac242724..f0e88fb2b 100644 --- a/src/core/Elsa.Core/Results/FinishWorkflowResult.cs +++ b/src/core/Elsa.Core/Results/FinishWorkflowResult.cs @@ -1,12 +1,20 @@ using Elsa.Models; +using NodaTime; namespace Elsa.Results { public class FinishWorkflowResult : ActivityExecutionResult { + private readonly Instant instant; + + public FinishWorkflowResult(Instant instant) + { + this.instant = instant; + } + protected override void Execute(IWorkflowInvoker invoker, WorkflowExecutionContext workflowContext) { - workflowContext.Finish(); + workflowContext.Finish(instant); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Results/HaltResult.cs b/src/core/Elsa.Core/Results/HaltResult.cs index 0b7466b3a..d8ebb990e 100644 --- a/src/core/Elsa.Core/Results/HaltResult.cs +++ b/src/core/Elsa.Core/Results/HaltResult.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using Elsa.Models; +using NodaTime; namespace Elsa.Results { @@ -9,6 +10,13 @@ namespace Elsa.Results /// public class HaltResult : ActivityExecutionResult { + private readonly Instant instant; + + public HaltResult(Instant instant) + { + this.instant = instant; + } + public override async Task ExecuteAsync(IWorkflowInvoker invoker, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken) { if (workflowContext.IsFirstPass) @@ -21,7 +29,7 @@ namespace Elsa.Results } else { - workflowContext.Halt(); + workflowContext.Halt(instant); } } } diff --git a/src/core/Elsa.Core/Serialization/Tokenizers/WorkflowTokenizer.cs b/src/core/Elsa.Core/Serialization/Tokenizers/WorkflowTokenizer.cs index bebc91c6f..2f01eaa2c 100644 --- a/src/core/Elsa.Core/Serialization/Tokenizers/WorkflowTokenizer.cs +++ b/src/core/Elsa.Core/Serialization/Tokenizers/WorkflowTokenizer.cs @@ -9,6 +9,8 @@ using Elsa.Models; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; +using NodaTime; +using NodaTime.Serialization.JsonNet; namespace Elsa.Serialization.Tokenizers { @@ -22,7 +24,13 @@ namespace Elsa.Serialization.Tokenizers { this.tokenizerInvoker = tokenizerInvoker; this.activityLibrary = activityLibrary; - jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); + + jsonSerializer = JsonSerializer.Create( + new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + } + .ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)); } public async Task TokenizeWorkflowAsync(Workflow value, CancellationToken cancellationToken) @@ -89,13 +97,13 @@ namespace Elsa.Serialization.Tokenizers var activityModel = JObject.FromObject(value, jsonSerializer); return Task.FromResult(activityModel); } - + public async Task DetokenizeActivityAsync(JToken token, CancellationToken cancellationToken) { var name = token["name"].Value(); var descriptor = await GetActivityDescriptorAsync(name, cancellationToken); var activityType = descriptor?.ActivityType ?? typeof(UnknownActivity); - var activity = (IActivity)token.ToObject(activityType); + var activity = (IActivity) token.ToObject(activityType); activity.Descriptor = descriptor; return activity; diff --git a/src/core/Elsa.Core/WorkflowInvoker.cs b/src/core/Elsa.Core/WorkflowInvoker.cs index f62a05399..08a3cef08 100644 --- a/src/core/Elsa.Core/WorkflowInvoker.cs +++ b/src/core/Elsa.Core/WorkflowInvoker.cs @@ -6,16 +6,22 @@ using System.Threading.Tasks; using Elsa.Models; using Elsa.Results; using Microsoft.Extensions.Logging; +using NodaTime; namespace Elsa { public class WorkflowInvoker : IWorkflowInvoker { + private readonly IClock clock; private readonly ILogger logger; - public WorkflowInvoker(IActivityInvoker activityInvoker, ILogger logger) + public WorkflowInvoker( + IActivityInvoker activityInvoker, + IClock clock, + ILogger logger) { ActivityInvoker = activityInvoker; + this.clock = clock; this.logger = logger; } @@ -31,6 +37,9 @@ namespace Elsa workflow.BlockingActivities.Remove(startActivity); else startActivity = workflow.Activities.First(); + + if (!isResuming) + workflow.StartedAt = clock.GetCurrentInstant(); workflowExecutionContext.Workflow.Status = WorkflowStatus.Executing; workflowExecutionContext.ScheduleActivity(startActivity); @@ -50,7 +59,7 @@ namespace Elsa } if(workflowExecutionContext.Workflow.Status != WorkflowStatus.Halted) - workflowExecutionContext.Finish(); + workflowExecutionContext.Finish(clock.GetCurrentInstant()); return workflowExecutionContext; } @@ -88,7 +97,7 @@ namespace Elsa ex, "An unhandled error occurred while executing an activity. Putting the workflow in the faulted state." ); - workflowContext.Fault(ex, activity); + workflowContext.Fault(ex, activity, clock.GetCurrentInstant()); } private async Task ExecuteOrResumeActivityAsync(WorkflowExecutionContext workflowContext, IActivity activity, bool isResuming, CancellationToken cancellationToken) diff --git a/src/core/Elsa.Persistence.Abstractions/Elsa.Persistence.Abstractions.csproj b/src/core/Elsa.Persistence.Abstractions/Elsa.Persistence.Abstractions.csproj index ff484011c..7d2844ec1 100644 --- a/src/core/Elsa.Persistence.Abstractions/Elsa.Persistence.Abstractions.csproj +++ b/src/core/Elsa.Persistence.Abstractions/Elsa.Persistence.Abstractions.csproj @@ -14,7 +14,11 @@ - + + + + + diff --git a/src/core/Elsa.Persistence.Abstractions/IWorkflowSpecificationVisitor.cs b/src/core/Elsa.Persistence.Abstractions/IWorkflowSpecificationVisitor.cs index 5a8bef046..2a2a8c853 100644 --- a/src/core/Elsa.Persistence.Abstractions/IWorkflowSpecificationVisitor.cs +++ b/src/core/Elsa.Persistence.Abstractions/IWorkflowSpecificationVisitor.cs @@ -10,5 +10,9 @@ namespace Elsa.Persistence void Visit(WorkflowIsBlockedOnActivity specification); void Visit(WorkflowIsDefinition specification); void Visit(WorkflowIsInstance specification); + void Visit(WorkflowIsInstanceOf specification); + void Visit(WorkflowIsFinished specification); + void Visit(WorkflowIsHalted specification); + void Visit(WorkflowIsFaulted specification); } } \ No newline at end of file diff --git a/src/core/Elsa.Persistence.Abstractions/Specifications/Primitives/And.cs b/src/core/Elsa.Persistence.Abstractions/Specifications/Primitives/And.cs index 6a1f2ca30..ad3970411 100644 --- a/src/core/Elsa.Persistence.Abstractions/Specifications/Primitives/And.cs +++ b/src/core/Elsa.Persistence.Abstractions/Specifications/Primitives/And.cs @@ -14,4 +14,4 @@ public void Accept(TVisitor visitor) => visitor.Visit(this); public bool IsSatisfiedBy(T obj) => Left.IsSatisfiedBy(obj) && Right.IsSatisfiedBy(obj); } -} +} \ No newline at end of file diff --git a/src/core/Elsa.Persistence.Abstractions/Specifications/Primitives/Or.cs b/src/core/Elsa.Persistence.Abstractions/Specifications/Primitives/Or.cs index aef2afea7..c2f7675ae 100644 --- a/src/core/Elsa.Persistence.Abstractions/Specifications/Primitives/Or.cs +++ b/src/core/Elsa.Persistence.Abstractions/Specifications/Primitives/Or.cs @@ -14,4 +14,4 @@ public void Accept(TVisitor visitor) => visitor.Visit(this); public bool IsSatisfiedBy(T obj) => Left.IsSatisfiedBy(obj) || Right.IsSatisfiedBy(obj); } -} +} \ No newline at end of file diff --git a/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsBlockedOnActivity.cs b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsBlockedOnActivity.cs index d9cbe2e2a..e41963039 100644 --- a/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsBlockedOnActivity.cs +++ b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsBlockedOnActivity.cs @@ -1,4 +1,5 @@ using System.Linq; +using Elsa.Extensions; using Elsa.Models; namespace Elsa.Persistence.Specifications @@ -16,7 +17,7 @@ namespace Elsa.Persistence.Specifications { var query = from activity in value.BlockingActivities - where activity.Name == ActivityName + where value.IsInstance() && activity.Name == ActivityName select activity; return query.Any(); diff --git a/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsFaulted.cs b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsFaulted.cs new file mode 100644 index 000000000..cc0d0fe22 --- /dev/null +++ b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsFaulted.cs @@ -0,0 +1,22 @@ +using Elsa.Extensions; +using Elsa.Models; + +namespace Elsa.Persistence.Specifications +{ + public class WorkflowIsFaulted : ISpecification + { + private readonly string parentId; + + public WorkflowIsFaulted(string parentId) + { + this.parentId = parentId; + } + + public bool IsSatisfiedBy(Workflow item) => item.Metadata.ParentId == parentId && item.IsFaulted(); + + public void Accept(IWorkflowSpecificationVisitor visitor) + { + visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsFinished.cs b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsFinished.cs new file mode 100644 index 000000000..0b20c72ec --- /dev/null +++ b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsFinished.cs @@ -0,0 +1,22 @@ +using Elsa.Extensions; +using Elsa.Models; + +namespace Elsa.Persistence.Specifications +{ + public class WorkflowIsFinished : ISpecification + { + private readonly string parentId; + + public WorkflowIsFinished(string parentId) + { + this.parentId = parentId; + } + + public bool IsSatisfiedBy(Workflow item) => item.Metadata.ParentId == parentId && item.IsFinished(); + + public void Accept(IWorkflowSpecificationVisitor visitor) + { + visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsHalted.cs b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsHalted.cs new file mode 100644 index 000000000..eea5c40f6 --- /dev/null +++ b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsHalted.cs @@ -0,0 +1,22 @@ +using Elsa.Extensions; +using Elsa.Models; + +namespace Elsa.Persistence.Specifications +{ + public class WorkflowIsHalted : ISpecification + { + private readonly string parentId; + + public WorkflowIsHalted(string parentId) + { + this.parentId = parentId; + } + + public bool IsSatisfiedBy(Workflow item) => item.Metadata.ParentId == parentId && item.IsHalted(); + + public void Accept(IWorkflowSpecificationVisitor visitor) + { + visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsInstanceOf.cs b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsInstanceOf.cs new file mode 100644 index 000000000..5f6e62946 --- /dev/null +++ b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowIsInstanceOf.cs @@ -0,0 +1,21 @@ +using Elsa.Models; + +namespace Elsa.Persistence.Specifications +{ + public class WorkflowIsInstanceOf : ISpecification + { + private readonly string parentId; + + public WorkflowIsInstanceOf(string parentId) + { + this.parentId = parentId; + } + + public bool IsSatisfiedBy(Workflow item) => item.Metadata.ParentId == parentId; + + public void Accept(IWorkflowSpecificationVisitor visitor) + { + visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowStartsWithActivity.cs b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowStartsWithActivity.cs index f311c39e5..caf70bc34 100644 --- a/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowStartsWithActivity.cs +++ b/src/core/Elsa.Persistence.Abstractions/Specifications/WorkflowStartsWithActivity.cs @@ -1,4 +1,5 @@ using System.Linq; +using Elsa.Extensions; using Elsa.Models; namespace Elsa.Persistence.Specifications @@ -15,9 +16,10 @@ namespace Elsa.Persistence.Specifications public bool IsSatisfiedBy(Workflow value) { var query = - from activity in value.Activities - where activity.Name == ActivityName - where !value.Connections.Select(x => x.Target.Activity).Contains(activity) + from activity in value.Activities + where value.IsDefinition() + && activity.Name == ActivityName + && !value.Connections.Select(x => x.Target.Activity).Contains(activity) select activity; return query.Any(); @@ -28,4 +30,4 @@ namespace Elsa.Persistence.Specifications visitor.Visit(this); } } -} +} \ No newline at end of file diff --git a/src/core/Elsa.Persistence.FileSystem/Elsa.Persistence.FileSystem.csproj b/src/core/Elsa.Persistence.FileSystem/Elsa.Persistence.FileSystem.csproj index 86e8f31b9..ebab62d8d 100644 --- a/src/core/Elsa.Persistence.FileSystem/Elsa.Persistence.FileSystem.csproj +++ b/src/core/Elsa.Persistence.FileSystem/Elsa.Persistence.FileSystem.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netstandard2.0 1.0.0 Sipke Schoorstra Elsa is a set of workflowing libraries and tools to enable super-fast workflowing capabilities in any .NET Core application. diff --git a/src/core/Elsa.Persistence.FileSystem/FileSystemWorkflowStore.cs b/src/core/Elsa.Persistence.FileSystem/FileSystemWorkflowStore.cs index 7612a12dd..95c1ab793 100644 --- a/src/core/Elsa.Persistence.FileSystem/FileSystemWorkflowStore.cs +++ b/src/core/Elsa.Persistence.FileSystem/FileSystemWorkflowStore.cs @@ -7,6 +7,7 @@ using Elsa.Models; using Elsa.Persistence.FileSystem.Options; using Elsa.Serialization; using Microsoft.Extensions.Options; +using NodaTime; namespace Elsa.Persistence.FileSystem { @@ -15,14 +16,21 @@ namespace Elsa.Persistence.FileSystem private readonly IFileSystem fileSystem; private readonly IIdGenerator idGenerator; private readonly IWorkflowSerializer workflowSerializer; + private readonly IClock clock; private readonly string rootDirectory; private readonly string format; - public FileSystemWorkflowStore(IOptions options, IFileSystem fileSystem, IIdGenerator idGenerator, IWorkflowSerializer workflowSerializer) + public FileSystemWorkflowStore( + IOptions options, + IFileSystem fileSystem, + IIdGenerator idGenerator, + IWorkflowSerializer workflowSerializer, + IClock clock) { this.fileSystem = fileSystem; this.idGenerator = idGenerator; this.workflowSerializer = workflowSerializer; + this.clock = clock; rootDirectory = options.Value.RootDirectory; format = options.Value.Format; } @@ -47,6 +55,7 @@ namespace Elsa.Persistence.FileSystem var id = $"{idGenerator.Generate()}{fileExtension}"; value.Metadata.Id = id; + value.CreatedAt = clock.GetCurrentInstant(); await UpdateAsync(value, cancellationToken); } diff --git a/src/core/Elsa.Persistence.InMemory/InMemoryWorkflowStore.cs b/src/core/Elsa.Persistence.InMemory/InMemoryWorkflowStore.cs index 7e0c4c8ac..a135361db 100644 --- a/src/core/Elsa.Persistence.InMemory/InMemoryWorkflowStore.cs +++ b/src/core/Elsa.Persistence.InMemory/InMemoryWorkflowStore.cs @@ -3,13 +3,19 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Elsa.Models; +using NodaTime; namespace Elsa.Persistence.InMemory { public class InMemoryWorkflowStore : IWorkflowStore { - public InMemoryWorkflowStore() + private readonly IIdGenerator idGenerator; + private readonly IClock clock; + + public InMemoryWorkflowStore(IIdGenerator idGenerator, IClock clock) { + this.idGenerator = idGenerator; + this.clock = clock; Workflows = new List(); } @@ -31,6 +37,9 @@ namespace Elsa.Persistence.InMemory public Task AddAsync(Workflow value, CancellationToken cancellationToken) { + value.Metadata.Id = idGenerator.Generate(); + value.CreatedAt = clock.GetCurrentInstant(); + Workflows.Add(value); return Task.CompletedTask; } diff --git a/src/web/modules/Elsa.Web.Management/Controllers/WorkflowsController.cs b/src/web/modules/Elsa.Web.Management/Controllers/WorkflowsController.cs index 89de10eb6..5fc15d290 100644 --- a/src/web/modules/Elsa.Web.Management/Controllers/WorkflowsController.cs +++ b/src/web/modules/Elsa.Web.Management/Controllers/WorkflowsController.cs @@ -1,12 +1,15 @@ +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Elsa.Extensions; using Elsa.Models; using Elsa.Persistence; using Elsa.Persistence.Specifications; using Elsa.Serialization; using Elsa.Serialization.Extensions; +using Elsa.Web.Management.ViewModels; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; @@ -34,14 +37,47 @@ namespace Elsa.Web.Management.Controllers } [HttpGet] - public async Task Index(CancellationToken cancellationToken) + public async Task Definitions(CancellationToken cancellationToken) { - var workflows = await workflowStore.GetManyAsync(new AllWorkflows(), cancellationToken); - return View(workflows.ToList()); + var workflows = await workflowStore.GetManyAsync(new WorkflowIsDefinition(), cancellationToken).ToListAsync(); + var viewModels = await Task.WhenAll(workflows.Select(async x => await CreateWorkflowSummaryViewModelAsync(x, cancellationToken)).ToList()); + + return View(viewModels); + } + + [HttpGet("{parentId}/finished")] + public Task Finished(string parentId, CancellationToken cancellationToken) + { + return Instances(parentId, new WorkflowIsFinished(parentId), cancellationToken); + } + + [HttpGet("{parentId}/halted")] + public Task Halted(string parentId, CancellationToken cancellationToken) + { + return Instances(parentId, new WorkflowIsHalted(parentId), cancellationToken); + } + + [HttpGet("{parentId}/faulted")] + public Task Faulted(string parentId, CancellationToken cancellationToken) + { + return Instances(parentId, new WorkflowIsFaulted(parentId), cancellationToken); + } + + private async Task Instances(string parentId, ISpecification specification, CancellationToken cancellationToken) + { + var definition = await workflowStore.GetAsync(parentId, cancellationToken); + var workflows = await workflowStore.GetManyAsync(specification, cancellationToken).ToListAsync(); + var viewModel = new WorkflowInstancesViewModel + { + Definition = definition, + Instances = workflows + }; + + return View("Instances", viewModel); } [HttpGet("new")] - public IActionResult New(CancellationToken cancellationToken) + public IActionResult New() { var workflow = new Workflow { @@ -65,14 +101,14 @@ namespace Elsa.Web.Management.Controllers }); } - [HttpGet("{id}")] + [HttpGet("{id}/edit")] public async Task Edit(string id, CancellationToken cancellationToken) { var workflow = await workflowStore.GetAsync(id, cancellationToken); return View(workflow); } - [HttpPut("{id}")] + [HttpPut("{id}/update")] public async Task Update(string id, [FromBody] JToken workflowData, CancellationToken cancellationToken) { var workflow = await workflowSerializer.DeserializeAsync(workflowData, cancellationToken); @@ -91,5 +127,25 @@ namespace Elsa.Web.Management.Controllers var contentType = formatter.ContentType; return File(bytes, contentType, fileName); } + + [HttpGet("{id}/details")] + public async Task Details(string id, CancellationToken cancellationToken) + { + var workflow = await workflowStore.GetAsync(id, cancellationToken); + return View(workflow); + } + + private async Task CreateWorkflowSummaryViewModelAsync(Workflow workflow, CancellationToken cancellationToken) + { + var instances = await workflowStore.GetManyAsync(new WorkflowIsInstanceOf(workflow.Metadata.Id), cancellationToken).ToListAsync(); + + return new WorkflowSummaryViewModel + { + Workflow = workflow, + NumHalted = instances.Count(x => x.IsHalted()), + NumFaulted = instances.Count(x => x.IsFaulted()), + NumFinished = instances.Count(x => x.IsFinished()) + }; + } } } \ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/Elsa.Web.Management.csproj b/src/web/modules/Elsa.Web.Management/Elsa.Web.Management.csproj index aae3d9034..7fcc80688 100644 --- a/src/web/modules/Elsa.Web.Management/Elsa.Web.Management.csproj +++ b/src/web/modules/Elsa.Web.Management/Elsa.Web.Management.csproj @@ -13,18 +13,18 @@ - - - - - - - - + + + + + + + + - + \ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/ViewModels/WorkflowInstancesViewModel.cs b/src/web/modules/Elsa.Web.Management/ViewModels/WorkflowInstancesViewModel.cs new file mode 100644 index 000000000..90a3674e8 --- /dev/null +++ b/src/web/modules/Elsa.Web.Management/ViewModels/WorkflowInstancesViewModel.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Elsa.Models; + +namespace Elsa.Web.Management.ViewModels +{ + public class WorkflowInstancesViewModel + { + public Workflow Definition { get; set; } + public ICollection Instances { get; set; } + } +} \ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/ViewModels/WorkflowSummaryViewModel.cs b/src/web/modules/Elsa.Web.Management/ViewModels/WorkflowSummaryViewModel.cs new file mode 100644 index 000000000..62644570c --- /dev/null +++ b/src/web/modules/Elsa.Web.Management/ViewModels/WorkflowSummaryViewModel.cs @@ -0,0 +1,12 @@ +using Elsa.Models; + +namespace Elsa.Web.Management.ViewModels +{ + public class WorkflowSummaryViewModel + { + public Workflow Workflow { get; set; } + public int NumHalted { get; set; } + public int NumFaulted { get; set; } + public int NumFinished { get; set; } + } +} \ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/Views/Workflows/Definitions.cshtml b/src/web/modules/Elsa.Web.Management/Views/Workflows/Definitions.cshtml new file mode 100644 index 000000000..2f6678135 --- /dev/null +++ b/src/web/modules/Elsa.Web.Management/Views/Workflows/Definitions.cshtml @@ -0,0 +1,73 @@ +@model ICollection + +
+
+

@T["Workflows"]

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + @{ + var index = 0; + } + @foreach (var workflowSummary in Model) + { + var workflowDefinition = workflowSummary.Workflow; + var workflowId = workflowDefinition.Metadata.Id; + var rowClass = index % 2 == 0 ? "even" : "odd"; + index++; + + + + + + + + + } + +
IDNameDescriptionExecutionActions
IDNameDescriptionExecutionActions
+ @workflowId + @workflowDefinition.Metadata.Name@workflowDefinition.Metadata.Description + @if (workflowSummary.NumHalted > 0) + { + @T["Halted"] @workflowSummary.NumHalted + } + @if (workflowSummary.NumFaulted > 0) + { + @T["Faulted"] @workflowSummary.NumFaulted + } + @if (workflowSummary.NumFinished > 0) + { + @T["Finished"] @workflowSummary.NumFinished + } + @T["edit"] | delete | enable
+ +
\ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/Views/Workflows/Details.cshtml b/src/web/modules/Elsa.Web.Management/Views/Workflows/Details.cshtml new file mode 100644 index 000000000..228c52637 --- /dev/null +++ b/src/web/modules/Elsa.Web.Management/Views/Workflows/Details.cshtml @@ -0,0 +1,2 @@ +@model Workflow +@Html.Partial("WorkflowEditor") \ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/Views/Workflows/Edit.cshtml b/src/web/modules/Elsa.Web.Management/Views/Workflows/Edit.cshtml index dcbc924f5..228c52637 100644 --- a/src/web/modules/Elsa.Web.Management/Views/Workflows/Edit.cshtml +++ b/src/web/modules/Elsa.Web.Management/Views/Workflows/Edit.cshtml @@ -1,2 +1,2 @@ -@model Elsa.Models.Workflow +@model Workflow @Html.Partial("WorkflowEditor") \ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/Views/Workflows/Index.cshtml b/src/web/modules/Elsa.Web.Management/Views/Workflows/Index.cshtml deleted file mode 100644 index 6a16f8831..000000000 --- a/src/web/modules/Elsa.Web.Management/Views/Workflows/Index.cshtml +++ /dev/null @@ -1,48 +0,0 @@ -@model ICollection - -
-
-
-
-

Workflow Definitions

- - - - - - - - - - - - - - - - - - - @{ - var index = 0; - } - @foreach (var workflowDefinition in Model) - { - var rowClass = index % 2 == 0 ? "even" : "odd"; - index++; - - - - - - - - } - -
IDNameDescriptionActions
IDNameDescriptionActions
- @workflowDefinition.Metadata.Id - @workflowDefinition.Metadata.Name@workflowDefinition.Metadata.Descriptionedit | delete | enable
-
-
-
-
\ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/Views/Workflows/Instances.cshtml b/src/web/modules/Elsa.Web.Management/Views/Workflows/Instances.cshtml new file mode 100644 index 000000000..e9398e1b9 --- /dev/null +++ b/src/web/modules/Elsa.Web.Management/Views/Workflows/Instances.cshtml @@ -0,0 +1,50 @@ +@model WorkflowInstancesViewModel + +
+
+

+ @Model.Definition.Metadata.Name + @T["Finished Workflows"] +

+
+ + + + + + + + + + + + + + + + + + + + @{ + var index = 0; + } + @foreach (var workflow in Model.Instances) + { + var workflowId = workflow.Metadata.Id; + var rowClass = index % 2 == 0 ? "even" : "odd"; + index++; + + + + + + + + } + +
IDStartedFinishedActions
IDStartedFinishedActions
+ @workflowId + @workflow.StartedAt@workflow.FinishedAt@T["details"] | @T["delete"]
+ +
\ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Management/Views/_ViewImports.cshtml b/src/web/modules/Elsa.Web.Management/Views/_ViewImports.cshtml index 3d14f3de3..af3b952bf 100644 --- a/src/web/modules/Elsa.Web.Management/Views/_ViewImports.cshtml +++ b/src/web/modules/Elsa.Web.Management/Views/_ViewImports.cshtml @@ -1,3 +1,5 @@ +@using Elsa.Models +@using Elsa.Web.Management.ViewModels @inherits OrchardCore.DisplayManagement.Razor.RazorPage @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Elsa.Web.Components diff --git a/src/web/modules/Elsa.Web.Persistence.FileSystem/Services/FileSystemWorkflowStore.cs b/src/web/modules/Elsa.Web.Persistence.FileSystem/Services/FileSystemWorkflowStore.cs index 5bfbc8d13..d4de642c7 100644 --- a/src/web/modules/Elsa.Web.Persistence.FileSystem/Services/FileSystemWorkflowStore.cs +++ b/src/web/modules/Elsa.Web.Persistence.FileSystem/Services/FileSystemWorkflowStore.cs @@ -8,23 +8,29 @@ using Elsa.Serialization; using Elsa.Serialization.Extensions; using Elsa.Serialization.Formatters; using Elsa.Web.Persistence.FileSystem.Extensions; +using NodaTime; using OrchardCore.FileStorage; namespace Elsa.Web.Persistence.FileSystem.Services { public class FileSystemWorkflowStore : IWorkflowStore { + private const string Format = YamlTokenFormatter.FormatName; private readonly IWorkflowsFileStore fileStore; private readonly IWorkflowSerializer workflowSerializer; private readonly IIdGenerator idGenerator; - private const string Format = YamlTokenFormatter.FormatName; - private const string RootDirectory = "workflows"; + private readonly IClock clock; - public FileSystemWorkflowStore(IWorkflowsFileStore fileStore, IWorkflowSerializer workflowSerializer, IIdGenerator idGenerator) + public FileSystemWorkflowStore( + IWorkflowsFileStore fileStore, + IWorkflowSerializer workflowSerializer, + IIdGenerator idGenerator, + IClock clock) { this.fileStore = fileStore; this.workflowSerializer = workflowSerializer; this.idGenerator = idGenerator; + this.clock = clock; } public async Task> GetManyAsync(ISpecification specification, CancellationToken cancellationToken) @@ -45,7 +51,8 @@ namespace Elsa.Web.Persistence.FileSystem.Services { var fileExtension = $".{Format.ToLower()}"; var id = $"{idGenerator.Generate()}{fileExtension}"; - + + value.CreatedAt = clock.GetCurrentInstant(); value.Metadata.Id = id; await UpdateAsync(value, cancellationToken); @@ -53,10 +60,9 @@ namespace Elsa.Web.Persistence.FileSystem.Services public async Task UpdateAsync(Workflow value, CancellationToken cancellationToken) { - await EnsureWorkflowsDirectoryAsync(); var data = await workflowSerializer.SerializeAsync(value, Format, cancellationToken); var fileName = value.Metadata.Id; - var path = fileStore.Combine(RootDirectory, fileName); + var path = fileName; using (var stream = data.ToStream()) { @@ -66,8 +72,7 @@ namespace Elsa.Web.Persistence.FileSystem.Services private async Task> ListAsync(CancellationToken cancellationToken) { - await EnsureWorkflowsDirectoryAsync(); - var files = await fileStore.GetDirectoryContentAsync(RootDirectory); + var files = await fileStore.GetDirectoryContentAsync(); var loadTasks = files.Select(x => LoadWorkflowDefinitionAsync(x, cancellationToken)); return await Task.WhenAll(loadTasks); } @@ -79,10 +84,5 @@ namespace Elsa.Web.Persistence.FileSystem.Services workflow.Metadata.Id = file.Name; return workflow; } - - private async Task EnsureWorkflowsDirectoryAsync() - { - await fileStore.TryCreateDirectoryAsync(RootDirectory); - } } } \ No newline at end of file diff --git a/src/web/modules/Elsa.Web.Persistence.FileSystem/Startup.cs b/src/web/modules/Elsa.Web.Persistence.FileSystem/Startup.cs index aa73e3ea9..2d554fa70 100644 --- a/src/web/modules/Elsa.Web.Persistence.FileSystem/Startup.cs +++ b/src/web/modules/Elsa.Web.Persistence.FileSystem/Startup.cs @@ -20,7 +20,7 @@ namespace Elsa.Web.Persistence.FileSystem { var shellOptions = serviceProvider.GetRequiredService>().Value; var shellSettings = serviceProvider.GetRequiredService(); - var mediaPath = Path.Combine(shellOptions.ShellsApplicationDataPath, shellOptions.ShellsContainerName, shellSettings.Name, "elsa"); + var mediaPath = Path.Combine(shellOptions.ShellsApplicationDataPath, shellOptions.ShellsContainerName, shellSettings.Name, "workflows"); var fileStore = new FileSystemStore(mediaPath); return new WorkflowsFileStore(fileStore); diff --git a/src/web/themes/Elsa.Web.BootstrapTheme/Elsa.Web.BootstrapTheme.csproj b/src/web/themes/Elsa.Web.BootstrapTheme/Elsa.Web.BootstrapTheme.csproj index 160533c9f..a770a5072 100644 --- a/src/web/themes/Elsa.Web.BootstrapTheme/Elsa.Web.BootstrapTheme.csproj +++ b/src/web/themes/Elsa.Web.BootstrapTheme/Elsa.Web.BootstrapTheme.csproj @@ -13,27 +13,26 @@ - - - + + + - - - + + + - - - - - + + + + - + \ No newline at end of file diff --git a/src/web/themes/Elsa.Web.BootstrapTheme/Views/Elsa.Web.Management/Workflows/Index.cshtml b/src/web/themes/Elsa.Web.BootstrapTheme/Views/Elsa.Web.Management/Workflows/Index.cshtml deleted file mode 100644 index e31ab9102..000000000 --- a/src/web/themes/Elsa.Web.BootstrapTheme/Views/Elsa.Web.Management/Workflows/Index.cshtml +++ /dev/null @@ -1,55 +0,0 @@ -@model ICollection - -
-
-

@T["Workflows"]

-
- -
-
- - - - - - - - - - - - - - - - - - - - @{ - var index = 0; - } - @foreach (var workflowDefinition in Model) - { - var rowClass = index % 2 == 0 ? "even" : "odd"; - index++; - - - - - - - - } - -
IDNameDescriptionActions
IDNameDescriptionActions
- @workflowDefinition.Metadata.Id - @workflowDefinition.Metadata.Name@workflowDefinition.Metadata.Descriptionedit | delete | enable
- -
\ No newline at end of file diff --git a/src/web/themes/Elsa.Web.BootstrapTheme/Views/Shared/Sidebar.cshtml b/src/web/themes/Elsa.Web.BootstrapTheme/Views/Shared/Sidebar.cshtml index d389ec1d0..774be9b94 100644 --- a/src/web/themes/Elsa.Web.BootstrapTheme/Views/Shared/Sidebar.cshtml +++ b/src/web/themes/Elsa.Web.BootstrapTheme/Views/Shared/Sidebar.cshtml @@ -8,7 +8,7 @@