Incremental work on track and trace

This commit is contained in:
Sipke Schoorstra 2019-01-11 16:40:45 +01:00
parent 62585c1900
commit cd3fb844ed
29 changed files with 196 additions and 139 deletions

View file

@ -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");
}
}

View file

@ -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;
}
}
}

View file

@ -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; }
}
}

View file

@ -1,12 +0,0 @@
namespace Elsa.Models
{
public class ActivityFault
{
public ActivityFault(string message)
{
Message = message;
}
public string Message { get; set; }
}
}

View file

@ -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; }
}
}

View file

@ -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());
}
}
}

View file

@ -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));

View file

@ -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)

View file

@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using OrchardCore.DisplayManagement;
namespace Elsa.Web.Services

View file

@ -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;
}
}

View file

@ -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',

View file

@ -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,

View file

@ -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;

View file

@ -34,4 +34,4 @@
<None Remove="Assets\scripts\contextmenu\contextmenu.js" />
</ItemGroup>
</Project>
</Project>

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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)

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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"]

View file

@ -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>

View file

@ -1,2 +1,2 @@
@model Workflow
@Html.Partial("WorkflowEditor")
<vc:workflow-editor workflow="@Model"></vc:workflow-editor>

View file

@ -1,2 +1,2 @@
@model Workflow
@Html.Partial("WorkflowEditor")
<vc:workflow-editor workflow="@Model"></vc:workflow-editor>

View file

@ -1,2 +1,2 @@
@model Elsa.Models.Workflow
@Html.Partial("WorkflowEditor")
@model Workflow
<vc:workflow-editor workflow="@Model"></vc:workflow-editor>

View file

@ -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

View file

@ -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>

View file

@ -1,2 +0,0 @@
@model Elsa.Models.Workflow
@Html.Partial("WorkflowEditor")

View file

@ -1,2 +0,0 @@
@model Elsa.Models.Workflow
@Html.Partial("WorkflowEditor")