mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Use crypto.randomUUID()
to initialize node id if missing on new canvas (#11873)
This commit is contained in:
parent
3320436a6f
commit
bc4857a1b3
|
@ -90,7 +90,6 @@ import type {
|
||||||
Workflow,
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { deepCopy, NodeConnectionType, NodeHelpers, TelemetryHelpers } from 'n8n-workflow';
|
import { deepCopy, NodeConnectionType, NodeHelpers, TelemetryHelpers } from 'n8n-workflow';
|
||||||
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';
|
||||||
|
@ -759,7 +758,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
node: AddNodeDataWithTypeVersion,
|
node: AddNodeDataWithTypeVersion,
|
||||||
nodeTypeDescription: INodeTypeDescription,
|
nodeTypeDescription: INodeTypeDescription,
|
||||||
) {
|
) {
|
||||||
const id = node.id ?? uuid();
|
const id = node.id ?? nodeHelpers.assignNodeId(node as INodeUi);
|
||||||
const name = node.name ?? (nodeTypeDescription.defaults.name as string);
|
const name = node.name ?? (nodeTypeDescription.defaults.name as string);
|
||||||
const type = nodeTypeDescription.name;
|
const type = nodeTypeDescription.name;
|
||||||
const typeVersion = node.typeVersion;
|
const typeVersion = node.typeVersion;
|
||||||
|
@ -1027,7 +1026,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
|
|
||||||
function resolveNodeWebhook(node: INodeUi, nodeTypeDescription: INodeTypeDescription) {
|
function resolveNodeWebhook(node: INodeUi, nodeTypeDescription: INodeTypeDescription) {
|
||||||
if (nodeTypeDescription.webhooks?.length && !node.webhookId) {
|
if (nodeTypeDescription.webhooks?.length && !node.webhookId) {
|
||||||
node.webhookId = uuid();
|
nodeHelpers.assignWebhookId(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it's a webhook and the path is empty set the UUID as the default path
|
// if it's a webhook and the path is empty set the UUID as the default path
|
||||||
|
@ -1613,7 +1612,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
(n) => n.webhookId === node.webhookId,
|
(n) => n.webhookId === node.webhookId,
|
||||||
);
|
);
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
node.webhookId = uuid();
|
nodeHelpers.assignWebhookId(node);
|
||||||
|
|
||||||
if (node.parameters.path) {
|
if (node.parameters.path) {
|
||||||
node.parameters.path = node.webhookId as string;
|
node.parameters.path = node.webhookId as string;
|
||||||
|
@ -1623,13 +1622,13 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set all new ids when pasting/importing workflows
|
// Set all new ids when pasting/importing workflows
|
||||||
if (node.id) {
|
if (node.id) {
|
||||||
const newId = uuid();
|
const previousId = node.id;
|
||||||
nodeIdMap[newId] = node.id;
|
const newId = nodeHelpers.assignNodeId(node);
|
||||||
node.id = newId;
|
nodeIdMap[newId] = previousId;
|
||||||
} else {
|
} else {
|
||||||
node.id = uuid();
|
nodeHelpers.assignNodeId(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,4 +208,30 @@ describe('useNodeHelpers()', () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('assignNodeId()', () => {
|
||||||
|
it('should assign a unique id to the node', () => {
|
||||||
|
const { assignNodeId } = useNodeHelpers();
|
||||||
|
const node = createTestNode({
|
||||||
|
id: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
assignNodeId(node);
|
||||||
|
expect(node.id).not.toBe('');
|
||||||
|
expect(node.id).toMatch(/\w+(-\w+)+/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('assignWebhookId', () => {
|
||||||
|
it('should assign a unique id to the webhook', () => {
|
||||||
|
const { assignWebhookId } = useNodeHelpers();
|
||||||
|
const webhook = createTestNode({
|
||||||
|
id: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
assignWebhookId(webhook);
|
||||||
|
expect(webhook.webhookId).not.toBe('');
|
||||||
|
expect(webhook.webhookId).toMatch(/\w+(-\w+)+/);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { ref, nextTick } from 'vue';
|
import { ref, nextTick } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import type { Connection, ConnectionDetachedParams } from '@jsplumb/core';
|
import type { Connection, ConnectionDetachedParams } from '@jsplumb/core';
|
||||||
import { useHistoryStore } from '@/stores/history.store';
|
import { useHistoryStore } from '@/stores/history.store';
|
||||||
import {
|
import {
|
||||||
|
@ -1187,7 +1186,7 @@ export function useNodeHelpers() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!newNode.id) {
|
if (!newNode.id) {
|
||||||
newNode.id = uuid();
|
assignNodeId(newNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeType = nodeTypesStore.getNodeType(newNode.type, newNode.typeVersion);
|
nodeType = nodeTypesStore.getNodeType(newNode.type, newNode.typeVersion);
|
||||||
|
@ -1257,6 +1256,18 @@ export function useNodeHelpers() {
|
||||||
canvasStore.jsPlumbInstance?.setSuspendDrawing(false, true);
|
canvasStore.jsPlumbInstance?.setSuspendDrawing(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assignNodeId(node: INodeUi) {
|
||||||
|
const id = window.crypto.randomUUID();
|
||||||
|
node.id = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignWebhookId(node: INodeUi) {
|
||||||
|
const id = window.crypto.randomUUID();
|
||||||
|
node.webhookId = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasProxyAuth,
|
hasProxyAuth,
|
||||||
isCustomApiCallSelected,
|
isCustomApiCallSelected,
|
||||||
|
@ -1292,5 +1303,7 @@ export function useNodeHelpers() {
|
||||||
addPinDataConnections,
|
addPinDataConnections,
|
||||||
removePinDataConnections,
|
removePinDataConnections,
|
||||||
getNodeTaskData,
|
getNodeTaskData,
|
||||||
|
assignNodeId,
|
||||||
|
assignWebhookId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,6 @@ import { useTemplatesStore } from '@/stores/templates.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { getSourceItems } from '@/utils/pairedItemUtils';
|
import { getSourceItems } from '@/utils/pairedItemUtils';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||||
|
@ -64,13 +63,12 @@ import { useCanvasStore } from '@/stores/canvas.store';
|
||||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
import { tryToParseNumber } from '@/utils/typesUtils';
|
import { tryToParseNumber } from '@/utils/typesUtils';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import type { useRouter } from 'vue-router';
|
import type { useRouter, NavigationGuardNext } from 'vue-router';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { useProjectsStore } from '@/stores/projects.store';
|
import { useProjectsStore } from '@/stores/projects.store';
|
||||||
import { useTagsStore } from '@/stores/tags.store';
|
import { useTagsStore } from '@/stores/tags.store';
|
||||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||||
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
||||||
import type { NavigationGuardNext } from 'vue-router';
|
|
||||||
|
|
||||||
type ResolveParameterOptions = {
|
type ResolveParameterOptions = {
|
||||||
targetItem?: TargetItem;
|
targetItem?: TargetItem;
|
||||||
|
@ -937,7 +935,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
||||||
|
|
||||||
if (resetNodeIds) {
|
if (resetNodeIds) {
|
||||||
workflowDataRequest.nodes = workflowDataRequest.nodes!.map((node) => {
|
workflowDataRequest.nodes = workflowDataRequest.nodes!.map((node) => {
|
||||||
node.id = uuid();
|
nodeHelpers.assignNodeId(node);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
});
|
});
|
||||||
|
@ -946,8 +944,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
||||||
if (resetWebhookUrls) {
|
if (resetWebhookUrls) {
|
||||||
workflowDataRequest.nodes = workflowDataRequest.nodes!.map((node) => {
|
workflowDataRequest.nodes = workflowDataRequest.nodes!.map((node) => {
|
||||||
if (node.webhookId) {
|
if (node.webhookId) {
|
||||||
const newId = uuid();
|
const newId = nodeHelpers.assignWebhookId(node);
|
||||||
node.webhookId = newId;
|
|
||||||
node.parameters.path = newId;
|
node.parameters.path = newId;
|
||||||
changedNodes[node.name] = node.webhookId;
|
changedNodes[node.name] = node.webhookId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useSettingsStore } from './settings.store';
|
import { useSettingsStore } from './settings.store';
|
||||||
import { openPopUpWindow } from '@/utils/executionUtils';
|
import { openPopUpWindow } from '@/utils/executionUtils';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
|
|
||||||
const defaults: Omit<IWorkflowDb, 'id'> & { settings: NonNullable<IWorkflowDb['settings']> } = {
|
const defaults: Omit<IWorkflowDb, 'id'> & { settings: NonNullable<IWorkflowDb['settings']> } = {
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -117,6 +118,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
const workflowHelpers = useWorkflowHelpers({ router });
|
const workflowHelpers = useWorkflowHelpers({ router });
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
// -1 means the backend chooses the default
|
// -1 means the backend chooses the default
|
||||||
// 0 is the old flow
|
// 0 is the old flow
|
||||||
|
@ -1037,10 +1039,15 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
|
|
||||||
function setNodes(nodes: INodeUi[]): void {
|
function setNodes(nodes: INodeUi[]): void {
|
||||||
workflow.value.nodes = nodes;
|
workflow.value.nodes = nodes;
|
||||||
nodeMetadata.value = nodes.reduce<NodeMetadataMap>((acc, node) => {
|
nodes.forEach((node) => {
|
||||||
acc[node.name] = { pristine: true };
|
if (!node.id) {
|
||||||
return acc;
|
nodeHelpers.assignNodeId(node);
|
||||||
}, {});
|
}
|
||||||
|
|
||||||
|
if (!nodeMetadata.value[node.name]) {
|
||||||
|
nodeMetadata.value[node.name] = { pristine: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setConnections(connections: IConnections, updateWorkflow = false): void {
|
function setConnections(connections: IConnections, updateWorkflow = false): void {
|
||||||
|
|
Loading…
Reference in a new issue