mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
✨ Introduce telemetry (#2099)
* introduce analytics * add user survey backend * add user survey backend * set answers on survey submit Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> * change name to personalization * lint Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> * N8n 2495 add personalization modal (#2280) * update modals * add onboarding modal * implement questions * introduce analytics * simplify impl * implement survey handling * add personalized cateogry * update modal behavior * add thank you view * handle empty cases * rename modal * standarize modal names * update image, add tags to headings * remove unused file * remove unused interfaces * clean up footer spacing * introduce analytics * refactor to fix bug * update endpoint * set min height * update stories * update naming from questions to survey * remove spacing after core categories * fix bug in logic * sort nodes * rename types * merge with be * rename userSurvey * clean up rest api * use constants for keys * use survey keys * clean up types * move personalization to its own file Co-authored-by: ahsan-virani <ahsan.virani@gmail.com> * Survey new options (#2300) * split up options * fix quotes * remove unused import * add user created workflow event (#2301) * simplify env vars * fix versionCli on FE * update personalization env * fix event User opened Credentials panel * fix select modal spacing * fix nodes panel event * fix workflow id in workflow execute event * improve telemetry error logging * fix config and stop process events * add flush call on n8n stop * ready for release * improve telemetry process exit * fix merge * improve n8n stop events Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Co-authored-by: Mutasem <mutdmour@gmail.com> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
151
packages/cli/src/telemetry/index.ts
Normal file
151
packages/cli/src/telemetry/index.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import TelemetryClient = require('@rudderstack/rudder-sdk-node');
|
||||
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;
|
||||
}
|
||||
|
||||
interface IExecutionCountsBuffer {
|
||||
[workflowId: string]: IExecutionCountsBufferItem;
|
||||
}
|
||||
|
||||
export class Telemetry {
|
||||
private client?: TelemetryClient;
|
||||
|
||||
private instanceId: string;
|
||||
|
||||
private pulseIntervalReference: NodeJS.Timeout;
|
||||
|
||||
private executionCountsBuffer: IExecutionCountsBuffer = {};
|
||||
|
||||
constructor(instanceId: string) {
|
||||
this.instanceId = instanceId;
|
||||
|
||||
const enabled = config.get('diagnostics.enabled') as boolean;
|
||||
if (enabled) {
|
||||
const conf = config.get('diagnostics.config.backend') as string;
|
||||
const [key, url] = conf.split(';');
|
||||
|
||||
if (!key || !url) {
|
||||
const logger = getLogger();
|
||||
LoggerProxy.init(logger);
|
||||
logger.warn('Diagnostics backend config is invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
this.client = new TelemetryClient(key, url);
|
||||
|
||||
this.pulseIntervalReference = setInterval(async () => {
|
||||
void this.pulse();
|
||||
}, 6 * 60 * 60 * 1000); // every 6 hours
|
||||
}
|
||||
}
|
||||
|
||||
private async pulse(): Promise<unknown> {
|
||||
if (!this.client) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const allPromises = Object.keys(this.executionCountsBuffer).map(async (workflowId) => {
|
||||
const promise = this.track('Workflow execution count', {
|
||||
workflow_id: workflowId,
|
||||
...this.executionCountsBuffer[workflowId],
|
||||
});
|
||||
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;
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
allPromises.push(this.track('pulse'));
|
||||
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] ?? {
|
||||
manual_error_count: 0,
|
||||
manual_success_count: 0,
|
||||
prod_error_count: 0,
|
||||
prod_success_count: 0,
|
||||
};
|
||||
|
||||
if (
|
||||
properties.success === false &&
|
||||
properties.error_node_type &&
|
||||
(properties.error_node_type as string).startsWith('n8n-nodes-base')
|
||||
) {
|
||||
// errored exec
|
||||
void this.track('Workflow execution errored', properties);
|
||||
|
||||
if (properties.is_manual) {
|
||||
this.executionCountsBuffer[workflowId].manual_error_count++;
|
||||
} else {
|
||||
this.executionCountsBuffer[workflowId].prod_error_count++;
|
||||
}
|
||||
} else if (properties.is_manual) {
|
||||
this.executionCountsBuffer[workflowId].manual_success_count++;
|
||||
} else {
|
||||
this.executionCountsBuffer[workflowId].prod_success_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async trackN8nStop(): Promise<void> {
|
||||
clearInterval(this.pulseIntervalReference);
|
||||
void this.track('User instance stopped');
|
||||
return new Promise<void>((resolve) => {
|
||||
if (this.client) {
|
||||
this.client.flush(resolve);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async identify(traits?: IDataObject): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (this.client) {
|
||||
this.client.identify(
|
||||
{
|
||||
userId: this.instanceId,
|
||||
traits: {
|
||||
...traits,
|
||||
instanceId: this.instanceId,
|
||||
},
|
||||
},
|
||||
resolve,
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async track(eventName: string, properties?: IDataObject): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (this.client) {
|
||||
this.client.track(
|
||||
{
|
||||
userId: this.instanceId,
|
||||
event: eventName,
|
||||
properties,
|
||||
},
|
||||
resolve,
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user