mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-31 15:37:26 -08:00
fix(editor): Remove invalid connections after node handles change (#12247)
This commit is contained in:
parent
4d8e9cfc61
commit
6330bec4db
|
@ -39,6 +39,8 @@ const emit = defineEmits<{
|
|||
'update:node:selected': [id: string];
|
||||
'update:node:name': [id: string];
|
||||
'update:node:parameters': [id: string, parameters: Record<string, unknown>];
|
||||
'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<string, unknown>)
|
|||
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"
|
||||
/>
|
||||
|
|
|
@ -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<CanvasNodeData> & {
|
||||
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<string, unknown>];
|
||||
'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);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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 '';
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<typeof useR
|
|||
});
|
||||
}
|
||||
|
||||
function revalidateNodeConnections(id: string, connectionMode: CanvasConnectionMode) {
|
||||
const node = workflowsStore.getNodeById(id);
|
||||
const isInput = connectionMode === CanvasConnectionMode.Input;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
if (!nodeType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connections = mapLegacyConnectionsToCanvasConnections(
|
||||
workflowsStore.workflow.connections,
|
||||
workflowsStore.workflow.nodes,
|
||||
);
|
||||
|
||||
connections.forEach((connection) => {
|
||||
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<typeof useR
|
|||
deleteConnection,
|
||||
revertDeleteConnection,
|
||||
deleteConnectionsByNodeId,
|
||||
revalidateNodeInputConnections,
|
||||
revalidateNodeOutputConnections,
|
||||
isConnectionAllowed,
|
||||
filterConnectionsByNodes,
|
||||
importWorkflowData,
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-redundant-type-constituents */
|
||||
import type {
|
||||
ExecutionStatus,
|
||||
INodeConnections,
|
||||
IConnection,
|
||||
NodeConnectionType,
|
||||
} from 'n8n-workflow';
|
||||
import type { ExecutionStatus, INodeConnections, NodeConnectionType } from 'n8n-workflow';
|
||||
import type {
|
||||
DefaultEdge,
|
||||
Node,
|
||||
|
@ -15,11 +10,8 @@ import type {
|
|||
} from '@vue-flow/core';
|
||||
import type { IExecutionResponse, INodeUi } from '@/Interface';
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { PartialBy } from '@/utils/typeHelpers';
|
||||
import type { EventBus } from 'n8n-design-system';
|
||||
|
||||
export type CanvasConnectionPortType = NodeConnectionType;
|
||||
|
||||
export const enum CanvasConnectionMode {
|
||||
Input = 'inputs',
|
||||
Output = 'outputs',
|
||||
|
@ -31,10 +23,11 @@ export const canvasConnectionModes = [
|
|||
] as const;
|
||||
|
||||
export type CanvasConnectionPort = {
|
||||
type: CanvasConnectionPortType;
|
||||
node?: string;
|
||||
type: NodeConnectionType;
|
||||
index: number;
|
||||
required?: boolean;
|
||||
maxConnections?: number;
|
||||
index: number;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
|
@ -124,7 +117,6 @@ export type CanvasNode = Node<CanvasNodeData>;
|
|||
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<IConnection, 'node'>;
|
||||
target: PartialBy<IConnection, 'node'>;
|
||||
source: CanvasConnectionPort;
|
||||
target: CanvasConnectionPort;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -180,6 +180,8 @@ const {
|
|||
revertCreateConnection,
|
||||
deleteConnection,
|
||||
revertDeleteConnection,
|
||||
revalidateNodeInputConnections,
|
||||
revalidateNodeOutputConnections,
|
||||
setNodeActiveByName,
|
||||
addConnections,
|
||||
importWorkflowData,
|
||||
|
@ -723,6 +725,14 @@ function onUpdateNodeParameters(id: string, parameters: Record<string, unknown>)
|
|||
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"
|
||||
|
|
Loading…
Reference in a new issue