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,
|
||||
MAX_ITEM_COUNT_FOR_PAIRING,
|
||||
} from './pairedItemUtils';
|
||||
import { type ITaskData } from 'n8n-workflow';
|
||||
|
||||
const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
||||
data: {
|
||||
|
@ -396,5 +397,41 @@ describe('pairedItemUtils', () => {
|
|||
|
||||
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 { isNotNull } from '@/utils/typeGuards';
|
||||
|
||||
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
|
||||
*/
|
||||
|
@ -51,29 +58,36 @@ export function getSourceItems(
|
|||
}
|
||||
|
||||
function addPairing(
|
||||
paths: { [item: string]: string[][] },
|
||||
paths: Paths,
|
||||
pairedItemId: string,
|
||||
pairedItem: IPairedItemData,
|
||||
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 sourceNode = sources[input]?.previousNode;
|
||||
if (!sourceNode) {
|
||||
// trigger nodes for example
|
||||
paths[pairedItemId].push([pairedItemId]);
|
||||
paths.data[pairedItemId].push([pairedItemId]);
|
||||
paths.size++;
|
||||
return;
|
||||
}
|
||||
const sourceNodeOutput = sources[input]?.previousNodeOutput || 0;
|
||||
const sourceNodeRun = sources[input]?.previousNodeRun || 0;
|
||||
|
||||
const sourceItem = getPairedItemId(sourceNode, sourceNodeRun, sourceNodeOutput, pairedItem.item);
|
||||
if (!paths[sourceItem]) {
|
||||
paths[sourceItem] = [[sourceItem]]; // pinned data case
|
||||
if (!paths.data[sourceItem]) {
|
||||
paths.data[sourceItem] = [[sourceItem]]; // pinned data case
|
||||
paths.size++;
|
||||
}
|
||||
paths[sourceItem]?.forEach((path) => {
|
||||
paths?.[pairedItemId]?.push([...path, pairedItemId]);
|
||||
paths.data[sourceItem]?.forEach((path) => {
|
||||
paths.data[pairedItemId]?.push([...path, pairedItemId]);
|
||||
paths.size++;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -82,7 +96,7 @@ function addPairedItemIdsRec(
|
|||
runIndex: number,
|
||||
runData: IRunData,
|
||||
seen: Set<string>,
|
||||
paths: { [item: string]: string[][] },
|
||||
paths: Paths,
|
||||
pinned: Set<string>,
|
||||
) {
|
||||
const key = `${node}_r${runIndex}`;
|
||||
|
@ -128,7 +142,7 @@ function addPairedItemIdsRec(
|
|||
outputData.forEach((executionData, item: number) => {
|
||||
const pairedItemId = getPairedItemId(node, runIndex, output, item);
|
||||
if (!executionData.pairedItem) {
|
||||
paths[pairedItemId] = [];
|
||||
paths.data[pairedItemId] = [];
|
||||
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> } = {};
|
||||
|
||||
Object.keys(paths).forEach((item) => {
|
||||
paths?.[item]?.forEach((path) => {
|
||||
Object.keys(paths.data).forEach((item) => {
|
||||
paths.data[item]?.forEach((path) => {
|
||||
path.forEach((otherItem) => {
|
||||
if (otherItem !== item) {
|
||||
mapping[otherItem] = mapping[otherItem] || new Set();
|
||||
|
@ -205,15 +219,20 @@ export function getPairedItemsMapping(executionResponse: Partial<IExecutionRespo
|
|||
return {};
|
||||
}
|
||||
|
||||
const seen = new Set<string>();
|
||||
const pinned = new Set(Object.keys(executionResponse.data.resultData.pinData || {}));
|
||||
const paths: Paths = { size: 0, data: {} };
|
||||
|
||||
const paths: { [item: string]: string[][] } = {};
|
||||
Object.keys(runData).forEach((node) => {
|
||||
runData[node].forEach((_, runIndex: number) => {
|
||||
addPairedItemIdsRec(node, runIndex, runData, seen, paths, pinned);
|
||||
try {
|
||||
const seen = new Set<string>();
|
||||
const pinned = new Set(Object.keys(executionResponse.data.resultData.pinData ?? {}));
|
||||
|
||||
Object.keys(runData).forEach((node) => {
|
||||
runData[node].forEach((_, runIndex: number) => {
|
||||
addPairedItemIdsRec(node, runIndex, runData, seen, paths, pinned);
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
|
||||
return getMapping(paths);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue