Initial views and controllers displaying workflow instances
This commit is contained in:
parent
e619f2079e
commit
02d8cd071b
7
Elsa.sln
7
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}
|
||||
|
|
|
|||
|
|
@ -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<ReadLine>
|
||||
{
|
||||
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<ActivityExecutionResult> OnExecuteAsync(ReadLine activity, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
|
||||
{
|
||||
if (input == null)
|
||||
return Halt();
|
||||
return Halt(clock.GetCurrentInstant());
|
||||
|
||||
var value = await input.ReadLineAsync();
|
||||
workflowContext.SetLastResult(value);
|
||||
|
|
|
|||
|
|
@ -16,8 +16,4 @@
|
|||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Handlers" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="1.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="NodaTime" Version="2.4.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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<IActivity> 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<IActivity> Activities { get; set; } = new List<IActivity>();
|
||||
public IList<Connection> Connections { get; set; } = new List<Connection>();
|
||||
public Stack<WorkflowExecutionScope> Scopes { get; set; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
|
||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.1.0" />
|
||||
|
||||
<PackageReference Include="YamlDotNet.NetStandard" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -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<IIdGenerator, DefaultIdGenerator>();
|
||||
services.TryAddSingleton<IClock>(SystemClock.Instance);
|
||||
services.TryAddScoped<IWorkflowSerializer, WorkflowSerializer>();
|
||||
services.TryAddScoped<IWorkflowTokenizer, WorkflowTokenizer>();
|
||||
services.AddScoped<IActivityHarvester, TypedActivityHarvester>();
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Results;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Handlers
|
||||
{
|
||||
public abstract class ActivityDriver<T> : ActivityDriverBase<T> where T : IActivity
|
||||
{
|
||||
protected HaltResult Halt() => new HaltResult();
|
||||
protected HaltResult Halt(Instant instant) => new HaltResult(instant);
|
||||
protected TriggerEndpointsResult TriggerEndpoints(IEnumerable<string> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,22 @@
|
|||
using Elsa.Activities;
|
||||
using Elsa.Models;
|
||||
using Elsa.Results;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Handlers
|
||||
{
|
||||
public class UnknownActivityDriver : ActivityDriver<UnknownActivity>
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<JToken> TokenizeWorkflowAsync(Workflow value, CancellationToken cancellationToken)
|
||||
|
|
@ -89,13 +97,13 @@ namespace Elsa.Serialization.Tokenizers
|
|||
var activityModel = JObject.FromObject(value, jsonSerializer);
|
||||
return Task.FromResult<JToken>(activityModel);
|
||||
}
|
||||
|
||||
|
||||
public async Task<IActivity> DetokenizeActivityAsync(JToken token, CancellationToken cancellationToken)
|
||||
{
|
||||
var name = token["name"].Value<string>();
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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<WorkflowInvoker> logger)
|
||||
public WorkflowInvoker(
|
||||
IActivityInvoker activityInvoker,
|
||||
IClock clock,
|
||||
ILogger<WorkflowInvoker> 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<ActivityExecutionResult> ExecuteOrResumeActivityAsync(WorkflowExecutionContext workflowContext, IActivity activity, bool isResuming, CancellationToken cancellationToken)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Abstractions\Elsa.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Abstractions\Elsa.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NodaTime" Version="2.4.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,4 +14,4 @@
|
|||
public void Accept(TVisitor visitor) => visitor.Visit(this);
|
||||
public bool IsSatisfiedBy(T obj) => Left.IsSatisfiedBy(obj) && Right.IsSatisfiedBy(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,4 +14,4 @@
|
|||
public void Accept(TVisitor visitor) => visitor.Visit(this);
|
||||
public bool IsSatisfiedBy(T obj) => Left.IsSatisfiedBy(obj) || Right.IsSatisfiedBy(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Persistence.Specifications
|
||||
{
|
||||
public class WorkflowIsFaulted : ISpecification<Workflow, IWorkflowSpecificationVisitor>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Persistence.Specifications
|
||||
{
|
||||
public class WorkflowIsFinished : ISpecification<Workflow, IWorkflowSpecificationVisitor>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Persistence.Specifications
|
||||
{
|
||||
public class WorkflowIsHalted : ISpecification<Workflow, IWorkflowSpecificationVisitor>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Persistence.Specifications
|
||||
{
|
||||
public class WorkflowIsInstanceOf : ISpecification<Workflow, IWorkflowSpecificationVisitor>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageVersion>1.0.0</PackageVersion>
|
||||
<Authors>Sipke Schoorstra</Authors>
|
||||
<Description>Elsa is a set of workflowing libraries and tools to enable super-fast workflowing capabilities in any .NET Core application.</Description>
|
||||
|
|
|
|||
|
|
@ -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<FileSystemStoreOptions> options, IFileSystem fileSystem, IIdGenerator idGenerator, IWorkflowSerializer workflowSerializer)
|
||||
public FileSystemWorkflowStore(
|
||||
IOptions<FileSystemStoreOptions> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Workflow>();
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IActionResult> Index(CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> 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<IActionResult> Finished(string parentId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Instances(parentId, new WorkflowIsFinished(parentId), cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{parentId}/halted")]
|
||||
public Task<IActionResult> Halted(string parentId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Instances(parentId, new WorkflowIsHalted(parentId), cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{parentId}/faulted")]
|
||||
public Task<IActionResult> Faulted(string parentId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Instances(parentId, new WorkflowIsFaulted(parentId), cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> Instances(string parentId, ISpecification<Workflow, IWorkflowSpecificationVisitor> 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<IActionResult> Edit(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflow = await workflowStore.GetAsync(id, cancellationToken);
|
||||
return View(workflow);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[HttpPut("{id}/update")]
|
||||
public async Task<IActionResult> 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<IActionResult> Details(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflow = await workflowStore.GetAsync(id, cancellationToken);
|
||||
return View(workflow);
|
||||
}
|
||||
|
||||
private async Task<WorkflowSummaryViewModel> 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())
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,18 +13,18 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OrchardCore" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.Mvc.Core" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.Infrastructure" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.Settings" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.Mvc.Core" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.Infrastructure" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.Settings" Version="1.0.0-beta3-70362" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Web.Components\Elsa.Web.Components.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Web.Components\Elsa.Web.Components.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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<Workflow> Instances { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
@model ICollection<WorkflowSummaryViewModel>
|
||||
|
||||
<div>
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">@T["Workflows"]</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-1">
|
||||
<nav class="admin-toolbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-sm btn-primary" asp-action="New">@T["Create Workflow"]</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table id="dataTable" class="table table-striped table-bordered dataTable" width="100%" role="grid" aria-describedby="dataTable_info" style="width: 100%;" data-turbolinks="false">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" aria-label="Id: activate to sort column ascending" style="width: 214px;">ID</th>
|
||||
<th class="sorting_asc" tabindex="0" aria-controls="dataTable" aria-sort="ascending" aria-label="Name: activate to sort column descending" style="width: 281px;">Name</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" aria-label="Description: activate to sort column ascending" style="width: 313px;">Description</th>
|
||||
<th tabindex="0" aria-controls="dataTable" style="width: 314px;">Execution</th>
|
||||
<th tabindex="0" aria-controls="dataTable" style="width: 214px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th rowspan="1" colspan="1">ID</th>
|
||||
<th rowspan="1" colspan="1">Name</th>
|
||||
<th rowspan="1" colspan="1">Description</th>
|
||||
<th rowspan="1" colspan="1">Execution</th>
|
||||
<th rowspan="1" colspan="1">Actions</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
@{
|
||||
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++;
|
||||
|
||||
<tr role="row" class="@rowClass">
|
||||
<td>
|
||||
<a asp-action="Edit" asp-route-id="@workflowId">@workflowId</a>
|
||||
</td>
|
||||
<td class="sorting_1">@workflowDefinition.Metadata.Name</td>
|
||||
<td>@workflowDefinition.Metadata.Description</td>
|
||||
<td>
|
||||
@if (workflowSummary.NumHalted > 0)
|
||||
{
|
||||
<a asp-action="Halted" asp-route-parentId="@workflowId" class="btn btn-sm btn-info">@T["Halted"] <span class="badge badge-light">@workflowSummary.NumHalted</span></a>
|
||||
}
|
||||
@if (workflowSummary.NumFaulted > 0)
|
||||
{
|
||||
<a asp-action="Faulted" asp-route-parentId="@workflowId" class="btn btn-sm btn-danger">@T["Faulted"] <span class="badge badge-light">@workflowSummary.NumFaulted</span></a>
|
||||
}
|
||||
@if (workflowSummary.NumFinished > 0)
|
||||
{
|
||||
<a asp-action="Finished" asp-route-parentId="@workflowId" class="btn btn-sm btn-success">@T["Finished"] <span class="badge badge-light">@workflowSummary.NumFinished</span></a>
|
||||
}
|
||||
</td>
|
||||
<td><a asp-action="Edit" asp-route-id="@workflowDefinition.Metadata.Id">@T["edit"]</a> | delete | enable</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@model Workflow
|
||||
@Html.Partial("WorkflowEditor")
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
@model Elsa.Models.Workflow
|
||||
@model Workflow
|
||||
@Html.Partial("WorkflowEditor")
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
@model ICollection<Elsa.Models.Workflow>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="bgc-white bd bdrs-3 p-20 mB-20">
|
||||
<h4 class="c-grey-900 mB-20">Workflow Definitions</h4>
|
||||
<table id="dataTable" class="table table-striped table-bordered dataTable" cellspacing="0" width="100%" role="grid" aria-describedby="dataTable_info" style="width: 100%;" data-turbolinks="false">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Id: activate to sort column ascending" style="width: 214px;">ID</th>
|
||||
<th class="sorting_asc" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-sort="ascending" aria-label="Name: activate to sort column descending" style="width: 281px;">Name</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Position: activate to sort column ascending" style="width: 413px;">Description</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Office: activate to sort column ascending" style="width: 214px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th rowspan="1" colspan="1">ID</th>
|
||||
<th rowspan="1" colspan="1">Name</th>
|
||||
<th rowspan="1" colspan="1">Description</th>
|
||||
<th rowspan="1" colspan="1">Actions</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
@{
|
||||
var index = 0;
|
||||
}
|
||||
@foreach (var workflowDefinition in Model)
|
||||
{
|
||||
var rowClass = index % 2 == 0 ? "even" : "odd";
|
||||
index++;
|
||||
|
||||
<tr role="row" class="@rowClass">
|
||||
<td>
|
||||
<a asp-action="Edit" asp-route-id="@workflowDefinition.Metadata.Id">@workflowDefinition.Metadata.Id</a>
|
||||
</td>
|
||||
<td class="sorting_1">@workflowDefinition.Metadata.Name</td>
|
||||
<td>@workflowDefinition.Metadata.Description</td>
|
||||
<td><a asp-action="Edit" asp-route-id="@workflowDefinition.Metadata.Id">edit</a> | delete | enable</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
@model WorkflowInstancesViewModel
|
||||
|
||||
<div>
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">
|
||||
@Model.Definition.Metadata.Name
|
||||
<small class="text-muted">@T["Finished Workflows"]</small>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<table id="dataTable" class="table table-striped table-bordered dataTable" width="100%" role="grid" aria-describedby="dataTable_info" style="width: 100%;" data-turbolinks="false">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" aria-label="Id: activate to sort column ascending" style="width: 214px;">ID</th>
|
||||
<th class="sorting_asc" tabindex="0" aria-controls="dataTable" aria-sort="ascending" aria-label="Name: activate to sort column descending" style="width: 281px;">Started</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" aria-label="Description: activate to sort column ascending" style="width: 313px;">Finished</th>
|
||||
<th tabindex="0" aria-controls="dataTable" style="width: 214px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th rowspan="1" colspan="1">ID</th>
|
||||
<th rowspan="1" colspan="1">Started</th>
|
||||
<th rowspan="1" colspan="1">Finished</th>
|
||||
<th rowspan="1" colspan="1">Actions</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
@{
|
||||
var index = 0;
|
||||
}
|
||||
@foreach (var workflow in Model.Instances)
|
||||
{
|
||||
var workflowId = workflow.Metadata.Id;
|
||||
var rowClass = index % 2 == 0 ? "even" : "odd";
|
||||
index++;
|
||||
|
||||
<tr role="row" class="@rowClass">
|
||||
<td>
|
||||
<a asp-action="Details" asp-route-id="@workflowId">@workflowId</a>
|
||||
</td>
|
||||
<td class="sorting_1">@workflow.StartedAt</td>
|
||||
<td class="sorting_1">@workflow.FinishedAt</td>
|
||||
<td><a asp-action="Details" asp-route-id="@workflowId">@T["details"]</a> | @T["delete"]</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
@using Elsa.Models
|
||||
@using Elsa.Web.Management.ViewModels
|
||||
@inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel>
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, Elsa.Web.Components
|
||||
|
|
|
|||
|
|
@ -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<IEnumerable<Workflow>> GetManyAsync(ISpecification<Workflow, IWorkflowSpecificationVisitor> 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<IEnumerable<Workflow>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ namespace Elsa.Web.Persistence.FileSystem
|
|||
{
|
||||
var shellOptions = serviceProvider.GetRequiredService<IOptions<ShellOptions>>().Value;
|
||||
var shellSettings = serviceProvider.GetRequiredService<ShellSettings>();
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -13,27 +13,26 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.Theme.Targets" Version="1.0.0-beta3-70362"/>
|
||||
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-70362" />
|
||||
<PackageReference Include="OrchardCore.Theme.Targets" Version="1.0.0-beta3-70362" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Assets\node_modules\**"/>
|
||||
<Compile Remove="Assets\node_modules\**"/>
|
||||
<Folder Remove="Assets\node_modules\**"/>
|
||||
<EmbeddedResource Remove="Assets\node_modules\**" />
|
||||
<Compile Remove="Assets\node_modules\**" />
|
||||
<Folder Remove="Assets\node_modules\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\scripts\dashboard.ts"/>
|
||||
<Content Include="Assets\styles\dashboard.scss"/>
|
||||
<Content Include="Views\Elsa.Web.Management\Workflows\New.cshtml"/>
|
||||
<Content Include="Views\Elsa.Web.Management\Workflows\Edit.cshtml"/>
|
||||
<Content Include="Views\Elsa.Web.Management\Workflows\Index.cshtml"/>
|
||||
<Content Include="Assets\scripts\dashboard.ts" />
|
||||
<Content Include="Assets\styles\dashboard.scss" />
|
||||
<Content Include="Views\Elsa.Web.Management\Workflows\New.cshtml" />
|
||||
<Content Include="Views\Elsa.Web.Management\Workflows\Edit.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Web.Management\Elsa.Web.Management.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Web.Management\Elsa.Web.Management.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
@model ICollection<Elsa.Models.Workflow>
|
||||
|
||||
<div>
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">@T["Workflows"]</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-1">
|
||||
<nav class="admin-toolbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-sm btn-primary" asp-action="New">@T["Create Workflow"]</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table id="dataTable" class="table table-striped table-bordered dataTable" width="100%" role="grid" aria-describedby="dataTable_info" style="width: 100%;" data-turbolinks="false">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Id: activate to sort column ascending" style="width: 214px;">ID</th>
|
||||
<th class="sorting_asc" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-sort="ascending" aria-label="Name: activate to sort column descending" style="width: 281px;">Name</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Position: activate to sort column ascending" style="width: 413px;">Description</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Office: activate to sort column ascending" style="width: 214px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th rowspan="1" colspan="1">ID</th>
|
||||
<th rowspan="1" colspan="1">Name</th>
|
||||
<th rowspan="1" colspan="1">Description</th>
|
||||
<th rowspan="1" colspan="1">Actions</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
@{
|
||||
var index = 0;
|
||||
}
|
||||
@foreach (var workflowDefinition in Model)
|
||||
{
|
||||
var rowClass = index % 2 == 0 ? "even" : "odd";
|
||||
index++;
|
||||
|
||||
<tr role="row" class="@rowClass">
|
||||
<td>
|
||||
<a asp-area="Elsa.Web.Management" asp-controller="Workflows" asp-action="Edit" asp-route-id="@workflowDefinition.Metadata.Id">@workflowDefinition.Metadata.Id</a>
|
||||
</td>
|
||||
<td class="sorting_1">@workflowDefinition.Metadata.Name</td>
|
||||
<td>@workflowDefinition.Metadata.Description</td>
|
||||
<td><a asp-area="Elsa.Web.Management" asp-controller="Workflows" asp-action="Edit" asp-route-id="@workflowDefinition.Metadata.Id">edit</a> | delete | enable</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" asp-action="Index" asp-controller="Workflows" asp-area="Elsa.Web.Management">
|
||||
<a class="nav-link" asp-action="Definitions" asp-controller="Workflows" asp-area="Elsa.Web.Management">
|
||||
<span data-feather="file"></span>
|
||||
@T["Workflows"]
|
||||
</a>
|
||||
|
|
|
|||
Loading…
Reference in a new issue