mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
feat: Add test overrides (#5642)
* feat: Add test overrides * feat: add more func to test with * test: add tests for posthog store * fix: only init once * fix: only init once * test: fix
This commit is contained in:
parent
78c9707fa7
commit
696e43a919
|
@ -71,6 +71,11 @@ declare global {
|
|||
analytics?: {
|
||||
track(event: string, proeprties?: ITelemetryTrackProperties): void;
|
||||
};
|
||||
featureFlags?: {
|
||||
getAll: () => FeatureFlags;
|
||||
getVariant: (name: string) => string | boolean | undefined;
|
||||
override: (name: string, value: string) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -579,6 +584,7 @@ export interface IUserResponse {
|
|||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
createdAt?: string;
|
||||
globalRole?: {
|
||||
name: IRole;
|
||||
id: string;
|
||||
|
@ -599,7 +605,6 @@ export interface IUser extends IUserResponse {
|
|||
isOwner: boolean;
|
||||
inviteAcceptUrl?: string;
|
||||
fullName?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface IVersionNotificationSettings {
|
||||
|
|
|
@ -325,6 +325,7 @@ export const LOCAL_STORAGE_PIN_DATA_DISCOVERY_CANVAS_FLAG = 'N8N_PIN_DATA_DISCOV
|
|||
export const LOCAL_STORAGE_MAPPING_IS_ONBOARDED = 'N8N_MAPPING_ONBOARDED';
|
||||
export const LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH = 'N8N_MAIN_PANEL_RELATIVE_WIDTH';
|
||||
export const LOCAL_STORAGE_THEME = 'N8N_THEME';
|
||||
export const LOCAL_STORAGE_EXPERIMENT_OVERRIDES = 'N8N_EXPERIMENT_OVERRIDES';
|
||||
export const BASE_NODE_SURVEY_URL = 'https://n8n-community.typeform.com/to/BvmzxqYv#nodename=';
|
||||
|
||||
export const HIRING_BANNER = `
|
||||
|
|
150
packages/editor-ui/src/stores/posthog.test.ts
Normal file
150
packages/editor-ui/src/stores/posthog.test.ts
Normal file
|
@ -0,0 +1,150 @@
|
|||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { usePostHog } from './posthog';
|
||||
import { useUsersStore } from './users';
|
||||
import { useSettingsStore } from './settings';
|
||||
import { IN8nUISettings } from '@/Interface';
|
||||
import { useRootStore } from './n8nRootStore';
|
||||
import { useTelemetryStore } from './telemetry';
|
||||
|
||||
const DEFAULT_POSTHOG_SETTINGS: IN8nUISettings['posthog'] = {
|
||||
enabled: true,
|
||||
apiHost: 'host',
|
||||
apiKey: 'key',
|
||||
autocapture: false,
|
||||
disableSessionRecording: true,
|
||||
debug: false,
|
||||
};
|
||||
const CURRENT_USER_ID = '1';
|
||||
const CURRENT_INSTANCE_ID = '456';
|
||||
|
||||
function setSettings(overrides?: Partial<IN8nUISettings>) {
|
||||
useSettingsStore().setSettings({
|
||||
posthog: DEFAULT_POSTHOG_SETTINGS,
|
||||
instanceId: CURRENT_INSTANCE_ID,
|
||||
...overrides,
|
||||
} as IN8nUISettings);
|
||||
|
||||
useRootStore().setInstanceId(CURRENT_INSTANCE_ID);
|
||||
}
|
||||
|
||||
function setCurrentUser() {
|
||||
useUsersStore().addUsers([
|
||||
{
|
||||
id: CURRENT_USER_ID,
|
||||
isPending: false,
|
||||
createdAt: '2023-03-17T14:01:36.432Z',
|
||||
},
|
||||
]);
|
||||
|
||||
useUsersStore().currentUserId = CURRENT_USER_ID;
|
||||
}
|
||||
|
||||
function resetStores() {
|
||||
useSettingsStore().$reset();
|
||||
useUsersStore().$reset();
|
||||
}
|
||||
|
||||
function setup() {
|
||||
setActivePinia(createPinia());
|
||||
window.posthog = {
|
||||
init: () => {},
|
||||
identify: () => {},
|
||||
};
|
||||
|
||||
const telemetryStore = useTelemetryStore();
|
||||
|
||||
vi.spyOn(window.posthog, 'init');
|
||||
vi.spyOn(window.posthog, 'identify');
|
||||
vi.spyOn(window.Storage.prototype, 'setItem');
|
||||
vi.spyOn(telemetryStore, 'track');
|
||||
}
|
||||
|
||||
describe('Posthog store', () => {
|
||||
describe('should not init', () => {
|
||||
beforeEach(() => {
|
||||
setup();
|
||||
});
|
||||
|
||||
it('should not init if posthog is not enabled', () => {
|
||||
setSettings({ posthog: { ...DEFAULT_POSTHOG_SETTINGS, enabled: false } });
|
||||
setCurrentUser();
|
||||
const posthog = usePostHog();
|
||||
posthog.init();
|
||||
|
||||
expect(window.posthog?.init).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not init if user is not logged in', () => {
|
||||
setSettings();
|
||||
const posthog = usePostHog();
|
||||
posthog.init();
|
||||
|
||||
expect(window.posthog?.init).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetStores();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should init posthog', () => {
|
||||
beforeEach(() => {
|
||||
setup();
|
||||
setSettings();
|
||||
setCurrentUser();
|
||||
});
|
||||
|
||||
it('should init store with serverside flags', () => {
|
||||
const TEST = 'test';
|
||||
const flags = {
|
||||
[TEST]: 'variant',
|
||||
};
|
||||
const posthog = usePostHog();
|
||||
posthog.init(flags);
|
||||
|
||||
expect(posthog.getVariant('test')).toEqual(flags[TEST]);
|
||||
expect(window.posthog?.init).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should identify user', () => {
|
||||
const posthog = usePostHog();
|
||||
posthog.init();
|
||||
|
||||
const userId = `${CURRENT_INSTANCE_ID}#${CURRENT_USER_ID}`;
|
||||
expect(window.posthog?.identify).toHaveBeenCalledWith(userId, {
|
||||
created_at_timestamp: 1679061696432,
|
||||
instance_id: CURRENT_INSTANCE_ID,
|
||||
});
|
||||
});
|
||||
|
||||
it('sets override feature flags', () => {
|
||||
const TEST = 'test';
|
||||
const flags = {
|
||||
[TEST]: 'variant',
|
||||
};
|
||||
const posthog = usePostHog();
|
||||
posthog.init(flags);
|
||||
|
||||
window.featureFlags?.override(TEST, 'override');
|
||||
|
||||
expect(posthog.getVariant('test')).toEqual('override');
|
||||
expect(window.posthog?.init).toHaveBeenCalled();
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||
'N8N_EXPERIMENT_OVERRIDES',
|
||||
JSON.stringify({ test: 'override' }),
|
||||
);
|
||||
|
||||
window.featureFlags?.override('other_test', 'override');
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||
'N8N_EXPERIMENT_OVERRIDES',
|
||||
JSON.stringify({ test: 'override', other_test: 'override' }),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetStores();
|
||||
window.localStorage.clear();
|
||||
window.featureFlags = undefined;
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,7 +4,11 @@ import { useUsersStore } from '@/stores/users';
|
|||
import { useRootStore } from '@/stores/n8nRootStore';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { FeatureFlags } from 'n8n-workflow';
|
||||
import { EXPERIMENTS_TO_TRACK, ONBOARDING_EXPERIMENT } from '@/constants';
|
||||
import {
|
||||
EXPERIMENTS_TO_TRACK,
|
||||
LOCAL_STORAGE_EXPERIMENT_OVERRIDES,
|
||||
ONBOARDING_EXPERIMENT,
|
||||
} from '@/constants';
|
||||
import { useTelemetryStore } from './telemetry';
|
||||
import { useSegment } from './segment';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
@ -23,6 +27,8 @@ export const usePostHog = defineStore('posthog', () => {
|
|||
const featureFlags: Ref<FeatureFlags | null> = ref(null);
|
||||
const trackedDemoExp: Ref<FeatureFlags> = ref({});
|
||||
|
||||
const overrides: Ref<Record<string, string | boolean>> = ref({});
|
||||
|
||||
const reset = () => {
|
||||
window.posthog?.reset?.();
|
||||
featureFlags.value = null;
|
||||
|
@ -37,6 +43,33 @@ export const usePostHog = defineStore('posthog', () => {
|
|||
return getVariant(experiment) === variant;
|
||||
};
|
||||
|
||||
if (!window.featureFlags) {
|
||||
// for testing
|
||||
const cachedOverrdies = localStorage.getItem(LOCAL_STORAGE_EXPERIMENT_OVERRIDES);
|
||||
if (cachedOverrdies) {
|
||||
try {
|
||||
overrides.value = JSON.parse(cachedOverrdies);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
window.featureFlags = {
|
||||
// since features are evaluated serverside, regular posthog mechanism to override clientside does not work
|
||||
override: (name: string, value: string | boolean) => {
|
||||
overrides.value[name] = value;
|
||||
featureFlags.value = {
|
||||
...featureFlags.value,
|
||||
[name]: value,
|
||||
};
|
||||
try {
|
||||
localStorage.setItem(LOCAL_STORAGE_EXPERIMENT_OVERRIDES, JSON.stringify(overrides.value));
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
getVariant,
|
||||
getAll: () => featureFlags.value || {},
|
||||
};
|
||||
}
|
||||
|
||||
const identify = () => {
|
||||
const instanceId = rootStore.instanceId;
|
||||
const user = usersStore.currentUser;
|
||||
|
@ -51,6 +84,13 @@ export const usePostHog = defineStore('posthog', () => {
|
|||
window.posthog?.identify?.(id, traits);
|
||||
};
|
||||
|
||||
const addExperimentOverrides = () => {
|
||||
featureFlags.value = {
|
||||
...featureFlags.value,
|
||||
...overrides.value,
|
||||
};
|
||||
};
|
||||
|
||||
const init = (evaluatedFeatureFlags?: FeatureFlags) => {
|
||||
if (!window.posthog) {
|
||||
return;
|
||||
|
@ -86,10 +126,12 @@ export const usePostHog = defineStore('posthog', () => {
|
|||
featureFlags: evaluatedFeatureFlags,
|
||||
};
|
||||
trackExperiments(evaluatedFeatureFlags);
|
||||
addExperimentOverrides();
|
||||
} else {
|
||||
// depend on client side evaluation if serverside evaluation fails
|
||||
window.posthog?.onFeatureFlags?.((keys: string[], map: FeatureFlags) => {
|
||||
featureFlags.value = map;
|
||||
addExperimentOverrides();
|
||||
trackExperiments(map);
|
||||
});
|
||||
}
|
||||
|
@ -101,6 +143,10 @@ export const usePostHog = defineStore('posthog', () => {
|
|||
|
||||
const trackExperiment = (featureFlags: FeatureFlags, name: string) => {
|
||||
const variant = featureFlags[name];
|
||||
if (!variant || trackedDemoExp.value[name] === variant) {
|
||||
return;
|
||||
}
|
||||
|
||||
telemetryStore.track(EVENTS.IS_PART_OF_EXPERIMENT, {
|
||||
name,
|
||||
variant,
|
||||
|
|
|
@ -179,13 +179,19 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
|||
setSettings(settings: IN8nUISettings): void {
|
||||
this.settings = settings;
|
||||
this.userManagement = settings.userManagement;
|
||||
this.userManagement.showSetupOnFirstLoad = !!settings.userManagement.showSetupOnFirstLoad;
|
||||
if (this.userManagement) {
|
||||
this.userManagement.showSetupOnFirstLoad = !!settings.userManagement.showSetupOnFirstLoad;
|
||||
}
|
||||
this.api = settings.publicApi;
|
||||
this.onboardingCallPromptEnabled = settings.onboardingCallPromptEnabled;
|
||||
this.ldap.loginEnabled = settings.sso.ldap.loginEnabled;
|
||||
this.ldap.loginLabel = settings.sso.ldap.loginLabel;
|
||||
this.saml.loginEnabled = settings.sso.saml.loginEnabled;
|
||||
this.saml.loginLabel = settings.sso.saml.loginLabel;
|
||||
if (settings.sso?.ldap) {
|
||||
this.ldap.loginEnabled = settings.sso.ldap.loginEnabled;
|
||||
this.ldap.loginLabel = settings.sso.ldap.loginLabel;
|
||||
}
|
||||
if (settings.sso?.saml) {
|
||||
this.saml.loginEnabled = settings.sso.saml.loginEnabled;
|
||||
this.saml.loginLabel = settings.sso.saml.loginLabel;
|
||||
}
|
||||
},
|
||||
async getSettings(): Promise<void> {
|
||||
const rootStore = useRootStore();
|
||||
|
|
Loading…
Reference in a new issue