Incremental work on track and trace
This commit is contained in:
parent
62585c1900
commit
cd3fb844ed
|
|
@ -19,8 +19,8 @@ namespace Elsa.Activities.Primitives.Drivers
|
|||
|
||||
protected override async Task<ActivityExecutionResult> OnExecuteAsync(SetVariable activity, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var value = await expressionEvaluator.EvaluateAsync(activity.ValueExpression, workflowContext, cancellationToken);
|
||||
workflowContext.CurrentScope.SetVariable(activity.VariableName, value);
|
||||
//var value = await expressionEvaluator.EvaluateAsync(activity.ValueExpression, workflowContext, cancellationToken);
|
||||
//workflowContext.CurrentScope.SetVariable(activity.VariableName, value);
|
||||
return TriggerEndpoint("Done");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Models;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Extensions
|
||||
{
|
||||
|
|
@ -28,5 +29,12 @@ namespace Elsa.Extensions
|
|||
|
||||
return query;
|
||||
}
|
||||
|
||||
public static LogEntry AddLogEntry(this Workflow workflow, string activityId, Instant instant, string message, bool faulted = false)
|
||||
{
|
||||
var entry = new LogEntry(activityId, instant, message, faulted);
|
||||
workflow.ExecutionLog.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,13 +5,8 @@
|
|||
public ActivityExecutionContext(IActivity activity)
|
||||
{
|
||||
Activity = activity;
|
||||
LogEntry = new LogEntry
|
||||
{
|
||||
ActivityId = activity.Id
|
||||
};
|
||||
}
|
||||
|
||||
public IActivity Activity { get; }
|
||||
public LogEntry LogEntry { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
namespace Elsa.Models
|
||||
{
|
||||
public class ActivityFault
|
||||
{
|
||||
public ActivityFault(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,24 @@
|
|||
using System.Collections.Generic;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Models
|
||||
{
|
||||
public class LogEntry
|
||||
{
|
||||
public LogEntry()
|
||||
{
|
||||
}
|
||||
|
||||
public LogEntry(string activityId, Instant timestamp, string message, bool faulted = false)
|
||||
{
|
||||
ActivityId = activityId;
|
||||
Timestamp = timestamp;
|
||||
Message = message;
|
||||
Faulted = faulted;
|
||||
}
|
||||
|
||||
public string ActivityId { get; set; }
|
||||
public Instant? ExecutedAt { get; set; }
|
||||
public Instant? HaltedAt { get; set; }
|
||||
public Instant? ResumedAt { get; set; }
|
||||
public Instant? CompletedAt { get; set; }
|
||||
public IList<string> TriggeredEndpoints { get; set; }
|
||||
public ActivityFault Fault { get; set; }
|
||||
public Instant Timestamp { get; set; }
|
||||
public bool Faulted { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Exceptions;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Models;
|
||||
using Elsa.Results;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -28,9 +29,10 @@ namespace Elsa
|
|||
{
|
||||
return await InvokeAsync(workflowContext, activity, (context, driver) =>
|
||||
{
|
||||
workflowContext.Workflow.ExecutionLog.Add(context.LogEntry);
|
||||
context.LogEntry.ExecutedAt = clock.GetCurrentInstant();
|
||||
return driver.ExecuteAsync(context, workflowContext, cancellationToken);
|
||||
workflowContext.Workflow.AddLogEntry(activity.Id, clock.GetCurrentInstant(), "Executing");
|
||||
var result = driver.ExecuteAsync(context, workflowContext, cancellationToken);
|
||||
workflowContext.Workflow.AddLogEntry(activity.Id, clock.GetCurrentInstant(), "Executed");
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -38,8 +40,10 @@ namespace Elsa
|
|||
{
|
||||
return await InvokeAsync(workflowContext, activity, (context, driver) =>
|
||||
{
|
||||
context.LogEntry.ResumedAt = clock.GetCurrentInstant();
|
||||
return driver.ResumeAsync(context, workflowContext, cancellationToken);
|
||||
workflowContext.Workflow.AddLogEntry(activity.Id, clock.GetCurrentInstant(), "Resuming");
|
||||
var result = driver.ResumeAsync(context, workflowContext, cancellationToken);
|
||||
workflowContext.Workflow.AddLogEntry(activity.Id, clock.GetCurrentInstant(), "Resumed");
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +51,10 @@ namespace Elsa
|
|||
{
|
||||
return await InvokeAsync(workflowContext, activity, (context, driver) =>
|
||||
{
|
||||
context.LogEntry.HaltedAt = clock.GetCurrentInstant();
|
||||
return driver.HaltedAsync(context, workflowContext, cancellationToken);
|
||||
workflowContext.Workflow.AddLogEntry(activity.Id, clock.GetCurrentInstant(), "Halting");
|
||||
var result = driver.HaltedAsync(context, workflowContext, cancellationToken);
|
||||
workflowContext.Workflow.AddLogEntry(activity.Id, clock.GetCurrentInstant(), "Halted");
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -70,8 +76,8 @@ namespace Elsa
|
|||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Error while invoking activity {ActivityId} of workflow {WorkflowId}", activity.Id, workflowContext.Workflow.Metadata.Id);
|
||||
activityContext.LogEntry.Fault = new ActivityFault(e.Message);
|
||||
return new FaultWorkflowResult(activityContext.LogEntry.Fault.Message, clock.GetCurrentInstant());
|
||||
workflowContext.Workflow.AddLogEntry(activity.Id, clock.GetCurrentInstant(), e.Message, true);
|
||||
return new FaultWorkflowResult(e, clock.GetCurrentInstant());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ namespace Elsa.Results
|
|||
{
|
||||
var currentActivity = workflowContext.CurrentActivity;
|
||||
|
||||
workflowContext.CurrentLogEntry.TriggeredEndpoints = EndpointNames.ToList();
|
||||
|
||||
foreach (var endpointName in EndpointNames)
|
||||
{
|
||||
workflowContext.ScheduleNextActivities(workflowContext, new SourceEndpoint(currentActivity, endpointName));
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Elsa.Serialization.Tokenizers
|
|||
private readonly ITokenizerInvoker tokenizerInvoker;
|
||||
private readonly IActivityLibrary activityLibrary;
|
||||
private readonly IClock clock;
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
private readonly JsonSerializer jsonSerializer;
|
||||
|
||||
public WorkflowTokenizer(ITokenizerInvoker tokenizerInvoker, IActivityLibrary activityLibrary, IClock clock)
|
||||
|
|
@ -29,14 +30,14 @@ namespace Elsa.Serialization.Tokenizers
|
|||
this.activityLibrary = activityLibrary;
|
||||
this.clock = clock;
|
||||
|
||||
var settings = new JsonSerializerSettings
|
||||
_serializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
}
|
||||
.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
|
||||
settings.Converters.Add(new StringEnumConverter());
|
||||
jsonSerializer = JsonSerializer.Create(settings);
|
||||
_serializerSettings.Converters.Add(new StringEnumConverter());
|
||||
jsonSerializer = JsonSerializer.Create(_serializerSettings);
|
||||
}
|
||||
|
||||
public async Task<JToken> TokenizeWorkflowAsync(Workflow value, CancellationToken cancellationToken)
|
||||
|
|
@ -266,11 +267,7 @@ namespace Elsa.Serialization.Tokenizers
|
|||
{
|
||||
var models = (JArray) token["executionLog"] ?? new JArray();
|
||||
|
||||
foreach (var model in models)
|
||||
{
|
||||
var entry = JsonConvert.DeserializeObject<LogEntry>(model.ToString(), new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb));
|
||||
yield return entry;
|
||||
}
|
||||
return JsonConvert.DeserializeObject<IList<LogEntry>>(models.ToString(), _serializerSettings);
|
||||
}
|
||||
|
||||
private IEnumerable<Connection> DeserializeConnections(JToken token, IDictionary<string, IActivity> activityDictionary)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Models;
|
||||
using OrchardCore.DisplayManagement;
|
||||
|
||||
namespace Elsa.Web.Services
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
///<reference path="source-endpoint.ts"/>
|
||||
///<reference path="target-endpoint.ts"/>
|
||||
namespace Elsa {
|
||||
export interface LogEntry {
|
||||
activityId: string;
|
||||
timestamp: Date;
|
||||
faulted: boolean;
|
||||
message: string;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,6 @@ namespace Elsa {
|
|||
return jsPlumb.getInstance({
|
||||
Anchor: "Continuous",
|
||||
DragOptions: {cursor: 'pointer', zIndex: 2000},
|
||||
//EndpointStyle: [{fillStyle: '#225588'}],
|
||||
//Endpoints: [["Dot", {radius: 7}], ["Blank"]],
|
||||
ConnectionOverlays: [
|
||||
["Arrow", {width: 12, length: 12, location: -5}],
|
||||
],
|
||||
|
|
@ -15,9 +13,9 @@ namespace Elsa {
|
|||
});
|
||||
}
|
||||
|
||||
static getSourceEndpointOptions(activityId: any, endpointName: any): any {
|
||||
const fill = '#7da7f2';
|
||||
const stroke = '#7da7f2';
|
||||
static getSourceEndpointOptions(activityId: any, endpointName: any, hasExecuted: boolean, hasFaulted: boolean): any {
|
||||
const fill = hasFaulted ? '#d23c3c' : hasExecuted? '#6faa44' : '#7da7f2';
|
||||
const stroke = fill;
|
||||
return {
|
||||
endpoint: 'Dot',
|
||||
anchor: 'Continuous',
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@
|
|||
///<reference path="activity-descriptor.ts"/>
|
||||
///<reference path="workflow.ts"/>
|
||||
///<reference path="activity-info.ts"/>
|
||||
///<reference path="log-entry.ts"/>
|
||||
|
||||
namespace Elsa {
|
||||
export class WorkflowDesigner {
|
||||
private readonly container: JQuery<HTMLElement>;
|
||||
private readonly canvasContainer: JQuery<HTMLElement>;
|
||||
private readonly isDefinition: boolean;
|
||||
private readonly executionLog: Array<LogEntry>;
|
||||
private readonly plumber: any;
|
||||
private dragStart: { left: number; top: number };
|
||||
private hasDragged: boolean;
|
||||
|
|
@ -18,6 +21,8 @@ namespace Elsa {
|
|||
constructor(containerElement: HTMLElement) {
|
||||
this.container = $(containerElement);
|
||||
this.canvasContainer = this.container.find('.workflow-canvas');
|
||||
this.isDefinition = this.canvasContainer.data('workflow-is-definition');
|
||||
this.executionLog = this.canvasContainer.data('workflow-execution-log');
|
||||
this.plumber = WorkflowDesignerConfig.createJsPlumbInstance(this.canvasContainer[0]);
|
||||
this.initializeNodes();
|
||||
this.container.on('contextmenu', '.workflow-canvas', this.onCanvasContextMenu);
|
||||
|
|
@ -137,7 +142,7 @@ namespace Elsa {
|
|||
};
|
||||
|
||||
private showContextMenu = (menu: JQuery<HTMLElement>, e: JQuery.Event<HTMLElement>) => {
|
||||
if (e.isDefaultPrevented())
|
||||
if (e.isDefaultPrevented() || !this.isDefinition)
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
|
|
@ -169,23 +174,25 @@ namespace Elsa {
|
|||
const activityId: string = activityElement.data('activity-id');
|
||||
const activityEndpoints: any[] = activityElement.data('activity-endpoints');
|
||||
|
||||
this.plumber.draggable(activityElement, {
|
||||
containment: true,
|
||||
start: (args: any) => {
|
||||
this.dragStart = {left: args.e.screenX, top: args.e.screenY};
|
||||
},
|
||||
stop: (args: any) => {
|
||||
this.hasDragged = this.dragStart.left !== args.e.screenX || this.dragStart.top !== args.e.screenY;
|
||||
if(this.isDefinition) {
|
||||
this.plumber.draggable(activityElement, {
|
||||
containment: true,
|
||||
start: (args: any) => {
|
||||
this.dragStart = {left: args.e.screenX, top: args.e.screenY};
|
||||
},
|
||||
stop: (args: any) => {
|
||||
this.hasDragged = this.dragStart.left !== args.e.screenX || this.dragStart.top !== args.e.screenY;
|
||||
|
||||
if (!this.hasDragged) {
|
||||
return;
|
||||
if (!this.hasDragged) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateActivityModel(activityElement, activity => {
|
||||
this.setActivityPosition(activity, args.pos[0], args.pos[1]);
|
||||
});
|
||||
}
|
||||
|
||||
this.updateActivityModel(activityElement, activity => {
|
||||
this.setActivityPosition(activity, args.pos[0], args.pos[1]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.plumber.makeTarget(activityElement, {
|
||||
dropOptions: {hoverClass: 'hover'},
|
||||
|
|
@ -209,23 +216,17 @@ namespace Elsa {
|
|||
private restoreConnections = (activityElement: HTMLElement) => {
|
||||
const activity: IActivity = $(activityElement).data('activity-model');
|
||||
const connections: IConnection[] = $(this.canvasContainer).data('workflow-connections');
|
||||
const plumber = this.plumber;
|
||||
const activityConnections = connections.filter(x => x.target.activityId == activity.id || x.source.activityId == activity.id);
|
||||
|
||||
for (let connection of activityConnections) {
|
||||
const sourceEndpointUuid: string = `${connection.source.activityId}-${connection.source.name}`;
|
||||
const sourceEndpoint: Endpoint = plumber.getEndpoint(sourceEndpointUuid);
|
||||
const destinationElementId: string = `activity-${connection.target.activityId}`;
|
||||
|
||||
plumber.connect({
|
||||
source: sourceEndpoint,
|
||||
target: destinationElementId
|
||||
});
|
||||
}
|
||||
this.createConnectionsInternal(activityConnections);
|
||||
};
|
||||
|
||||
private createConnections = () => {
|
||||
const connections: IConnection[] = $(this.canvasContainer).data('workflow-connections');
|
||||
this.createConnectionsInternal(connections);
|
||||
};
|
||||
|
||||
private createConnectionsInternal = (connections: IConnection[]) => {
|
||||
const plumber = this.plumber;
|
||||
|
||||
for (let connection of connections) {
|
||||
|
|
@ -241,8 +242,12 @@ namespace Elsa {
|
|||
};
|
||||
|
||||
private addSourceEndpoints = (activityElement: HTMLElement, activityId: string, endpoints: any[]) => {
|
||||
const $activityElement = $(activityElement);
|
||||
|
||||
for (let endpoint of endpoints) {
|
||||
const sourceEndpointOptions: any = WorkflowDesignerConfig.getSourceEndpointOptions(activityId, endpoint.name);
|
||||
const hasExecuted: boolean = $activityElement.data('activity-executed');
|
||||
const hasFaulted: boolean = $activityElement.data('activity-faulted');
|
||||
const sourceEndpointOptions: any = WorkflowDesignerConfig.getSourceEndpointOptions(activityId, endpoint.name, hasExecuted, hasFaulted);
|
||||
this.plumber.addEndpoint(activityElement, {
|
||||
connectorOverlays: [['Label', {
|
||||
label: endpoint.name,
|
||||
|
|
|
|||
|
|
@ -30,12 +30,20 @@
|
|||
-o-transition: -o-box-shadow 0.15s ease-in;
|
||||
transition: box-shadow 0.15s ease-in;
|
||||
|
||||
&.activity-executed {
|
||||
border: 1px solid #6faa44;
|
||||
}
|
||||
|
||||
&.activity-start, &.activity-blocking {
|
||||
background-color: #f1f1f1;
|
||||
color: #3d3d3d;
|
||||
border: 1px solid #5a8fee;
|
||||
}
|
||||
|
||||
&.activity-faulted {
|
||||
border: 1px solid #d23c3c;
|
||||
}
|
||||
|
||||
header {
|
||||
h4 {
|
||||
font-size: 1.4em;
|
||||
|
|
|
|||
|
|
@ -34,4 +34,4 @@
|
|||
<None Remove="Assets\scripts\contextmenu\contextmenu.js" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Models;
|
||||
using Elsa.Web.Components.Models;
|
||||
using Elsa.Web.Services;
|
||||
using OrchardCore.DisplayManagement;
|
||||
|
|
|
|||
|
|
@ -12,34 +12,34 @@ namespace Elsa.Web.Components.ViewComponents
|
|||
{
|
||||
public class WorkflowDesigner : ViewComponent
|
||||
{
|
||||
private readonly IWorkflowStore workflowStore;
|
||||
private readonly IActivityShapeFactory activityShapeFactory;
|
||||
|
||||
public WorkflowDesigner(
|
||||
IWorkflowStore workflowStore,
|
||||
IActivityShapeFactory activityShapeFactory)
|
||||
public WorkflowDesigner( IActivityShapeFactory activityShapeFactory)
|
||||
{
|
||||
this.workflowStore = workflowStore;
|
||||
this.activityShapeFactory = activityShapeFactory;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(Workflow workflow, CancellationToken cancellationToken)
|
||||
{
|
||||
var activities = workflow.Activities;
|
||||
var activityShapes = await BuildActivityShapesAsync(activities, cancellationToken);
|
||||
var activityShapes = await BuildActivityShapesAsync(workflow, cancellationToken);
|
||||
var viewModel = new WorkflowDesignerViewModel(workflow, activityShapes);
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
private async Task<ICollection<dynamic>> BuildActivityShapesAsync(IEnumerable<IActivity> activities, CancellationToken cancellationToken)
|
||||
private async Task<ICollection<dynamic>> BuildActivityShapesAsync(Workflow workflow, CancellationToken cancellationToken)
|
||||
{
|
||||
return await Task.WhenAll(activities.Select((x, i) => BuildActivityShapeAsync(x, cancellationToken)));
|
||||
return await Task.WhenAll(workflow.Activities.Select((x, i) => BuildActivityShapeAsync(workflow, x, cancellationToken)));
|
||||
}
|
||||
|
||||
private async Task<dynamic> BuildActivityShapeAsync(IActivity activity, CancellationToken cancellationToken)
|
||||
private async Task<dynamic> BuildActivityShapeAsync(Workflow workflow, IActivity activity, CancellationToken cancellationToken)
|
||||
{
|
||||
return await activityShapeFactory.BuildDesignShapeAsync(activity, cancellationToken);
|
||||
var shape = (dynamic)await activityShapeFactory.BuildDesignShapeAsync(activity, cancellationToken);
|
||||
var logEntries = workflow.ExecutionLog.Where(x => x.ActivityId == activity.Id).OrderByDescending(x => x.Timestamp).ToList();
|
||||
|
||||
shape.LogEntries = logEntries;
|
||||
|
||||
return shape;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,26 +6,46 @@
|
|||
@using Newtonsoft.Json.Serialization
|
||||
@model dynamic
|
||||
@{
|
||||
var activity = (IActivity)Model.Activity;
|
||||
var activity = (IActivity) Model.Activity;
|
||||
var descriptor = (ActivityDescriptor) Model.ActivityDescriptor;
|
||||
var id = activity.Id;
|
||||
var endpoints = (IList<LocalizedString>)Model.Endpoints;
|
||||
var endpoints = (IList<LocalizedString>) Model.Endpoints;
|
||||
var serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||
var activityJson = JsonConvert.SerializeObject(activity, serializerSettings);
|
||||
var activityEndpoints = JsonConvert.SerializeObject(endpoints, serializerSettings);
|
||||
var designer = (ActivityDesignerMetadata)Model.Designer;
|
||||
var x = designer.X;
|
||||
var y = designer.Y;
|
||||
var designer = (ActivityDesignerMetadata) Model.Designer;
|
||||
var logEntries = Model.LogEntries != null ? (ICollection<LogEntry>)Model.LogEntries : new List<LogEntry>();
|
||||
var logEntriesJson = JsonConvert.SerializeObject(logEntries, serializerSettings);
|
||||
var hasFaulted = logEntries.Any(x => x.Faulted);
|
||||
var hasExecuted = logEntries.Any();
|
||||
var cssClasses = new List<string> { "activity" };
|
||||
var left = designer.X;
|
||||
var top = designer.Y;
|
||||
|
||||
if (hasExecuted)
|
||||
{
|
||||
cssClasses.Add("activity-executed");
|
||||
}
|
||||
|
||||
if (hasFaulted)
|
||||
{
|
||||
cssClasses.Add("activity-faulted");
|
||||
}
|
||||
|
||||
var cssClass = string.Join(" ", cssClasses);
|
||||
}
|
||||
<div
|
||||
class="activity"
|
||||
style="left:@(x)px; top:@(y)px;"
|
||||
<div
|
||||
class="@cssClass"
|
||||
style="left: @(left)px; top: @(top)px;"
|
||||
id="@($"activity-{id}")"
|
||||
title="@descriptor.DisplayText"
|
||||
data-activity-name="@descriptor.Name"
|
||||
data-activity-id="@id"
|
||||
data-activity-id="@id"
|
||||
data-activity-model="@activityJson"
|
||||
data-activity-endpoints="@activityEndpoints">
|
||||
data-activity-endpoints="@activityEndpoints"
|
||||
data-activity-executed="@hasExecuted.ToString().ToLower()"
|
||||
data-activity-faulted="@hasFaulted.ToString().ToLower()"
|
||||
data-activity-log="@logEntriesJson">
|
||||
@await DisplayAsync(Model.Content)
|
||||
<div class="dropdown-menu context-menu activity-context-menu">
|
||||
<a class="dropdown-item" href="#edit">Edit</a>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
@using Elsa.Extensions
|
||||
@using Newtonsoft.Json
|
||||
@using Newtonsoft.Json.Serialization
|
||||
@model Elsa.Web.Components.ViewModels.WorkflowDesignerViewModel
|
||||
@model WorkflowDesignerViewModel
|
||||
@{
|
||||
var serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||
var workflowMetadataJson = JsonConvert.SerializeObject(Model.Workflow.Metadata, serializerSettings);
|
||||
var connectionsJson = JsonConvert.SerializeObject(Model.Connections, serializerSettings);
|
||||
var executionLogJson = JsonConvert.SerializeObject(Model.Workflow.ExecutionLog);
|
||||
}
|
||||
<div id="elsa-workflow-designer">
|
||||
<div class="workflow-canvas"
|
||||
data-workflow-is-definition="@Model.Workflow.IsDefinition().ToString().ToLower()"
|
||||
data-workflow-metadata="@workflowMetadataJson"
|
||||
data-workflow-status="@Model.Workflow.Status"
|
||||
data-workflow-connections="@connectionsJson">
|
||||
data-workflow-connections="@connectionsJson"
|
||||
data-workflow-execution-log="@executionLogJson">
|
||||
@foreach (var activityShape in Model.ActivityShapes)
|
||||
{
|
||||
@await DisplayAsync(activityShape)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ namespace Elsa.Web.Management.Controllers
|
|||
return View(workflow);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/update")]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(string id, [FromBody] JToken workflowData, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflow = await workflowSerializer.DeserializeAsync(workflowData, cancellationToken);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Elsa.Web.Management.ViewComponents
|
||||
{
|
||||
public class WorkflowEditor : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(Workflow workflow)
|
||||
{
|
||||
return View(workflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,40 @@
|
|||
@model Elsa.Models.Workflow
|
||||
@using Elsa.Extensions
|
||||
@model Workflow
|
||||
@{
|
||||
var title = Model.Metadata.Id == null ? T["New Workflow"] : @T["Edit {0}", Model.Metadata.Name];
|
||||
var title = Model.Metadata.Id == null ? T["New Workflow"] : T["Edit {0}", Model.Metadata.Name];
|
||||
var isDefinition = Model.IsDefinition();
|
||||
}
|
||||
<div class="workflow-editor-container" data-workflow-id="@Model.Metadata.Id">
|
||||
<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">@title</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-1">
|
||||
<nav class="admin-toolbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<button type="button" class="add-trigger-button btn btn-sm btn-secondary" data-toggle="modal" data-target="#elsa-activity-picker" data-picker-title="@T["Available Triggers"]" data-activity-trigger="true">@T["Add Trigger"]</button>
|
||||
<button type="button" class="add-action-button btn btn-sm btn-secondary" data-toggle="modal" data-target="#elsa-activity-picker" data-picker-title="@T["Available Actions"]" data-activity-trigger="false">@T["Add Action"]</button>
|
||||
</li>
|
||||
</ul>
|
||||
@if (isDefinition)
|
||||
{
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<button type="button" class="add-trigger-button btn btn-sm btn-secondary" data-toggle="modal" data-target="#elsa-activity-picker" data-picker-title="@T["Available Triggers"]" data-activity-trigger="true">@T["Add Trigger"]</button>
|
||||
<button type="button" class="add-action-button btn btn-sm btn-secondary" data-toggle="modal" data-target="#elsa-activity-picker" data-picker-title="@T["Available Actions"]" data-activity-trigger="false">@T["Add Action"]</button>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@await Component.InvokeAsync("WorkflowDesigner", new { workflow = Model })
|
||||
@await Component.InvokeAsync("ActivityPicker")
|
||||
@await Component.InvokeAsync("ActivityEditor")
|
||||
|
||||
|
||||
@if (isDefinition)
|
||||
{
|
||||
@await Component.InvokeAsync("ActivityPicker")
|
||||
@await Component.InvokeAsync("ActivityEditor")
|
||||
}
|
||||
|
||||
<div class="fixed-bottom p-3 m-3 clearfix">
|
||||
<div class="float-right">
|
||||
<button id="save-workflow-button" type="button" class="btn btn-sm btn-primary">Save</button>
|
||||
@if (isDefinition)
|
||||
{
|
||||
<button id="save-workflow-button" type="button" class="btn btn-sm btn-primary">Save</button>
|
||||
}
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@T["Export"]
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
@model Elsa.Models.Workflow
|
||||
@{
|
||||
var title = Model.Metadata.Id == null ? T["New Workflow"] : @T["Edit {0}", Model.Metadata.Name];
|
||||
}
|
||||
<div class="workflow-editor-container" data-workflow-id="@Model.Metadata.Id">
|
||||
<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">@title</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-1">
|
||||
<button type="button" class="btn btn-sm btn-primary">@T["Add Activity"]</button>
|
||||
</div>
|
||||
</div>
|
||||
@await Component.InvokeAsync("WorkflowDesigner", new { workflowId = Model.Metadata.Id })
|
||||
</div>
|
||||
<script asp-name="elsa-workflow-editor-script" asp-src="~/Elsa.Web.Management/workflow-editor.js" at="Foot"></script>
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
@model Workflow
|
||||
@Html.Partial("WorkflowEditor")
|
||||
<vc:workflow-editor workflow="@Model"></vc:workflow-editor>
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
@model Workflow
|
||||
@Html.Partial("WorkflowEditor")
|
||||
<vc:workflow-editor workflow="@Model"></vc:workflow-editor>
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
@model Elsa.Models.Workflow
|
||||
@Html.Partial("WorkflowEditor")
|
||||
@model Workflow
|
||||
<vc:workflow-editor workflow="@Model"></vc:workflow-editor>
|
||||
|
|
@ -3,5 +3,6 @@
|
|||
@inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel>
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, Elsa.Web.Components
|
||||
@addTagHelper *, Elsa.Web.Management
|
||||
@addTagHelper *, OrchardCore.ResourceManagement
|
||||
@addTagHelper *, OrchardCore.DisplayManagement
|
||||
|
|
@ -27,12 +27,14 @@
|
|||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Web.Management\Elsa.Web.Management.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<RazorGenerate Remove="Views\Elsa.Web.Management\Shared\WorkflowEditor.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@model Elsa.Models.Workflow
|
||||
@Html.Partial("WorkflowEditor")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@model Elsa.Models.Workflow
|
||||
@Html.Partial("WorkflowEditor")
|
||||
Loading…
Reference in a new issue