feat: Remove PostHog event calls (#6915)

This commit is contained in:
Ricardo Espinoza 2023-08-17 11:39:32 -04:00 committed by GitHub
parent 41c3cc89ca
commit 270946a93b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 86 additions and 53 deletions

View file

@ -124,7 +124,6 @@ export class InternalHooks implements IInternalHooksClass {
return this.telemetry.track( return this.telemetry.track(
'User responded to personalization questions', 'User responded to personalization questions',
personalizationSurveyData, personalizationSurveyData,
{ withPostHog: true },
); );
} }
@ -190,21 +189,17 @@ export class InternalHooks implements IInternalHooksClass {
workflowName: workflow.name, workflowName: workflow.name,
}, },
}), }),
this.telemetry.track( this.telemetry.track('User saved workflow', {
'User saved workflow', user_id: user.id,
{ workflow_id: workflow.id,
user_id: user.id, node_graph_string: JSON.stringify(nodeGraph),
workflow_id: workflow.id, notes_count_overlapping: overlappingCount,
node_graph_string: JSON.stringify(nodeGraph), notes_count_non_overlapping: notesCount - overlappingCount,
notes_count_overlapping: overlappingCount, version_cli: N8N_VERSION,
notes_count_non_overlapping: notesCount - overlappingCount, num_tags: workflow.tags?.length ?? 0,
version_cli: N8N_VERSION, public_api: publicApi,
num_tags: workflow.tags?.length ?? 0, sharing_role: userRole,
public_api: publicApi, }),
sharing_role: userRole,
},
{ withPostHog: true },
),
]); ]);
} }
@ -415,11 +410,7 @@ export class InternalHooks implements IInternalHooksClass {
node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode], node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode],
}; };
promises.push( promises.push(this.telemetry.track('Manual node exec finished', telemetryPayload));
this.telemetry.track('Manual node exec finished', telemetryPayload, {
withPostHog: true,
}),
);
} else { } else {
nodeGraphResult.webhookNodeNames.forEach((name: string) => { nodeGraphResult.webhookNodeNames.forEach((name: string) => {
const execJson = runData.data.resultData.runData[name]?.[0]?.data?.main?.[0]?.[0] const execJson = runData.data.resultData.runData[name]?.[0]?.data?.main?.[0]?.[0]
@ -432,9 +423,7 @@ export class InternalHooks implements IInternalHooksClass {
}); });
promises.push( promises.push(
this.telemetry.track('Manual workflow exec finished', manualExecEventProperties, { this.telemetry.track('Manual workflow exec finished', manualExecEventProperties),
withPostHog: true,
}),
); );
} }
} }
@ -484,7 +473,7 @@ export class InternalHooks implements IInternalHooksClass {
user_id_list: userList, user_id_list: userList,
}; };
return this.telemetry.track('User updated workflow sharing', properties, { withPostHog: true }); return this.telemetry.track('User updated workflow sharing', properties);
} }
async onN8nStop(): Promise<void> { async onN8nStop(): Promise<void> {
@ -1017,7 +1006,7 @@ export class InternalHooks implements IInternalHooksClass {
user_id: string; user_id: string;
workflow_id: string; workflow_id: string;
}): Promise<void> { }): Promise<void> {
return this.telemetry.track('Workflow first prod success', data, { withPostHog: true }); return this.telemetry.track('Workflow first prod success', data);
} }
async onFirstWorkflowDataLoad(data: { async onFirstWorkflowDataLoad(data: {
@ -1028,7 +1017,7 @@ export class InternalHooks implements IInternalHooksClass {
credential_type?: string; credential_type?: string;
credential_id?: string; credential_id?: string;
}): Promise<void> { }): Promise<void> {
return this.telemetry.track('Workflow first data fetched', data, { withPostHog: true }); return this.telemetry.track('Workflow first data fetched', data);
} }
/** /**

View file

@ -490,7 +490,7 @@ export class Server extends AbstractServer {
const controllers: object[] = [ const controllers: object[] = [
new EventBusController(), new EventBusController(),
new AuthController({ config, internalHooks, repositories, logger, postHog }), new AuthController({ config, internalHooks, repositories, logger, postHog }),
new OwnerController({ config, internalHooks, repositories, logger }), new OwnerController({ config, internalHooks, repositories, logger, postHog }),
new MeController({ externalHooks, internalHooks, repositories, logger }), new MeController({ externalHooks, internalHooks, repositories, logger }),
new NodeTypesController({ config, nodeTypes }), new NodeTypesController({ config, nodeTypes }),
new PasswordResetController({ new PasswordResetController({

View file

@ -762,7 +762,7 @@ export const schema = {
externalFrontendHooksUrls: { externalFrontendHooksUrls: {
doc: 'URLs to external frontend hooks files, ; separated', doc: 'URLs to external frontend hooks files, ; separated',
format: String, format: String,
default: 'https://public.n8n.cloud/posthog-hooks.js', default: '',
env: 'EXTERNAL_FRONTEND_HOOKS_URLS', env: 'EXTERNAL_FRONTEND_HOOKS_URLS',
}, },

View file

@ -6,6 +6,7 @@ import {
hashPassword, hashPassword,
sanitizeUser, sanitizeUser,
validatePassword, validatePassword,
withFeatureFlags,
} from '@/UserManagement/UserManagementHelper'; } from '@/UserManagement/UserManagementHelper';
import { issueCookie } from '@/auth/jwt'; import { issueCookie } from '@/auth/jwt';
import { Response } from 'express'; import { Response } from 'express';
@ -14,6 +15,7 @@ import type { Config } from '@/config';
import { OwnerRequest } from '@/requests'; import { OwnerRequest } from '@/requests';
import type { IDatabaseCollections, IInternalHooksClass } from '@/Interfaces'; import type { IDatabaseCollections, IInternalHooksClass } from '@/Interfaces';
import type { SettingsRepository, UserRepository } from '@db/repositories'; import type { SettingsRepository, UserRepository } from '@db/repositories';
import type { PostHogClient } from '@/posthog';
@Authorized(['global', 'owner']) @Authorized(['global', 'owner'])
@RestController('/owner') @RestController('/owner')
@ -28,22 +30,27 @@ export class OwnerController {
private readonly settingsRepository: SettingsRepository; private readonly settingsRepository: SettingsRepository;
private readonly postHog?: PostHogClient;
constructor({ constructor({
config, config,
logger, logger,
internalHooks, internalHooks,
repositories, repositories,
postHog,
}: { }: {
config: Config; config: Config;
logger: ILogger; logger: ILogger;
internalHooks: IInternalHooksClass; internalHooks: IInternalHooksClass;
repositories: Pick<IDatabaseCollections, 'User' | 'Settings'>; repositories: Pick<IDatabaseCollections, 'User' | 'Settings'>;
postHog?: PostHogClient;
}) { }) {
this.config = config; this.config = config;
this.logger = logger; this.logger = logger;
this.internalHooks = internalHooks; this.internalHooks = internalHooks;
this.userRepository = repositories.User; this.userRepository = repositories.User;
this.settingsRepository = repositories.Settings; this.settingsRepository = repositories.Settings;
this.postHog = postHog;
} }
/** /**
@ -122,7 +129,7 @@ export class OwnerController {
void this.internalHooks.onInstanceOwnerSetup({ user_id: userId }); void this.internalHooks.onInstanceOwnerSetup({ user_id: userId });
return sanitizeUser(owner); return withFeatureFlags(this.postHog, sanitizeUser(owner));
} }
@Post('/dismiss-banner') @Post('/dismiss-banner')

View file

@ -95,15 +95,11 @@ export class Telemetry {
return sum > 0; return sum > 0;
}) })
.map(async (workflowId) => { .map(async (workflowId) => {
const promise = this.track( const promise = this.track('Workflow execution count', {
'Workflow execution count', event_version: '2',
{ workflow_id: workflowId,
event_version: '2', ...this.executionCountsBuffer[workflowId],
workflow_id: workflowId, });
...this.executionCountsBuffer[workflowId],
},
{ withPostHog: true },
);
return promise; return promise;
}); });

View file

@ -78,6 +78,12 @@ declare global {
reset?(resetDeviceId?: boolean): void; reset?(resetDeviceId?: boolean): void;
onFeatureFlags?(callback: (keys: string[], map: FeatureFlags) => void): void; onFeatureFlags?(callback: (keys: string[], map: FeatureFlags) => void): void;
reloadFeatureFlags?(): void; reloadFeatureFlags?(): void;
capture?(event: string, properties: IDataObject): void;
register?(metadata: IDataObject): void;
people?: {
set?(metadata: IDataObject): void;
};
debug?(): void;
}; };
analytics?: { analytics?: {
track(event: string, proeprties?: ITelemetryTrackProperties): void; track(event: string, proeprties?: ITelemetryTrackProperties): void;

View file

@ -28,7 +28,7 @@ export async function logout(context: IRestApiContext): Promise<void> {
export async function setupOwner( export async function setupOwner(
context: IRestApiContext, context: IRestApiContext,
params: { firstName: string; lastName: string; email: string; password: string }, params: { firstName: string; lastName: string; email: string; password: string },
): Promise<IUserResponse> { ): Promise<CurrentUserResponse> {
return makeRestApiRequest(context, 'POST', '/owner/setup', params as unknown as IDataObject); return makeRestApiRequest(context, 'POST', '/owner/setup', params as unknown as IDataObject);
} }

View file

@ -138,6 +138,7 @@ import { useSettingsStore } from '@/stores/settings.store';
import { useRootStore } from '@/stores/n8nRoot.store'; import { useRootStore } from '@/stores/n8nRoot.store';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { createEventBus } from 'n8n-design-system/utils'; import { createEventBus } from 'n8n-design-system/utils';
import { usePostHog } from '@/stores';
export default defineComponent({ export default defineComponent({
name: 'PersonalizationModal', name: 'PersonalizationModal',
@ -166,7 +167,7 @@ export default defineComponent({
}; };
}, },
computed: { computed: {
...mapStores(useRootStore, useSettingsStore, useUIStore, useUsersStore), ...mapStores(useRootStore, useSettingsStore, useUIStore, useUsersStore, usePostHog),
survey() { survey() {
const survey: IFormInputs = [ const survey: IFormInputs = [
{ {
@ -645,6 +646,8 @@ export default defineComponent({
await this.usersStore.submitPersonalizationSurvey(survey as IPersonalizationLatestVersion); await this.usersStore.submitPersonalizationSurvey(survey as IPersonalizationLatestVersion);
this.posthogStore.setMetadata(survey, 'user');
if (Object.keys(values).length === 0) { if (Object.keys(values).length === 0) {
this.closeDialog(); this.closeDialog();
} }

View file

@ -8,6 +8,7 @@ import { useSettingsStore } from '@/stores/settings.store';
import { useRootStore } from '@/stores/n8nRoot.store'; import { useRootStore } from '@/stores/n8nRoot.store';
import { useTelemetryStore } from '@/stores/telemetry.store'; import { useTelemetryStore } from '@/stores/telemetry.store';
import { SLACK_NODE_TYPE } from '@/constants'; import { SLACK_NODE_TYPE } from '@/constants';
import { usePostHog } from '@/stores/posthog.store';
export class Telemetry { export class Telemetry {
constructor( constructor(
@ -72,7 +73,11 @@ export class Telemetry {
} }
} }
track(event: string, properties?: ITelemetryTrackProperties) { track(
event: string,
properties?: ITelemetryTrackProperties,
{ withPostHog } = { withPostHog: false },
) {
if (!this.rudderStack) return; if (!this.rudderStack) return;
const updatedProperties = { const updatedProperties = {
@ -81,6 +86,10 @@ export class Telemetry {
}; };
this.rudderStack.track(event, updatedProperties); this.rudderStack.track(event, updatedProperties);
if (withPostHog) {
usePostHog().capture(event, updatedProperties);
}
} }
page(route: Route) { page(route: Route) {
@ -119,7 +128,7 @@ export class Telemetry {
properties.session_id = useRootStore().sessionId; properties.session_id = useRootStore().sessionId;
switch (event) { switch (event) {
case 'askAi.generationFinished': case 'askAi.generationFinished':
this.track('Ai code generation finished', properties); this.track('Ai code generation finished', properties, { withPostHog: true });
case 'ask.generationClicked': case 'ask.generationClicked':
this.track('User clicked on generate code button', properties); this.track('User clicked on generate code button', properties);
default: default:
@ -189,7 +198,7 @@ export class Telemetry {
this.track('User viewed node category', properties); this.track('User viewed node category', properties);
break; break;
case 'nodeView.addNodeButton': case 'nodeView.addNodeButton':
this.track('User added node to workflow canvas', properties); this.track('User added node to workflow canvas', properties, { withPostHog: true });
break; break;
case 'nodeView.addSticky': case 'nodeView.addSticky':
this.track('User inserted workflow note', properties); this.track('User inserted workflow note', properties);

View file

@ -4,7 +4,7 @@ import { defineStore } from 'pinia';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { useRootStore } from '@/stores/n8nRoot.store'; import { useRootStore } from '@/stores/n8nRoot.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import type { FeatureFlags } from 'n8n-workflow'; import type { FeatureFlags, IDataObject } from 'n8n-workflow';
import { EXPERIMENTS_TO_TRACK, LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants'; import { EXPERIMENTS_TO_TRACK, LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants';
import { useTelemetryStore } from './telemetry.store'; import { useTelemetryStore } from './telemetry.store';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
@ -161,10 +161,29 @@ export const usePostHog = defineStore('posthog', () => {
trackedDemoExp.value[name] = variant; trackedDemoExp.value[name] = variant;
}; };
const capture = (event: string, properties: IDataObject) => {
if (typeof window.posthog?.capture === 'function') {
window.posthog.capture(event, properties);
}
};
const setMetadata = (metadata: IDataObject, target: 'user' | 'events') => {
if (typeof window.posthog?.people?.set !== 'function') return;
if (typeof window.posthog?.register !== 'function') return;
if (target === 'user') {
window.posthog?.people?.set(metadata);
} else if (target === 'events') {
window.posthog?.register(metadata);
}
};
return { return {
init, init,
isVariantEnabled, isVariantEnabled,
getVariant, getVariant,
reset, reset,
capture,
setMetadata,
}; };
}); });

View file

@ -200,6 +200,7 @@ export const useUsersStore = defineStore(STORES.USERS, {
this.addUsers([user]); this.addUsers([user]);
this.currentUserId = user.id; this.currentUserId = user.id;
settingsStore.stopShowingSetupPage(); settingsStore.stopShowingSetupPage();
usePostHog().init(user.featureFlags);
} }
}, },
async validateSignupToken(params: { async validateSignupToken(params: {
@ -221,9 +222,8 @@ export const useUsersStore = defineStore(STORES.USERS, {
if (user) { if (user) {
this.addUsers([user]); this.addUsers([user]);
this.currentUserId = user.id; this.currentUserId = user.id;
usePostHog().init(user.featureFlags);
} }
usePostHog().init(user.featureFlags);
}, },
async sendForgotPasswordEmail(params: { email: string }): Promise<void> { async sendForgotPasswordEmail(params: { email: string }): Promise<void> {
const rootStore = useRootStore(); const rootStore = useRootStore();

View file

@ -68,6 +68,7 @@ import type {
import { setPageTitle } from '@/utils'; import { setPageTitle } from '@/utils';
import { VIEWS } from '@/constants'; import { VIEWS } from '@/constants';
import { useTemplatesStore } from '@/stores/templates.store'; import { useTemplatesStore } from '@/stores/templates.store';
import { usePostHog } from '@/stores/posthog.store';
export default defineComponent({ export default defineComponent({
name: 'TemplatesCollectionView', name: 'TemplatesCollectionView',
@ -78,7 +79,7 @@ export default defineComponent({
TemplatesView, TemplatesView,
}, },
computed: { computed: {
...mapStores(useTemplatesStore), ...mapStores(useTemplatesStore, usePostHog),
collection(): null | ITemplatesCollectionFull { collection(): null | ITemplatesCollectionFull {
return this.templatesStore.getCollectionById(this.collectionId); return this.templatesStore.getCollectionById(this.collectionId);
}, },
@ -122,8 +123,9 @@ export default defineComponent({
source: 'collection', source: 'collection',
}; };
void this.$externalHooks().run('templatesCollectionView.onUseWorkflow', telemetryPayload); void this.$externalHooks().run('templatesCollectionView.onUseWorkflow', telemetryPayload);
this.$telemetry.track('User inserted workflow template', telemetryPayload); this.$telemetry.track('User inserted workflow template', telemetryPayload, {
withPostHog: true,
});
this.navigateTo(event, VIEWS.TEMPLATE_IMPORT, id); this.navigateTo(event, VIEWS.TEMPLATE_IMPORT, id);
}, },
navigateTo(e: MouseEvent, page: string, id: string) { navigateTo(e: MouseEvent, page: string, id: string) {

View file

@ -67,6 +67,7 @@ import { workflowHelpers } from '@/mixins/workflowHelpers';
import { setPageTitle } from '@/utils'; import { setPageTitle } from '@/utils';
import { VIEWS } from '@/constants'; import { VIEWS } from '@/constants';
import { useTemplatesStore } from '@/stores/templates.store'; import { useTemplatesStore } from '@/stores/templates.store';
import { usePostHog } from '@/stores/posthog.store';
export default defineComponent({ export default defineComponent({
name: 'TemplatesWorkflowView', name: 'TemplatesWorkflowView',
@ -77,7 +78,7 @@ export default defineComponent({
WorkflowPreview, WorkflowPreview,
}, },
computed: { computed: {
...mapStores(useTemplatesStore), ...mapStores(useTemplatesStore, usePostHog),
template(): ITemplatesWorkflow | ITemplatesWorkflowFull { template(): ITemplatesWorkflow | ITemplatesWorkflowFull {
return this.templatesStore.getTemplateById(this.templateId); return this.templatesStore.getTemplateById(this.templateId);
}, },
@ -101,8 +102,9 @@ export default defineComponent({
}; };
void this.$externalHooks().run('templatesWorkflowView.openWorkflow', telemetryPayload); void this.$externalHooks().run('templatesWorkflowView.openWorkflow', telemetryPayload);
this.$telemetry.track('User inserted workflow template', telemetryPayload); this.$telemetry.track('User inserted workflow template', telemetryPayload, {
withPostHog: true,
});
if (e.metaKey || e.ctrlKey) { if (e.metaKey || e.ctrlKey) {
const route = this.$router.resolve({ name: VIEWS.TEMPLATE_IMPORT, params: { id } }); const route = this.$router.resolve({ name: VIEWS.TEMPLATE_IMPORT, params: { id } });
window.open(route.href, '_blank'); window.open(route.href, '_blank');