mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
🔀 Merge master
This commit is contained in:
@@ -26,7 +26,7 @@ var nodeVersion = process.versions.node.split('.');
|
||||
|
||||
if (parseInt(nodeVersion[0], 10) < 14) {
|
||||
console.log(`\nYour Node.js version (${process.versions.node}) is too old to run n8n.\nPlease update to version 14 or later!\n`);
|
||||
process.exit(0);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
require('@oclif/command').run()
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
InternalHooksManager,
|
||||
IWorkflowBase,
|
||||
IWorkflowExecutionDataProcess,
|
||||
@@ -125,7 +126,8 @@ export class Execute extends Command {
|
||||
await externalHooks.init();
|
||||
|
||||
const instanceId = await UserSettings.getInstanceId();
|
||||
InternalHooksManager.init(instanceId);
|
||||
const { cli } = await GenericHelpers.getVersions();
|
||||
InternalHooksManager.init(instanceId, cli);
|
||||
|
||||
// Add the found types to an instance other parts of the application can use
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
InternalHooksManager,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
@@ -305,7 +306,8 @@ export class ExecuteBatch extends Command {
|
||||
await externalHooks.init();
|
||||
|
||||
const instanceId = await UserSettings.getInstanceId();
|
||||
InternalHooksManager.init(instanceId);
|
||||
const { cli } = await GenericHelpers.getVersions();
|
||||
InternalHooksManager.init(instanceId, cli);
|
||||
|
||||
// Add the found types to an instance other parts of the application can use
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
@@ -153,17 +153,6 @@ export class Start extends Command {
|
||||
LoggerProxy.init(logger);
|
||||
logger.info('Initializing n8n process');
|
||||
|
||||
logger.info(
|
||||
'\n' +
|
||||
'****************************************************\n' +
|
||||
'* *\n' +
|
||||
'* n8n now sends selected, anonymous telemetry. *\n' +
|
||||
'* For more details (and how to opt out): *\n' +
|
||||
'* https://docs.n8n.io/reference/telemetry.html *\n' +
|
||||
'* *\n' +
|
||||
'****************************************************\n',
|
||||
);
|
||||
|
||||
// Start directly with the init of the database to improve startup time
|
||||
const startDbInitPromise = Db.init().catch((error: Error) => {
|
||||
logger.error(`There was an error initializing DB: "${error.message}"`);
|
||||
@@ -313,7 +302,8 @@ export class Start extends Command {
|
||||
}
|
||||
|
||||
const instanceId = await UserSettings.getInstanceId();
|
||||
InternalHooksManager.init(instanceId);
|
||||
const { cli } = await GenericHelpers.getVersions();
|
||||
InternalHooksManager.init(instanceId, cli);
|
||||
|
||||
await Server.start();
|
||||
|
||||
|
||||
@@ -149,7 +149,8 @@ export class Webhook extends Command {
|
||||
await startDbInitPromise;
|
||||
|
||||
const instanceId = await UserSettings.getInstanceId();
|
||||
InternalHooksManager.init(instanceId);
|
||||
const { cli } = await GenericHelpers.getVersions();
|
||||
InternalHooksManager.init(instanceId, cli);
|
||||
|
||||
if (config.get('executions.mode') === 'queue') {
|
||||
const redisHost = config.get('queue.bull.redis.host');
|
||||
|
||||
@@ -271,10 +271,10 @@ export class Worker extends Command {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Worker.jobQueue.process(flags.concurrency, async (job) => this.runJob(job, nodeTypes));
|
||||
|
||||
const instanceId = await UserSettings.getInstanceId();
|
||||
InternalHooksManager.init(instanceId);
|
||||
|
||||
const versions = await GenericHelpers.getVersions();
|
||||
const instanceId = await UserSettings.getInstanceId();
|
||||
|
||||
InternalHooksManager.init(instanceId, versions.cli);
|
||||
|
||||
console.info('\nn8n worker is now ready');
|
||||
console.info(` * Version: ${versions.cli}`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n",
|
||||
"version": "0.152.0",
|
||||
"version": "0.153.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -112,8 +112,8 @@
|
||||
"lodash.get": "^4.4.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.96.0",
|
||||
"n8n-editor-ui": "~0.119.0",
|
||||
"n8n-nodes-base": "~0.149.0",
|
||||
"n8n-editor-ui": "~0.120.0",
|
||||
"n8n-nodes-base": "~0.150.0",
|
||||
"n8n-workflow": "~0.79.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { stringify } from 'flatted';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
@@ -82,6 +83,7 @@ export class ActiveExecutions {
|
||||
|
||||
const execution = {
|
||||
id: executionId,
|
||||
data: stringify(executionData.executionData!),
|
||||
waitTill: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -314,7 +314,10 @@ export interface IDiagnosticInfo {
|
||||
|
||||
export interface IInternalHooksClass {
|
||||
onN8nStop(): Promise<void>;
|
||||
onServerStarted(diagnosticInfo: IDiagnosticInfo): Promise<unknown[]>;
|
||||
onServerStarted(
|
||||
diagnosticInfo: IDiagnosticInfo,
|
||||
firstWorkflowCreatedAt?: Date,
|
||||
): Promise<unknown[]>;
|
||||
onPersonalizationSurveySubmitted(answers: IPersonalizationSurveyAnswers): Promise<void>;
|
||||
onWorkflowCreated(workflow: IWorkflowBase): Promise<void>;
|
||||
onWorkflowDeleted(workflowId: string): Promise<void>;
|
||||
@@ -404,10 +407,12 @@ export interface IN8nUISettings {
|
||||
}
|
||||
|
||||
export interface IPersonalizationSurveyAnswers {
|
||||
companySize: string | null;
|
||||
codingSkill: string | null;
|
||||
workArea: string | null;
|
||||
companyIndustry: string[];
|
||||
companySize: string | null;
|
||||
otherCompanyIndustry: string | null;
|
||||
otherWorkArea: string | null;
|
||||
workArea: string[] | string | null;
|
||||
}
|
||||
|
||||
export interface IPersonalizationSurvey {
|
||||
|
||||
@@ -9,9 +9,16 @@ import {
|
||||
import { Telemetry } from './telemetry';
|
||||
|
||||
export class InternalHooksClass implements IInternalHooksClass {
|
||||
constructor(private telemetry: Telemetry) {}
|
||||
private versionCli: string;
|
||||
|
||||
async onServerStarted(diagnosticInfo: IDiagnosticInfo): Promise<unknown[]> {
|
||||
constructor(private telemetry: Telemetry, versionCli: string) {
|
||||
this.versionCli = versionCli;
|
||||
}
|
||||
|
||||
async onServerStarted(
|
||||
diagnosticInfo: IDiagnosticInfo,
|
||||
earliestWorkflowCreatedAt?: Date,
|
||||
): Promise<unknown[]> {
|
||||
const info = {
|
||||
version_cli: diagnosticInfo.versionCli,
|
||||
db_type: diagnosticInfo.databaseType,
|
||||
@@ -25,7 +32,10 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||
|
||||
return Promise.all([
|
||||
this.telemetry.identify(info),
|
||||
this.telemetry.track('Instance started', info),
|
||||
this.telemetry.track('Instance started', {
|
||||
...info,
|
||||
earliest_workflow_created: earliestWorkflowCreatedAt,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -35,13 +45,17 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||
coding_skill: answers.codingSkill,
|
||||
work_area: answers.workArea,
|
||||
other_work_area: answers.otherWorkArea,
|
||||
company_industry: answers.companyIndustry,
|
||||
other_company_industry: answers.otherCompanyIndustry,
|
||||
});
|
||||
}
|
||||
|
||||
async onWorkflowCreated(workflow: IWorkflowBase): Promise<void> {
|
||||
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow);
|
||||
return this.telemetry.track('User created workflow', {
|
||||
workflow_id: workflow.id,
|
||||
node_graph: TelemetryHelpers.generateNodesGraph(workflow).nodeGraph,
|
||||
node_graph: nodeGraph,
|
||||
node_graph_string: JSON.stringify(nodeGraph),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,9 +66,13 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||
}
|
||||
|
||||
async onWorkflowSaved(workflow: IWorkflowBase): Promise<void> {
|
||||
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow);
|
||||
|
||||
return this.telemetry.track('User saved workflow', {
|
||||
workflow_id: workflow.id,
|
||||
node_graph: TelemetryHelpers.generateNodesGraph(workflow).nodeGraph,
|
||||
node_graph: nodeGraph,
|
||||
node_graph_string: JSON.stringify(nodeGraph),
|
||||
version_cli: this.versionCli,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,6 +80,7 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||
const properties: IDataObject = {
|
||||
workflow_id: workflow.id,
|
||||
is_manual: false,
|
||||
version_cli: this.versionCli,
|
||||
};
|
||||
|
||||
if (runData !== undefined) {
|
||||
@@ -92,6 +111,8 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||
if (properties.is_manual) {
|
||||
const nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow);
|
||||
properties.node_graph = nodeGraphResult.nodeGraph;
|
||||
properties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph);
|
||||
|
||||
if (errorNodeName) {
|
||||
properties.error_node_id = nodeGraphResult.nameIndices[errorNodeName];
|
||||
}
|
||||
|
||||
@@ -13,9 +13,12 @@ export class InternalHooksManager {
|
||||
throw new Error('InternalHooks not initialized');
|
||||
}
|
||||
|
||||
static init(instanceId: string): InternalHooksClass {
|
||||
static init(instanceId: string, versionCli: string): InternalHooksClass {
|
||||
if (!this.internalHooksInstance) {
|
||||
this.internalHooksInstance = new InternalHooksClass(new Telemetry(instanceId));
|
||||
this.internalHooksInstance = new InternalHooksClass(
|
||||
new Telemetry(instanceId, versionCli),
|
||||
versionCli,
|
||||
);
|
||||
}
|
||||
|
||||
return this.internalHooksInstance;
|
||||
|
||||
@@ -2952,7 +2952,23 @@ export async function start(): Promise<void> {
|
||||
deploymentType: config.get('deployment.type'),
|
||||
};
|
||||
|
||||
void InternalHooksManager.getInstance().onServerStarted(diagnosticInfo);
|
||||
void Db.collections
|
||||
.Workflow!.findOne({
|
||||
select: ['createdAt'],
|
||||
order: { createdAt: 'ASC' },
|
||||
})
|
||||
.then(async (workflow) =>
|
||||
InternalHooksManager.getInstance().onServerStarted(diagnosticInfo, workflow?.createdAt),
|
||||
);
|
||||
});
|
||||
|
||||
server.on('error', (error: Error & { code: string }) => {
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
console.log(
|
||||
`n8n's port ${PORT} is already in use. Do you have another instance of n8n running already?`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -509,7 +509,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||
this.workflowData,
|
||||
fullRunData,
|
||||
this.mode,
|
||||
undefined,
|
||||
this.executionId,
|
||||
this.retryOf,
|
||||
);
|
||||
}
|
||||
@@ -585,7 +585,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||
this.workflowData,
|
||||
fullRunData,
|
||||
this.mode,
|
||||
undefined,
|
||||
this.executionId,
|
||||
this.retryOf,
|
||||
);
|
||||
}
|
||||
@@ -635,7 +635,7 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
||||
this.workflowData,
|
||||
fullRunData,
|
||||
this.mode,
|
||||
undefined,
|
||||
this.executionId,
|
||||
this.retryOf,
|
||||
);
|
||||
}
|
||||
@@ -676,7 +676,13 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
executeErrorWorkflow(
|
||||
this.workflowData,
|
||||
fullRunData,
|
||||
this.mode,
|
||||
this.executionId,
|
||||
this.retryOf,
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
IWorkflowExecuteProcess,
|
||||
IWorkflowExecutionDataProcessWithExecution,
|
||||
NodeTypes,
|
||||
@@ -137,7 +138,8 @@ export class WorkflowRunnerProcess {
|
||||
await externalHooks.init();
|
||||
|
||||
const instanceId = (await UserSettings.prepareUserSettings()).instanceId ?? '';
|
||||
InternalHooksManager.init(instanceId);
|
||||
const { cli } = await GenericHelpers.getVersions();
|
||||
InternalHooksManager.init(instanceId, cli);
|
||||
|
||||
// Credentials should now be loaded from database.
|
||||
// We check if any node uses credentials. If it does, then
|
||||
|
||||
@@ -5,28 +5,57 @@ import { IDataObject, LoggerProxy } from 'n8n-workflow';
|
||||
import config = require('../../config');
|
||||
import { getLogger } from '../Logger';
|
||||
|
||||
interface IExecutionCountsBufferItem {
|
||||
manual_success_count: number;
|
||||
manual_error_count: number;
|
||||
prod_success_count: number;
|
||||
prod_error_count: number;
|
||||
}
|
||||
type CountBufferItemKey =
|
||||
| 'manual_success_count'
|
||||
| 'manual_error_count'
|
||||
| 'prod_success_count'
|
||||
| 'prod_error_count';
|
||||
|
||||
type FirstExecutionItemKey =
|
||||
| 'first_manual_success'
|
||||
| 'first_manual_error'
|
||||
| 'first_prod_success'
|
||||
| 'first_prod_error';
|
||||
|
||||
type IExecutionCountsBufferItem = {
|
||||
[key in CountBufferItemKey]: number;
|
||||
};
|
||||
|
||||
interface IExecutionCountsBuffer {
|
||||
[workflowId: string]: IExecutionCountsBufferItem;
|
||||
}
|
||||
|
||||
type IFirstExecutions = {
|
||||
[key in FirstExecutionItemKey]: Date | undefined;
|
||||
};
|
||||
|
||||
interface IExecutionsBuffer {
|
||||
counts: IExecutionCountsBuffer;
|
||||
firstExecutions: IFirstExecutions;
|
||||
}
|
||||
|
||||
export class Telemetry {
|
||||
private client?: TelemetryClient;
|
||||
|
||||
private instanceId: string;
|
||||
|
||||
private versionCli: string;
|
||||
|
||||
private pulseIntervalReference: NodeJS.Timeout;
|
||||
|
||||
private executionCountsBuffer: IExecutionCountsBuffer = {};
|
||||
private executionCountsBuffer: IExecutionsBuffer = {
|
||||
counts: {},
|
||||
firstExecutions: {
|
||||
first_manual_error: undefined,
|
||||
first_manual_success: undefined,
|
||||
first_prod_error: undefined,
|
||||
first_prod_success: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(instanceId: string) {
|
||||
constructor(instanceId: string, versionCli: string) {
|
||||
this.instanceId = instanceId;
|
||||
this.versionCli = versionCli;
|
||||
|
||||
const enabled = config.get('diagnostics.enabled') as boolean;
|
||||
if (enabled) {
|
||||
@@ -53,33 +82,41 @@ export class Telemetry {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const allPromises = Object.keys(this.executionCountsBuffer).map(async (workflowId) => {
|
||||
const allPromises = Object.keys(this.executionCountsBuffer.counts).map(async (workflowId) => {
|
||||
const promise = this.track('Workflow execution count', {
|
||||
version_cli: this.versionCli,
|
||||
workflow_id: workflowId,
|
||||
...this.executionCountsBuffer[workflowId],
|
||||
...this.executionCountsBuffer.counts[workflowId],
|
||||
...this.executionCountsBuffer.firstExecutions,
|
||||
});
|
||||
this.executionCountsBuffer[workflowId].manual_error_count = 0;
|
||||
this.executionCountsBuffer[workflowId].manual_success_count = 0;
|
||||
this.executionCountsBuffer[workflowId].prod_error_count = 0;
|
||||
this.executionCountsBuffer[workflowId].prod_success_count = 0;
|
||||
|
||||
this.executionCountsBuffer.counts[workflowId].manual_error_count = 0;
|
||||
this.executionCountsBuffer.counts[workflowId].manual_success_count = 0;
|
||||
this.executionCountsBuffer.counts[workflowId].prod_error_count = 0;
|
||||
this.executionCountsBuffer.counts[workflowId].prod_success_count = 0;
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
allPromises.push(this.track('pulse'));
|
||||
allPromises.push(this.track('pulse', { version_cli: this.versionCli }));
|
||||
return Promise.all(allPromises);
|
||||
}
|
||||
|
||||
async trackWorkflowExecution(properties: IDataObject): Promise<void> {
|
||||
if (this.client) {
|
||||
const workflowId = properties.workflow_id as string;
|
||||
this.executionCountsBuffer[workflowId] = this.executionCountsBuffer[workflowId] ?? {
|
||||
this.executionCountsBuffer.counts[workflowId] = this.executionCountsBuffer.counts[
|
||||
workflowId
|
||||
] ?? {
|
||||
manual_error_count: 0,
|
||||
manual_success_count: 0,
|
||||
prod_error_count: 0,
|
||||
prod_success_count: 0,
|
||||
};
|
||||
|
||||
let countKey: CountBufferItemKey;
|
||||
let firstExecKey: FirstExecutionItemKey;
|
||||
|
||||
if (
|
||||
properties.success === false &&
|
||||
properties.error_node_type &&
|
||||
@@ -89,15 +126,28 @@ export class Telemetry {
|
||||
void this.track('Workflow execution errored', properties);
|
||||
|
||||
if (properties.is_manual) {
|
||||
this.executionCountsBuffer[workflowId].manual_error_count++;
|
||||
firstExecKey = 'first_manual_error';
|
||||
countKey = 'manual_error_count';
|
||||
} else {
|
||||
this.executionCountsBuffer[workflowId].prod_error_count++;
|
||||
firstExecKey = 'first_prod_error';
|
||||
countKey = 'prod_error_count';
|
||||
}
|
||||
} else if (properties.is_manual) {
|
||||
this.executionCountsBuffer[workflowId].manual_success_count++;
|
||||
countKey = 'manual_success_count';
|
||||
firstExecKey = 'first_manual_success';
|
||||
} else {
|
||||
this.executionCountsBuffer[workflowId].prod_success_count++;
|
||||
countKey = 'prod_success_count';
|
||||
firstExecKey = 'first_prod_success';
|
||||
}
|
||||
|
||||
if (
|
||||
!this.executionCountsBuffer.firstExecutions[firstExecKey] &&
|
||||
this.executionCountsBuffer.counts[workflowId][countKey] === 0
|
||||
) {
|
||||
this.executionCountsBuffer.firstExecutions[firstExecKey] = new Date();
|
||||
}
|
||||
|
||||
this.executionCountsBuffer.counts[workflowId][countKey]++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user