mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 20:54:07 -08:00
fix: Ensure user id for early track events (#10885)
This commit is contained in:
parent
557db9c170
commit
23c09eae42
|
@ -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();
|
||||||
|
|
|
@ -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 = [];
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -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,
|
||||||
|
|
61
packages/editor-ui/src/stores/users.store.test.ts
Normal file
61
packages/editor-ui/src/stores/users.store.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue