fix: Ensure user id for early track events (#10885)

This commit is contained in:
Mutasem Aldmour 2024-09-23 14:13:06 +02:00 committed by GitHub
parent 557db9c170
commit 23c09eae42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 79 additions and 43 deletions

View file

@ -42,12 +42,6 @@ watch(telemetry, () => {
init(); init();
}); });
watch(currentUserId, (userId) => {
if (isTelemetryEnabled.value) {
telemetryPlugin.identify(rootStore.instanceId, userId);
}
});
watch(isTelemetryEnabledOnRoute, (enabled) => { watch(isTelemetryEnabledOnRoute, (enabled) => {
if (enabled) { if (enabled) {
init(); init();

View file

@ -15,7 +15,6 @@ import { useRootStore } from '@/stores/root.store';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { usePostHog } from '@/stores/posthog.store'; import { usePostHog } from '@/stores/posthog.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { useTelemetryStore } from '@/stores/telemetry.store';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
export class Telemetry { export class Telemetry {
@ -74,7 +73,6 @@ export class Telemetry {
configUrl: 'https://api-rs.n8n.io', configUrl: 'https://api-rs.n8n.io',
...logging, ...logging,
}); });
useTelemetryStore().init(this);
this.identify(instanceId, userId, versionCli, projectId); this.identify(instanceId, userId, versionCli, projectId);
@ -146,6 +144,10 @@ export class Telemetry {
} }
} }
reset() {
this.rudderStack?.reset();
}
flushPageEvents() { flushPageEvents() {
const queue = this.pageEventQueue; const queue = this.pageEventQueue;
this.pageEventQueue = []; this.pageEventQueue = [];

View file

@ -3,11 +3,11 @@ import { usePostHog } from '@/stores/posthog.store';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { useRootStore } from '@/stores/root.store'; import { useRootStore } from '@/stores/root.store';
import { useTelemetryStore } from '@/stores/telemetry.store';
import type { FrontendSettings } from '@n8n/api-types'; import type { FrontendSettings } from '@n8n/api-types';
import { LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants'; import { LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { defaultSettings } from '../../__tests__/defaults'; import { defaultSettings } from '../../__tests__/defaults';
import { useTelemetry } from '@/composables/useTelemetry';
export const DEFAULT_POSTHOG_SETTINGS: FrontendSettings['posthog'] = { export const DEFAULT_POSTHOG_SETTINGS: FrontendSettings['posthog'] = {
enabled: true, enabled: true,
@ -55,11 +55,11 @@ function setup() {
identify: () => {}, identify: () => {},
}; };
const telemetryStore = useTelemetryStore(); const telemetry = useTelemetry();
vi.spyOn(window.posthog, 'init'); vi.spyOn(window.posthog, 'init');
vi.spyOn(window.posthog, 'identify'); vi.spyOn(window.posthog, 'identify');
vi.spyOn(telemetryStore, 'track'); vi.spyOn(telemetry, 'track');
} }
describe('Posthog store', () => { describe('Posthog store', () => {

View file

@ -11,8 +11,8 @@ import {
EXPERIMENTS_TO_TRACK, EXPERIMENTS_TO_TRACK,
LOCAL_STORAGE_EXPERIMENT_OVERRIDES, LOCAL_STORAGE_EXPERIMENT_OVERRIDES,
} from '@/constants'; } from '@/constants';
import { useTelemetryStore } from './telemetry.store';
import { useDebounce } from '@/composables/useDebounce'; import { useDebounce } from '@/composables/useDebounce';
import { useTelemetry } from '@/composables/useTelemetry';
const EVENTS = { const EVENTS = {
IS_PART_OF_EXPERIMENT: 'User is part of experiment', IS_PART_OF_EXPERIMENT: 'User is part of experiment',
@ -23,7 +23,7 @@ export type PosthogStore = ReturnType<typeof usePostHog>;
export const usePostHog = defineStore('posthog', () => { export const usePostHog = defineStore('posthog', () => {
const usersStore = useUsersStore(); const usersStore = useUsersStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const telemetryStore = useTelemetryStore(); const telemetry = useTelemetry();
const rootStore = useRootStore(); const rootStore = useRootStore();
const { debounce } = useDebounce(); const { debounce } = useDebounce();
@ -110,7 +110,7 @@ export const usePostHog = defineStore('posthog', () => {
return; return;
} }
telemetryStore.track(EVENTS.IS_PART_OF_EXPERIMENT, { telemetry.track(EVENTS.IS_PART_OF_EXPERIMENT, {
name, name,
variant, variant,
}); });

View file

@ -1,22 +0,0 @@
import type { Telemetry } from '@/plugins/telemetry';
import type { ITelemetryTrackProperties } from 'n8n-workflow';
import { defineStore } from 'pinia';
import type { Ref } from 'vue';
import { ref } from 'vue';
export const useTelemetryStore = defineStore('telemetry', () => {
const telemetry: Ref<Telemetry | undefined> = ref();
const init = (tel: Telemetry) => {
telemetry.value = tel;
};
const track = (event: string, properties?: ITelemetryTrackProperties) => {
telemetry.value?.track(event, properties);
};
return {
init,
track,
};
});

View file

@ -60,7 +60,6 @@ import { useCloudPlanStore } from '@/stores/cloudPlan.store';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { hasPermission } from '@/utils/rbac/permissions'; import { hasPermission } from '@/utils/rbac/permissions';
import { useTelemetryStore } from '@/stores/telemetry.store';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { dismissBannerPermanently } from '@/api/ui'; import { dismissBannerPermanently } from '@/api/ui';
import type { BannerName } from 'n8n-workflow'; import type { BannerName } from 'n8n-workflow';
@ -73,6 +72,7 @@ import {
} from './ui.utils'; } from './ui.utils';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import type { Connection } from '@vue-flow/core'; import type { Connection } from '@vue-flow/core';
import { useTelemetry } from '@/composables/useTelemetry';
let savedTheme: ThemeOption = 'system'; let savedTheme: ThemeOption = 'system';
try { try {
@ -205,7 +205,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
const rootStore = useRootStore(); const rootStore = useRootStore();
const telemetryStore = useTelemetryStore(); const telemetry = useTelemetry();
const cloudPlanStore = useCloudPlanStore(); const cloudPlanStore = useCloudPlanStore();
const userStore = useUsersStore(); const userStore = useUsersStore();
@ -564,7 +564,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
const { executionsLeft, workflowsLeft } = usageLeft; const { executionsLeft, workflowsLeft } = usageLeft;
const deploymentType = settingsStore.deploymentType; const deploymentType = settingsStore.deploymentType;
telemetryStore.track('User clicked upgrade CTA', { telemetry.track('User clicked upgrade CTA', {
source, source,
isTrial: userIsTrialing, isTrial: userIsTrialing,
deploymentType, deploymentType,

View file

@ -0,0 +1,61 @@
import type { CurrentUserResponse } from '@/Interface';
import { useUsersStore } from './users.store';
import { createPinia, setActivePinia } from 'pinia';
const { loginCurrentUser, identify } = vi.hoisted(() => {
return {
loginCurrentUser: vi.fn(),
identify: vi.fn(),
};
});
vi.mock('@/api/users', () => ({
loginCurrentUser,
}));
vi.mock('@/composables/useTelemetry', () => ({
useTelemetry: vi.fn(() => ({
identify,
})),
}));
vi.mock('@/stores/root.store', () => ({
useRootStore: vi.fn(() => ({
instanceId: 'test-instance-id',
})),
}));
const mockUser: CurrentUserResponse = {
id: '1',
firstName: 'John Doe',
role: 'global:owner',
isPending: false,
};
describe('users.store', () => {
beforeEach(() => {
vi.restoreAllMocks();
setActivePinia(createPinia());
});
describe('loginWithCookie', () => {
it('should set current user', async () => {
const usersStore = useUsersStore();
loginCurrentUser.mockResolvedValueOnce(mockUser);
await usersStore.loginWithCookie();
expect(loginCurrentUser).toHaveBeenCalled();
expect(usersStore.currentUserId).toEqual(mockUser.id);
expect(usersStore.currentUser).toEqual({
...mockUser,
fullName: `${mockUser.firstName} `,
isDefaultUser: false,
isPendingUser: false,
});
expect(identify).toHaveBeenCalledWith('test-instance-id', mockUser.id);
});
});
});

View file

@ -16,7 +16,7 @@ import type {
} from '@/Interface'; } from '@/Interface';
import { getPersonalizedNodeTypes } from '@/utils/userUtils'; import { getPersonalizedNodeTypes } from '@/utils/userUtils';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useRootStore } from './root.store'; import { useRootStore } from '@/stores/root.store';
import { usePostHog } from './posthog.store'; import { usePostHog } from './posthog.store';
import { useSettingsStore } from './settings.store'; import { useSettingsStore } from './settings.store';
import { useUIStore } from './ui.store'; import { useUIStore } from './ui.store';
@ -28,6 +28,7 @@ import type { Scope } from '@n8n/permissions';
import * as invitationsApi from '@/api/invitation'; import * as invitationsApi from '@/api/invitation';
import { useNpsSurveyStore } from './npsSurvey.store'; import { useNpsSurveyStore } from './npsSurvey.store';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useTelemetry } from '@/composables/useTelemetry';
const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending; const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending;
const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner; const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner;
@ -48,6 +49,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
const rootStore = useRootStore(); const rootStore = useRootStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const cloudPlanStore = useCloudPlanStore(); const cloudPlanStore = useCloudPlanStore();
const telemetry = useTelemetry();
// Composables // Composables
@ -115,6 +117,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
const defaultScopes: Scope[] = []; const defaultScopes: Scope[] = [];
RBACStore.setGlobalScopes(user.globalScopes || defaultScopes); RBACStore.setGlobalScopes(user.globalScopes || defaultScopes);
telemetry.identify(rootStore.instanceId, user.id);
postHogStore.init(user.featureFlags); postHogStore.init(user.featureFlags);
npsSurveyStore.setupNpsSurveyOnLogin(user.id, user.settings); npsSurveyStore.setupNpsSurveyOnLogin(user.id, user.settings);
}; };
@ -142,6 +145,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
const unsetCurrentUser = () => { const unsetCurrentUser = () => {
currentUserId.value = null; currentUserId.value = null;
currentUserCloudInfo.value = null; currentUserCloudInfo.value = null;
telemetry.reset();
RBACStore.setGlobalScopes([]); RBACStore.setGlobalScopes([]);
}; };
@ -373,11 +377,8 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
globalRoleName, globalRoleName,
personalizedNodeTypes, personalizedNodeTypes,
addUsers, addUsers,
setCurrentUser,
loginWithCookie, loginWithCookie,
initialize, initialize,
unsetCurrentUser,
deleteUserById,
setPersonalizationAnswers, setPersonalizationAnswers,
loginWithCreds, loginWithCreds,
logout, logout,