From 012e12c51c266d7fa126575803a9a4e6bfbfa20c Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Mon, 14 Oct 2019 19:46:55 +0200 Subject: [PATCH] Implement activity execution logging (#89) * Implement activity logging * Update Calculator example --- samples/Sample04/CalculatorWorkflow.cs | 45 ++++++------ samples/Sample04/Program.cs | 5 ++ .../Services/IWorkflowEventHandler.cs | 5 ++ .../Services/WorkflowEventHandlerBase.cs | 34 +++++++-- .../Elsa.Core/Activities/ControlFlow/Join.cs | 18 +++-- .../Extensions/ServiceCollectionExtensions.cs | 3 + .../Elsa.Core/Results/FaultWorkflowResult.cs | 22 +++++- .../ActivityLoggingWorkflowEventHandler.cs | 32 +++++++++ .../PersistenceWorkflowEventHandler.cs | 69 ++++++++++--------- .../WorkflowDefinitionController.cs | 31 +++++---- .../Controllers/WorkflowInstanceController.cs | 9 ++- .../ViewModels/WorkflowDefinitionEditModel.cs | 2 + .../WorkflowInstanceDetailsModel.cs | 4 +- .../Shared/WorkflowDefinitionEditor.cshtml | 3 +- .../Areas/Elsa/Views/Shared/_Layout.cshtml | 2 +- .../Views/WorkflowInstance/Details.cshtml | 4 +- .../assets/js/workflow-instance-viewer.js | 18 +---- .../ActivityDescriber.cs | 14 ++-- .../Elsa.WorkflowDesigner.csproj | 3 +- .../Models/ActivityDefinition.cs | 9 ++- .../Models/ActivityDesignerSettings.cs | 4 +- .../Models/ActivityPropertyDescriptor.cs | 19 +++-- .../WorkflowDesignerViewComponent.cs | 34 ++++++--- .../WorkflowDesignerViewComponentModel.cs | 12 +++- 24 files changed, 263 insertions(+), 138 deletions(-) create mode 100644 src/core/Elsa.Core/WorkflowEventHandlers/ActivityLoggingWorkflowEventHandler.cs rename src/core/Elsa.Core/{Services => WorkflowEventHandlers}/PersistenceWorkflowEventHandler.cs (95%) diff --git a/samples/Sample04/CalculatorWorkflow.cs b/samples/Sample04/CalculatorWorkflow.cs index e35128e20..23bc2d453 100644 --- a/samples/Sample04/CalculatorWorkflow.cs +++ b/samples/Sample04/CalculatorWorkflow.cs @@ -13,13 +13,13 @@ namespace Sample04 public void Build(IWorkflowBuilder builder) { builder - .StartWith(x => x.TextExpression = new LiteralExpression("Welcome to Calculator Workflow!")) - .Then(x => x.TextExpression = new LiteralExpression("Enter number 1:"), id: "start") - .Then(x => x.VariableName = "number1") - .Then(x => x.TextExpression = new LiteralExpression("Enter number 2:")) - .Then(x => x.VariableName = "number2") - .Then(x => x.TextExpression = new LiteralExpression("Now enter the operation you wish to apply. Options are: add, subtract, multiply or divide:")) - .Then(x => x.VariableName = "operation") + .StartWith(x => x.TextExpression = new LiteralExpression("Welcome to Calculator Workflow!"), "welcome") + .Then(x => x.TextExpression = new LiteralExpression("Enter number 1:"), id: "enter-first-number-prompt") + .Then(x => x.VariableName = "number1", id: "read-first-number") + .Then(x => x.TextExpression = new LiteralExpression("Enter number 2:"), id: "enter-second-number-prompt") + .Then(x => x.VariableName = "number2", id: "read-second-number") + .Then(x => x.TextExpression = new LiteralExpression("Now enter the operation you wish to apply. Options are: add, subtract, multiply or divide:"), id: "enter-operation-prompt") + .Then(x => x.VariableName = "operation", id: "read-operation") .Then(@switch => { @switch.Expression = new JavaScriptExpression("operation"); @@ -29,40 +29,41 @@ namespace Sample04 { @switch .When("add") - .Then(SetupOperation) - .Then("showResult"); + .Then(SetupOperation, id: "perform-sum") + .Then("show-result"); @switch .When("subtract") - .Then(SetupOperation) - .Then("showResult"); + .Then(SetupOperation, id: "perform-subtract") + .Then("show-result"); @switch .When("multiply") - .Then(SetupOperation) - .Then("showResult"); + .Then(SetupOperation, id: "perform-multiply") + .Then("show-result"); @switch .When("divide") - .Then(SetupOperation) - .Then("showResult"); - } + .Then(SetupOperation, id: "perform-divide") + .Then("show-result"); + }, + "inspect-selected-operation" ) - .Add(x => x.TextExpression = new JavaScriptExpression("`Result: ${result}`"), "showResult") - .Then(x => x.TextExpression = new LiteralExpression("Try again? (y/n)")) - .Then(x => x.VariableName = "retry") + .Add(x => x.TextExpression = new JavaScriptExpression("`Result: ${result}`"), "show-result") + .Then(x => x.TextExpression = new LiteralExpression("Try again? (y/n)"), id: "try-again-prompt") + .Then(x => x.VariableName = "retry", id: "read-try-again") .Then( x => x.ConditionExpression = new JavaScriptExpression("retry.toLowerCase() === 'y'"), ifElse => { ifElse .When(OutcomeNames.True) - .Then("start"); + .Then("enter-first-number-prompt"); ifElse .When(OutcomeNames.False) - .Then(x => x.TextExpression = new LiteralExpression("Bye!")); - });; + .Then(x => x.TextExpression = new LiteralExpression("Bye!"), id: "say-good-bye"); + }, id: "inspect-retry"); } private void SetupOperation(ArithmeticOperation operation) diff --git a/samples/Sample04/Program.cs b/samples/Sample04/Program.cs index 6425928fb..58c7dd34a 100644 --- a/samples/Sample04/Program.cs +++ b/samples/Sample04/Program.cs @@ -35,6 +35,11 @@ namespace Sample04 var invoker = services.GetService(); await invoker.StartAsync(workflow); + Console.WriteLine("Workflow has ended. Here are the activities that have executed:"); + foreach (var logEntry in workflow.ExecutionLog) + { + Console.WriteLine("{0}: {1}", logEntry.Timestamp, logEntry.ActivityId); + } Console.ReadLine(); } } diff --git a/src/core/Elsa.Abstractions/Services/IWorkflowEventHandler.cs b/src/core/Elsa.Abstractions/Services/IWorkflowEventHandler.cs index 773f6083e..c6cd9cf30 100644 --- a/src/core/Elsa.Abstractions/Services/IWorkflowEventHandler.cs +++ b/src/core/Elsa.Abstractions/Services/IWorkflowEventHandler.cs @@ -15,6 +15,11 @@ namespace Elsa.Services /// Task ActivityExecutedAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, CancellationToken cancellationToken); + /// + /// Invoked when an activity has faulted. + /// + Task ActivityFaultedAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, string message, CancellationToken cancellationToken); + /// /// Invoked when halted activities are about to be executed. /// diff --git a/src/core/Elsa.Abstractions/Services/WorkflowEventHandlerBase.cs b/src/core/Elsa.Abstractions/Services/WorkflowEventHandlerBase.cs index ca37985a4..2155c014f 100644 --- a/src/core/Elsa.Abstractions/Services/WorkflowEventHandlerBase.cs +++ b/src/core/Elsa.Abstractions/Services/WorkflowEventHandlerBase.cs @@ -6,19 +6,36 @@ namespace Elsa.Services { public abstract class WorkflowEventHandlerBase : IWorkflowEventHandler { - public virtual Task ActivityExecutedAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, CancellationToken cancellationToken) + public virtual Task ActivityExecutedAsync( + WorkflowExecutionContext workflowExecutionContext, + IActivity activity, + CancellationToken cancellationToken) { ActivityExecuted(workflowExecutionContext, activity); return Task.CompletedTask; } - public virtual Task InvokingHaltedActivitiesAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) + public virtual Task ActivityFaultedAsync( + WorkflowExecutionContext workflowExecutionContext, + IActivity activity, + string message, + CancellationToken cancellationToken) + { + ActivityFaulted(workflowExecutionContext, activity, message); + return Task.CompletedTask; + } + + public virtual Task InvokingHaltedActivitiesAsync( + WorkflowExecutionContext workflowExecutionContext, + CancellationToken cancellationToken) { InvokingHaltedActivities(workflowExecutionContext); return Task.CompletedTask; } - public virtual Task WorkflowInvokedAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) + public virtual Task WorkflowInvokedAsync( + WorkflowExecutionContext workflowExecutionContext, + CancellationToken cancellationToken) { WorkflowInvoked(workflowExecutionContext); return Task.CompletedTask; @@ -27,11 +44,18 @@ namespace Elsa.Services protected virtual void ActivityExecuted(WorkflowExecutionContext workflowExecutionContext, IActivity activity) { } - + + protected virtual void ActivityFaulted( + WorkflowExecutionContext workflowExecutionContext, + IActivity activity, + string message) + { + } + protected virtual void InvokingHaltedActivities(WorkflowExecutionContext workflowExecutionContext) { } - + protected virtual void WorkflowInvoked(WorkflowExecutionContext workflowExecutionContext) { } diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Join.cs b/src/core/Elsa.Core/Activities/ControlFlow/Join.cs index 4bd82dedd..86115b9ec 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/Join.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/Join.cs @@ -98,7 +98,7 @@ namespace Elsa.Activities.ControlFlow // For each inbound connection, record the transition. foreach (var inboundConnection in inboundConnections) { - var joinActivity = (Join) inboundConnection.Target.Activity; + var joinActivity = (Join)inboundConnection.Target.Activity; var inboundTransitions = joinActivity.InboundTransitions ?? new List(); joinActivity.InboundTransitions = inboundTransitions .Union(new[] { GetTransitionKey(inboundConnection) }) @@ -115,7 +115,9 @@ namespace Elsa.Activities.ControlFlow return $"@{sourceActivityId}_{sourceOutcomeName}"; } - Task IWorkflowEventHandler.ActivityExecutedAsync(WorkflowExecutionContext workflowContext, IActivity activity, + Task IWorkflowEventHandler.ActivityExecutedAsync( + WorkflowExecutionContext workflowContext, + IActivity activity, CancellationToken cancellationToken) { RecordInboundTransitions(workflowContext, activity); @@ -123,10 +125,18 @@ namespace Elsa.Activities.ControlFlow return Task.CompletedTask; } - Task IWorkflowEventHandler.InvokingHaltedActivitiesAsync(WorkflowExecutionContext workflowExecutionContext, + public Task ActivityFaultedAsync( + WorkflowExecutionContext workflowExecutionContext, + IActivity activity, + string message, CancellationToken cancellationToken) => Task.CompletedTask; - Task IWorkflowEventHandler.WorkflowInvokedAsync(WorkflowExecutionContext workflowExecutionContext, + Task IWorkflowEventHandler.InvokingHaltedActivitiesAsync( + WorkflowExecutionContext workflowExecutionContext, + CancellationToken cancellationToken) => Task.CompletedTask; + + Task IWorkflowEventHandler.WorkflowInvokedAsync( + WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) => Task.CompletedTask; } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs index f21c52a03..c7cabd16b 100644 --- a/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ServiceCollectionExtensions.cs @@ -7,6 +7,7 @@ using Elsa.Activities.Workflows; using Elsa.AutoMapper.Extensions; using Elsa.Expressions; using Elsa.Mapping; +using Elsa.Persistence; using Elsa.Persistence.Memory; using Elsa.Runtime; using Elsa.Scripting; @@ -16,6 +17,7 @@ using Elsa.Services; using Elsa.Services.Models; using Elsa.StartupTasks; using Elsa.WorkflowBuilders; +using Elsa.WorkflowEventHandlers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using NodaTime; @@ -61,6 +63,7 @@ namespace Elsa.Extensions .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddStartupTask() .AddSingleton>(sp => sp.GetRequiredService) .AddAutoMapperProfile(ServiceLifetime.Singleton) diff --git a/src/core/Elsa.Core/Results/FaultWorkflowResult.cs b/src/core/Elsa.Core/Results/FaultWorkflowResult.cs index 8af43162d..2018e6dcd 100644 --- a/src/core/Elsa.Core/Results/FaultWorkflowResult.cs +++ b/src/core/Elsa.Core/Results/FaultWorkflowResult.cs @@ -1,6 +1,11 @@ using System; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Extensions; using Elsa.Services; using Elsa.Services.Models; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Elsa.Results { @@ -11,14 +16,25 @@ namespace Elsa.Results public FaultWorkflowResult(Exception exception) : this(exception.Message) { } - + public FaultWorkflowResult(string errorMessage) { this.errorMessage = errorMessage; } - - protected override void Execute(IWorkflowInvoker invoker, WorkflowExecutionContext workflowContext) + + public override async Task ExecuteAsync( + IWorkflowInvoker invoker, + WorkflowExecutionContext workflowContext, + CancellationToken cancellationToken) { + var eventHandlers = workflowContext.ServiceProvider.GetServices(); + var logger = workflowContext.ServiceProvider.GetRequiredService>(); + var currentActivity = workflowContext.CurrentActivity; + + await eventHandlers.InvokeAsync( + x => x.ActivityFaultedAsync(workflowContext, currentActivity, errorMessage, cancellationToken), + logger); + workflowContext.Fault(workflowContext.CurrentActivity, errorMessage); } } diff --git a/src/core/Elsa.Core/WorkflowEventHandlers/ActivityLoggingWorkflowEventHandler.cs b/src/core/Elsa.Core/WorkflowEventHandlers/ActivityLoggingWorkflowEventHandler.cs new file mode 100644 index 000000000..4a7b021a9 --- /dev/null +++ b/src/core/Elsa.Core/WorkflowEventHandlers/ActivityLoggingWorkflowEventHandler.cs @@ -0,0 +1,32 @@ +using Elsa.Models; +using Elsa.Services; +using Elsa.Services.Models; +using NodaTime; + +namespace Elsa.WorkflowEventHandlers +{ + public class ActivityLoggingWorkflowEventHandler : WorkflowEventHandlerBase + { + private readonly IClock clock; + + public ActivityLoggingWorkflowEventHandler(IClock clock) + { + this.clock = clock; + } + + protected override void ActivityExecuted(WorkflowExecutionContext workflowExecutionContext, IActivity activity) + { + workflowExecutionContext.Workflow.ExecutionLog.Add( + new LogEntry(activity.Id, clock.GetCurrentInstant(), "Executed")); + } + + protected override void ActivityFaulted( + WorkflowExecutionContext workflowExecutionContext, + IActivity activity, + string message) + { + workflowExecutionContext.Workflow.ExecutionLog.Add( + new LogEntry(activity.Id, clock.GetCurrentInstant(), message, true)); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/PersistenceWorkflowEventHandler.cs b/src/core/Elsa.Core/WorkflowEventHandlers/PersistenceWorkflowEventHandler.cs similarity index 95% rename from src/core/Elsa.Core/Services/PersistenceWorkflowEventHandler.cs rename to src/core/Elsa.Core/WorkflowEventHandlers/PersistenceWorkflowEventHandler.cs index 2621b59b1..95912b18c 100644 --- a/src/core/Elsa.Core/Services/PersistenceWorkflowEventHandler.cs +++ b/src/core/Elsa.Core/WorkflowEventHandlers/PersistenceWorkflowEventHandler.cs @@ -1,35 +1,36 @@ -using System.Threading; -using System.Threading.Tasks; -using Elsa.Persistence; -using Elsa.Services.Models; - -namespace Elsa.Services -{ - public class PersistenceWorkflowEventHandler : WorkflowEventHandlerBase - { - private readonly IWorkflowInstanceStore workflowInstanceStore; - - public PersistenceWorkflowEventHandler(IWorkflowInstanceStore workflowInstanceStore) - { - this.workflowInstanceStore = workflowInstanceStore; - } - - public override async Task InvokingHaltedActivitiesAsync(WorkflowExecutionContext workflowExecutionContext, - CancellationToken cancellationToken) - { - await SaveWorkflowAsync(workflowExecutionContext.Workflow, cancellationToken); - } - - public override async Task WorkflowInvokedAsync(WorkflowExecutionContext workflowExecutionContext, - CancellationToken cancellationToken) - { - await SaveWorkflowAsync(workflowExecutionContext.Workflow, cancellationToken); - } - - private async Task SaveWorkflowAsync(Workflow workflow, CancellationToken cancellationToken) - { - var workflowInstance = workflow.ToInstance(); - await workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken); - } - } +using System.Threading; +using System.Threading.Tasks; +using Elsa.Persistence; +using Elsa.Services; +using Elsa.Services.Models; + +namespace Elsa.WorkflowEventHandlers +{ + public class PersistenceWorkflowEventHandler : WorkflowEventHandlerBase + { + private readonly IWorkflowInstanceStore workflowInstanceStore; + + public PersistenceWorkflowEventHandler(IWorkflowInstanceStore workflowInstanceStore) + { + this.workflowInstanceStore = workflowInstanceStore; + } + + public override async Task InvokingHaltedActivitiesAsync(WorkflowExecutionContext workflowExecutionContext, + CancellationToken cancellationToken) + { + await SaveWorkflowAsync(workflowExecutionContext.Workflow, cancellationToken); + } + + public override async Task WorkflowInvokedAsync(WorkflowExecutionContext workflowExecutionContext, + CancellationToken cancellationToken) + { + await SaveWorkflowAsync(workflowExecutionContext.Workflow, cancellationToken); + } + + private async Task SaveWorkflowAsync(Workflow workflow, CancellationToken cancellationToken) + { + var workflowInstance = workflow.ToInstance(); + await workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken); + } + } } \ No newline at end of file diff --git a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Controllers/WorkflowDefinitionController.cs b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Controllers/WorkflowDefinitionController.cs index 28ea54b2f..6159d4767 100644 --- a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Controllers/WorkflowDefinitionController.cs +++ b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Controllers/WorkflowDefinitionController.cs @@ -27,6 +27,7 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers private readonly IWorkflowInstanceStore workflowInstanceStore; private readonly IWorkflowPublisher publisher; private readonly IWorkflowSerializer serializer; + private readonly IWorkflowFactory workflowFactory; private readonly IOptions options; private readonly IIdGenerator idGenerator; private readonly INotifier notifier; @@ -36,6 +37,7 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers IWorkflowInstanceStore workflowInstanceStore, IWorkflowPublisher publisher, IWorkflowSerializer serializer, + IWorkflowFactory workflowFactory, IOptions options, IIdGenerator idGenerator, INotifier notifier) @@ -44,6 +46,7 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers this.workflowDefinitionStore = workflowDefinitionStore; this.workflowInstanceStore = workflowInstanceStore; this.serializer = serializer; + this.workflowFactory = workflowFactory; this.options = options; this.idGenerator = idGenerator; this.notifier = notifier; @@ -72,16 +75,17 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers [HttpGet("create")] public ViewResult Create() { - var workflow = publisher.New(); + var workflowDefinition = publisher.New(); + var workflow = workflowFactory.CreateWorkflow(workflowDefinition); var model = new WorkflowDefinitionEditModel { - Name = workflow.Name, - Json = serializer.Serialize(workflow, JsonTokenFormatter.FormatName), + Name = workflowDefinition.Name, + Workflow = workflow, ActivityDefinitions = options.Value.ActivityDefinitions.ToArray(), - IsSingleton = workflow.IsSingleton, - IsDisabled = workflow.IsDisabled, - Description = workflow.Description + IsSingleton = workflowDefinition.IsSingleton, + IsDisabled = workflowDefinition.IsDisabled, + Description = workflowDefinition.Description }; return View(model); @@ -103,19 +107,18 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers [HttpGet("edit/{id}")] public async Task Edit(string id, CancellationToken cancellationToken) { - var workflow = await publisher.GetDraftAsync(id, cancellationToken); + var workflowDefinition = await publisher.GetDraftAsync(id, cancellationToken); - if (workflow == null) + if (workflowDefinition == null) return NotFound(); var model = new WorkflowDefinitionEditModel { - Id = workflow.DefinitionId, - Name = workflow.Name, - Description = workflow.Description, - IsSingleton = workflow.IsSingleton, - IsDisabled = workflow.IsDisabled, - Json = serializer.Serialize(workflow, JsonTokenFormatter.FormatName), + Id = workflowDefinition.DefinitionId, + Name = workflowDefinition.Name, + Description = workflowDefinition.Description, + IsSingleton = workflowDefinition.IsSingleton, + IsDisabled = workflowDefinition.IsDisabled, ActivityDefinitions = options.Value.ActivityDefinitions.ToArray() }; diff --git a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Controllers/WorkflowInstanceController.cs b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Controllers/WorkflowInstanceController.cs index 3fc3afe4a..283f46f83 100644 --- a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Controllers/WorkflowInstanceController.cs +++ b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Controllers/WorkflowInstanceController.cs @@ -11,6 +11,7 @@ using Elsa.Models; using Elsa.Persistence; using Elsa.Serialization; using Elsa.Serialization.Formatters; +using Elsa.Services; using Jint.Native.Json; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -25,6 +26,7 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers private readonly IWorkflowDefinitionStore workflowDefinitionStore; private readonly IOptions options; private readonly IWorkflowSerializer serializer; + private readonly IWorkflowFactory workflowFactory; private readonly INotifier notifier; public WorkflowInstanceController( @@ -32,12 +34,14 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers IWorkflowDefinitionStore workflowDefinitionStore, IOptions options, IWorkflowSerializer serializer, + IWorkflowFactory workflowFactory, INotifier notifier) { this.workflowInstanceStore = workflowInstanceStore; this.workflowDefinitionStore = workflowDefinitionStore; this.options = options; this.serializer = serializer; + this.workflowFactory = workflowFactory; this.notifier = notifier; } @@ -90,14 +94,13 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers cancellationToken ); - var json = serializer.Serialize(definition, JsonTokenFormatter.FormatName); + var workflow = workflowFactory.CreateWorkflow(definition, Variables.Empty, instance); var model = new WorkflowInstanceDetailsModel { ReturnUrl = returnUrl, - Json = json, WorkflowDefinition = definition, - WorkflowInstance = instance, + Workflow = workflow, ActivityDefinitions = options.Value.ActivityDefinitions.ToArray() }; diff --git a/src/dashboard/Elsa.Dashboard/Areas/Elsa/ViewModels/WorkflowDefinitionEditModel.cs b/src/dashboard/Elsa.Dashboard/Areas/Elsa/ViewModels/WorkflowDefinitionEditModel.cs index 43620ed6c..94be71179 100644 --- a/src/dashboard/Elsa.Dashboard/Areas/Elsa/ViewModels/WorkflowDefinitionEditModel.cs +++ b/src/dashboard/Elsa.Dashboard/Areas/Elsa/ViewModels/WorkflowDefinitionEditModel.cs @@ -1,3 +1,4 @@ +using Elsa.Services.Models; using Elsa.WorkflowDesigner.Models; namespace Elsa.Dashboard.Areas.Elsa.ViewModels @@ -5,6 +6,7 @@ namespace Elsa.Dashboard.Areas.Elsa.ViewModels public class WorkflowDefinitionEditModel { public string Id { get; set; } + public Workflow Workflow { get; set; } public string Json { get; set; } public string SubmitAction { get; set; } public string Name { get; set; } diff --git a/src/dashboard/Elsa.Dashboard/Areas/Elsa/ViewModels/WorkflowInstanceDetailsModel.cs b/src/dashboard/Elsa.Dashboard/Areas/Elsa/ViewModels/WorkflowInstanceDetailsModel.cs index 15198ad04..df467ddda 100644 --- a/src/dashboard/Elsa.Dashboard/Areas/Elsa/ViewModels/WorkflowInstanceDetailsModel.cs +++ b/src/dashboard/Elsa.Dashboard/Areas/Elsa/ViewModels/WorkflowInstanceDetailsModel.cs @@ -1,4 +1,5 @@ using Elsa.Models; +using Elsa.Services.Models; using ActivityDefinition = Elsa.WorkflowDesigner.Models.ActivityDefinition; namespace Elsa.Dashboard.Areas.Elsa.ViewModels @@ -6,9 +7,8 @@ namespace Elsa.Dashboard.Areas.Elsa.ViewModels public class WorkflowInstanceDetailsModel { public string ReturnUrl { get; set; } - public string Json { get; set; } public WorkflowDefinitionVersion WorkflowDefinition { get; set; } - public WorkflowInstance WorkflowInstance { get; set; } + public Workflow Workflow { get; set; } public ActivityDefinition[] ActivityDefinitions { get; set; } } } \ No newline at end of file diff --git a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/Shared/WorkflowDefinitionEditor.cshtml b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/Shared/WorkflowDefinitionEditor.cshtml index 07c7cb8dc..11d12df5a 100644 --- a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/Shared/WorkflowDefinitionEditor.cshtml +++ b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/Shared/WorkflowDefinitionEditor.cshtml @@ -24,8 +24,7 @@
- - +
diff --git a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/Shared/_Layout.cshtml b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/Shared/_Layout.cshtml index 522b3dbd6..cf0f93cf2 100644 --- a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/Shared/_Layout.cshtml +++ b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/Shared/_Layout.cshtml @@ -32,7 +32,7 @@ - + @await RenderSectionAsync("HeadScripts", false) diff --git a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/WorkflowInstance/Details.cshtml b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/WorkflowInstance/Details.cshtml index 11ed6e11c..9c8f9a9c5 100644 --- a/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/WorkflowInstance/Details.cshtml +++ b/src/dashboard/Elsa.Dashboard/Areas/Elsa/Views/WorkflowInstance/Details.cshtml @@ -1,5 +1,5 @@ @model WorkflowInstanceDetailsModel -
+
@@ -18,7 +18,7 @@
- + diff --git a/src/dashboard/Elsa.Dashboard/Theme/argon-dashboard/assets/js/workflow-instance-viewer.js b/src/dashboard/Elsa.Dashboard/Theme/argon-dashboard/assets/js/workflow-instance-viewer.js index 4e094fb78..3467395ef 100644 --- a/src/dashboard/Elsa.Dashboard/Theme/argon-dashboard/assets/js/workflow-instance-viewer.js +++ b/src/dashboard/Elsa.Dashboard/Theme/argon-dashboard/assets/js/workflow-instance-viewer.js @@ -1,7 +1,4 @@ const designer = document.querySelector("#designerHost"); -let workflow = null; - -//designer.addEventListener('componentReady', onWorkflowDesignerReady); function exportWorkflow() { designer.export({ @@ -10,17 +7,4 @@ function exportWorkflow() { mimeType: 'application/json', displayName: 'JSON' }); -} - -function onWorkflowDesignerReady() { - const input = document.querySelector('[data-workflow]'); - const json = input.attributes['data-workflow'].value; - - if (!json) - return; - - designer.workflow = workflow = JSON.parse(json); -} - -// Temporary workaround until I figure out how to listen for the workflow designer component's ready event. -setTimeout(onWorkflowDesignerReady, 100); \ No newline at end of file +} \ No newline at end of file diff --git a/src/dashboard/Elsa.WorkflowDesigner/ActivityDescriber.cs b/src/dashboard/Elsa.WorkflowDesigner/ActivityDescriber.cs index a142ff47b..ed45d993b 100644 --- a/src/dashboard/Elsa.WorkflowDesigner/ActivityDescriber.cs +++ b/src/dashboard/Elsa.WorkflowDesigner/ActivityDescriber.cs @@ -63,13 +63,13 @@ namespace Elsa.WorkflowDesigner yield break; yield return new ActivityPropertyDescriptor - { - Name = (activityProperty.Name ?? propertyInfo.Name).Camelize(), - Label = activityProperty.Label ?? propertyInfo.Name.Humanize(LetterCasing.Title), - Type = (activityProperty.Type ?? DeterminePropertyType(propertyInfo)).Camelize(), - Hint = activityProperty.Hint, - Options = GetPropertyTypeOptions(propertyInfo) - }; + ( + (activityProperty.Name ?? propertyInfo.Name).Camelize(), + (activityProperty.Type ?? DeterminePropertyType(propertyInfo)).Camelize(), + activityProperty.Label ?? propertyInfo.Name.Humanize(LetterCasing.Title), + activityProperty.Hint, + GetPropertyTypeOptions(propertyInfo) + ); } } diff --git a/src/dashboard/Elsa.WorkflowDesigner/Elsa.WorkflowDesigner.csproj b/src/dashboard/Elsa.WorkflowDesigner/Elsa.WorkflowDesigner.csproj index 2f155cdba..54ee45818 100644 --- a/src/dashboard/Elsa.WorkflowDesigner/Elsa.WorkflowDesigner.csproj +++ b/src/dashboard/Elsa.WorkflowDesigner/Elsa.WorkflowDesigner.csproj @@ -2,7 +2,8 @@ netstandard2.0 - latest + 8.0 + enable 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/dashboard/Elsa.WorkflowDesigner/Models/ActivityDefinition.cs b/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityDefinition.cs index 3eecec3f8..067f0cced 100644 --- a/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityDefinition.cs +++ b/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityDefinition.cs @@ -4,14 +4,19 @@ namespace Elsa.WorkflowDesigner.Models { public ActivityDefinition() { + Type = "Activity"; Properties = new ActivityPropertyDescriptor[0]; + Category = "Miscellaneous"; + DisplayName = "Activity"; + Properties = new ActivityPropertyDescriptor[0]; + Designer = new ActivityDesignerSettings(); } public string Type { get; set; } public string DisplayName { get; set; } - public string Description { get; set; } + public string? Description { get; set; } public string Category { get; set; } - public string Icon { get; set; } + public string? Icon { get; set; } public ActivityPropertyDescriptor[] Properties { get; set; } public ActivityDesignerSettings Designer { get; set; } } diff --git a/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityDesignerSettings.cs b/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityDesignerSettings.cs index 09fe0a954..02a76c395 100644 --- a/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityDesignerSettings.cs +++ b/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityDesignerSettings.cs @@ -2,7 +2,7 @@ namespace Elsa.WorkflowDesigner.Models { public class ActivityDesignerSettings { - public object Description { get; set; } - public object Outcomes { get; set; } + public object? Description { get; set; } + public object? Outcomes { get; set; } } } \ No newline at end of file diff --git a/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityPropertyDescriptor.cs b/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityPropertyDescriptor.cs index dbce67b82..6feaf6f26 100644 --- a/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityPropertyDescriptor.cs +++ b/src/dashboard/Elsa.WorkflowDesigner/Models/ActivityPropertyDescriptor.cs @@ -2,10 +2,19 @@ namespace Elsa.WorkflowDesigner.Models { public class ActivityPropertyDescriptor { - public string Name { get; set; } - public string Type { get; set; } - public string Label { get; set; } - public string Hint { get; set; } - public object Options { get; set; } + public ActivityPropertyDescriptor(string name, string type, string label, string? hint = null, object? options = null) + { + Name = name; + Type = type; + Label = label; + Hint = hint; + Options = options; + } + + public string Name { get; } + public string Type { get; } + public string Label { get; } + public string? Hint { get; } + public object? Options { get; } } } \ No newline at end of file diff --git a/src/dashboard/Elsa.WorkflowDesigner/ViewComponents/WorkflowDesignerViewComponent.cs b/src/dashboard/Elsa.WorkflowDesigner/ViewComponents/WorkflowDesignerViewComponent.cs index 717c742a2..bb683478b 100644 --- a/src/dashboard/Elsa.WorkflowDesigner/ViewComponents/WorkflowDesignerViewComponent.cs +++ b/src/dashboard/Elsa.WorkflowDesigner/ViewComponents/WorkflowDesignerViewComponent.cs @@ -1,33 +1,47 @@ -using Elsa.WorkflowDesigner.Models; +using Elsa.Serialization; +using Elsa.Services.Models; using Elsa.WorkflowDesigner.ViewModels; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using ActivityDefinition = Elsa.WorkflowDesigner.Models.ActivityDefinition; namespace Elsa.WorkflowDesigner.ViewComponents { public class WorkflowDesignerViewComponent : ViewComponent { - public IViewComponentResult Invoke(ActivityDefinition[] activityDefinitions, string id) + private readonly IWorkflowSerializer serializer; + + public WorkflowDesignerViewComponent(IWorkflowSerializer serializer) { - var model = new WorkflowDesignerViewComponentModel - { - Id = id, - ActivityDefinitionsJson = GetActivityDefinitionOptions(activityDefinitions) - }; + this.serializer = serializer; + } + + public IViewComponentResult Invoke( + string id, + ActivityDefinition[]? activityDefinitions = null, + Workflow? workflow = null) + { + var model = new WorkflowDesignerViewComponentModel( + id, + Serialize(activityDefinitions ?? new ActivityDefinition[0]), + Serialize(workflow) + ); return View(model); } - private string GetActivityDefinitionOptions(ActivityDefinition[] activityDefinitions) + private static string? Serialize(object? value) { - var definitions = activityDefinitions ?? new ActivityDefinition[0]; + if (value == null) + return null; + var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore }; - return JsonConvert.SerializeObject(definitions, settings); + return JsonConvert.SerializeObject(value, settings); } } } \ No newline at end of file diff --git a/src/dashboard/Elsa.WorkflowDesigner/ViewModels/WorkflowDesignerViewComponentModel.cs b/src/dashboard/Elsa.WorkflowDesigner/ViewModels/WorkflowDesignerViewComponentModel.cs index e56125b4f..3dbb1f48d 100644 --- a/src/dashboard/Elsa.WorkflowDesigner/ViewModels/WorkflowDesignerViewComponentModel.cs +++ b/src/dashboard/Elsa.WorkflowDesigner/ViewModels/WorkflowDesignerViewComponentModel.cs @@ -2,7 +2,15 @@ namespace Elsa.WorkflowDesigner.ViewModels { public class WorkflowDesignerViewComponentModel { - public string Id { get; set; } - public string ActivityDefinitionsJson { get; set; } + public WorkflowDesignerViewComponentModel(string id, string? activityDefinitionsJson, string? workflowJson) + { + Id = id; + ActivityDefinitionsJson = activityDefinitionsJson; + WorkflowJson = workflowJson; + } + + public string Id { get; } + public string? ActivityDefinitionsJson { get; set; } + public string? WorkflowJson { get; set; } } } \ No newline at end of file