mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 08:34:07 -08:00
feat(editor): Add logic for renaming duplicate nodes in new canvas (no-changelog) (#11081)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
This commit is contained in:
parent
ce69218f1b
commit
4b0187e7e8
|
@ -58,7 +58,6 @@ import type {
|
||||||
import { CanvasConnectionMode } from '@/types';
|
import { CanvasConnectionMode } from '@/types';
|
||||||
import {
|
import {
|
||||||
createCanvasConnectionHandleString,
|
createCanvasConnectionHandleString,
|
||||||
getUniqueNodeName,
|
|
||||||
mapCanvasConnectionToLegacyConnection,
|
mapCanvasConnectionToLegacyConnection,
|
||||||
mapLegacyConnectionsToCanvasConnections,
|
mapLegacyConnectionsToCanvasConnections,
|
||||||
mapLegacyConnectionToCanvasConnection,
|
mapLegacyConnectionToCanvasConnection,
|
||||||
|
@ -96,6 +95,7 @@ import { v4 as uuid } from 'uuid';
|
||||||
import { computed, nextTick, ref } from 'vue';
|
import { computed, nextTick, ref } from 'vue';
|
||||||
import type { useRouter } from 'vue-router';
|
import type { useRouter } from 'vue-router';
|
||||||
import { useClipboard } from '@/composables/useClipboard';
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
import { useUniqueNodeName } from '@/composables/useUniqueNodeName';
|
||||||
import { isPresent } from '../utils/typesUtils';
|
import { isPresent } from '../utils/typesUtils';
|
||||||
|
|
||||||
type AddNodeData = Partial<INodeUi> & {
|
type AddNodeData = Partial<INodeUi> & {
|
||||||
|
@ -201,12 +201,12 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
if (currentName === newName) {
|
if (currentName === newName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackHistory) {
|
if (trackHistory) {
|
||||||
historyStore.startRecordingUndo();
|
historyStore.startRecordingUndo();
|
||||||
}
|
}
|
||||||
|
|
||||||
newName = getUniqueNodeName(newName, workflowsStore.canvasNames);
|
const { uniqueNodeName } = useUniqueNodeName();
|
||||||
|
newName = uniqueNodeName(newName);
|
||||||
|
|
||||||
// Rename the node and update the connections
|
// Rename the node and update the connections
|
||||||
const workflow = workflowsStore.getCurrentWorkflow(true);
|
const workflow = workflowsStore.getCurrentWorkflow(true);
|
||||||
|
@ -1066,9 +1066,10 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveNodeName(node: INodeUi) {
|
function resolveNodeName(node: INodeUi) {
|
||||||
|
const { uniqueNodeName } = useUniqueNodeName();
|
||||||
const localizedName = i18n.localizeNodeName(node.name, node.type);
|
const localizedName = i18n.localizeNodeName(node.name, node.type);
|
||||||
|
|
||||||
node.name = getUniqueNodeName(localizedName, workflowsStore.canvasNames);
|
node.name = uniqueNodeName(localizedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveNodeWebhook(node: INodeUi, nodeTypeDescription: INodeTypeDescription) {
|
function resolveNodeWebhook(node: INodeUi, nodeTypeDescription: INodeTypeDescription) {
|
||||||
|
@ -1482,6 +1483,8 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
data.nodes.map((node) => ({ name: node.type, version: node.typeVersion })),
|
data.nodes.map((node) => ({ name: node.type, version: node.typeVersion })),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { uniqueNodeName } = useUniqueNodeName();
|
||||||
|
|
||||||
data.nodes.forEach((node) => {
|
data.nodes.forEach((node) => {
|
||||||
if (nodeTypesCount[node.type] !== undefined) {
|
if (nodeTypesCount[node.type] !== undefined) {
|
||||||
if (nodeTypesCount[node.type].exist >= nodeTypesCount[node.type].max) {
|
if (nodeTypesCount[node.type].exist >= nodeTypesCount[node.type].max) {
|
||||||
|
@ -1503,7 +1506,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
|
|
||||||
const localized = i18n.localizeNodeName(node.name, node.type);
|
const localized = i18n.localizeNodeName(node.name, node.type);
|
||||||
|
|
||||||
newName = getUniqueNodeName(localized, newNodeNames);
|
newName = uniqueNodeName(localized, Array.from(newNodeNames));
|
||||||
|
|
||||||
newNodeNames.add(newName);
|
newNodeNames.add(newName);
|
||||||
nodeNameTable[oldName] = newName;
|
nodeNameTable[oldName] = newName;
|
||||||
|
@ -1624,6 +1627,8 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { uniqueNodeName } = useUniqueNodeName();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nodeIdMap: { [prev: string]: string } = {};
|
const nodeIdMap: { [prev: string]: string } = {};
|
||||||
if (workflowData.nodes) {
|
if (workflowData.nodes) {
|
||||||
|
@ -1632,7 +1637,10 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
// Provide a new name for nodes that don't have one
|
// Provide a new name for nodes that don't have one
|
||||||
if (!node.name) {
|
if (!node.name) {
|
||||||
const nodeType = nodeTypesStore.getNodeType(node.type);
|
const nodeType = nodeTypesStore.getNodeType(node.type);
|
||||||
const newName = getUniqueNodeName(nodeType?.displayName ?? node.type, nodeNames);
|
const newName = uniqueNodeName(
|
||||||
|
nodeType?.displayName ?? node.type,
|
||||||
|
Array.from(nodeNames),
|
||||||
|
);
|
||||||
node.name = newName;
|
node.name = newName;
|
||||||
nodeNames.add(newName);
|
nodeNames.add(newName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
createCanvasConnectionHandleString,
|
createCanvasConnectionHandleString,
|
||||||
createCanvasConnectionId,
|
createCanvasConnectionId,
|
||||||
getUniqueNodeName,
|
|
||||||
mapCanvasConnectionToLegacyConnection,
|
mapCanvasConnectionToLegacyConnection,
|
||||||
mapLegacyConnectionsToCanvasConnections,
|
mapLegacyConnectionsToCanvasConnections,
|
||||||
mapLegacyEndpointsToCanvasConnectionPort,
|
mapLegacyEndpointsToCanvasConnectionPort,
|
||||||
|
@ -827,36 +826,6 @@ describe('mapLegacyEndpointsToCanvasConnectionPort', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getUniqueNodeName', () => {
|
|
||||||
it('should return the original name if it is unique', () => {
|
|
||||||
const name = 'Node A';
|
|
||||||
const existingNames = new Set(['Node B', 'Node C']);
|
|
||||||
const result = getUniqueNodeName(name, existingNames);
|
|
||||||
expect(result).toBe(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should append a number to the name if it already exists', () => {
|
|
||||||
const name = 'Node A';
|
|
||||||
const existingNames = new Set(['Node A', 'Node B']);
|
|
||||||
const result = getUniqueNodeName(name, existingNames);
|
|
||||||
expect(result).toBe('Node A 1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find the next available number for the name', () => {
|
|
||||||
const name = 'Node A';
|
|
||||||
const existingNames = new Set(['Node A', 'Node A 1', 'Node A 2']);
|
|
||||||
const result = getUniqueNodeName(name, existingNames);
|
|
||||||
expect(result).toBe('Node A 3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use UUID if more than 99 variations exist', () => {
|
|
||||||
const name = 'Node A';
|
|
||||||
const existingNames = new Set([...Array(100).keys()].map((i) => `Node A ${i}`).concat([name]));
|
|
||||||
const result = getUniqueNodeName(name, existingNames);
|
|
||||||
expect(result).toBe('Node A mock-uuid');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('checkOverlap', () => {
|
describe('checkOverlap', () => {
|
||||||
it('should return true when nodes overlap', () => {
|
it('should return true when nodes overlap', () => {
|
||||||
const node1 = { x: 0, y: 0, width: 10, height: 10 };
|
const node1 = { x: 0, y: 0, width: 10, height: 10 };
|
||||||
|
|
|
@ -3,7 +3,6 @@ import type { INodeUi } from '@/Interface';
|
||||||
import type { BoundingBox, CanvasConnection, CanvasConnectionPort } from '@/types';
|
import type { BoundingBox, CanvasConnection, CanvasConnectionPort } from '@/types';
|
||||||
import { CanvasConnectionMode } from '@/types';
|
import { CanvasConnectionMode } from '@/types';
|
||||||
import type { Connection } from '@vue-flow/core';
|
import type { Connection } from '@vue-flow/core';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { isValidCanvasConnectionMode, isValidNodeConnectionType } from '@/utils/typeGuards';
|
import { isValidCanvasConnectionMode, isValidNodeConnectionType } from '@/utils/typeGuards';
|
||||||
import { NodeConnectionType } from 'n8n-workflow';
|
import { NodeConnectionType } from 'n8n-workflow';
|
||||||
|
|
||||||
|
@ -195,21 +194,6 @@ export function mapLegacyEndpointsToCanvasConnectionPort(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUniqueNodeName(name: string, existingNames: Set<string>): string {
|
|
||||||
if (!existingNames.has(name)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 1; i < 100; i++) {
|
|
||||||
const newName = `${name} ${i}`;
|
|
||||||
if (!existingNames.has(newName)) {
|
|
||||||
return newName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${name} ${uuid()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkOverlap(node1: BoundingBox, node2: BoundingBox) {
|
export function checkOverlap(node1: BoundingBox, node2: BoundingBox) {
|
||||||
return !(
|
return !(
|
||||||
// node1 is completely to the left of node2
|
// node1 is completely to the left of node2
|
||||||
|
|
Loading…
Reference in a new issue