From 50f53bb52b68a93a8714e25495e20bbdc82c6a93 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 7 Jul 2022 14:01:38 +0200 Subject: [PATCH] Fix switch activity designer support --- .../workflow-definition-editor.tsx | 51 ++++++++++++------- .../workflow-navigator/workflow-navigator.tsx | 15 ++++-- .../plugins/switch/switch-port-provider.ts | 12 +++++ .../src/services/default-port-provider.ts | 13 ++++- .../src/services/port-provider.ts | 2 + .../Helpers/ObjectConverter.cs | 3 +- 6 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/designer/elsa-workflows-designer/src/components/designer/workflow-definition-editor/workflow-definition-editor.tsx b/src/designer/elsa-workflows-designer/src/components/designer/workflow-definition-editor/workflow-definition-editor.tsx index 6ddef7e38..593712437 100644 --- a/src/designer/elsa-workflows-designer/src/components/designer/workflow-definition-editor/workflow-definition-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/components/designer/workflow-definition-editor/workflow-definition-editor.tsx @@ -4,18 +4,18 @@ import {Container} from "typedi"; import {PanelPosition, PanelStateChangedArgs} from '../panel/models'; import { Activity, + ActivityDeletedArgs, ActivityDescriptor, ActivitySelectedArgs, ChildActivitySelectedArgs, + Container as ContainerActivity, ContainerSelectedArgs, EditChildActivityArgs, GraphUpdatedArgs, - WorkflowDefinition, - Container as ContainerActivity, - ActivityDeletedArgs + WorkflowDefinition } from '../../../models'; import {ActivityIdUpdatedArgs, ActivityUpdatedArgs, DeleteActivityRequestedArgs} from './activity-properties-editor'; -import {ActivityDriverRegistry, ActivityNameFormatter, ActivityNode, createActivityMap, createActivityNodeMap, EventBus, flatten, flattenList, PluginRegistry, walkActivities} from '../../../services'; +import {ActivityDriverRegistry, ActivityNameFormatter, ActivityNode, createActivityMap, createActivityNodeMap, EventBus, flatten, flattenList, PluginRegistry, PortProviderRegistry, walkActivities} from '../../../services'; import {MonacoEditorSettings} from "../../../services/monaco-editor-settings"; import {Flowchart} from "../../activities/flowchart/models"; import {ActivityPropertyChangedEventArgs, WorkflowDefinitionPropsUpdatedArgs, WorkflowDefinitionUpdatedArgs, WorkflowEditorEventTypes} from "./models"; @@ -34,6 +34,7 @@ export class WorkflowDefinitionEditor { private readonly pluginRegistry: PluginRegistry; private readonly eventBus: EventBus; private readonly activityNameFormatter: ActivityNameFormatter; + private readonly portProviderRegistry: PortProviderRegistry; private canvas: HTMLElsaCanvasElement; private container: HTMLDivElement; private toolbox: HTMLElsaWorkflowDefinitionEditorToolboxElement; @@ -42,13 +43,16 @@ export class WorkflowDefinitionEditor { private applyActivityChanges: (activity: Activity) => void; private deleteActivity: (activity: Activity) => void; private readonly emitActivityChangedDebounced: (e: ActivityPropertyChangedEventArgs) => void; + private readonly updateModelDebounced: () => void; private readonly saveChangesDebounced: () => void; constructor() { this.eventBus = Container.get(EventBus); this.pluginRegistry = Container.get(PluginRegistry); this.activityNameFormatter = Container.get(ActivityNameFormatter); + this.portProviderRegistry = Container.get(PortProviderRegistry); this.emitActivityChangedDebounced = debounce(this.emitActivityChanged, 100); + this.updateModelDebounced = debounce(this.updateModel, 10); this.saveChangesDebounced = debounce(this.saveChanges, 1000); } @@ -110,6 +114,7 @@ export class WorkflowDefinitionEditor { @Listen('graphUpdated') private async handleGraphUpdated(e: CustomEvent) { + this.updateModelDebounced(); this.saveChangesDebounced(); } @@ -119,16 +124,18 @@ export class WorkflowDefinitionEditor { const currentActivityId = this.currentWorkflowPath[this.currentWorkflowPath.length - 1].activityId; const currentActivity = this.nodeMap[currentActivityId]; const parentActivity = this.nodeMap[parentActivityId]; - const indexInParent = currentActivity.activities.findIndex(x => x == parentActivity); + const parentActivityDescriptor = descriptorsStore.activityDescriptors.find(x => x.activityType == parentActivity.typeName); + const indexInParent = currentActivity.activities?.findIndex(x => x == parentActivity); + const portName = e.detail.port.name; const item: WorkflowNavigationItem = { activityId: parentActivityId, - portName: e.detail.port.name, + portName: portName, index: indexInParent }; - const portName = camelCase(e.detail.port.name); - const activityProperty = parentActivity[portName] as Activity | Array; + const portProvider = this.portProviderRegistry.get(parentActivity.typeName); + const activityProperty = portProvider.resolvePort(portName, {activity: parentActivity, activityDescriptor: parentActivityDescriptor }); const isContainer = Array.isArray(activityProperty); if (!activityProperty) { @@ -299,17 +306,17 @@ export class WorkflowDefinitionEditor { } if (!!currentPortName) { - const portName = camelCase(currentPortName); - if (currentActivityDescriptor.isContainer) { const parentNavigationItem = this.currentWorkflowPath[this.currentWorkflowPath.length - 2]; const parentActivityId = parentNavigationItem.activityId; const parentActivity = this.nodeMap[parentActivityId] as ContainerActivity; - const parentActivitiesProp = parentActivity[portName] as Array; + const parentActivityDescriptor = descriptorsStore.activityDescriptors.find(x => x.activityType == parentActivity.typeName); + const portProvider = this.portProviderRegistry.get(parentActivity.typeName); + const parentActivitiesProp = portProvider.resolvePort(currentPortName, {activity: parentActivity, activityDescriptor: parentActivityDescriptor}) as Array; parentActivitiesProp[currentWorkflowNavigationItem.index] = activity; } else { - debugger; - currentActivity[portName] = activity; + const portProvider = this.portProviderRegistry.get(currentActivity.typeName); + portProvider.assignPort(currentPortName, activity, {activity: currentActivity, activityDescriptor: currentActivityDescriptor}); } } else { workflowDefinition.root = activity; @@ -323,9 +330,12 @@ export class WorkflowDefinitionEditor { await this.eventBus.emit(WorkflowEditorEventTypes.Activity.PropertyChanged, this, activity, propertyName, this); }; - private saveChanges = async (): Promise => { + private updateModel = async (): Promise => { const workflowDefinition = await this.getWorkflowDefinitionInternal(); await this.updateWorkflowDefinition(workflowDefinition); + }; + + private saveChanges = async (): Promise => { this.workflowUpdated.emit({workflowDefinition: this.workflowDefinitionState}); }; @@ -365,7 +375,14 @@ export class WorkflowDefinitionEditor { if (!currentItem) return this.workflowDefinitionState.root; - return this.nodeMap[currentItem.activityId]; + const activity = this.nodeMap[currentItem.activityId]; + const activityDescriptor = descriptorsStore.activityDescriptors.find(x => x.activityType == activity.typeName); + + if(activityDescriptor.isContainer) + return activity; + + const portProvider = this.portProviderRegistry.get(activity.typeName); + return portProvider.resolvePort(currentItem.portName, {activity, activityDescriptor}) as Activity; }; private createNodeMap = (workflowDefinition: WorkflowDefinition): void => { @@ -395,8 +412,6 @@ export class WorkflowDefinitionEditor { }); this.nodeMap[newName] = activity; - - requestAnimationFrame(() => {}); }; private onZoomToFit = async () => await this.canvas.zoomToFit() @@ -405,7 +420,7 @@ export class WorkflowDefinitionEditor { const updatedActivity = e.detail.activity; this.nodeMap[updatedActivity.id] = updatedActivity; - if(!!this.applyActivityChanges) + if (!!this.applyActivityChanges) this.applyActivityChanges(updatedActivity); this.emitActivityChangedDebounced({...e.detail, workflowEditor: this.el}); diff --git a/src/designer/elsa-workflows-designer/src/components/designer/workflow-navigator/workflow-navigator.tsx b/src/designer/elsa-workflows-designer/src/components/designer/workflow-navigator/workflow-navigator.tsx index 3ba485da7..5da1ae8e4 100644 --- a/src/designer/elsa-workflows-designer/src/components/designer/workflow-navigator/workflow-navigator.tsx +++ b/src/designer/elsa-workflows-designer/src/components/designer/workflow-navigator/workflow-navigator.tsx @@ -1,6 +1,6 @@ import {Component, FunctionalComponent, h, Prop, Event, EventEmitter} from "@stencil/core"; import {Container} from "typedi"; -import {ActivityIconRegistry, ActivityNode, flatten, walkActivities} from "../../../services"; +import {ActivityIconRegistry, ActivityNode, flatten, PortProviderRegistry, walkActivities} from "../../../services"; import {WorkflowNavigationItem} from "./models"; import {Port, WorkflowDefinition} from "../../../models"; import descriptorsStore from "../../../data/descriptors-store"; @@ -10,6 +10,13 @@ import descriptorsStore from "../../../data/descriptors-store"; shadow: false }) export class WorkflowNavigator { + private readonly iconRegistry: ActivityIconRegistry; + private readonly portProviderRegistry: PortProviderRegistry; + + constructor() { + this.iconRegistry = Container.get(ActivityIconRegistry); + this.portProviderRegistry = Container.get(PortProviderRegistry); + } @Prop() items: Array = []; @Prop() workflowDefinition: WorkflowDefinition; @@ -37,11 +44,10 @@ export class WorkflowNavigator { } private renderPathItem = (item: WorkflowNavigationItem, index: number, nodes: Array) => { - const iconRegistry = Container.get(ActivityIconRegistry); const activityId = item.activityId; const activity = nodes.find(x => x.activity.id == activityId).activity; const activityDescriptor = descriptorsStore.activityDescriptors.find(x => x.activityType == activity.typeName); - const icon = iconRegistry.get(activity.typeName)(); + const icon = this.iconRegistry.get(activity.typeName)(); const listElements = []; const isLastItem = index == this.items.length - 1; @@ -53,7 +59,8 @@ export class WorkflowNavigator { let port: Port = null; if (!!item.portName) { - const ports = activityDescriptor.ports; + const portProvider = this.portProviderRegistry.get(activity.typeName); + const ports = portProvider.getOutboundPorts({activity, activityDescriptor}); if (ports.length > 1) port = ports.find(x => x.name == item.portName); diff --git a/src/designer/elsa-workflows-designer/src/plugins/switch/switch-port-provider.ts b/src/designer/elsa-workflows-designer/src/plugins/switch/switch-port-provider.ts index a0fb9ffc7..2858935a9 100644 --- a/src/designer/elsa-workflows-designer/src/plugins/switch/switch-port-provider.ts +++ b/src/designer/elsa-workflows-designer/src/plugins/switch/switch-port-provider.ts @@ -1,5 +1,6 @@ import 'reflect-metadata'; import {Service} from "typedi"; +import {camelCase} from 'lodash'; import {Activity, Port, PortMode} from "../../models"; import {SwitchActivity, SwitchCase} from "./models"; import {PortProvider, PortProviderContext} from "../../services"; @@ -31,4 +32,15 @@ export class SwitchPortProvider implements PortProvider { return caseItem.activity; } + + assignPort(portName: string, activity: Activity, context: PortProviderContext) { + const switchActivity = context.activity as SwitchActivity; + const cases: Array = switchActivity.cases ?? []; + const caseItem = cases.find(x => x.label == portName); + + if(!caseItem) + return null; + + caseItem.activity = activity; + } } diff --git a/src/designer/elsa-workflows-designer/src/services/default-port-provider.ts b/src/designer/elsa-workflows-designer/src/services/default-port-provider.ts index bfd1fbfd9..1bfe19222 100644 --- a/src/designer/elsa-workflows-designer/src/services/default-port-provider.ts +++ b/src/designer/elsa-workflows-designer/src/services/default-port-provider.ts @@ -7,7 +7,6 @@ import {Activity, Port} from "../models"; @Service() export class DefaultPortProvider implements PortProvider { getInboundPorts(context: PortProviderContext): Array { - const {activityDescriptor} = context; return []; } @@ -20,10 +19,20 @@ export class DefaultPortProvider implements PortProvider { const propName = camelCase(portName); const activity = context.activity; - if(!activity) + if (!activity) return null; return activity[propName] as Activity | Array; } + assignPort(portName: string, activity: Activity, context: PortProviderContext) { + const propName = camelCase(portName); + const container = context.activity; + + if (!container) + return null; + + container[propName] = activity; + } + } diff --git a/src/designer/elsa-workflows-designer/src/services/port-provider.ts b/src/designer/elsa-workflows-designer/src/services/port-provider.ts index 66a6e9967..846b69a9a 100644 --- a/src/designer/elsa-workflows-designer/src/services/port-provider.ts +++ b/src/designer/elsa-workflows-designer/src/services/port-provider.ts @@ -6,6 +6,8 @@ export interface PortProvider { getOutboundPorts(context: PortProviderContext): Array; resolvePort(portName: string, context: PortProviderContext): Activity | Array; + + assignPort(portName: string, activity: Activity, context: PortProviderContext); } export interface PortProviderContext { diff --git a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs index e44d647f9..af901425b 100644 --- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs +++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Dahomey.Json; using Dahomey.Json.Serialization.Conventions; +using Dahomey.Json.Util; using Elsa.Expressions.Exceptions; using DahomeyJsonNode = System.Text.Json.JsonNode; @@ -55,7 +56,7 @@ public static class ObjectConverter var targetTypeConverter = TypeDescriptor.GetConverter(underlyingTargetType); if (targetTypeConverter.CanConvertFrom(underlyingSourceType)) - return targetTypeConverter.ConvertFrom(value); + return targetTypeConverter.IsValid(value) ? targetTypeConverter.ConvertFrom(value) : targetType.GetDefaultValue(); var sourceTypeConverter = TypeDescriptor.GetConverter(underlyingSourceType);