mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Fix browser crash with large execution result (#13580)
This commit is contained in:
parent
2cb9d9e29f
commit
1c8c7e34f9
|
@ -5,6 +5,7 @@ import {
|
||||||
getPairedItemsMapping,
|
getPairedItemsMapping,
|
||||||
MAX_ITEM_COUNT_FOR_PAIRING,
|
MAX_ITEM_COUNT_FOR_PAIRING,
|
||||||
} from './pairedItemUtils';
|
} from './pairedItemUtils';
|
||||||
|
import { type ITaskData } from 'n8n-workflow';
|
||||||
|
|
||||||
const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
||||||
data: {
|
data: {
|
||||||
|
@ -396,5 +397,41 @@ describe('pairedItemUtils', () => {
|
||||||
|
|
||||||
expect(getPairedItemsMapping(mockExecution)).toEqual({});
|
expect(getPairedItemsMapping(mockExecution)).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should abort mapping and return empty object if execution has too many pairs', () => {
|
||||||
|
const nodeCount = 10;
|
||||||
|
const runCount = 3;
|
||||||
|
const itemCountPerRun = 3;
|
||||||
|
const pairedItemCount = 3;
|
||||||
|
const mockExecution: Partial<IExecutionResponse> = {
|
||||||
|
data: {
|
||||||
|
resultData: {
|
||||||
|
runData: Object.fromEntries(
|
||||||
|
Array.from({ length: nodeCount }).map<[string, ITaskData[]]>((_, j) => [
|
||||||
|
`node_${j}`,
|
||||||
|
Array.from({ length: runCount }).map(() => ({
|
||||||
|
startTime: 1706027170005,
|
||||||
|
executionTime: 0,
|
||||||
|
source: j === 0 ? [] : [{ previousNode: `node_${j - 1}` }],
|
||||||
|
executionStatus: 'success',
|
||||||
|
data: {
|
||||||
|
main: [
|
||||||
|
Array.from({ length: itemCountPerRun }).map(() => ({
|
||||||
|
json: {},
|
||||||
|
pairedItem: Array.from({ length: pairedItemCount }).map((__, i) => ({
|
||||||
|
item: i,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPairedItemsMapping(mockExecution)).toEqual({});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import type { IPairedItemData, IRunData, ITaskData } from 'n8n-workflow';
|
import { type IPairedItemData, type IRunData, type ITaskData } from 'n8n-workflow';
|
||||||
import type { IExecutionResponse, TargetItem } from '@/Interface';
|
import type { IExecutionResponse, TargetItem } from '@/Interface';
|
||||||
import { isNotNull } from '@/utils/typeGuards';
|
import { isNotNull } from '@/utils/typeGuards';
|
||||||
|
|
||||||
export const MAX_ITEM_COUNT_FOR_PAIRING = 1000;
|
export const MAX_ITEM_COUNT_FOR_PAIRING = 1000;
|
||||||
|
|
||||||
|
const MAX_PAIR_COUNT = 100000;
|
||||||
|
|
||||||
|
interface Paths {
|
||||||
|
data: { [item: string]: string[][] };
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Utility functions that provide shared functionalities used to add paired item support to nodes
|
Utility functions that provide shared functionalities used to add paired item support to nodes
|
||||||
*/
|
*/
|
||||||
|
@ -51,29 +58,36 @@ export function getSourceItems(
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPairing(
|
function addPairing(
|
||||||
paths: { [item: string]: string[][] },
|
paths: Paths,
|
||||||
pairedItemId: string,
|
pairedItemId: string,
|
||||||
pairedItem: IPairedItemData,
|
pairedItem: IPairedItemData,
|
||||||
sources: ITaskData['source'],
|
sources: ITaskData['source'],
|
||||||
) {
|
) {
|
||||||
paths[pairedItemId] = paths[pairedItemId] || [];
|
if (paths.size >= MAX_PAIR_COUNT) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.data[pairedItemId] = paths.data[pairedItemId] || [];
|
||||||
|
|
||||||
const input = pairedItem.input || 0;
|
const input = pairedItem.input || 0;
|
||||||
const sourceNode = sources[input]?.previousNode;
|
const sourceNode = sources[input]?.previousNode;
|
||||||
if (!sourceNode) {
|
if (!sourceNode) {
|
||||||
// trigger nodes for example
|
// trigger nodes for example
|
||||||
paths[pairedItemId].push([pairedItemId]);
|
paths.data[pairedItemId].push([pairedItemId]);
|
||||||
|
paths.size++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const sourceNodeOutput = sources[input]?.previousNodeOutput || 0;
|
const sourceNodeOutput = sources[input]?.previousNodeOutput || 0;
|
||||||
const sourceNodeRun = sources[input]?.previousNodeRun || 0;
|
const sourceNodeRun = sources[input]?.previousNodeRun || 0;
|
||||||
|
|
||||||
const sourceItem = getPairedItemId(sourceNode, sourceNodeRun, sourceNodeOutput, pairedItem.item);
|
const sourceItem = getPairedItemId(sourceNode, sourceNodeRun, sourceNodeOutput, pairedItem.item);
|
||||||
if (!paths[sourceItem]) {
|
if (!paths.data[sourceItem]) {
|
||||||
paths[sourceItem] = [[sourceItem]]; // pinned data case
|
paths.data[sourceItem] = [[sourceItem]]; // pinned data case
|
||||||
|
paths.size++;
|
||||||
}
|
}
|
||||||
paths[sourceItem]?.forEach((path) => {
|
paths.data[sourceItem]?.forEach((path) => {
|
||||||
paths?.[pairedItemId]?.push([...path, pairedItemId]);
|
paths.data[pairedItemId]?.push([...path, pairedItemId]);
|
||||||
|
paths.size++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +96,7 @@ function addPairedItemIdsRec(
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
runData: IRunData,
|
runData: IRunData,
|
||||||
seen: Set<string>,
|
seen: Set<string>,
|
||||||
paths: { [item: string]: string[][] },
|
paths: Paths,
|
||||||
pinned: Set<string>,
|
pinned: Set<string>,
|
||||||
) {
|
) {
|
||||||
const key = `${node}_r${runIndex}`;
|
const key = `${node}_r${runIndex}`;
|
||||||
|
@ -128,7 +142,7 @@ function addPairedItemIdsRec(
|
||||||
outputData.forEach((executionData, item: number) => {
|
outputData.forEach((executionData, item: number) => {
|
||||||
const pairedItemId = getPairedItemId(node, runIndex, output, item);
|
const pairedItemId = getPairedItemId(node, runIndex, output, item);
|
||||||
if (!executionData.pairedItem) {
|
if (!executionData.pairedItem) {
|
||||||
paths[pairedItemId] = [];
|
paths.data[pairedItemId] = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,11 +164,11 @@ function addPairedItemIdsRec(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMapping(paths: { [item: string]: string[][] }): { [item: string]: Set<string> } {
|
function getMapping(paths: Paths): { [item: string]: Set<string> } {
|
||||||
const mapping: { [itemId: string]: Set<string> } = {};
|
const mapping: { [itemId: string]: Set<string> } = {};
|
||||||
|
|
||||||
Object.keys(paths).forEach((item) => {
|
Object.keys(paths.data).forEach((item) => {
|
||||||
paths?.[item]?.forEach((path) => {
|
paths.data[item]?.forEach((path) => {
|
||||||
path.forEach((otherItem) => {
|
path.forEach((otherItem) => {
|
||||||
if (otherItem !== item) {
|
if (otherItem !== item) {
|
||||||
mapping[otherItem] = mapping[otherItem] || new Set();
|
mapping[otherItem] = mapping[otherItem] || new Set();
|
||||||
|
@ -205,15 +219,20 @@ export function getPairedItemsMapping(executionResponse: Partial<IExecutionRespo
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const seen = new Set<string>();
|
const paths: Paths = { size: 0, data: {} };
|
||||||
const pinned = new Set(Object.keys(executionResponse.data.resultData.pinData || {}));
|
|
||||||
|
|
||||||
const paths: { [item: string]: string[][] } = {};
|
try {
|
||||||
Object.keys(runData).forEach((node) => {
|
const seen = new Set<string>();
|
||||||
runData[node].forEach((_, runIndex: number) => {
|
const pinned = new Set(Object.keys(executionResponse.data.resultData.pinData ?? {}));
|
||||||
addPairedItemIdsRec(node, runIndex, runData, seen, paths, pinned);
|
|
||||||
|
Object.keys(runData).forEach((node) => {
|
||||||
|
runData[node].forEach((_, runIndex: number) => {
|
||||||
|
addPairedItemIdsRec(node, runIndex, runData, seen, paths, pinned);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
return getMapping(paths);
|
return getMapping(paths);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue