Skip to content

Commit

Permalink
Merge branch 'master' of github.com:n8n-io/n8n into feature-sub-workf…
Browse files Browse the repository at this point in the history
…low-inputs
  • Loading branch information
igatanasov committed Dec 10, 2024
2 parents 0cea411 + db09d0e commit e4d9e7f
Show file tree
Hide file tree
Showing 88 changed files with 3,993 additions and 716 deletions.
1 change: 1 addition & 0 deletions cypress/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"cypress:install": "cypress install",
"test:e2e:ui": "scripts/run-e2e.js ui",
"test:e2e:dev": "scripts/run-e2e.js dev",
"test:e2e:dev:v2": "scripts/run-e2e.js dev:v2",
"test:e2e:all": "scripts/run-e2e.js all",
"format": "biome format --write .",
"format:check": "biome ci .",
Expand Down
11 changes: 11 additions & 0 deletions cypress/scripts/run-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ switch (scenario) {
},
});
break;
case 'dev:v2':
runTests({
startCommand: 'develop',
url: 'http://localhost:8080/favicon.ico',
testCommand: 'cypress open',
customEnv: {
CYPRESS_NODE_VIEW_VERSION: 2,
CYPRESS_BASE_URL: 'http://localhost:8080',
},
});
break;
case 'all':
const specSuiteFilter = process.argv[3];
const specParam = specSuiteFilter ? ` --spec **/*${specSuiteFilter}*` : '';
Expand Down
2 changes: 1 addition & 1 deletion docker/images/n8n-custom/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ COPY docker/images/n8n/docker-entrypoint.sh /

# Setup the Task Runner Launcher
ARG TARGETPLATFORM
ARG LAUNCHER_VERSION=0.7.0-rc
ARG LAUNCHER_VERSION=1.0.0
COPY docker/images/n8n/n8n-task-runners.json /etc/n8n-task-runners.json
# Download, verify, then extract the launcher binary
RUN \
Expand Down
2 changes: 1 addition & 1 deletion docker/images/n8n/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ RUN set -eux; \

# Setup the Task Runner Launcher
ARG TARGETPLATFORM
ARG LAUNCHER_VERSION=0.7.0-rc
ARG LAUNCHER_VERSION=1.0.0
COPY n8n-task-runners.json /etc/n8n-task-runners.json
# Download, verify, then extract the launcher binary
RUN \
Expand Down
1 change: 1 addition & 0 deletions docker/images/n8n/n8n-task-runners.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"N8N_RUNNERS_TASK_BROKER_URI",
"N8N_RUNNERS_MAX_PAYLOAD",
"N8N_RUNNERS_MAX_CONCURRENCY",
"N8N_RUNNERS_TASK_TIMEOUT",
"N8N_RUNNERS_HEALTH_CHECK_SERVER_ENABLED",
"N8N_RUNNERS_HEALTH_CHECK_SERVER_HOST",
"N8N_RUNNERS_HEALTH_CHECK_SERVER_PORT",
Expand Down
1 change: 1 addition & 0 deletions packages/@n8n/api-types/src/dto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { RoleChangeRequestDto } from './user/role-change-request.dto';
export { SettingsUpdateRequestDto } from './user/settings-update-request.dto';
export { UserUpdateRequestDto } from './user/user-update-request.dto';
export { CommunityRegisteredRequestDto } from './license/community-registered-request.dto';
export { VariableListRequestDto } from './variables/variables-list-request.dto';
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { z } from 'zod';
import { Z } from 'zod-class';

