Remove & merge Node base type

This commit is contained in:
Sipke Schoorstra 2022-03-15 13:01:10 +01:00
parent 059356029d
commit 1c47d4b80d
43 changed files with 183 additions and 160 deletions

View file

@ -35,7 +35,7 @@ public class Join : Activity
var workflowExecutionContext = context.WorkflowExecutionContext;
// Record last executed activity ID.
var lastActivityId = context.WorkflowExecutionContext.ExecutionLog.LastOrDefault(x => x.EventName == WorkflowExecutionLogEventNames.Executed)?.NodeId;
var lastActivityId = context.WorkflowExecutionContext.ExecutionLog.LastOrDefault(x => x.EventName == WorkflowExecutionLogEventNames.Executed)?.ActivityId;
if (lastActivityId != null)
{

View file

@ -18,10 +18,10 @@ public class OutboundActivityNodeResolver : IActivityNodeResolver
private static IEnumerable<IActivity?> GetSinglePorts(IActivity activity)
{
var nodeType = activity.GetType();
var activityType = activity.GetType();
var ports =
from prop in nodeType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
from prop in activityType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
where typeof(IActivity).IsAssignableFrom(prop.PropertyType) || typeof(IEnumerable<IActivity>).IsAssignableFrom(prop.PropertyType)
let portAttr = prop.GetCustomAttribute<OutboundAttribute>()
let nodeAttr = prop.GetCustomAttribute<NodeAttribute>()

View file

@ -104,9 +104,9 @@ public class WorkflowExecutionContext
public void RemoveCompletionCallback(ActivityCompletionCallbackEntry entry) => _completionCallbackEntries.Remove(entry);
public ActivityNode FindNodeById(string nodeId) => NodeIdLookup[nodeId];
public ActivityNode FindActivityNodeById(string nodeId) => NodeIdLookup[nodeId];
public ActivityNode FindNodeByActivity(IActivity activity) => NodeActivityLookup[activity];
public IActivity FindActivityById(string activityId) => FindNodeById(activityId).Activity;
public IActivity FindActivityById(string activityId) => FindActivityNodeById(activityId).Activity;
public T? GetProperty<T>(string key) => Properties.TryGetValue(key, out var value) ? (T?)value : default(T);
public void SetProperty<T>(string key, T value) => Properties[key] = value;

View file

@ -1,3 +1,3 @@
namespace Elsa.Models;
public record WorkflowExecutionLogEntry(string NodeId, string NodeType, DateTimeOffset Timestamp, string? EventName, string? Message, string? Source, object? Payload);
public record WorkflowExecutionLogEntry(string ActivityId, string ActivityType, DateTimeOffset Timestamp, string? EventName, string? Message, string? Source, object? Payload);

View file

@ -78,16 +78,16 @@ public class IdentityGraphService : IIdentityGraphService
return name;
}
private int GetNextIndexFor(string nodeType, IDictionary<string, int> identityCounters)
private int GetNextIndexFor(string activityType, IDictionary<string, int> identityCounters)
{
if (!identityCounters.TryGetValue(nodeType, out var index))
if (!identityCounters.TryGetValue(activityType, out var index))
{
identityCounters[nodeType] = index;
identityCounters[activityType] = index;
}
else
{
index = identityCounters[nodeType] + 1;
identityCounters[nodeType] = index;
index = identityCounters[activityType] + 1;
identityCounters[activityType] = index;
}
return index;

View file

@ -66,17 +66,17 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
private void SetOutput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
{
foreach (var nodeEntry in state.ActivityOutput)
foreach (var entry in state.ActivityOutput)
{
var nodeId = nodeEntry.Key;
var node = workflowExecutionContext.FindNodeById(nodeId);
var nodeType = node.GetType();
var activityId = entry.Key;
var node = workflowExecutionContext.FindActivityNodeById(activityId);
var activityType = node.Activity.GetType();
foreach (var outputEntry in nodeEntry.Value)
foreach (var outputEntry in entry.Value)
{
var propertyName = outputEntry.Key;
var propertyValue = outputEntry.Value;
var propertyInfo = nodeType.GetProperty(propertyName, BindingFlags.Public)!;
var propertyInfo = activityType.GetProperty(propertyName, BindingFlags.Public)!;
propertyInfo.SetValue(node, propertyValue);
}
}
@ -87,7 +87,7 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
foreach (var completionCallbackEntry in state.CompletionCallbacks)
{
var owner = workflowExecutionContext.ActivityExecutionContexts.First(x => x.Id == completionCallbackEntry.OwnerId);
var child = workflowExecutionContext.FindNodeById(completionCallbackEntry.ChildId).Activity;
var child = workflowExecutionContext.FindActivityNodeById(completionCallbackEntry.ChildId).Activity;
var callbackName = completionCallbackEntry.MethodName;
var callbackDelegate = owner.Activity.GetActivityCompletionCallback(callbackName);
workflowExecutionContext.AddCompletionCallback(owner, child, callbackDelegate);

View file

@ -4,8 +4,15 @@ using Elsa.Contracts;
namespace Elsa.Management.Models;
public class ActivityDescriptor : NodeDescriptor
public class ActivityDescriptor
{
public string ActivityType { get; init; } = default!;
public string Category { get; init; } = default!;
public string? DisplayName { get; init; }
public string? Description { get; init; }
public ICollection<InputDescriptor> InputProperties { get; init; } = new List<InputDescriptor>();
public ICollection<OutputDescriptor> OutputProperties { get; init; } = new List<OutputDescriptor>();
[JsonIgnore] public Func<ActivityConstructorContext, IActivity> Constructor { get; init; } = default!;
public ActivityTraits Traits { get; set; } = ActivityTraits.Action;
public ICollection<Port> InPorts { get; init; } = new List<Port>();

View file

@ -1,11 +0,0 @@
namespace Elsa.Management.Models;
public abstract class NodeDescriptor
{
public string NodeType { get; init; } = default!;
public string Category { get; init; } = default!;
public string? DisplayName { get; init; }
public string? Description { get; init; }
public ICollection<InputDescriptor> InputProperties { get; init; } = new List<InputDescriptor>();
public ICollection<OutputDescriptor> OutputProperties { get; init; } = new List<OutputDescriptor>();
}

View file

@ -1,12 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Contracts;
namespace Elsa.Management.Models;
public class TriggerDescriptor : NodeDescriptor
{
[JsonIgnore] public Func<TriggerConstructorContext, IEventGenerator> Constructor { get; init; } = default!;
}
public record TriggerConstructorContext(JsonElement Element, JsonSerializerOptions SerializerOptions);

View file

@ -25,7 +25,7 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
if (!JsonDocument.TryParseValue(ref reader, out var doc))
throw new JsonException("Failed to parse JsonDocument");
if (!doc.RootElement.TryGetProperty("nodeType", out var activityTypeNameElement))
if (!doc.RootElement.TryGetProperty("typeName", out var activityTypeNameElement))
throw new JsonException("Failed to extract activity type property");
var activityTypeName = activityTypeNameElement.GetString()!;

View file

@ -54,12 +54,12 @@ public class FlowchartJsonConverter : JsonConverter<Flowchart>
var model = new
{
NodeType = value.TypeName,
Id = value.Id,
Metadata = value.Metadata,
value.TypeName,
value.Id,
value.Metadata,
Start = value.Start?.Id,
Activities = value.Activities,
Connections = value.Connections
value.Activities,
value.Connections
};
JsonSerializer.Serialize(writer, model, connectionSerializerOptions);

View file

@ -57,7 +57,7 @@ public class ActivityDescriber : IActivityDescriber
{
Category = category,
Description = description,
NodeType = fullTypeName,
ActivityType = fullTypeName,
DisplayName = displayName,
Traits = isTrigger ? ActivityTraits.Trigger : ActivityTraits.Action,
OutPorts = outboundPorts.ToList(),
@ -106,7 +106,8 @@ public class ActivityDescriber : IActivityDescriber
foreach (var propertyInfo in properties)
{
var activityPropertyAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
var wrappedPropertyType = propertyInfo.PropertyType.GenericTypeArguments[0];
var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
yield return new OutputDescriptor
(

View file

@ -29,7 +29,7 @@ public class ActivityRegistry : IActivityRegistry
var descriptors = ListByProvider(provider).ToList();
foreach (var descriptor in descriptors)
_activityDescriptors.Remove(descriptor.NodeType);
_activityDescriptors.Remove(descriptor.ActivityType);
_providedActivityDescriptors.Remove(provider);
}
@ -41,7 +41,7 @@ public class ActivityRegistry : IActivityRegistry
private void Add(ActivityDescriptor descriptor, ICollection<ActivityDescriptor> target)
{
_activityDescriptors.Add(descriptor.NodeType, descriptor);
_activityDescriptors.Add(descriptor.ActivityType, descriptor);
target.Add(descriptor);
}

View file

@ -9,7 +9,7 @@ import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDescriptor, Acti
import { ActivityUpdatedArgs, DeleteActivityRequestedArgs } from "./components/designer/activity-properties-editor/activity-properties-editor";
import { ContainerActivityComponent } from "./components/activities/container-activity-component";
import { AddActivityArgs } from "./components/designer/canvas/canvas";
import { NodeInputContext } from "./services/node-input-driver";
import { ActivityInputContext } from "./services/node-input-driver";
import { MenuItem } from "./components/shared/context-menu/models";
import { DropdownButtonItem, DropdownButtonOrigin } from "./components/shared/dropdown-button/models";
import { Graph } from "@antv/x6";
@ -38,10 +38,10 @@ export namespace Components {
"updateLayout": () => Promise<void>;
}
interface ElsaCheckListInput {
"inputContext": NodeInputContext;
"inputContext": ActivityInputContext;
}
interface ElsaCodeEditorInput {
"inputContext": NodeInputContext;
"inputContext": ActivityInputContext;
}
interface ElsaContextMenu {
"menuItems": Array<MenuItem>;
@ -53,7 +53,7 @@ export namespace Components {
"text": string;
}
interface ElsaDropdownInput {
"inputContext": NodeInputContext;
"inputContext": ActivityInputContext;
}
interface ElsaFlowchart {
"activityDescriptors": Array<ActivityDescriptor>;
@ -112,10 +112,10 @@ export namespace Components {
"value": string;
}
interface ElsaMultiLineInput {
"inputContext": NodeInputContext;
"inputContext": ActivityInputContext;
}
interface ElsaMultiTextInput {
"inputContext": NodeInputContext;
"inputContext": ActivityInputContext;
}
interface ElsaPager {
"page": number;
@ -126,10 +126,10 @@ export namespace Components {
"position": PanelPosition;
}
interface ElsaRadioListInput {
"inputContext": NodeInputContext;
"inputContext": ActivityInputContext;
}
interface ElsaSingleLineInput {
"inputContext": NodeInputContext;
"inputContext": ActivityInputContext;
}
interface ElsaSlideOverPanel {
"actions": Array<ActionDefinition>;
@ -145,7 +145,7 @@ export namespace Components {
"serverUrl": string;
}
interface ElsaSwitchEditor {
"inputContext": NodeInputContext;
"inputContext": ActivityInputContext;
}
interface ElsaToolbox {
"graph": Graph;
@ -423,10 +423,10 @@ declare namespace LocalJSX {
"interactiveMode"?: boolean;
}
interface ElsaCheckListInput {
"inputContext"?: NodeInputContext;
"inputContext"?: ActivityInputContext;
}
interface ElsaCodeEditorInput {
"inputContext"?: NodeInputContext;
"inputContext"?: ActivityInputContext;
}
interface ElsaContextMenu {
"menuItems"?: Array<MenuItem>;
@ -439,7 +439,7 @@ declare namespace LocalJSX {
"text"?: string;
}
interface ElsaDropdownInput {
"inputContext"?: NodeInputContext;
"inputContext"?: ActivityInputContext;
}
interface ElsaFlowchart {
"activityDescriptors"?: Array<ActivityDescriptor>;
@ -504,10 +504,10 @@ declare namespace LocalJSX {
"value"?: string;
}
interface ElsaMultiLineInput {
"inputContext"?: NodeInputContext;
"inputContext"?: ActivityInputContext;
}
interface ElsaMultiTextInput {
"inputContext"?: NodeInputContext;
"inputContext"?: ActivityInputContext;
}
interface ElsaPager {
"onPaginated"?: (event: CustomEvent<PagerData>) => void;
@ -520,10 +520,10 @@ declare namespace LocalJSX {
"position"?: PanelPosition;
}
interface ElsaRadioListInput {
"inputContext"?: NodeInputContext;
"inputContext"?: ActivityInputContext;
}
interface ElsaSingleLineInput {
"inputContext"?: NodeInputContext;
"inputContext"?: ActivityInputContext;
}
interface ElsaSlideOverPanel {
"actions"?: Array<ActionDefinition>;
@ -539,7 +539,7 @@ declare namespace LocalJSX {
"serverUrl"?: string;
}
interface ElsaSwitchEditor {
"inputContext"?: NodeInputContext;
"inputContext"?: ActivityInputContext;
}
interface ElsaToolbox {
"graph"?: Graph;

View file

@ -62,9 +62,9 @@ function walkRecursive(node: ActivityNode, activity: Activity, collectedActiviti
function getPorts(node: ActivityNode, activity: Activity, descriptors: Array<ActivityDescriptor>): Array<ActivityPort> {
const portProviderRegistry = Container.get(PortProviderRegistry);
const transposeHandlerRegistry = Container.get(TransposeHandlerRegistry);
const transposeHandler = transposeHandlerRegistry.get(activity.nodeType);
const transposeHandler = transposeHandlerRegistry.get(activity.typeName);
const portProvider = portProviderRegistry.get(activity.nodeType);
const activityDescriptor = descriptors.find(x => x.nodeType == activity.nodeType);
const activityDescriptor = descriptors.find(x => x.activityType == activity.typeName);
const untransposedConnections = transposeHandler.untranspose({activity, activityDescriptor});
return untransposedConnections.map(x => ({activity: x.target, port: x.sourcePort}));
}

View file

@ -15,7 +15,7 @@ export class DefaultNodeHandler implements ActivityNodeHandler {
createDesignerNode(context: CreateUINodeContext): Node.Metadata {
const {activityDescriptor, activity, x, y} = context;
const provider = this.portProviderRegistry.get(activityDescriptor.nodeType);
const provider = this.portProviderRegistry.get(activityDescriptor.activityType);
const providerContext: PortProviderContext = {activityDescriptor, activity};
let inPorts = provider.getInboundPorts(providerContext);
let outPorts = provider.getOutboundPorts(providerContext);

View file

@ -68,7 +68,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
const activity: Activity = {
id: uuid(),
nodeType: descriptor.nodeType,
typeName: descriptor.activityType,
metadata: {
designer: {
position: {
@ -161,9 +161,9 @@ export class FlowchartComponent implements ContainerActivityComponent {
for (const connection of connections) {
const source = activities.find(x => x.id == connection.source);
const target = activities.find(x => x.id == connection.target);
const sourceDescriptor = activityDescriptors.find(x => x.nodeType == source.nodeType);
const targetDescriptor = activityDescriptors.find(x => x.nodeType == target.nodeType);
const transposeHandler = transposeHandlerRegistry.get(source.nodeType);
const sourceDescriptor = activityDescriptors.find(x => x.activityType == source.typeName);
const targetDescriptor = activityDescriptors.find(x => x.activityType == target.typeName);
const transposeHandler = transposeHandlerRegistry.get(source.typeName);
if (transposeHandler.transpose({source, target, connection, sourceDescriptor, targetDescriptor})) {
// Remove the target activity from the list.
@ -175,7 +175,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
}
return {
nodeType: 'Workflows.Flowchart',
typeName: 'Workflows.Flowchart',
activities: remainingActivities,
connections: remainingConnections,
id: this.rootId,
@ -207,7 +207,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
const activity = activityNode.activity;
const position = activity.metadata.designer?.position || {x: 100, y: 100};
const {x, y} = position;
const descriptor = descriptors.find(x => x.nodeType == activity.nodeType)
const descriptor = descriptors.find(x => x.activityType == activity.typeName)
return this.nodeFactory.createNode(descriptor, activity, x, y);
});

View file

@ -13,7 +13,7 @@ export class NodeFactory {
}
public createNode(activityDescriptor: ActivityDescriptor, activity: Activity, x: number, y: number): Node.Metadata {
const handler = this.handlerRegistry.createHandler(activityDescriptor.nodeType);
const handler = this.handlerRegistry.createHandler(activityDescriptor.activityType);
return handler.createDesignerNode({activityDescriptor, activity, x, y});
}
}

View file

@ -71,7 +71,7 @@ export class ActivityNode extends Shape.HTML {
createHtml() {
const activityDescriptor = this.activityDescriptor as ActivityDescriptor;
const activity = this.activity as Activity;
const activityType = activityDescriptor.nodeType;
const activityType = activityDescriptor.activityType;
const driverRegistry = Container.get(ActivityDriverRegistry);
const driver = driverRegistry.createDriver(activityType);

View file

@ -5,14 +5,14 @@ import {
Activity,
ActivityDescriptor,
DefaultActions, InputDescriptor,
RenderNodePropContext,
RenderNodePropsContext,
RenderActivityPropContext,
RenderActivityPropsContext,
TabChangedArgs,
TabDefinition
} from '../../../models';
import {InputDriverRegistry} from "../../../services";
import {Container} from "typedi";
import {NodeInputContext} from "../../../services/node-input-driver";
import {ActivityInputContext} from "../../../services/node-input-driver";
import {FormEntry} from "../../shared/forms/form-entry";
export interface ActivityUpdatedArgs {
@ -31,7 +31,7 @@ export interface DeleteActivityRequestedArgs {
})
export class ActivityPropertiesEditor {
private slideOverPanel: HTMLElsaSlideOverPanelElement;
private renderContext: RenderNodePropsContext;
private renderContext: RenderActivityPropsContext;
private readonly inputDriverRegistry: InputDriverRegistry;
constructor() {
@ -58,11 +58,11 @@ export class ActivityPropertiesEditor {
public componentWillRender() {
const activity = this.activity;
const activityDescriptor = this.findActivityDescriptor();
const title = activityDescriptor?.displayName ?? activityDescriptor?.nodeType ?? 'Unknown Activity';
const title = activityDescriptor?.displayName ?? activityDescriptor?.activityType ?? 'Unknown Activity';
const driverRegistry = this.inputDriverRegistry;
const renderPropertyContexts: Array<RenderNodePropContext> = activityDescriptor.inputProperties.map(inputDescriptor => {
const renderInputContext: NodeInputContext = {
const renderPropertyContexts: Array<RenderActivityPropContext> = activityDescriptor.inputProperties.map(inputDescriptor => {
const renderInputContext: ActivityInputContext = {
node: activity,
nodeDescriptor: activityDescriptor,
inputDescriptor,
@ -80,15 +80,15 @@ export class ActivityPropertiesEditor {
});
this.renderContext = {
node: activity,
nodeDescriptor: activityDescriptor,
activity,
activityDescriptor,
title,
properties: renderPropertyContexts
}
}
public render() {
const {nodeDescriptor, title} = this.renderContext;
const {activityDescriptor, title} = this.renderContext;
const propertiesTab: TabDefinition = {
displayText: 'Properties',
@ -100,7 +100,7 @@ export class ActivityPropertiesEditor {
content: () => this.renderCommonTab()
};
const tabs = !!nodeDescriptor ? [propertiesTab, commonTab] : [];
const tabs = !!activityDescriptor ? [propertiesTab, commonTab] : [];
const actions = [DefaultActions.Delete(this.onDeleteActivity)];
return (
@ -111,7 +111,7 @@ export class ActivityPropertiesEditor {
);
}
private findActivityDescriptor = (): ActivityDescriptor => !!this.activity ? this.activityDescriptors.find(x => x.nodeType == this.activity.nodeType) : null;
private findActivityDescriptor = (): ActivityDescriptor => !!this.activity ? this.activityDescriptors.find(x => x.activityType == this.activity.typeName) : null;
private onSelectedTabIndexChanged = (e: CustomEvent<TabChangedArgs>) => this.selectedTabIndex = e.detail.selectedTabIndex
private onActivityIdChanged = (e: any) => {
@ -148,8 +148,8 @@ export class ActivityPropertiesEditor {
private onDeleteActivity = () => this.deleteActivityRequested.emit({activity: this.activity});
private renderPropertiesTab = () => {
const {node, properties} = this.renderContext;
const activityId = node.id;
const {activity, properties} = this.renderContext;
const activityId = activity.id;
return <div>
<FormEntry fieldId="ActivityId" label="ID" hint="The ID of the activity.">
@ -157,7 +157,7 @@ export class ActivityPropertiesEditor {
</FormEntry>
{properties.filter(x => !!x.inputControl).map(propertyContext => {
const key = `${node.id}-${propertyContext.inputContext.inputDescriptor.name}`;
const key = `${activity.id}-${propertyContext.inputContext.inputDescriptor.name}`;
return <div key={key}>
{propertyContext.inputControl}
</div>;

View file

@ -56,11 +56,11 @@ export class ToolboxActivities {
const activityDriverRegistry = Container.get(ActivityDriverRegistry);
for (const activityDescriptor of value) {
const nodeType = activityDescriptor.nodeType;
const driver = activityDriverRegistry.createDriver(nodeType);
const activityType = activityDescriptor.activityType;
const driver = activityDriverRegistry.createDriver(activityType);
const html = driver.display({displayType: 'picker', activityDescriptor: activityDescriptor});
renderedActivities.set(nodeType, html);
renderedActivities.set(activityType, html);
}
this.renderedActivities = renderedActivities;
@ -108,7 +108,7 @@ export class ToolboxActivities {
<div class={`space-y-1 ${categoryContentClass}`}>
{activityDescriptors.map(activityDescriptor => {
const activityHtml = renderedActivities.get(activityDescriptor.nodeType);
const activityHtml = renderedActivities.get(activityDescriptor.activityType);
return (
<div class="w-full flex items-center pl-10 pr-2 py-2">
<div class="cursor-move"

View file

@ -1,7 +1,7 @@
import {Component, h, Prop, State} from '@stencil/core';
import {uniq} from 'lodash'
import {JsonExpression, LiteralExpression, SelectList, SyntaxNames} from "../../models";
import {NodeInputContext} from "../../services/node-input-driver";
import {ActivityInputContext} from "../../services/node-input-driver";
import {getSelectListItems, getInputPropertyValue, parseJson} from "../../utils";
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
@ -12,7 +12,7 @@ import {ExpressionChangedArs} from "../designer/input-control-switch/input-contr
export class CheckList {
private selectList: SelectList = {items: [], isFlagsEnum: false};
@Prop() public inputContext: NodeInputContext;
@Prop() public inputContext: ActivityInputContext;
@State() private selectedValues?: Array<string> = [];
@State() private selectedValue?: number;

View file

@ -1,6 +1,6 @@
import {Component, h, Prop} from '@stencil/core';
import {EditorHeight, LiteralExpression, SyntaxNames} from "../../models";
import {NodeInputContext} from "../../services/node-input-driver";
import {ActivityInputContext} from "../../services/node-input-driver";
import {getInputPropertyValue} from "../../utils";
import {MonacoValueChangedArgs} from "../shared/monaco-editor/monaco-editor";
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
@ -16,7 +16,7 @@ interface CodeEditorOptions {
shadow: false
})
export class CodeEditorInput {
@Prop() public inputContext: NodeInputContext;
@Prop() public inputContext: ActivityInputContext;
public render() {
const inputContext = this.inputContext;

View file

@ -1,6 +1,6 @@
import {Component, Prop, h} from '@stencil/core';
import {LiteralExpression, SelectList, SyntaxNames} from "../../models";
import {NodeInputContext} from "../../services/node-input-driver";
import {ActivityInputContext} from "../../services/node-input-driver";
import {getInputPropertyValue, getSelectListItems} from "../../utils";
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
@ -9,7 +9,7 @@ import {ExpressionChangedArs} from "../designer/input-control-switch/input-contr
shadow: false
})
export class SingleLineInput {
@Prop() public inputContext: NodeInputContext;
@Prop() public inputContext: ActivityInputContext;
private selectList: SelectList = {items: [], isFlagsEnum: false};

View file

@ -1,6 +1,6 @@
import {Component, Prop, h} from '@stencil/core';
import {LiteralExpression, SyntaxNames} from "../../models";
import {NodeInputContext} from "../../services/node-input-driver";
import {ActivityInputContext} from "../../services/node-input-driver";
import {getInputPropertyValue} from "../../utils";
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
@ -9,7 +9,7 @@ import {ExpressionChangedArs} from "../designer/input-control-switch/input-contr
shadow: false
})
export class MultiLineInput {
@Prop() inputContext: NodeInputContext;
@Prop() inputContext: ActivityInputContext;
public render() {
const inputContext = this.inputContext;

View file

@ -1,6 +1,6 @@
import {Component, Prop, h} from '@stencil/core';
import {JsonExpression, LiteralExpression, SyntaxNames} from "../../models";
import {NodeInputContext} from "../../services/node-input-driver";
import {ActivityInputContext} from "../../services/node-input-driver";
import {getInputPropertyValue, parseJson} from "../../utils";
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
@ -9,7 +9,7 @@ import {ExpressionChangedArs} from "../designer/input-control-switch/input-contr
shadow: false
})
export class MultiTextInput {
@Prop() inputContext: NodeInputContext;
@Prop() inputContext: ActivityInputContext;
public render() {
const inputContext = this.inputContext;

View file

@ -1,6 +1,6 @@
import {Component, h, Prop, State} from '@stencil/core';
import {LiteralExpression, SelectList, SyntaxNames} from "../../models";
import {NodeInputContext} from "../../services/node-input-driver";
import {ActivityInputContext} from "../../services/node-input-driver";
import {getSelectListItems, getInputPropertyValue} from "../../utils";
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
@ -11,7 +11,7 @@ import {ExpressionChangedArs} from "../designer/input-control-switch/input-contr
export class RadioList {
private selectList: SelectList = {items: [], isFlagsEnum: false};
@Prop() public inputContext: NodeInputContext;
@Prop() public inputContext: ActivityInputContext;
@State() private selectedValue?: string;
public async componentWillLoad() {

View file

@ -1,6 +1,6 @@
import {Component, h, Prop} from '@stencil/core';
import {LiteralExpression, SyntaxNames} from "../../models";
import {NodeInputContext} from "../../services/node-input-driver";
import {ActivityInputContext} from "../../services/node-input-driver";
import {getInputPropertyValue} from "../../utils";
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
@ -9,7 +9,7 @@ import {ExpressionChangedArs} from "../designer/input-control-switch/input-contr
shadow: false
})
export class SingleLineInput {
@Prop() public inputContext: NodeInputContext;
@Prop() public inputContext: ActivityInputContext;
public render() {
const inputContext = this.inputContext;

View file

@ -15,13 +15,13 @@ export class DefaultActivityDriver implements ActivityDriver {
display(context: ActivityDisplayContext): any {
const iconRegistry = this.iconRegistry;
const activityDescriptor = context.activityDescriptor;
const nodeType = activityDescriptor.nodeType;
const activityType = activityDescriptor.activityType;
const text = activityDescriptor?.displayName;
const isTrigger = (activityDescriptor?.traits & ActivityTraits.Trigger) == ActivityTraits.Trigger;
const borderColor = isTrigger ? 'border-green-600' : 'border-blue-600';
const backgroundColor = isTrigger ? 'bg-green-400' : 'bg-blue-400';
const iconBackgroundColor = isTrigger ? 'bg-green-500' : 'bg-blue-500';
const icon = iconRegistry.has(nodeType) ? iconRegistry.get(nodeType) : null;
const icon = iconRegistry.has(activityType) ? iconRegistry.get(activityType) : null;
return (`
<div>

View file

@ -1,4 +1,4 @@
import {NodeInputDriver, NodeInputContext} from "../../services/node-input-driver";
import {NodeInputDriver, ActivityInputContext} from "../../services/node-input-driver";
import {Container} from "typedi";
import {InputControlRegistry} from "../../services/input-control-registry";
import {h} from "@stencil/core";
@ -16,14 +16,14 @@ export class DefaultInputDriver implements NodeInputDriver {
return -1;
}
renderInput(context: NodeInputContext): any {
renderInput(context: ActivityInputContext): any {
const inputDescriptor = context.inputDescriptor;
const uiHint = inputDescriptor.uiHint;
const inputControl = this.inputControlRegistry.get(uiHint);
return inputControl(context);
}
supportsInput(context: NodeInputContext): boolean {
supportsInput(context: ActivityInputContext): boolean {
const uiHint = context.inputDescriptor.uiHint;
return this.inputControlRegistry.has(uiHint);
}

View file

@ -5,14 +5,11 @@ export interface ActivityDescriptorResponse {
activityDescriptors: Array<ActivityDescriptor>;
}
export interface NodeDescriptor {
nodeType: string;
export interface ActivityDescriptor {
activityType: string;
displayName: string;
category: string;
inputProperties: Array<InputDescriptor>
}
export interface ActivityDescriptor extends NodeDescriptor {
traits: ActivityTraits;
inPorts: Array<Port>;
outPorts: Array<Port>;
@ -23,7 +20,7 @@ export enum ActivityTraits {
Trigger = 2
}
export interface NodePropertyDescriptor {
export interface PropertyDescriptor {
name: string;
type: Type;
displayName?: string;
@ -32,7 +29,7 @@ export interface NodePropertyDescriptor {
isBrowsable?: boolean;
}
export interface InputDescriptor extends NodePropertyDescriptor {
export interface InputDescriptor extends PropertyDescriptor {
uiHint?: string;
options?: any;
category?: string;
@ -42,7 +39,7 @@ export interface InputDescriptor extends NodePropertyDescriptor {
isReadOnly?: boolean;
}
export interface OutputDescriptor extends NodePropertyDescriptor {
export interface OutputDescriptor extends PropertyDescriptor {
}
export interface Port {

View file

@ -2,17 +2,14 @@ import {Type} from "./shared";
import {Expression} from "./expressions";
import {VersionOptions} from "./api";
export interface Node {
export interface Activity {
id: string;
nodeType: string;
typeName: string;
metadata: any;
[name: string]: any;
}
export interface Activity extends Node {
}
export interface Trigger extends Activity {
}

View file

@ -1,6 +1,6 @@
import {Activity, Node} from "./core";
import {ActivityDescriptor, InputDescriptor, NodeDescriptor} from "./api";
import {NodeInputContext} from "../services/node-input-driver";
import {ActivityInputContext} from "../services/node-input-driver";
import {Activity} from "./core";
import {ActivityDescriptor} from "./api";
export interface TabDefinition {
displayText: string;
@ -66,15 +66,15 @@ export class DefaultActions {
});
}
export interface RenderNodePropsContext {
node: Node;
nodeDescriptor: NodeDescriptor;
export interface RenderActivityPropsContext {
activity: Activity;
activityDescriptor: ActivityDescriptor;
title: string;
properties: Array<RenderNodePropContext>;
properties: Array<RenderActivityPropContext>;
}
export interface RenderNodePropContext {
inputContext: NodeInputContext
export interface RenderActivityPropContext {
inputContext: ActivityInputContext
inputControl?: any;
}

View file

@ -1,6 +1,6 @@
import {Component, h, Prop, State} from "@stencil/core";
import {camelCase} from 'lodash';
import {NodeInputContext} from "../../services/node-input-driver";
import {ActivityInputContext} from "../../services/node-input-driver";
import {mapSyntaxToLanguage} from "../../utils";
import {SyntaxNames} from "../../models";
import {SwitchCase} from "./models";
@ -14,7 +14,7 @@ import {FormEntry} from "../../components/shared/forms/form-entry";
shadow: false
})
export class SwitchEditor {
@Prop() public inputContext: NodeInputContext;
@Prop() public inputContext: ActivityInputContext;
@State() private cases: Array<SwitchCase> = [];
private supportedSyntaxes: Array<string> = [SyntaxNames.JavaScript, SyntaxNames.Literal];

View file

@ -1,6 +1,6 @@
import _ from 'lodash';
import {Service} from 'typedi';
import {NodeInputDriver, NodeInputContext} from './node-input-driver';
import {NodeInputDriver, ActivityInputContext} from './node-input-driver';
import {DefaultInputDriver} from "../drivers/input/default-input-driver";
@Service()
@ -12,7 +12,7 @@ export class InputDriverRegistry {
this.drivers = _.orderBy(drivers, x => x.priority, 'desc');
}
public get(context: NodeInputContext): NodeInputDriver {
public get(context: ActivityInputContext): NodeInputDriver {
return this.drivers.find(x => x.supportsInput(context));
}
}

View file

@ -1,17 +1,17 @@
import {InputDescriptor, NodeDescriptor, Node} from "../models";
import {Activity, ActivityDescriptor, InputDescriptor} from "../models";
export interface NodeInputContext {
node: Node;
nodeDescriptor: NodeDescriptor;
export interface ActivityInputContext {
node: Activity;
nodeDescriptor: ActivityDescriptor;
inputDescriptor: InputDescriptor;
notifyInputChanged: () => void;
inputChanged: (value: any, syntax: string) => void;
}
export interface NodeInputDriver {
supportsInput(context: NodeInputContext): boolean;
supportsInput(context: ActivityInputContext): boolean;
get priority(): number;
renderInput(context: NodeInputContext): any;
renderInput(context: ActivityInputContext): any;
}

View file

@ -1,7 +1,7 @@
import moment from 'moment';
import {camelCase} from 'lodash';
import {ActivityInput, VersionOptions} from '../models';
import {NodeInputContext} from '../services/node-input-driver';
import {ActivityInputContext} from '../services/node-input-driver';
export function formatTimestamp(timestamp?: Date, defaultText?: string): string {
return !!timestamp ? moment(timestamp).format('DD-MM-YYYY HH:mm:ss') : defaultText;
@ -50,13 +50,13 @@ export const mapSyntaxToLanguage = (syntax: string): string => {
}
};
export const getInputPropertyName = (inputContext: NodeInputContext) => {
export const getInputPropertyName = (inputContext: ActivityInputContext) => {
const inputProperty = inputContext.inputDescriptor;
const propertyName = inputProperty.name;
return camelCase(propertyName);
}
export const getInputPropertyValue = (inputContext: NodeInputContext): ActivityInput => {
export const getInputPropertyValue = (inputContext: ActivityInputContext): ActivityInput => {
const propName = getInputPropertyName(inputContext);
return inputContext.node[propName] as ActivityInput;
};

View file

@ -39,7 +39,7 @@ public class WorkflowBookmarkScheduler : IWorkflowBookmarkScheduler
var resumeAt = payload.ResumeAt;
var job = new ResumeWorkflowJob(workflowInstanceId, bookmark.ToBookmark());
var schedule = new SpecificInstantSchedule(resumeAt);
await _jobScheduler.ScheduleAsync(job, schedule, groupKeys, cancellationToken);
await _jobScheduler.ScheduleAsync(job, bookmark.Id, schedule, groupKeys, cancellationToken);
}
}
@ -50,8 +50,7 @@ public class WorkflowBookmarkScheduler : IWorkflowBookmarkScheduler
foreach (var bookmark in delayBookmarks)
{
var job = new ResumeWorkflowJob(workflowInstanceId, bookmark.ToBookmark());
await _jobScheduler.UnscheduleAsync(job, cancellationToken);
await _jobScheduler.UnscheduleAsync(bookmark.Id, cancellationToken);
}
}
}

View file

@ -36,7 +36,7 @@ public class WorkflowTriggerScheduler : IWorkflowTriggerScheduler
// Schedule trigger.
var (dateTime, timeSpan) = JsonSerializer.Deserialize<TimerPayload>(trigger.Data!)!;
var groupKeys = new[] { RootGroupKey, trigger.WorkflowDefinitionId };
await _jobScheduler.ScheduleAsync(new RunWorkflowJob(trigger.WorkflowDefinitionId), new RecurringSchedule(dateTime, timeSpan), groupKeys, cancellationToken);
await _jobScheduler.ScheduleAsync(new RunWorkflowJob(trigger.WorkflowDefinitionId), trigger.WorkflowDefinitionId, new RecurringSchedule(dateTime, timeSpan), groupKeys, cancellationToken);
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Elsa.Attributes;
using Elsa.Contracts;
using Elsa.Models;
@ -11,6 +12,20 @@ namespace Elsa.Modules.Scheduling.Triggers;
/// </summary>
public class Timer : EventGenerator
{
[JsonConstructor]
public Timer()
{
}
public Timer(TimeSpan interval) : this(new Input<TimeSpan>(interval))
{
}
public Timer(Input<TimeSpan> interval)
{
Interval = interval;
}
[Input] public Input<TimeSpan> Interval { get; set; } = default!;
protected override IEnumerable<object> GetTriggerData(TriggerIndexingContext context)
@ -20,6 +35,9 @@ public class Timer : EventGenerator
var executeAt = clock.UtcNow.Add(interval);
yield return new TimerPayload(executeAt, interval);
}
public static Timer FromTimeSpan(TimeSpan value) => new(value);
public static Timer FromSeconds(double value) => FromTimeSpan(TimeSpan.FromSeconds(value));
}
public record TimerPayload(DateTimeOffset StartAt, TimeSpan Interval);

View file

@ -31,8 +31,8 @@ public class PersistWorkflowExecutionLogMiddleware : WorkflowExecutionMiddleware
var entries = context.ExecutionLog.Select(x => new WorkflowExecutionLogRecord
{
Id = _identityGenerator.GenerateId(),
ActivityId = x.NodeId,
ActivityType = x.NodeType,
ActivityId = x.ActivityId,
ActivityType = x.ActivityType,
Message = x.Message,
EventName = x.EventName,
WorkflowInstanceId = context.Id,

View file

@ -45,7 +45,7 @@ services
.AddProtoActorWorkflowHost()
.IndexWorkflowTriggers()
.AddElsaManagement()
.AddJobs(new QuartzJobSchedulerProvider(), new HangfireJobQueueProvider())
.AddJobServices(new QuartzJobSchedulerProvider(), new HangfireJobQueueProvider())
.AddHttpActivityServices()
.AddJobServices()
.AddAzureServiceBusServices(options => configuration.GetSection("AzureServiceBus").Bind(options))
@ -53,6 +53,7 @@ services
{
// Register workflows.
options.Workflows.Add(nameof(HelloWorldWorkflow), new HelloWorldWorkflow());
options.Workflows.Add(nameof(HeartbeatWorkflow), new HeartbeatWorkflow());
options.Workflows.Add(nameof(HttpWorkflow), new HttpWorkflow());
options.Workflows.Add(nameof(ForkedHttpWorkflow), new ForkedHttpWorkflow());
options.Workflows.Add(nameof(CompositeActivitiesWorkflow), new CompositeActivitiesWorkflow());

View file

@ -0,0 +1,26 @@
using System;
using Elsa.Activities.Console;
using Elsa.Activities.Workflows;
using Elsa.Contracts;
using Elsa.Modules.Scheduling.Triggers;
using Elsa.Runtime.Contracts;
namespace Elsa.Samples.Web1.Workflows;
public class HeartbeatWorkflow : IWorkflow
{
public void Build(IWorkflowDefinitionBuilder workflow)
{
workflow.WithRoot(new Sequence
{
Activities =
{
new Timer(TimeSpan.FromSeconds(10))
{
CanStartWorkflow = true
},
new WriteLine(context => $"Heartbeat at {context.GetRequiredService<ISystemClock>().UtcNow}")
}
});
}
}