mirror of
https://github.com/n8n-io/n8n.git
synced 2024-09-19 22:37:31 -07:00
feat(editor): Add support for connection validation (no-changelog) (#10059)
This commit is contained in:
parent
0d12f0a6b3
commit
bb354f3c88
|
@ -2,6 +2,7 @@
|
|||
import { computed, h, provide, toRef, useCssModule } from 'vue';
|
||||
import type { CanvasConnectionPort, CanvasElementPortWithPosition } from '@/types';
|
||||
|
||||
import type { ValidConnectionFunc } from '@vue-flow/core';
|
||||
import { Handle } from '@vue-flow/core';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import CanvasHandleMainInput from '@/components/canvas/elements/handles/render-types/CanvasHandleMainInput.vue';
|
||||
|
@ -16,6 +17,7 @@ const props = defineProps<{
|
|||
index: CanvasConnectionPort['index'];
|
||||
position: CanvasElementPortWithPosition['position'];
|
||||
offset: CanvasElementPortWithPosition['offset'];
|
||||
isValidConnection: ValidConnectionFunc;
|
||||
}>();
|
||||
|
||||
const $style = useCssModule();
|
||||
|
@ -66,6 +68,7 @@ provide(CanvasNodeHandleKey, {
|
|||
:style="offset"
|
||||
:connectable-start="isConnectableStart"
|
||||
:connectable-end="isConnectableEnd"
|
||||
:is-valid-connection="isValidConnection"
|
||||
>
|
||||
<Render :label="label" />
|
||||
</Handle>
|
||||
|
|
|
@ -27,11 +27,12 @@ const nodeTypesStore = useNodeTypesStore();
|
|||
const inputs = computed(() => props.data.inputs);
|
||||
const outputs = computed(() => props.data.outputs);
|
||||
const connections = computed(() => props.data.connections);
|
||||
const { mainInputs, nonMainInputs, mainOutputs, nonMainOutputs } = useNodeConnections({
|
||||
inputs,
|
||||
outputs,
|
||||
connections,
|
||||
});
|
||||
const { mainInputs, nonMainInputs, mainOutputs, nonMainOutputs, isValidConnection } =
|
||||
useNodeConnections({
|
||||
inputs,
|
||||
outputs,
|
||||
connections,
|
||||
});
|
||||
|
||||
const isDisabled = computed(() => props.data.disabled);
|
||||
|
||||
|
@ -39,13 +40,6 @@ const nodeType = computed(() => {
|
|||
return nodeTypesStore.getNodeType(props.data.type, props.data.typeVersion);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.selected,
|
||||
(selected) => {
|
||||
emit('select', props.id, selected);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Inputs
|
||||
*/
|
||||
|
@ -68,6 +62,14 @@ const outputsWithPosition = computed(() => {
|
|||
];
|
||||
});
|
||||
|
||||
/**
|
||||
* Node icon
|
||||
*/
|
||||
|
||||
const nodeIconSize = computed(() =>
|
||||
'configuration' in data.value.render.options && data.value.render.options.configuration ? 30 : 40,
|
||||
);
|
||||
|
||||
/**
|
||||
* Endpoints
|
||||
*/
|
||||
|
@ -89,26 +91,9 @@ const mapEndpointWithPosition =
|
|||
};
|
||||
|
||||
/**
|
||||
* Provide
|
||||
* Events
|
||||
*/
|
||||
|
||||
const id = toRef(props, 'id');
|
||||
const data = toRef(props, 'data');
|
||||
const label = toRef(props, 'label');
|
||||
const selected = toRef(props, 'selected');
|
||||
|
||||
provide(CanvasNodeKey, {
|
||||
id,
|
||||
data,
|
||||
label,
|
||||
selected,
|
||||
nodeType,
|
||||
});
|
||||
|
||||
const nodeIconSize = computed(() =>
|
||||
'configuration' in data.value.render.options && data.value.render.options.configuration ? 30 : 40,
|
||||
);
|
||||
|
||||
function onDelete() {
|
||||
emit('delete', props.id);
|
||||
}
|
||||
|
@ -132,6 +117,34 @@ function onUpdate(parameters: Record<string, unknown>) {
|
|||
function onMove(position: XYPosition) {
|
||||
emit('move', props.id, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide
|
||||
*/
|
||||
|
||||
const id = toRef(props, 'id');
|
||||
const data = toRef(props, 'data');
|
||||
const label = toRef(props, 'label');
|
||||
const selected = toRef(props, 'selected');
|
||||
|
||||
provide(CanvasNodeKey, {
|
||||
id,
|
||||
data,
|
||||
label,
|
||||
selected,
|
||||
nodeType,
|
||||
});
|
||||
|
||||
/**
|
||||
* Lifecycle
|
||||
*/
|
||||
|
||||
watch(
|
||||
() => props.selected,
|
||||
(selected) => {
|
||||
emit('select', props.id, selected);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -145,6 +158,7 @@ function onMove(position: XYPosition) {
|
|||
:index="source.index"
|
||||
:position="source.position"
|
||||
:offset="source.offset"
|
||||
:is-valid-connection="isValidConnection"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
@ -157,6 +171,7 @@ function onMove(position: XYPosition) {
|
|||
:index="target.index"
|
||||
:position="target.position"
|
||||
:offset="target.offset"
|
||||
:is-valid-connection="isValidConnection"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import { ref } from 'vue';
|
|||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { useNodeConnections } from '@/composables/useNodeConnections';
|
||||
import type { CanvasNodeData } from '@/types';
|
||||
import { CanvasConnectionMode } from '@/types';
|
||||
import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
|
||||
|
||||
describe('useNodeConnections', () => {
|
||||
const defaultConnections = { input: {}, output: {} };
|
||||
|
@ -158,4 +160,87 @@ describe('useNodeConnections', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidConnection', () => {
|
||||
const inputs = ref<CanvasNodeData['inputs']>([]);
|
||||
const outputs = ref<CanvasNodeData['outputs']>([]);
|
||||
|
||||
const { isValidConnection } = useNodeConnections({
|
||||
inputs,
|
||||
outputs,
|
||||
connections: defaultConnections,
|
||||
});
|
||||
|
||||
it('returns false if source and target nodes are the same', () => {
|
||||
const connection = {
|
||||
source: 'node1',
|
||||
target: 'node1',
|
||||
sourceHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Output,
|
||||
type: NodeConnectionType.Main,
|
||||
index: 0,
|
||||
}),
|
||||
targetHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Input,
|
||||
type: NodeConnectionType.Main,
|
||||
index: 0,
|
||||
}),
|
||||
};
|
||||
expect(isValidConnection(connection)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if source and target handles are of the same mode', () => {
|
||||
const connection = {
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
sourceHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Output,
|
||||
type: NodeConnectionType.Main,
|
||||
index: 0,
|
||||
}),
|
||||
targetHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Output,
|
||||
type: NodeConnectionType.Main,
|
||||
index: 0,
|
||||
}),
|
||||
};
|
||||
expect(isValidConnection(connection)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if source and target handles are of different types', () => {
|
||||
const connection = {
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
sourceHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Output,
|
||||
type: NodeConnectionType.Main,
|
||||
index: 0,
|
||||
}),
|
||||
targetHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Input,
|
||||
type: NodeConnectionType.AiMemory,
|
||||
index: 0,
|
||||
}),
|
||||
};
|
||||
expect(isValidConnection(connection)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if source and target nodes are different, modes are different, and types are the same', () => {
|
||||
const connection = {
|
||||
source: 'node1',
|
||||
target: 'node2',
|
||||
sourceHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Output,
|
||||
type: NodeConnectionType.Main,
|
||||
index: 0,
|
||||
}),
|
||||
targetHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Input,
|
||||
type: NodeConnectionType.Main,
|
||||
index: 0,
|
||||
}),
|
||||
};
|
||||
expect(isValidConnection(connection)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,8 @@ import type { CanvasNodeData } from '@/types';
|
|||
import type { MaybeRef } from 'vue';
|
||||
import { computed, unref } from 'vue';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import type { Connection } from '@vue-flow/core';
|
||||
import { parseCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
|
||||
|
||||
export function useNodeConnections({
|
||||
inputs,
|
||||
|
@ -48,6 +50,25 @@ export function useNodeConnections({
|
|||
() => unref(connections).output[NodeConnectionType.Main] ?? [],
|
||||
);
|
||||
|
||||
/**
|
||||
* Connection validation
|
||||
*/
|
||||
|
||||
function isValidConnection(connection: Connection) {
|
||||
const { type: sourceType, mode: sourceMode } = parseCanvasConnectionHandleString(
|
||||
connection.sourceHandle,
|
||||
);
|
||||
const { type: targetType, mode: targetMode } = parseCanvasConnectionHandleString(
|
||||
connection.targetHandle,
|
||||
);
|
||||
|
||||
const isSameNode = connection.source === connection.target;
|
||||
const isSameMode = sourceMode === targetMode;
|
||||
const isSameType = sourceType === targetType;
|
||||
|
||||
return !isSameNode && !isSameMode && isSameType;
|
||||
}
|
||||
|
||||
return {
|
||||
mainInputs,
|
||||
nonMainInputs,
|
||||
|
@ -56,5 +77,6 @@ export function useNodeConnections({
|
|||
mainOutputs,
|
||||
nonMainOutputs,
|
||||
mainOutputConnections,
|
||||
isValidConnection,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue