Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate PostHog - Part 1: Groundwork #3753

Merged
merged 44 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3da4566
:heavy_plus_sign: Add `posthog-js`
ivov Jul 21, 2022
26b4f52
:package: Update `package-lock.json`
ivov Jul 21, 2022
bcb35fa
:blue_book: Fix TS import issue
ivov Jul 21, 2022
991a92d
:zap: Send `deploymentType` to FE
ivov Jul 21, 2022
fe80b96
:zap: Add `deploymentType` getter
ivov Jul 21, 2022
f72b74c
:zap: Set up PostHog constants
ivov Jul 21, 2022
0d89e28
:blue_book: Split types into own file
ivov Jul 21, 2022
42e25a3
:zap: Set up Telemetry component
ivov Jul 21, 2022
b0ab2b3
:sparkles: Introduce PostHog to Telemetry class
ivov Jul 21, 2022
c3ddaa3
:construction: Temporarily init PH from `App.vue`
ivov Jul 21, 2022
55c7b6e
:construction: Set up sample event and feature flag
ivov Jul 21, 2022
b7cd0a9
:zap: Move PH init to `beforeMount`
ivov Jul 21, 2022
72b8255
:zap: Add metadata: survey, `is_owner`
ivov Jul 21, 2022
412b440
:truck: Move `posthog-js` to regular deps
ivov Jul 22, 2022
0afb6be
:truck: Move PostHog init to App component
ivov Jul 22, 2022
ac428a2
:zap: Minor client improvements
ivov Jul 22, 2022
43a071a
:heavy_plus_sign: Add `posthog-node`
ivov Jul 22, 2022
e221948
:package: Update `package-lock.json`
ivov Jul 22, 2022
b1983ea
:sparkles: Set up PostHog in server
ivov Jul 22, 2022
a0f5a3c
:pencil2: Update comment
ivov Jul 22, 2022
446edf2
:zap: Mirror BE `identify()` call
ivov Jul 22, 2022
b5b0750
:fire: Remove unneeded comment
ivov Jul 22, 2022
ce862e5
:pencil2: Update comments
ivov Jul 22, 2022
ad4f729
:zap: Add `isFeatureFlagEnabled` to BE
ivov Jul 22, 2022
7713183
:zap: Handle error on `initPostHog`
ivov Jul 22, 2022
4503b5f
:pencil2: Add comments
ivov Jul 22, 2022
63e3c62
:pencil2: Update comments
ivov Jul 22, 2022
425f64f
:test_tube: Update test value
ivov Jul 22, 2022
3b0de41
:twisted_rightwards_arrows: Merge master
ivov Jul 22, 2022
746957c
:blue_book: Improve types
ivov Jul 25, 2022
33216d6
:twisted_rightwards_arrows: Merge master
ivov Jul 25, 2022
f4ed30e
:fire: Remove PH from `editor-ui`
ivov Jul 25, 2022
ecb7f42
:fire: Remove PH from BE `Telemetry` class
ivov Jul 25, 2022
a799360
:fire: Remove constant
ivov Jul 25, 2022
dc31b99
:zap: Inject PH into `index.html`
ivov Jul 25, 2022
f52e1dc
:fire: Remove leftover method
ivov Jul 25, 2022
bc1d6a0
:pencil2: Update comment
ivov Jul 26, 2022
787b39b
:rewind: Restore `mapGetters`
ivov Jul 26, 2022
4c37c52
:fire: Remove extra newline
ivov Jul 26, 2022
84f4703
:zap: Simplify in client
ivov Jul 26, 2022
ac8d568
:rewind: Restore formatting
ivov Jul 26, 2022
c313e05
:pencil2: Add comment
ivov Jul 26, 2022
5d63d18
:blue_book: Add `posthog` type
ivov Jul 26, 2022
57ff67f
:recycle: Adjust single line to multi-line
ivov Jul 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions packages/cli/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,20 @@ export const schema = {
env: 'N8N_DIAGNOSTICS_ENABLED',
},
config: {
posthog: {
apiKey: {
doc: 'API key for PostHog',
format: String,
default: 'phc_QLECpA3yiIJcyafUv0pe6EoxvdfIDZ5A12dGpGXH4jV',
env: 'N8N_DIAGNOSTICS_POSTHOG_API_KEY',
},
apiHost: {
doc: 'API host for PostHog',
format: String,
default: 'https://app.posthog.com',
env: 'N8N_DIAGNOSTICS_POSTHOG_API_HOST',
},
},
frontend: {
doc: 'Diagnostics config for frontend.',
format: String,
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@
"typescript": "~4.6.0"
},
"dependencies": {
"@oclif/core": "^1.9.3",
"@apidevtools/swagger-cli": "4.0.0",
"@oclif/command": "^1.5.18",
"@oclif/core": "^1.9.3",
"@oclif/errors": "^1.2.2",
"@rudderstack/rudder-sdk-node": "1.0.6",
"@types/json-diff": "^0.5.1",
Expand Down Expand Up @@ -156,6 +156,7 @@
"passport-cookie": "^1.0.9",
"passport-jwt": "^4.0.0",
"pg": "^8.3.0",
"posthog-node": "^1.3.0",
"prom-client": "^13.1.0",
"psl": "^1.8.0",
"request-promise-native": "^1.0.7",
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@ export interface IN8nUISettings {
missingPackages?: boolean;
executionMode: 'regular' | 'queue';
communityNodesEnabled: boolean;
deployment: {
type: string;
};
}

export interface IPersonalizationSurveyAnswers {
Expand Down
25 changes: 25 additions & 0 deletions packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ import {
} from './UserManagement/UserManagementHelper';
import { loadPublicApiVersions } from './PublicApi';
import { SharedWorkflow } from './databases/entities/SharedWorkflow';
import * as telemetryScripts from './telemetry/scripts';

require('body-parser-xml')(bodyParser);

Expand Down Expand Up @@ -337,6 +338,9 @@ class App {
},
executionMode: config.getEnv('executions.mode'),
communityNodesEnabled: config.getEnv('nodes.communityPackages.enabled'),
deployment: {
type: config.getEnv('deployment.type'),
},
};
}

Expand Down Expand Up @@ -2604,6 +2608,27 @@ class App {
readIndexFile = readIndexFile.replace(/\/%BASE_PATH%\//g, n8nPath);
readIndexFile = readIndexFile.replace(/\/favicon.ico/g, `${n8nPath}favicon.ico`);

if (this.frontendSettings.telemetry.enabled) {
// @TODO_PART_3:
// Confirm if `autocapture` is needed for session recording, if so enable

// @TODO_ON_COMPLETION:
// Set `disableSessionRecording` to `!['desktop_mac', 'desktop_win', 'cloud'].includes(deploymentType)`
// Set `debug` to `config.getEnv('logs.level') === 'debug'`

const postHogScript = telemetryScripts.postHog({
apiKey: config.getEnv('diagnostics.config.posthog.apiKey'),
apiHost: config.getEnv('diagnostics.config.posthog.apiHost'),
autocapture: false,
disableSessionRecording: true,
debug: true,
});

const firstLinkedScript = '<link href="/js/';

readIndexFile = readIndexFile.replace(firstLinkedScript, postHogScript + firstLinkedScript);
}

// Serve the altered index.html file separately
this.app.get(`/index.html`, async (req: express.Request, res: express.Response) => {
res.send(readIndexFile);
Expand Down
60 changes: 40 additions & 20 deletions packages/cli/src/telemetry/index.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -24,7 +25,9 @@ interface IExecutionsBuffer {
}

export class Telemetry {
private client?: TelemetryClient;
private rudderStack?: RudderStack;

private postHog?: PostHog;

private instanceId: string;

Expand All @@ -51,18 +54,14 @@ export class Telemetry {
return;
}

this.client = this.createTelemetryClient(key, url, logLevel);
this.rudderStack = this.initRudderStack(key, url, logLevel);

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 startPulse() {
Expand All @@ -72,9 +71,7 @@ export class Telemetry {
}

private async pulse(): Promise<unknown> {
if (!this.client) {
return Promise.resolve();
}
if (!this.rudderStack) return Promise.resolve();
ivov marked this conversation as resolved.
Show resolved Hide resolved

const allPromises = Object.keys(this.executionCountsBuffer).map(async (workflowId) => {
const promise = this.track('Workflow execution count', {
Expand All @@ -92,7 +89,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;

Expand Down Expand Up @@ -122,8 +119,10 @@ 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();
}
Expand All @@ -134,8 +133,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',
Expand All @@ -154,15 +163,15 @@ export class Telemetry {

async track(eventName: string, properties: ITelemetryTrackProperties = {}): Promise<void> {
return new Promise<void>((resolve) => {
if (this.client) {
if (this.rudderStack) {
const { user_id } = properties;
const updatedProperties: ITelemetryTrackProperties = {
...properties,
instance_id: this.instanceId,
version_cli: this.versionCli,
};

this.client.track(
this.rudderStack.track(
{
userId: `${this.instanceId}${user_id ? `#${user_id}` : ''}`,
anonymousId: '000000000000',
Expand All @@ -177,6 +186,17 @@ export class Telemetry {
});
}

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 {
Expand Down
Loading