mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08: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:
parent
9c0fe413d9
commit
46fe544b9a
|
@ -489,4 +489,31 @@ describe('Execution', () => {
|
|||
.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,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
IPinData,
|
||||
IWorkflowBase,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
NodeHelpers,
|
||||
|
@ -29,6 +31,55 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
|
|||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
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({
|
||||
setup() {
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
|
@ -181,43 +232,21 @@ export const workflowRun = defineComponent({
|
|||
|
||||
const runData = this.workflowsStore.getWorkflowRunData;
|
||||
|
||||
let newRunData: IRunData | undefined;
|
||||
|
||||
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) {
|
||||
// If there is no data for any of the parent nodes make sure
|
||||
// that run data is empty that it runs regularly
|
||||
newRunData = undefined;
|
||||
}
|
||||
if (this.workflowsStore.isNewWorkflow) {
|
||||
await this.workflowHelpers.saveCurrentWorkflow();
|
||||
}
|
||||
|
||||
const workflowData = await this.workflowHelpers.getWorkflowDataToSave();
|
||||
|
||||
const consolidatedData = consolidateRunDataAndStartNodes(
|
||||
directParentNodes,
|
||||
runData,
|
||||
workflowData.pinData,
|
||||
workflow,
|
||||
);
|
||||
|
||||
const { startNodes } = consolidatedData;
|
||||
let { runData: newRunData } = consolidatedData;
|
||||
let executedNode: string | undefined;
|
||||
if (
|
||||
startNodes.length === 0 &&
|
||||
|
@ -236,12 +265,6 @@ export const workflowRun = defineComponent({
|
|||
executedNode = options.triggerNode;
|
||||
}
|
||||
|
||||
if (this.workflowsStore.isNewWorkflow) {
|
||||
await this.workflowHelpers.saveCurrentWorkflow();
|
||||
}
|
||||
|
||||
const workflowData = await this.workflowHelpers.getWorkflowDataToSave();
|
||||
|
||||
const startRunData: IStartRunData = {
|
||||
workflowData,
|
||||
runData: newRunData,
|
||||
|
|
Loading…
Reference in a new issue