From 4f6d76cd25215e75e4b03eb84f0476b346cbedcd Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 4 Mar 2025 17:49:03 +0100 Subject: [PATCH] fix(editor): Don't flag uiStore as dirty on node selected (#13641) --- .../src/stores/workflows.store.test.ts | 49 +++++++++++++++++++ .../editor-ui/src/stores/workflows.store.ts | 19 ++++--- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/packages/frontend/editor-ui/src/stores/workflows.store.test.ts b/packages/frontend/editor-ui/src/stores/workflows.store.test.ts index 6ae267f3ec..d00ab16731 100644 --- a/packages/frontend/editor-ui/src/stores/workflows.store.test.ts +++ b/packages/frontend/editor-ui/src/stores/workflows.store.test.ts @@ -681,6 +681,55 @@ describe('useWorkflowsStore', () => { }); }); + describe('updateNodeAtIndex', () => { + it.each([ + { + description: 'should update node at given index with provided data', + nodeIndex: 0, + nodeData: { name: 'Updated Node' }, + initialNodes: [{ name: 'Original Node' }], + expectedNodes: [{ name: 'Updated Node' }], + expectedResult: true, + }, + { + description: 'should not update node if index is invalid', + nodeIndex: -1, + nodeData: { name: 'Updated Node' }, + initialNodes: [{ name: 'Original Node' }], + expectedNodes: [{ name: 'Original Node' }], + expectedResult: false, + }, + { + description: 'should return false if node data is unchanged', + nodeIndex: 0, + nodeData: { name: 'Original Node' }, + initialNodes: [{ name: 'Original Node' }], + expectedNodes: [{ name: 'Original Node' }], + expectedResult: false, + }, + { + description: 'should update multiple properties of a node', + nodeIndex: 0, + nodeData: { name: 'Updated Node', type: 'newType' }, + initialNodes: [{ name: 'Original Node', type: 'oldType' }], + expectedNodes: [{ name: 'Updated Node', type: 'newType' }], + expectedResult: true, + }, + ])('$description', ({ nodeIndex, nodeData, initialNodes, expectedNodes, expectedResult }) => { + workflowsStore.workflow.nodes = initialNodes as unknown as IWorkflowDb['nodes']; + + const result = workflowsStore.updateNodeAtIndex(nodeIndex, nodeData); + + expect(result).toBe(expectedResult); + expect(workflowsStore.workflow.nodes).toEqual(expectedNodes); + }); + + it('should throw error if out of bounds', () => { + workflowsStore.workflow.nodes = []; + expect(() => workflowsStore.updateNodeAtIndex(0, { name: 'Updated Node' })).toThrowError(); + }); + }); + test.each([ // check userVersion behavior [-1, 1, 1], // userVersion -1, use default (1) diff --git a/packages/frontend/editor-ui/src/stores/workflows.store.ts b/packages/frontend/editor-ui/src/stores/workflows.store.ts index eb6fa21f4e..c98328da36 100644 --- a/packages/frontend/editor-ui/src/stores/workflows.store.ts +++ b/packages/frontend/editor-ui/src/stores/workflows.store.ts @@ -62,7 +62,7 @@ import { SEND_AND_WAIT_OPERATION, Workflow, } from 'n8n-workflow'; -import { findLast } from 'lodash-es'; +import { findLast, pick, isEqual } from 'lodash-es'; import { useRootStore } from '@/stores/root.store'; import * as workflowsApi from '@/api/workflows'; @@ -1138,10 +1138,17 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { return true; } - function updateNodeAtIndex(nodeIndex: number, nodeData: Partial): void { + /** + * @returns `true` if the object was changed + */ + function updateNodeAtIndex(nodeIndex: number, nodeData: Partial): boolean { if (nodeIndex !== -1) { - Object.assign(workflow.value.nodes[nodeIndex], nodeData); + const node = workflow.value.nodes[nodeIndex]; + const changed = !isEqual(pick(node, Object.keys(nodeData)), nodeData); + Object.assign(node, nodeData); + return changed; } + return false; } function setNodeIssue(nodeIssueData: INodeIssueData): boolean { @@ -1270,12 +1277,12 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { ); } - uiStore.stateIsDirty = true; - - updateNodeAtIndex(nodeIndex, { + const changed = updateNodeAtIndex(nodeIndex, { [updateInformation.key]: updateInformation.value, }); + uiStore.stateIsDirty = uiStore.stateIsDirty || changed; + const excludeKeys = ['position', 'notes', 'notesInFlow']; if (!excludeKeys.includes(updateInformation.key)) {