feat(editor, core): Integrate PostHog (#3865)

* Integrate PostHog - Part 1: Groundwork (#3753)

* Integrate PostHog - Part 2: Event capture (#3779)

* Integrate PostHog - Part 3: Session recordings (#3789)

* Integrate PostHog - Part 4: Experiments (#3825)

* Finalize PostHog integration (#3866)

* 📦 Update `package-lock.json`

* 🐛 Account for absent PH hooks file

*  Create new env `EXTERNAL_FRONTEND_HOOKS_FILES`

*  Adjust env used for injecting PostHog

* 🐛 Switch to semicolon delimiter

*  Simplify to `externalFrontendHookPath`

* Refactor FE hooks flow (#3884)

* Add env var for session recordings

* inject frontend hooks even when telemetry is off

* allow multiple hooks files

* cr

* 🐛 Handle missing ref errors

* 🔥 Remove outdated `continue`

* 🎨 Change one-liners to blocks

* 📦 Update `package-lock.json`

Co-authored-by: Ahsan Virani <ahsan.virani@gmail.com>
This commit is contained in:
Iván Ovejero
2022-08-19 15:35:39 +02:00
committed by GitHub
parent 2b4f5c6c78
commit 43e054f5ab
37 changed files with 676 additions and 217 deletions

View File

@@ -1,9 +1,10 @@
/* eslint-disable import/no-cycle */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import TelemetryClient from '@rudderstack/rudder-sdk-node';
import RudderStack from '@rudderstack/rudder-sdk-node';
import PostHog from 'posthog-node';
import { ITelemetryTrackProperties, LoggerProxy } from 'n8n-workflow';
import * as config from '../../config';
import config from '../../config';
import { IExecutionTrackProperties } from '../Interfaces';
import { getLogger } from '../Logger';
@@ -24,7 +25,9 @@ interface IExecutionsBuffer {
}
export class Telemetry {
private client?: TelemetryClient;
private rudderStack?: RudderStack;
private postHog?: PostHog;
private instanceId: string;
@@ -51,18 +54,19 @@ export class Telemetry {
return;
}
this.client = this.createTelemetryClient(key, url, logLevel);
this.rudderStack = this.initRudderStack(key, url, logLevel);
this.postHog = this.initPostHog();
this.startPulse();
}
}
private createTelemetryClient(
key: string,
url: string,
logLevel: string,
): TelemetryClient | undefined {
return new TelemetryClient(key, url, { logLevel });
private initRudderStack(key: string, url: string, logLevel: string): RudderStack {
return new RudderStack(key, url, { logLevel });
}
private initPostHog(): PostHog {
return new PostHog(config.getEnv('diagnostics.config.posthog.apiKey'));
}
private startPulse() {
@@ -72,7 +76,7 @@ export class Telemetry {
}
private async pulse(): Promise<unknown> {
if (!this.client) {
if (!this.rudderStack) {
return Promise.resolve();
}
@@ -92,7 +96,7 @@ export class Telemetry {
}
async trackWorkflowExecution(properties: IExecutionTrackProperties): Promise<void> {
if (this.client) {
if (this.rudderStack) {
const execTime = new Date();
const workflowId = properties.workflow_id;
@@ -122,8 +126,12 @@ export class Telemetry {
clearInterval(this.pulseIntervalReference);
void this.track('User instance stopped');
return new Promise<void>((resolve) => {
if (this.client) {
this.client.flush(resolve);
if (this.postHog) {
this.postHog.shutdown();
}
if (this.rudderStack) {
this.rudderStack.flush(resolve);
} else {
resolve();
}
@@ -134,8 +142,18 @@ export class Telemetry {
[key: string]: string | number | boolean | object | undefined | null;
}): Promise<void> {
return new Promise<void>((resolve) => {
if (this.client) {
this.client.identify(
if (this.postHog) {
this.postHog.identify({
distinctId: this.instanceId,
properties: {
...traits,
instanceId: this.instanceId,
},
});
}
if (this.rudderStack) {
this.rudderStack.identify(
{
userId: this.instanceId,
anonymousId: '000000000000',
@@ -152,9 +170,13 @@ export class Telemetry {
});
}
async track(eventName: string, properties: ITelemetryTrackProperties = {}): Promise<void> {
async track(
eventName: string,
properties: ITelemetryTrackProperties = {},
{ withPostHog } = { withPostHog: false }, // whether to additionally track with PostHog
): Promise<void> {
return new Promise<void>((resolve) => {
if (this.client) {
if (this.rudderStack) {
const { user_id } = properties;
const updatedProperties: ITelemetryTrackProperties = {
...properties,
@@ -162,21 +184,35 @@ export class Telemetry {
version_cli: this.versionCli,
};
this.client.track(
{
userId: `${this.instanceId}${user_id ? `#${user_id}` : ''}`,
anonymousId: '000000000000',
event: eventName,
properties: updatedProperties,
},
resolve,
);
const payload = {
userId: `${this.instanceId}${user_id ? `#${user_id}` : ''}`,
anonymousId: '000000000000',
event: eventName,
properties: updatedProperties,
};
if (withPostHog && this.postHog) {
this.postHog.capture({ ...payload, distinctId: payload.userId });
}
this.rudderStack.track(payload, resolve);
} else {
resolve();
}
});
}
async isFeatureFlagEnabled(
featureFlagName: string,
{ user_id: userId }: ITelemetryTrackProperties = {},
): Promise<boolean> {
if (!this.postHog) return Promise.resolve(false);
const fullId = [this.instanceId, userId].join('_'); // PostHog disallows # in ID
return this.postHog.isFeatureEnabled(featureFlagName, fullId);
}
// test helpers
getCountsBuffer(): IExecutionsBuffer {