mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
fix(editor): Don't mark node as dirty when NDV is opened (#15222)
This commit is contained in:
@@ -55,13 +55,16 @@ describe('Sub-workflow creation and typed usage', () => {
|
|||||||
|
|
||||||
openNode('Execute Workflow');
|
openNode('Execute Workflow');
|
||||||
|
|
||||||
|
let openedUrl = '';
|
||||||
|
|
||||||
// Prevent sub-workflow from opening in new window
|
// Prevent sub-workflow from opening in new window
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
cy.stub(win, 'open').callsFake((url) => {
|
cy.stub(win, 'open').callsFake((url) => {
|
||||||
cy.visit(url);
|
openedUrl = url;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
selectResourceLocatorItem('workflowId', 0, 'Create a');
|
selectResourceLocatorItem('workflowId', 0, 'Create a');
|
||||||
|
cy.then(() => cy.visit(openedUrl));
|
||||||
// **************************
|
// **************************
|
||||||
// NAVIGATE TO CHILD WORKFLOW
|
// NAVIGATE TO CHILD WORKFLOW
|
||||||
// **************************
|
// **************************
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type {
|
|||||||
ResourceMapperFields,
|
ResourceMapperFields,
|
||||||
ResourceMapperValue,
|
ResourceMapperValue,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeHelpers } from 'n8n-workflow';
|
import { deepCopy, NodeHelpers } from 'n8n-workflow';
|
||||||
import { computed, onMounted, reactive, watch } from 'vue';
|
import { computed, onMounted, reactive, watch } from 'vue';
|
||||||
import MappingModeSelect from './MappingModeSelect.vue';
|
import MappingModeSelect from './MappingModeSelect.vue';
|
||||||
import MatchingColumnsSelect from './MatchingColumnsSelect.vue';
|
import MatchingColumnsSelect from './MatchingColumnsSelect.vue';
|
||||||
@@ -537,7 +537,9 @@ function emitValueChanged(): void {
|
|||||||
pruneParamValues();
|
pruneParamValues();
|
||||||
emit('valueChanged', {
|
emit('valueChanged', {
|
||||||
name: `${props.path}`,
|
name: `${props.path}`,
|
||||||
value: state.paramValue,
|
// deepCopy ensures that mutations to state.paramValue that occur in
|
||||||
|
// this component are never visible to the store without explicit event emits
|
||||||
|
value: deepCopy(state.paramValue),
|
||||||
node: props.node?.name,
|
node: props.node?.name,
|
||||||
});
|
});
|
||||||
updateNodeIssues();
|
updateNodeIssues();
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import * as apiUtils from '@/utils/apiUtils';
|
|||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useLocalStorage } from '@vueuse/core';
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { createTestNode } from '@/__tests__/mocks';
|
||||||
|
|
||||||
vi.mock('@/stores/ndv.store', () => ({
|
vi.mock('@/stores/ndv.store', () => ({
|
||||||
useNDVStore: vi.fn(() => ({
|
useNDVStore: vi.fn(() => ({
|
||||||
@@ -1041,6 +1042,36 @@ describe('useWorkflowsStore', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setNodeParameters', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
workflowsStore.setNodes([createTestNode({ name: 'a', parameters: { p: 1, q: true } })]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set node parameters', () => {
|
||||||
|
expect(workflowsStore.nodesByName.a.parameters).toEqual({ p: 1, q: true });
|
||||||
|
|
||||||
|
workflowsStore.setNodeParameters({ name: 'a', value: { q: false, r: 's' } });
|
||||||
|
|
||||||
|
expect(workflowsStore.nodesByName.a.parameters).toEqual({ q: false, r: 's' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set node parameters preserving existing ones if append=true', () => {
|
||||||
|
expect(workflowsStore.nodesByName.a.parameters).toEqual({ p: 1, q: true });
|
||||||
|
|
||||||
|
workflowsStore.setNodeParameters({ name: 'a', value: { q: false, r: 's' } }, true);
|
||||||
|
|
||||||
|
expect(workflowsStore.nodesByName.a.parameters).toEqual({ p: 1, q: false, r: 's' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update last parameter update time if parameters are set to the same value', () => {
|
||||||
|
expect(workflowsStore.getParametersLastUpdate('a')).toEqual(undefined);
|
||||||
|
|
||||||
|
workflowsStore.setNodeParameters({ name: 'a', value: { p: 1, q: true } });
|
||||||
|
|
||||||
|
expect(workflowsStore.getParametersLastUpdate('a')).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('renameNodeSelectedAndExecution', () => {
|
describe('renameNodeSelectedAndExecution', () => {
|
||||||
it('should rename node and update execution data', () => {
|
it('should rename node and update execution data', () => {
|
||||||
const nodeName = 'Rename me';
|
const nodeName = 'Rename me';
|
||||||
|
|||||||
@@ -1290,12 +1290,12 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNodeIssue(nodeIssueData: INodeIssueData): boolean {
|
function setNodeIssue(nodeIssueData: INodeIssueData): void {
|
||||||
const nodeIndex = workflow.value.nodes.findIndex((node) => {
|
const nodeIndex = workflow.value.nodes.findIndex((node) => {
|
||||||
return node.name === nodeIssueData.node;
|
return node.name === nodeIssueData.node;
|
||||||
});
|
});
|
||||||
if (nodeIndex === -1) {
|
if (nodeIndex === -1) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = workflow.value.nodes[nodeIndex];
|
const node = workflow.value.nodes[nodeIndex];
|
||||||
@@ -1304,7 +1304,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
// Remove the value if one exists
|
// Remove the value if one exists
|
||||||
if (node.issues?.[nodeIssueData.type] === undefined) {
|
if (node.issues?.[nodeIssueData.type] === undefined) {
|
||||||
// No values for type exist so nothing has to get removed
|
// No values for type exist so nothing has to get removed
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { [nodeIssueData.type]: removedNodeIssue, ...remainingNodeIssues } = node.issues;
|
const { [nodeIssueData.type]: removedNodeIssue, ...remainingNodeIssues } = node.issues;
|
||||||
@@ -1319,7 +1319,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNode(nodeData: INodeUi): void {
|
function addNode(nodeData: INodeUi): void {
|
||||||
@@ -1390,12 +1389,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
|
|
||||||
if (nodeIndex !== -1) {
|
if (nodeIndex !== -1) {
|
||||||
for (const key of Object.keys(updateInformation.properties)) {
|
for (const key of Object.keys(updateInformation.properties)) {
|
||||||
uiStore.stateIsDirty = true;
|
|
||||||
|
|
||||||
const typedKey = key as keyof INodeUpdatePropertiesInformation['properties'];
|
const typedKey = key as keyof INodeUpdatePropertiesInformation['properties'];
|
||||||
const property = updateInformation.properties[typedKey];
|
const property = updateInformation.properties[typedKey];
|
||||||
|
|
||||||
updateNodeAtIndex(nodeIndex, { [key]: property });
|
const changed = updateNodeAtIndex(nodeIndex, { [key]: property });
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
uiStore.stateIsDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1420,7 +1421,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
|
|
||||||
const excludeKeys = ['position', 'notes', 'notesInFlow'];
|
const excludeKeys = ['position', 'notes', 'notesInFlow'];
|
||||||
|
|
||||||
if (!excludeKeys.includes(updateInformation.key)) {
|
if (changed && !excludeKeys.includes(updateInformation.key)) {
|
||||||
nodeMetadata.value[workflow.value.nodes[nodeIndex].name].parametersLastUpdatedAt = Date.now();
|
nodeMetadata.value[workflow.value.nodes[nodeIndex].name].parametersLastUpdatedAt = Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1439,17 +1440,19 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
|
|
||||||
const node = workflow.value.nodes[nodeIndex];
|
const node = workflow.value.nodes[nodeIndex];
|
||||||
|
|
||||||
uiStore.stateIsDirty = true;
|
|
||||||
const newParameters =
|
const newParameters =
|
||||||
!!append && isObject(updateInformation.value)
|
!!append && isObject(updateInformation.value)
|
||||||
? { ...node.parameters, ...updateInformation.value }
|
? { ...node.parameters, ...updateInformation.value }
|
||||||
: updateInformation.value;
|
: updateInformation.value;
|
||||||
|
|
||||||
updateNodeAtIndex(nodeIndex, {
|
const changed = updateNodeAtIndex(nodeIndex, {
|
||||||
parameters: newParameters as INodeParameters,
|
parameters: newParameters as INodeParameters,
|
||||||
});
|
});
|
||||||
|
|
||||||
nodeMetadata.value[node.name].parametersLastUpdatedAt = Date.now();
|
if (changed) {
|
||||||
|
uiStore.stateIsDirty = true;
|
||||||
|
nodeMetadata.value[node.name].parametersLastUpdatedAt = Date.now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLastNodeParameters(updateInformation: IUpdateInformation): void {
|
function setLastNodeParameters(updateInformation: IUpdateInformation): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user