mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-21 03:42:16 +00:00
perf(core): Batch items sent in runonceforeachitem mode (no-changelog) (#11870)
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
@@ -111,10 +111,11 @@ export class Code implements INodeType {
|
||||
if (runnersConfig.enabled && language === 'javaScript') {
|
||||
const code = this.getNodeParameter(codeParameterName, 0) as string;
|
||||
const sandbox = new JsTaskRunnerSandbox(code, nodeMode, workflowMode, this);
|
||||
const numInputItems = this.getInputData().length;
|
||||
|
||||
return nodeMode === 'runOnceForAllItems'
|
||||
? [await sandbox.runCodeAllItems()]
|
||||
: [await sandbox.runCodeForEachItem()];
|
||||
: [await sandbox.runCodeForEachItem(numInputItems)];
|
||||
}
|
||||
|
||||
const getSandbox = (index = 0) => {
|
||||
|
||||
@@ -18,6 +18,7 @@ export class JsTaskRunnerSandbox {
|
||||
private readonly nodeMode: CodeExecutionMode,
|
||||
private readonly workflowMode: WorkflowExecuteMode,
|
||||
private readonly executeFunctions: IExecuteFunctions,
|
||||
private readonly chunkSize = 1000,
|
||||
) {}
|
||||
|
||||
async runCodeAllItems(): Promise<INodeExecutionData[]> {
|
||||
@@ -39,24 +40,37 @@ export class JsTaskRunnerSandbox {
|
||||
: this.throwExecutionError(executionResult.error);
|
||||
}
|
||||
|
||||
async runCodeForEachItem(): Promise<INodeExecutionData[]> {
|
||||
async runCodeForEachItem(numInputItems: number): Promise<INodeExecutionData[]> {
|
||||
validateNoDisallowedMethodsInRunForEach(this.jsCode, 0);
|
||||
|
||||
const itemIndex = 0;
|
||||
const chunks = this.chunkInputItems(numInputItems);
|
||||
let executionResults: INodeExecutionData[] = [];
|
||||
|
||||
const executionResult = await this.executeFunctions.startJob<INodeExecutionData[]>(
|
||||
'javascript',
|
||||
{
|
||||
code: this.jsCode,
|
||||
nodeMode: this.nodeMode,
|
||||
workflowMode: this.workflowMode,
|
||||
continueOnFail: this.executeFunctions.continueOnFail(),
|
||||
},
|
||||
itemIndex,
|
||||
);
|
||||
for (const chunk of chunks) {
|
||||
const executionResult = await this.executeFunctions.startJob<INodeExecutionData[]>(
|
||||
'javascript',
|
||||
{
|
||||
code: this.jsCode,
|
||||
nodeMode: this.nodeMode,
|
||||
workflowMode: this.workflowMode,
|
||||
continueOnFail: this.executeFunctions.continueOnFail(),
|
||||
chunk: {
|
||||
startIndex: chunk.startIdx,
|
||||
count: chunk.count,
|
||||
},
|
||||
},
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
return executionResult.ok
|
||||
? executionResult.result
|
||||
: this.throwExecutionError(executionResult.error);
|
||||
if (!executionResult.ok) {
|
||||
return this.throwExecutionError(executionResult.error);
|
||||
}
|
||||
|
||||
executionResults = executionResults.concat(executionResult.result);
|
||||
}
|
||||
|
||||
return executionResults;
|
||||
}
|
||||
|
||||
private throwExecutionError(error: unknown): never {
|
||||
@@ -70,4 +84,22 @@ export class JsTaskRunnerSandbox {
|
||||
|
||||
throw new ApplicationError(`Unknown error: ${JSON.stringify(error)}`);
|
||||
}
|
||||
|
||||
/** Chunks the input items into chunks of 1000 items each */
|
||||
private chunkInputItems(numInputItems: number) {
|
||||
const numChunks = Math.ceil(numInputItems / this.chunkSize);
|
||||
const chunks = [];
|
||||
|
||||
for (let i = 0; i < numChunks; i++) {
|
||||
const startIdx = i * this.chunkSize;
|
||||
const isLastChunk = i === numChunks - 1;
|
||||
const count = isLastChunk ? numInputItems - startIdx : this.chunkSize;
|
||||
chunks.push({
|
||||
startIdx,
|
||||
count,
|
||||
});
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
import { createResultOk } from 'n8n-workflow';
|
||||
|
||||
import { JsTaskRunnerSandbox } from '../JsTaskRunnerSandbox';
|
||||
|
||||
describe('JsTaskRunnerSandbox', () => {
|
||||
describe('runCodeForEachItem', () => {
|
||||
it('should chunk the input items and execute the code for each chunk', async () => {
|
||||
const jsCode = 'console.log($item);';
|
||||
const nodeMode = 'runOnceForEachItem';
|
||||
const workflowMode = 'manual';
|
||||
const executeFunctions = mock<IExecuteFunctions>();
|
||||
const sandbox = new JsTaskRunnerSandbox(jsCode, nodeMode, workflowMode, executeFunctions, 2);
|
||||
let i = 1;
|
||||
executeFunctions.startJob.mockResolvedValue(createResultOk([{ json: { item: i++ } }]));
|
||||
|
||||
const numInputItems = 5;
|
||||
await sandbox.runCodeForEachItem(numInputItems);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
expect(executeFunctions.startJob).toHaveBeenCalledTimes(3);
|
||||
const calls = executeFunctions.startJob.mock.calls;
|
||||
expect(calls).toEqual([
|
||||
[
|
||||
'javascript',
|
||||
{
|
||||
code: jsCode,
|
||||
nodeMode,
|
||||
workflowMode,
|
||||
continueOnFail: executeFunctions.continueOnFail(),
|
||||
chunk: { startIndex: 0, count: 2 },
|
||||
},
|
||||
0,
|
||||
],
|
||||
[
|
||||
'javascript',
|
||||
{
|
||||
code: jsCode,
|
||||
nodeMode,
|
||||
workflowMode,
|
||||
continueOnFail: executeFunctions.continueOnFail(),
|
||||
chunk: { startIndex: 2, count: 2 },
|
||||
},
|
||||
0,
|
||||
],
|
||||
[
|
||||
'javascript',
|
||||
{
|
||||
code: jsCode,
|
||||
nodeMode,
|
||||
workflowMode,
|
||||
continueOnFail: executeFunctions.continueOnFail(),
|
||||
chunk: { startIndex: 4, count: 1 },
|
||||
},
|
||||
0,
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user