From 6330bec4db0175b558f2747837323fdbb25b634a Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 17 Dec 2024 16:57:40 +0200 Subject: [PATCH] fix(editor): Remove invalid connections after node handles change (#12247) --- .../src/components/canvas/Canvas.vue | 12 + .../canvas/elements/nodes/CanvasNode.vue | 15 + .../src/composables/useCanvasMapping.test.ts | 9 +- .../src/composables/useCanvasMapping.ts | 2 +- .../composables/useCanvasOperations.test.ts | 300 ++++++++++++++++++ .../src/composables/useCanvasOperations.ts | 59 +++- packages/editor-ui/src/types/canvas.ts | 20 +- .../editor-ui/src/utils/canvasUtilsV2.test.ts | 30 +- packages/editor-ui/src/utils/canvasUtilsV2.ts | 6 +- packages/editor-ui/src/views/NodeView.v2.vue | 12 + 10 files changed, 433 insertions(+), 32 deletions(-) diff --git a/packages/editor-ui/src/components/canvas/Canvas.vue b/packages/editor-ui/src/components/canvas/Canvas.vue index 82566cdd68..1824cd3390 100644 --- a/packages/editor-ui/src/components/canvas/Canvas.vue +++ b/packages/editor-ui/src/components/canvas/Canvas.vue @@ -39,6 +39,8 @@ const emit = defineEmits<{ 'update:node:selected': [id: string]; 'update:node:name': [id: string]; 'update:node:parameters': [id: string, parameters: Record]; + 'update:node:inputs': [id: string]; + 'update:node:outputs': [id: string]; 'click:node:add': [id: string, handle: string]; 'run:node': [id: string]; 'delete:node': [id: string]; @@ -302,6 +304,14 @@ function onUpdateNodeParameters(id: string, parameters: Record) emit('update:node:parameters', id, parameters); } +function onUpdateNodeInputs(id: string) { + emit('update:node:inputs', id); +} + +function onUpdateNodeOutputs(id: string) { + emit('update:node:outputs', id); +} + /** * Connections / Edges */ @@ -679,6 +689,8 @@ provide(CanvasKey, { @activate="onSetNodeActive" @open:contextmenu="onOpenNodeContextMenu" @update="onUpdateNodeParameters" + @update:inputs="onUpdateNodeInputs" + @update:outputs="onUpdateNodeOutputs" @move="onUpdateNodePosition" @add="onClickNodeAdd" /> diff --git a/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue b/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue index d1d104206f..dd558bbef5 100644 --- a/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue +++ b/packages/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue @@ -31,6 +31,7 @@ import { useCanvas } from '@/composables/useCanvas'; import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2'; import type { EventBus } from 'n8n-design-system'; import { createEventBus } from 'n8n-design-system'; +import { isEqual } from 'lodash-es'; type Props = NodeProps & { readOnly?: boolean; @@ -47,6 +48,8 @@ const emit = defineEmits<{ activate: [id: string]; 'open:contextmenu': [id: string, event: MouseEvent, source: 'node-button' | 'node-right-click']; update: [id: string, parameters: Record]; + 'update:inputs': [id: string]; + 'update:outputs': [id: string]; move: [id: string, position: XYPosition]; }>(); @@ -265,6 +268,18 @@ watch( }, ); +watch(inputs, (newValue, oldValue) => { + if (!isEqual(newValue, oldValue)) { + emit('update:inputs', props.id); + } +}); + +watch(outputs, (newValue, oldValue) => { + if (!isEqual(newValue, oldValue)) { + emit('update:outputs', props.id); + } +}); + onMounted(() => { props.eventBus?.on('nodes:action', emitCanvasNodeEvent); }); diff --git a/packages/editor-ui/src/composables/useCanvasMapping.test.ts b/packages/editor-ui/src/composables/useCanvasMapping.test.ts index a37f836cca..49ea69506f 100644 --- a/packages/editor-ui/src/composables/useCanvasMapping.test.ts +++ b/packages/editor-ui/src/composables/useCanvasMapping.test.ts @@ -1161,13 +1161,14 @@ describe('useCanvasMapping', () => { expect(mappedConnections.value).toEqual([ { data: { - fromNodeName: manualTriggerNode.name, source: { + node: manualTriggerNode.name, index: 0, type: NodeConnectionType.Main, }, status: undefined, target: { + node: setNode.name, index: 0, type: NodeConnectionType.Main, }, @@ -1249,13 +1250,14 @@ describe('useCanvasMapping', () => { expect(mappedConnections.value).toEqual([ { data: { - fromNodeName: manualTriggerNode.name, source: { + node: manualTriggerNode.name, index: 0, type: NodeConnectionType.AiTool, }, status: undefined, target: { + node: setNode.name, index: 0, type: NodeConnectionType.AiTool, }, @@ -1271,13 +1273,14 @@ describe('useCanvasMapping', () => { }, { data: { - fromNodeName: manualTriggerNode.name, source: { + node: manualTriggerNode.name, index: 0, type: NodeConnectionType.AiDocument, }, status: undefined, target: { + node: setNode.name, index: 1, type: NodeConnectionType.AiDocument, }, diff --git a/packages/editor-ui/src/composables/useCanvasMapping.ts b/packages/editor-ui/src/composables/useCanvasMapping.ts index 205b4f7cbb..517463fc6c 100644 --- a/packages/editor-ui/src/composables/useCanvasMapping.ts +++ b/packages/editor-ui/src/composables/useCanvasMapping.ts @@ -614,7 +614,7 @@ export function useCanvasMapping({ } function getConnectionLabel(connection: CanvasConnection): string { - const fromNode = nodes.value.find((node) => node.name === connection.data?.fromNodeName); + const fromNode = nodes.value.find((node) => node.name === connection.data?.source.node); if (!fromNode) { return ''; } diff --git a/packages/editor-ui/src/composables/useCanvasOperations.test.ts b/packages/editor-ui/src/composables/useCanvasOperations.test.ts index 5858bb6ca5..1d20a020e0 100644 --- a/packages/editor-ui/src/composables/useCanvasOperations.test.ts +++ b/packages/editor-ui/src/composables/useCanvasOperations.test.ts @@ -32,6 +32,7 @@ import { waitFor } from '@testing-library/vue'; import { createTestingPinia } from '@pinia/testing'; import { mockedStore } from '@/__tests__/utils'; import { + AGENT_NODE_TYPE, FORM_TRIGGER_NODE_TYPE, SET_NODE_TYPE, STICKY_NODE_TYPE, @@ -41,6 +42,7 @@ import { import type { Connection } from '@vue-flow/core'; import { useClipboard } from '@/composables/useClipboard'; import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2'; +import { nextTick } from 'vue'; vi.mock('vue-router', async (importOriginal) => { const actual = await importOriginal<{}>(); @@ -1934,6 +1936,304 @@ describe('useCanvasOperations', () => { }); }); + describe('revalidateNodeInputConnections', () => { + it('should not delete connections when target node does not exist', () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const nonexistentId = 'nonexistent'; + workflowsStore.getNodeById.mockReturnValue(undefined); + + const { revalidateNodeInputConnections } = useCanvasOperations({ router }); + revalidateNodeInputConnections(nonexistentId); + + expect(workflowsStore.removeConnection).not.toHaveBeenCalled(); + }); + + it('should not delete connections when node type description is not found', () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const nodeTypesStore = mockedStore(useNodeTypesStore); + const nodeId = 'test-node'; + const node = createTestNode({ id: nodeId, type: 'unknown-type' }); + + workflowsStore.getNodeById.mockReturnValue(node); + nodeTypesStore.getNodeType = () => null; + + const { revalidateNodeInputConnections } = useCanvasOperations({ router }); + revalidateNodeInputConnections(nodeId); + + expect(workflowsStore.removeConnection).not.toHaveBeenCalled(); + }); + + it('should remove invalid connections that do not match input type', async () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const nodeTypesStore = mockedStore(useNodeTypesStore); + + workflowsStore.removeConnection = vi.fn(); + + const targetNodeId = 'target'; + const targetNode = createTestNode({ + id: targetNodeId, + name: 'Target Node', + type: SET_NODE_TYPE, + }); + const targetNodeType = mockNodeTypeDescription({ + name: SET_NODE_TYPE, + inputs: [NodeConnectionType.Main], + }); + + const sourceNodeId = 'source'; + const sourceNode = createTestNode({ + id: sourceNodeId, + name: 'Source Node', + type: AGENT_NODE_TYPE, + }); + const sourceNodeType = mockNodeTypeDescription({ + name: AGENT_NODE_TYPE, + outputs: [NodeConnectionType.AiTool], + }); + + workflowsStore.workflow.nodes = [sourceNode, targetNode]; + workflowsStore.workflow.connections = { + [sourceNode.name]: { + [NodeConnectionType.AiTool]: [ + [{ node: targetNode.name, type: NodeConnectionType.Main, index: 0 }], + ], + }, + }; + + workflowsStore.getNodeById + .mockReturnValueOnce(sourceNode) + .mockReturnValueOnce(targetNode) + .mockReturnValueOnce(sourceNode) + .mockReturnValueOnce(targetNode); + + nodeTypesStore.getNodeType = vi + .fn() + .mockReturnValueOnce(targetNodeType) + .mockReturnValueOnce(sourceNodeType); + + const workflowObject = createTestWorkflowObject(workflowsStore.workflow); + workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject); + + const { revalidateNodeInputConnections } = useCanvasOperations({ router }); + revalidateNodeInputConnections(targetNodeId); + + await nextTick(); + + expect(workflowsStore.removeConnection).toHaveBeenCalledWith({ + connection: [ + { node: sourceNode.name, type: NodeConnectionType.AiTool, index: 0 }, + { node: targetNode.name, type: NodeConnectionType.Main, index: 0 }, + ], + }); + }); + + it('should keep valid connections that match input type', () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const nodeTypesStore = mockedStore(useNodeTypesStore); + + workflowsStore.removeConnection = vi.fn(); + + const targetNodeId = 'target'; + const targetNode = createTestNode({ + id: targetNodeId, + name: 'Target Node', + type: SET_NODE_TYPE, + }); + const targetNodeType = mockNodeTypeDescription({ + name: SET_NODE_TYPE, + inputs: [NodeConnectionType.Main], + }); + + const sourceNodeId = 'source'; + const sourceNode = createTestNode({ + id: sourceNodeId, + name: 'Source Node', + type: AGENT_NODE_TYPE, + }); + const sourceNodeType = mockNodeTypeDescription({ + name: AGENT_NODE_TYPE, + outputs: [NodeConnectionType.Main], + }); + + workflowsStore.workflow.nodes = [sourceNode, targetNode]; + workflowsStore.workflow.connections = { + [sourceNode.name]: { + [NodeConnectionType.Main]: [ + [{ node: targetNode.name, type: NodeConnectionType.Main, index: 0 }], + ], + }, + }; + + workflowsStore.getNodeById + .mockReturnValueOnce(sourceNode) + .mockReturnValueOnce(targetNode) + .mockReturnValueOnce(sourceNode) + .mockReturnValueOnce(targetNode); + + nodeTypesStore.getNodeType = vi + .fn() + .mockReturnValueOnce(targetNodeType) + .mockReturnValueOnce(sourceNodeType); + + const workflowObject = createTestWorkflowObject(workflowsStore.workflow); + workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject); + + const { revalidateNodeInputConnections } = useCanvasOperations({ router }); + revalidateNodeInputConnections(targetNodeId); + + expect(workflowsStore.removeConnection).not.toHaveBeenCalled(); + }); + }); + + describe('revalidateNodeOutputConnections', () => { + it('should not delete connections when source node does not exist', () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const nonexistentId = 'nonexistent'; + workflowsStore.getNodeById.mockReturnValue(undefined); + + const { revalidateNodeOutputConnections } = useCanvasOperations({ router }); + revalidateNodeOutputConnections(nonexistentId); + + expect(workflowsStore.removeConnection).not.toHaveBeenCalled(); + }); + + it('should not delete connections when node type description is not found', () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const nodeTypesStore = mockedStore(useNodeTypesStore); + const nodeId = 'test-node'; + const node = createTestNode({ id: nodeId, type: 'unknown-type' }); + + workflowsStore.getNodeById.mockReturnValue(node); + nodeTypesStore.getNodeType = () => null; + + const { revalidateNodeOutputConnections } = useCanvasOperations({ router }); + revalidateNodeOutputConnections(nodeId); + + expect(workflowsStore.removeConnection).not.toHaveBeenCalled(); + }); + + it('should remove invalid connections that do not match output type', async () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const nodeTypesStore = mockedStore(useNodeTypesStore); + + workflowsStore.removeConnection = vi.fn(); + + const targetNodeId = 'target'; + const targetNode = createTestNode({ + id: targetNodeId, + name: 'Target Node', + type: SET_NODE_TYPE, + }); + const targetNodeType = mockNodeTypeDescription({ + name: SET_NODE_TYPE, + inputs: [NodeConnectionType.Main], + }); + + const sourceNodeId = 'source'; + const sourceNode = createTestNode({ + id: sourceNodeId, + name: 'Source Node', + type: AGENT_NODE_TYPE, + }); + const sourceNodeType = mockNodeTypeDescription({ + name: AGENT_NODE_TYPE, + outputs: [NodeConnectionType.AiTool], + }); + + workflowsStore.workflow.nodes = [sourceNode, targetNode]; + workflowsStore.workflow.connections = { + [sourceNode.name]: { + [NodeConnectionType.AiTool]: [ + [{ node: targetNode.name, type: NodeConnectionType.Main, index: 0 }], + ], + }, + }; + + workflowsStore.getNodeById + .mockReturnValueOnce(sourceNode) + .mockReturnValueOnce(targetNode) + .mockReturnValueOnce(sourceNode) + .mockReturnValueOnce(targetNode); + + nodeTypesStore.getNodeType = vi + .fn() + .mockReturnValueOnce(targetNodeType) + .mockReturnValueOnce(sourceNodeType); + + const workflowObject = createTestWorkflowObject(workflowsStore.workflow); + workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject); + + const { revalidateNodeOutputConnections } = useCanvasOperations({ router }); + revalidateNodeOutputConnections(sourceNodeId); + + await nextTick(); + + expect(workflowsStore.removeConnection).toHaveBeenCalledWith({ + connection: [ + { node: sourceNode.name, type: NodeConnectionType.AiTool, index: 0 }, + { node: targetNode.name, type: NodeConnectionType.Main, index: 0 }, + ], + }); + }); + + it('should keep valid connections that match output type', () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const nodeTypesStore = mockedStore(useNodeTypesStore); + + workflowsStore.removeConnection = vi.fn(); + + const targetNodeId = 'target'; + const targetNode = createTestNode({ + id: targetNodeId, + name: 'Target Node', + type: SET_NODE_TYPE, + }); + const targetNodeType = mockNodeTypeDescription({ + name: SET_NODE_TYPE, + inputs: [NodeConnectionType.Main], + }); + + const sourceNodeId = 'source'; + const sourceNode = createTestNode({ + id: sourceNodeId, + name: 'Source Node', + type: AGENT_NODE_TYPE, + }); + const sourceNodeType = mockNodeTypeDescription({ + name: AGENT_NODE_TYPE, + outputs: [NodeConnectionType.Main], + }); + + workflowsStore.workflow.nodes = [sourceNode, targetNode]; + workflowsStore.workflow.connections = { + [sourceNode.name]: { + [NodeConnectionType.AiTool]: [ + [{ node: targetNode.name, type: NodeConnectionType.Main, index: 0 }], + ], + }, + }; + + workflowsStore.getNodeById + .mockReturnValueOnce(sourceNode) + .mockReturnValueOnce(targetNode) + .mockReturnValueOnce(sourceNode) + .mockReturnValueOnce(targetNode); + + nodeTypesStore.getNodeType = vi + .fn() + .mockReturnValueOnce(targetNodeType) + .mockReturnValueOnce(sourceNodeType); + + const workflowObject = createTestWorkflowObject(workflowsStore.workflow); + workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject); + + const { revalidateNodeOutputConnections } = useCanvasOperations({ router }); + revalidateNodeOutputConnections(sourceNodeId); + + expect(workflowsStore.removeConnection).not.toHaveBeenCalled(); + }); + }); + describe('deleteConnectionsByNodeId', () => { it('should delete all connections for a given node ID', () => { const workflowsStore = mockedStore(useWorkflowsStore); diff --git a/packages/editor-ui/src/composables/useCanvasOperations.ts b/packages/editor-ui/src/composables/useCanvasOperations.ts index 939d2132b8..38db98fe01 100644 --- a/packages/editor-ui/src/composables/useCanvasOperations.ts +++ b/packages/editor-ui/src/composables/useCanvasOperations.ts @@ -52,6 +52,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import type { CanvasConnection, CanvasConnectionCreateData, + CanvasConnectionPort, CanvasNode, CanvasNodeMoveEvent, } from '@/types'; @@ -1230,11 +1231,63 @@ export function useCanvasOperations({ router }: { router: ReturnType { + const isRelevantConnection = isInput ? connection.target === id : connection.source === id; + + if (isRelevantConnection) { + const otherNodeId = isInput ? connection.source : connection.target; + + const otherNode = workflowsStore.getNodeById(otherNodeId); + if (!otherNode || !connection.data) { + return; + } + + const [firstNode, secondNode] = isInput ? [otherNode, node] : [node, otherNode]; + + if ( + !isConnectionAllowed( + firstNode, + secondNode, + connection.data.source, + connection.data.target, + ) + ) { + void nextTick(() => deleteConnection(connection)); + } + } + }); + } + + function revalidateNodeInputConnections(id: string) { + return revalidateNodeConnections(id, CanvasConnectionMode.Input); + } + + function revalidateNodeOutputConnections(id: string) { + return revalidateNodeConnections(id, CanvasConnectionMode.Output); + } + function isConnectionAllowed( sourceNode: INodeUi, targetNode: INodeUi, - sourceConnection: IConnection, - targetConnection: IConnection, + sourceConnection: IConnection | CanvasConnectionPort, + targetConnection: IConnection | CanvasConnectionPort, ): boolean { const blocklist = [STICKY_NODE_TYPE]; @@ -1908,6 +1961,8 @@ export function useCanvasOperations({ router }: { router: ReturnType; export interface CanvasConnectionData { source: CanvasConnectionPort; target: CanvasConnectionPort; - fromNodeName?: string; status?: 'success' | 'error' | 'pinned' | 'running'; maxConnections?: number; } @@ -137,8 +129,8 @@ export type CanvasConnectionCreateData = { target: string; targetHandle: string; data: { - source: PartialBy; - target: PartialBy; + source: CanvasConnectionPort; + target: CanvasConnectionPort; }; }; diff --git a/packages/editor-ui/src/utils/canvasUtilsV2.test.ts b/packages/editor-ui/src/utils/canvasUtilsV2.test.ts index 7eab644900..61425172b3 100644 --- a/packages/editor-ui/src/utils/canvasUtilsV2.test.ts +++ b/packages/editor-ui/src/utils/canvasUtilsV2.test.ts @@ -77,12 +77,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle, targetHandle, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 0, type: NodeConnectionType.Main, }, target: { + node: nodes[1].name, index: 0, type: NodeConnectionType.Main, }, @@ -215,12 +216,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle: sourceHandleA, targetHandle: targetHandleA, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 0, type: NodeConnectionType.Main, }, target: { + node: nodes[1].name, index: 0, type: NodeConnectionType.Main, }, @@ -233,12 +235,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle: sourceHandleB, targetHandle: targetHandleB, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 1, type: NodeConnectionType.Main, }, target: { + node: nodes[1].name, index: 1, type: NodeConnectionType.Main, }, @@ -334,12 +337,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle: sourceHandleA, targetHandle: targetHandleA, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 0, type: NodeConnectionType.Main, }, target: { + node: nodes[1].name, index: 0, type: NodeConnectionType.Main, }, @@ -352,12 +356,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle: sourceHandleB, targetHandle: targetHandleB, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 1, type: NodeConnectionType.Main, }, target: { + node: nodes[2].name, index: 0, type: NodeConnectionType.Main, }, @@ -475,12 +480,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle: sourceHandleA, targetHandle: targetHandleA, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 0, type: NodeConnectionType.Main, }, target: { + node: nodes[1].name, index: 0, type: NodeConnectionType.Main, }, @@ -493,12 +499,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle: sourceHandleB, targetHandle: targetHandleB, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 0, type: NodeConnectionType.AiMemory, }, target: { + node: nodes[2].name, index: 1, type: NodeConnectionType.AiMemory, }, @@ -511,12 +518,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle: sourceHandleC, targetHandle: targetHandleC, data: { - fromNodeName: nodes[1].name, source: { + node: nodes[1].name, index: 0, type: NodeConnectionType.Main, }, target: { + node: nodes[2].name, index: 0, type: NodeConnectionType.Main, }, @@ -587,12 +595,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle, targetHandle, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 1, type: NodeConnectionType.Main, }, target: { + node: nodes[1].name, index: 0, type: NodeConnectionType.Main, }, @@ -662,12 +671,13 @@ describe('mapLegacyConnectionsToCanvasConnections', () => { sourceHandle, targetHandle, data: { - fromNodeName: nodes[0].name, source: { + node: nodes[0].name, index: 1, type: NodeConnectionType.Main, }, target: { + node: nodes[1].name, index: 0, type: NodeConnectionType.Main, }, diff --git a/packages/editor-ui/src/utils/canvasUtilsV2.ts b/packages/editor-ui/src/utils/canvasUtilsV2.ts index c86e4a7f2c..d08f817d82 100644 --- a/packages/editor-ui/src/utils/canvasUtilsV2.ts +++ b/packages/editor-ui/src/utils/canvasUtilsV2.ts @@ -22,7 +22,8 @@ export function mapLegacyConnectionsToCanvasConnections( const fromPorts = legacyConnections[fromNodeName][fromConnectionType]; fromPorts?.forEach((toPorts, fromIndex) => { toPorts?.forEach((toPort) => { - const toId = nodes.find((node) => node.name === toPort.node)?.id ?? ''; + const toNodeName = toPort.node; + const toId = nodes.find((node) => node.name === toNodeName)?.id ?? ''; const toConnectionType = toPort.type as NodeConnectionType; const toIndex = toPort.index; @@ -53,12 +54,13 @@ export function mapLegacyConnectionsToCanvasConnections( sourceHandle, targetHandle, data: { - fromNodeName, source: { + node: fromNodeName, index: fromIndex, type: fromConnectionType, }, target: { + node: toNodeName, index: toIndex, type: toConnectionType, }, diff --git a/packages/editor-ui/src/views/NodeView.v2.vue b/packages/editor-ui/src/views/NodeView.v2.vue index d2962af705..06964ad1fb 100644 --- a/packages/editor-ui/src/views/NodeView.v2.vue +++ b/packages/editor-ui/src/views/NodeView.v2.vue @@ -180,6 +180,8 @@ const { revertCreateConnection, deleteConnection, revertDeleteConnection, + revalidateNodeInputConnections, + revalidateNodeOutputConnections, setNodeActiveByName, addConnections, importWorkflowData, @@ -723,6 +725,14 @@ function onUpdateNodeParameters(id: string, parameters: Record) setNodeParameters(id, parameters); } +function onUpdateNodeInputs(id: string) { + revalidateNodeInputConnections(id); +} + +function onUpdateNodeOutputs(id: string) { + revalidateNodeOutputConnections(id); +} + function onClickNodeAdd(source: string, sourceHandle: string) { nodeCreatorStore.openNodeCreatorForConnectingNode({ connection: { @@ -1618,6 +1628,8 @@ onBeforeUnmount(() => { @update:node:enabled="onToggleNodeDisabled" @update:node:name="onOpenRenameNodeModal" @update:node:parameters="onUpdateNodeParameters" + @update:node:inputs="onUpdateNodeInputs" + @update:node:outputs="onUpdateNodeOutputs" @click:node:add="onClickNodeAdd" @run:node="onRunWorkflowToNode" @delete:node="onDeleteNode"