export class VariableListRequestDto extends Z.class({
state: z.literal('empty').optional(),
}) {}
2 changes: 1 addition & 1 deletion packages/@n8n/api-types/src/frontend-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export interface FrontendSettings {
pruneTime: number;
licensePruneTime: number;
};
pruning: {
pruning?: {
isEnabled: boolean;
maxAge: number;
maxCount: number;
Expand Down
4 changes: 2 additions & 2 deletions packages/@n8n/config/src/configs/runners.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ export class TaskRunnersConfig {
@Env('N8N_RUNNERS_MAX_CONCURRENCY')
maxConcurrency: number = 5;

/** How long (in seconds) a task is allowed to take for completion, else the task will be aborted and the runner restarted. Must be greater than 0. */
/** How long (in seconds) a task is allowed to take for completion, else the task will be aborted. (In internal mode, the runner will also be restarted.) Must be greater than 0. */
@Env('N8N_RUNNERS_TASK_TIMEOUT')
taskTimeout: number = 60;

/** How often (in seconds) the runner must send a heartbeat to the broker, else the task will be aborted and the runner restarted. Must be greater than 0. */
/** How often (in seconds) the runner must send a heartbeat to the broker, else the task will be aborted. (In internal mode, the runner will also be restarted.) Must be greater than 0. */
@Env('N8N_RUNNERS_HEARTBEAT_INTERVAL')
heartbeatInterval: number = 30;
}
1 change: 0 additions & 1 deletion packages/@n8n/task-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
},
"dependencies": {
"@n8n/config": "workspace:*",
"@sentry/integrations": "catalog:",
"@sentry/node": "catalog:",
"acorn": "8.14.0",
"acorn-walk": "8.3.4",
Expand Down
3 changes: 3 additions & 0 deletions packages/@n8n/task-runner/src/config/base-runner-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export class BaseRunnerConfig {
@Env('GENERIC_TIMEZONE')
timezone: string = 'America/New_York';

@Env('N8N_RUNNERS_TASK_TIMEOUT')
taskTimeout: number = 60;

@Nested
healthcheckServer!: HealthcheckServerConfig;
}
14 changes: 10 additions & 4 deletions packages/@n8n/task-runner/src/error-reporter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { RewriteFrames } from '@sentry/integrations';
import { init, setTag, captureException, close } from '@sentry/node';
import type { ErrorEvent, EventHint } from '@sentry/types';
import {
init,
setTag,
captureException,
close,
rewriteFramesIntegration,
type EventHint,
type ErrorEvent,
} from '@sentry/node';
import * as a from 'assert/strict';
import { createHash } from 'crypto';
import { ApplicationError } from 'n8n-workflow';
Expand Down Expand Up @@ -52,7 +58,7 @@ export class ErrorReporter {
beforeSend: async (event, hint) => await this.beforeSend(event, hint),
integrations: (integrations) => [
...integrations.filter(({ name }) => ENABLED_INTEGRATIONS.includes(name)),
new RewriteFrames({ root: process.cwd() }),
rewriteFramesIntegration({ root: process.cwd() }),
],
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApplicationError } from 'n8n-workflow';
import { createServer } from 'node:http';

export class HealthcheckServer {
export class HealthCheckServer {
private server = createServer((_, res) => {
res.writeHead(200);
res.end('OK');
Expand All @@ -21,7 +21,7 @@ export class HealthcheckServer {

this.server.listen(port, host, () => {
this.server.removeListener('error', portInUseErrorHandler);
console.log(`Healthcheck server listening on ${host}, port ${port}`);
console.log(`Health check server listening on ${host}, port ${port}`);
resolve();
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { mock } from 'jest-mock-extended';
import { DateTime } from 'luxon';
import { setGlobalState, type CodeExecutionMode, type IDataObject } from 'n8n-workflow';
import fs from 'node:fs';
Expand Down Expand Up @@ -61,7 +62,7 @@ describe('JsTaskRunner', () => {
runner?: JsTaskRunner;
}) => {
jest.spyOn(runner, 'requestData').mockResolvedValue(taskData);
return await runner.executeTask(task);
return await runner.executeTask(task, mock<AbortSignal>());
};

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('TestRunner', () => {
maxPayloadSize: 1024,
taskBrokerUri: 'http://localhost:8080',
timezone: 'America/New_York',
taskTimeout: 60,
healthcheckServer: {
enabled: false,
host: 'localhost',
Expand All @@ -37,6 +38,8 @@ describe('TestRunner', () => {
maxPayload: 1024,
}),
);

runner.clearIdleTimer();
});

it('should handle different taskBrokerUri formats correctly', () => {
Expand All @@ -48,6 +51,7 @@ describe('TestRunner', () => {
maxPayloadSize: 1024,
taskBrokerUri: 'https://example.com:3000/path',
timezone: 'America/New_York',
taskTimeout: 60,
healthcheckServer: {
enabled: false,
host: 'localhost',
Expand All @@ -64,6 +68,8 @@ describe('TestRunner', () => {
maxPayload: 1024,
}),
);

runner.clearIdleTimer();
});

it('should throw an error if taskBrokerUri is invalid', () => {
Expand All @@ -77,6 +83,7 @@ describe('TestRunner', () => {
maxPayloadSize: 1024,
taskBrokerUri: 'not-a-valid-uri',
timezone: 'America/New_York',
taskTimeout: 60,
healthcheckServer: {
enabled: false,
host: 'localhost',
Expand All @@ -86,4 +93,65 @@ describe('TestRunner', () => {
).toThrowError(/Invalid URL/);
});
});

describe('taskCancelled', () => {
it('should reject pending requests when task is cancelled', () => {
const runner = new TestRunner({
taskType: 'test-task',
maxConcurrency: 5,
idleTimeout: 60,
grantToken: 'test-token',
maxPayloadSize: 1024,
taskBrokerUri: 'http://localhost:8080',
timezone: 'America/New_York',
taskTimeout: 60,
healthcheckServer: {
enabled: false,
host: 'localhost',
port: 8081,
},
});

const taskId = 'test-task';
runner.runningTasks.set(taskId, {
taskId,
active: false,
cancelled: false,
});

const dataRequestReject = jest.fn();
const nodeTypesRequestReject = jest.fn();

runner.dataRequests.set('data-req', {
taskId,
requestId: 'data-req',
resolve: jest.fn(),
reject: dataRequestReject,
});

runner.nodeTypesRequests.set('node-req', {
taskId,
requestId: 'node-req',
resolve: jest.fn(),
reject: nodeTypesRequestReject,
});

runner.taskCancelled(taskId, 'test-reason');

expect(dataRequestReject).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Task cancelled: test-reason',
}),
);

expect(nodeTypesRequestReject).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Task cancelled: test-reason',
}),
);

expect(runner.dataRequests.size).toBe(0);
expect(runner.nodeTypesRequests.size).toBe(0);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ApplicationError } from 'n8n-workflow';

export class TaskCancelledError extends ApplicationError {
constructor(reason: string) {
super(`Task cancelled: ${reason}`, { level: 'warning' });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ApplicationError } from 'n8n-workflow';

export class TimeoutError extends ApplicationError {
description: string;

constructor(taskTimeout: number) {
super(
`Task execution timed out after ${taskTimeout} ${taskTimeout === 1 ? 'second' : 'seconds'}`,
);

const subtitle = 'The task runner was taking too long on this task, so the task was aborted.';

const fixes = {
optimizeScript:
'Optimize your script to prevent long-running tasks, e.g. by processing data in smaller batches.',
ensureTermination:
'Ensure that all paths in your script are able to terminate, i.e. no infinite loops.',
};

const suggestions = [fixes.optimizeScript, fixes.ensureTermination];

const suggestionsText = suggestions
.map((suggestion, index) => `${index + 1}. ${suggestion}`)
.join('<br/>');

const description = `${subtitle} You can try the following:<br/><br/>${suggestionsText}`;

this.description = description;
}
}
Loading

0 comments on commit e4d9e7f

Please sign in to comment.