diff --git a/packages/editor-ui/src/components/Telemetry.vue b/packages/editor-ui/src/components/Telemetry.vue index b799ad617..a5b4b8614 100644 --- a/packages/editor-ui/src/components/Telemetry.vue +++ b/packages/editor-ui/src/components/Telemetry.vue @@ -42,12 +42,6 @@ watch(telemetry, () => { init(); }); -watch(currentUserId, (userId) => { - if (isTelemetryEnabled.value) { - telemetryPlugin.identify(rootStore.instanceId, userId); - } -}); - watch(isTelemetryEnabledOnRoute, (enabled) => { if (enabled) { init(); diff --git a/packages/editor-ui/src/plugins/telemetry/index.ts b/packages/editor-ui/src/plugins/telemetry/index.ts index 7c48a92f5..5bef0920f 100644 --- a/packages/editor-ui/src/plugins/telemetry/index.ts +++ b/packages/editor-ui/src/plugins/telemetry/index.ts @@ -15,7 +15,6 @@ import { useRootStore } from '@/stores/root.store'; import { useNDVStore } from '@/stores/ndv.store'; import { usePostHog } from '@/stores/posthog.store'; import { useSettingsStore } from '@/stores/settings.store'; -import { useTelemetryStore } from '@/stores/telemetry.store'; import { useUIStore } from '@/stores/ui.store'; export class Telemetry { @@ -74,7 +73,6 @@ export class Telemetry { configUrl: 'https://api-rs.n8n.io', ...logging, }); - useTelemetryStore().init(this); this.identify(instanceId, userId, versionCli, projectId); @@ -146,6 +144,10 @@ export class Telemetry { } } + reset() { + this.rudderStack?.reset(); + } + flushPageEvents() { const queue = this.pageEventQueue; this.pageEventQueue = []; diff --git a/packages/editor-ui/src/stores/__tests__/posthog.test.ts b/packages/editor-ui/src/stores/__tests__/posthog.test.ts index 00805c562..e2a5dd9d6 100644 --- a/packages/editor-ui/src/stores/__tests__/posthog.test.ts +++ b/packages/editor-ui/src/stores/__tests__/posthog.test.ts @@ -3,11 +3,11 @@ import { usePostHog } from '@/stores/posthog.store'; import { useUsersStore } from '@/stores/users.store'; import { useSettingsStore } from '@/stores/settings.store'; import { useRootStore } from '@/stores/root.store'; -import { useTelemetryStore } from '@/stores/telemetry.store'; import type { FrontendSettings } from '@n8n/api-types'; import { LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants'; import { nextTick } from 'vue'; import { defaultSettings } from '../../__tests__/defaults'; +import { useTelemetry } from '@/composables/useTelemetry'; export const DEFAULT_POSTHOG_SETTINGS: FrontendSettings['posthog'] = { enabled: true, @@ -55,11 +55,11 @@ function setup() { identify: () => {}, }; - const telemetryStore = useTelemetryStore(); + const telemetry = useTelemetry(); vi.spyOn(window.posthog, 'init'); vi.spyOn(window.posthog, 'identify'); - vi.spyOn(telemetryStore, 'track'); + vi.spyOn(telemetry, 'track'); } describe('Posthog store', () => { diff --git a/packages/editor-ui/src/stores/posthog.store.ts b/packages/editor-ui/src/stores/posthog.store.ts index df52fb2af..d540fe290 100644 --- a/packages/editor-ui/src/stores/posthog.store.ts +++ b/packages/editor-ui/src/stores/posthog.store.ts @@ -11,8 +11,8 @@ import { EXPERIMENTS_TO_TRACK, LOCAL_STORAGE_EXPERIMENT_OVERRIDES, } from '@/constants'; -import { useTelemetryStore } from './telemetry.store'; import { useDebounce } from '@/composables/useDebounce'; +import { useTelemetry } from '@/composables/useTelemetry'; const EVENTS = { IS_PART_OF_EXPERIMENT: 'User is part of experiment', @@ -23,7 +23,7 @@ export type PosthogStore = ReturnType; export const usePostHog = defineStore('posthog', () => { const usersStore = useUsersStore(); const settingsStore = useSettingsStore(); - const telemetryStore = useTelemetryStore(); + const telemetry = useTelemetry(); const rootStore = useRootStore(); const { debounce } = useDebounce(); @@ -110,7 +110,7 @@ export const usePostHog = defineStore('posthog', () => { return; } - telemetryStore.track(EVENTS.IS_PART_OF_EXPERIMENT, { + telemetry.track(EVENTS.IS_PART_OF_EXPERIMENT, { name, variant, }); diff --git a/packages/editor-ui/src/stores/telemetry.store.ts b/packages/editor-ui/src/stores/telemetry.store.ts deleted file mode 100644 index ed3b68bbf..000000000 --- a/packages/editor-ui/src/stores/telemetry.store.ts +++ /dev/null @@ -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 = ref(); - - const init = (tel: Telemetry) => { - telemetry.value = tel; - }; - - const track = (event: string, properties?: ITelemetryTrackProperties) => { - telemetry.value?.track(event, properties); - }; - - return { - init, - track, - }; -}); diff --git a/packages/editor-ui/src/stores/ui.store.ts b/packages/editor-ui/src/stores/ui.store.ts index 79220796f..f66f83f25 100644 --- a/packages/editor-ui/src/stores/ui.store.ts +++ b/packages/editor-ui/src/stores/ui.store.ts @@ -60,7 +60,6 @@ import { useCloudPlanStore } from '@/stores/cloudPlan.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useSettingsStore } from '@/stores/settings.store'; import { hasPermission } from '@/utils/rbac/permissions'; -import { useTelemetryStore } from '@/stores/telemetry.store'; import { useUsersStore } from '@/stores/users.store'; import { dismissBannerPermanently } from '@/api/ui'; import type { BannerName } from 'n8n-workflow'; @@ -73,6 +72,7 @@ import { } from './ui.utils'; import { computed, ref } from 'vue'; import type { Connection } from '@vue-flow/core'; +import { useTelemetry } from '@/composables/useTelemetry'; let savedTheme: ThemeOption = 'system'; try { @@ -205,7 +205,7 @@ export const useUIStore = defineStore(STORES.UI, () => { const settingsStore = useSettingsStore(); const workflowsStore = useWorkflowsStore(); const rootStore = useRootStore(); - const telemetryStore = useTelemetryStore(); + const telemetry = useTelemetry(); const cloudPlanStore = useCloudPlanStore(); const userStore = useUsersStore(); @@ -564,7 +564,7 @@ export const useUIStore = defineStore(STORES.UI, () => { const { executionsLeft, workflowsLeft } = usageLeft; const deploymentType = settingsStore.deploymentType; - telemetryStore.track('User clicked upgrade CTA', { + telemetry.track('User clicked upgrade CTA', { source, isTrial: userIsTrialing, deploymentType, diff --git a/packages/editor-ui/src/stores/users.store.test.ts b/packages/editor-ui/src/stores/users.store.test.ts new file mode 100644 index 000000000..8e9969e8e --- /dev/null +++ b/packages/editor-ui/src/stores/users.store.test.ts @@ -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); + }); + }); +}); diff --git a/packages/editor-ui/src/stores/users.store.ts b/packages/editor-ui/src/stores/users.store.ts index 605d54e7a..53d538012 100644 --- a/packages/editor-ui/src/stores/users.store.ts +++ b/packages/editor-ui/src/stores/users.store.ts @@ -16,7 +16,7 @@ import type { } from '@/Interface'; import { getPersonalizedNodeTypes } from '@/utils/userUtils'; import { defineStore } from 'pinia'; -import { useRootStore } from './root.store'; +import { useRootStore } from '@/stores/root.store'; import { usePostHog } from './posthog.store'; import { useSettingsStore } from './settings.store'; import { useUIStore } from './ui.store'; @@ -28,6 +28,7 @@ import type { Scope } from '@n8n/permissions'; import * as invitationsApi from '@/api/invitation'; import { useNpsSurveyStore } from './npsSurvey.store'; import { computed, ref } from 'vue'; +import { useTelemetry } from '@/composables/useTelemetry'; const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending; const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner; @@ -48,6 +49,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => { const rootStore = useRootStore(); const settingsStore = useSettingsStore(); const cloudPlanStore = useCloudPlanStore(); + const telemetry = useTelemetry(); // Composables @@ -115,6 +117,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => { const defaultScopes: Scope[] = []; RBACStore.setGlobalScopes(user.globalScopes || defaultScopes); + telemetry.identify(rootStore.instanceId, user.id); postHogStore.init(user.featureFlags); npsSurveyStore.setupNpsSurveyOnLogin(user.id, user.settings); }; @@ -142,6 +145,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => { const unsetCurrentUser = () => { currentUserId.value = null; currentUserCloudInfo.value = null; + telemetry.reset(); RBACStore.setGlobalScopes([]); }; @@ -373,11 +377,8 @@ export const useUsersStore = defineStore(STORES.USERS, () => { globalRoleName, personalizedNodeTypes, addUsers, - setCurrentUser, loginWithCookie, initialize, - unsetCurrentUser, - deleteUserById, setPersonalizationAnswers, loginWithCreds, logout,