mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 03:12:15 +00:00
fix(editor): Fix unnecessary execution of nodes when there is pin data (#8567)
Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
@@ -489,4 +489,31 @@ describe('Execution', () => {
|
|||||||
.should('have.class', 'has-run');
|
.should('have.class', 'has-run');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.only('should send proper payload for node rerun', () => {
|
||||||
|
cy.createFixtureWorkflow(
|
||||||
|
'Multiple_trigger_node_rerun.json',
|
||||||
|
`Multiple trigger node rerun ${uuid()}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
workflowPage.getters.zoomToFitButton().click();
|
||||||
|
workflowPage.getters.executeWorkflowButton().click();
|
||||||
|
|
||||||
|
workflowPage.getters.clearExecutionDataButton().should('be.visible');
|
||||||
|
|
||||||
|
cy.intercept('POST', '/rest/workflows/run').as('workflowRun');
|
||||||
|
|
||||||
|
workflowPage.getters
|
||||||
|
.canvasNodeByName('do something with them')
|
||||||
|
.findChildByTestId('execute-node-button')
|
||||||
|
.click({ force: true });
|
||||||
|
|
||||||
|
cy.wait('@workflowRun').then((interception) => {
|
||||||
|
expect(interception.request.body).to.have.property('runData').that.is.an('object');
|
||||||
|
const expectedKeys = ['When clicking "Test workflow"', 'fetch 5 random users'];
|
||||||
|
|
||||||
|
expect(Object.keys(interception.request.body.runData)).to.have.lengthOf(expectedKeys.length);
|
||||||
|
expect(interception.request.body.runData).to.include.all.keys(expectedKeys);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
133
cypress/fixtures/Multiple_trigger_node_rerun.json
Normal file
133
cypress/fixtures/Multiple_trigger_node_rerun.json
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
{
|
||||||
|
"name": "Multiple trigger node rerun",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "5ae8991f-08a2-4b27-b61c-85e3b8a83693",
|
||||||
|
"name": "When clicking \"Test workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
460,
|
||||||
|
460
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "https://random-data-api.com/api/v2/users?size=5",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "22511d75-ab54-49e1-b8af-08b8b3372373",
|
||||||
|
"name": "fetch 5 random users",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.1,
|
||||||
|
"position": [
|
||||||
|
680,
|
||||||
|
460
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.first_name_reversed = item.json = {\n firstName: item.json.first_name,\n firstnNameReversed: item.json.first_name_BUG.split(\"\").reverse().join(\"\")\n };\n}\n\nreturn $input.all();"
|
||||||
|
},
|
||||||
|
"id": "4b66b15a-1685-46c1-a5e3-ebf8cdb11d21",
|
||||||
|
"name": "do something with them",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
900,
|
||||||
|
460
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rule": {
|
||||||
|
"interval": [
|
||||||
|
{
|
||||||
|
"field": "cronExpression",
|
||||||
|
"expression": "* * * * *"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "d763fc3b-6c4a-4d39-8857-ff84f7b6dc83",
|
||||||
|
"name": "Schedule Trigger",
|
||||||
|
"type": "n8n-nodes-base.scheduleTrigger",
|
||||||
|
"typeVersion": 1.1,
|
||||||
|
"position": [
|
||||||
|
460,
|
||||||
|
660
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Schedule Trigger": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"timestamp": "2024-01-29T13:45:00.006+01:00",
|
||||||
|
"Readable date": "January 29th 2024, 1:45:00 pm",
|
||||||
|
"Readable time": "1:45:00 pm",
|
||||||
|
"Day of week": "Monday",
|
||||||
|
"Year": "2024",
|
||||||
|
"Month": "January",
|
||||||
|
"Day of month": "29",
|
||||||
|
"Hour": "13",
|
||||||
|
"Minute": "45",
|
||||||
|
"Second": "00",
|
||||||
|
"Timezone": "CET +01:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"When clicking \"Test workflow\"": [
|
||||||
|
{
|
||||||
|
"json": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Test workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "fetch 5 random users",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fetch 5 random users": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "do something with them",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Schedule Trigger": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "fetch 5 random users",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "b9a6c3b0-15cd-4359-a92e-12a691a36b7b",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7"
|
||||||
|
},
|
||||||
|
"id": "PymcwIrbqgNh3O0K",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
@@ -7,7 +7,9 @@ import type {
|
|||||||
IRunData,
|
IRunData,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
|
IPinData,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
@@ -29,6 +31,55 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
|
|||||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
export const consolidateRunDataAndStartNodes = (
|
||||||
|
directParentNodes: string[],
|
||||||
|
runData: IRunData | null,
|
||||||
|
pinData: IPinData | undefined,
|
||||||
|
workflow: Workflow,
|
||||||
|
): { runData: IRunData | undefined; startNodes: string[] } => {
|
||||||
|
const startNodes: string[] = [];
|
||||||
|
let newRunData: IRunData | undefined;
|
||||||
|
|
||||||
|
if (runData !== null && Object.keys(runData).length !== 0) {
|
||||||
|
newRunData = {};
|
||||||
|
// Go over the direct parents of the node
|
||||||
|
for (const directParentNode of directParentNodes) {
|
||||||
|
// Go over the parents of that node so that we can get a start
|
||||||
|
// node for each of the branches
|
||||||
|
const parentNodes = workflow.getParentNodes(directParentNode, NodeConnectionType.Main);
|
||||||
|
|
||||||
|
// Add also the enabled direct parent to be checked
|
||||||
|
if (workflow.nodes[directParentNode].disabled) continue;
|
||||||
|
|
||||||
|
parentNodes.push(directParentNode);
|
||||||
|
|
||||||
|
for (const parentNode of parentNodes) {
|
||||||
|
if (
|
||||||
|
(runData[parentNode] === undefined || runData[parentNode].length === 0) &&
|
||||||
|
pinData?.[parentNode].length === 0
|
||||||
|
) {
|
||||||
|
// When we hit a node which has no data we stop and set it
|
||||||
|
// as a start node the execution from and then go on with other
|
||||||
|
// direct input nodes
|
||||||
|
startNodes.push(parentNode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (runData[parentNode] !== undefined) {
|
||||||
|
newRunData[parentNode] = runData[parentNode]?.slice(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(newRunData).length === 0) {
|
||||||
|
// If there is no data for any of the parent nodes make sure
|
||||||
|
// that run data is empty that it runs regularly
|
||||||
|
newRunData = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { runData: newRunData, startNodes };
|
||||||
|
};
|
||||||
|
|
||||||
export const workflowRun = defineComponent({
|
export const workflowRun = defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const nodeHelpers = useNodeHelpers();
|
const nodeHelpers = useNodeHelpers();
|
||||||
@@ -181,43 +232,21 @@ export const workflowRun = defineComponent({
|
|||||||
|
|
||||||
const runData = this.workflowsStore.getWorkflowRunData;
|
const runData = this.workflowsStore.getWorkflowRunData;
|
||||||
|
|
||||||
let newRunData: IRunData | undefined;
|
if (this.workflowsStore.isNewWorkflow) {
|
||||||
|
await this.workflowHelpers.saveCurrentWorkflow();
|
||||||
const startNodes: string[] = [];
|
|
||||||
|
|
||||||
if (runData !== null && Object.keys(runData).length !== 0) {
|
|
||||||
newRunData = {};
|
|
||||||
|
|
||||||
// Go over the direct parents of the node
|
|
||||||
for (const directParentNode of directParentNodes) {
|
|
||||||
// Go over the parents of that node so that we can get a start
|
|
||||||
// node for each of the branches
|
|
||||||
const parentNodes = workflow.getParentNodes(directParentNode, NodeConnectionType.Main);
|
|
||||||
|
|
||||||
// Add also the enabled direct parent to be checked
|
|
||||||
if (workflow.nodes[directParentNode].disabled) continue;
|
|
||||||
|
|
||||||
parentNodes.push(directParentNode);
|
|
||||||
|
|
||||||
for (const parentNode of parentNodes) {
|
|
||||||
if (runData[parentNode] === undefined || runData[parentNode].length === 0) {
|
|
||||||
// When we hit a node which has no data we stop and set it
|
|
||||||
// as a start node the execution from and then go on with other
|
|
||||||
// direct input nodes
|
|
||||||
startNodes.push(parentNode);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
newRunData[parentNode] = runData[parentNode].slice(0, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(newRunData).length === 0) {
|
const workflowData = await this.workflowHelpers.getWorkflowDataToSave();
|
||||||
// If there is no data for any of the parent nodes make sure
|
|
||||||
// that run data is empty that it runs regularly
|
|
||||||
newRunData = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const consolidatedData = consolidateRunDataAndStartNodes(
|
||||||
|
directParentNodes,
|
||||||
|
runData,
|
||||||
|
workflowData.pinData,
|
||||||
|
workflow,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { startNodes } = consolidatedData;
|
||||||
|
let { runData: newRunData } = consolidatedData;
|
||||||
let executedNode: string | undefined;
|
let executedNode: string | undefined;
|
||||||
if (
|
if (
|
||||||
startNodes.length === 0 &&
|
startNodes.length === 0 &&
|
||||||
@@ -236,12 +265,6 @@ export const workflowRun = defineComponent({
|
|||||||
executedNode = options.triggerNode;
|
executedNode = options.triggerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.workflowsStore.isNewWorkflow) {
|
|
||||||
await this.workflowHelpers.saveCurrentWorkflow();
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowData = await this.workflowHelpers.getWorkflowDataToSave();
|
|
||||||
|
|
||||||
const startRunData: IStartRunData = {
|
const startRunData: IStartRunData = {
|
||||||
workflowData,
|
workflowData,
|
||||||
runData: newRunData,
|
runData: newRunData,
|
||||||
|
|||||||
Reference in New Issue
Block a user