mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 11:22:15 +00: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:
@@ -71,6 +71,11 @@ declare global {
|
|||||||
analytics?: {
|
analytics?: {
|
||||||
track(event: string, proeprties?: ITelemetryTrackProperties): void;
|
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;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
createdAt?: string;
|
||||||
globalRole?: {
|
globalRole?: {
|
||||||
name: IRole;
|
name: IRole;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -599,7 +605,6 @@ export interface IUser extends IUserResponse {
|
|||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
inviteAcceptUrl?: string;
|
inviteAcceptUrl?: string;
|
||||||
fullName?: string;
|
fullName?: string;
|
||||||
createdAt?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVersionNotificationSettings {
|
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_MAPPING_IS_ONBOARDED = 'N8N_MAPPING_ONBOARDED';
|
||||||
export const LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH = 'N8N_MAIN_PANEL_RELATIVE_WIDTH';
|
export const LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH = 'N8N_MAIN_PANEL_RELATIVE_WIDTH';
|
||||||
export const LOCAL_STORAGE_THEME = 'N8N_THEME';
|
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 BASE_NODE_SURVEY_URL = 'https://n8n-community.typeform.com/to/BvmzxqYv#nodename=';
|
||||||
|
|
||||||
export const HIRING_BANNER = `
|
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 { useRootStore } from '@/stores/n8nRootStore';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { FeatureFlags } from 'n8n-workflow';
|
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 { useTelemetryStore } from './telemetry';
|
||||||
import { useSegment } from './segment';
|
import { useSegment } from './segment';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
@@ -23,6 +27,8 @@ export const usePostHog = defineStore('posthog', () => {
|
|||||||
const featureFlags: Ref<FeatureFlags | null> = ref(null);
|
const featureFlags: Ref<FeatureFlags | null> = ref(null);
|
||||||
const trackedDemoExp: Ref<FeatureFlags> = ref({});
|
const trackedDemoExp: Ref<FeatureFlags> = ref({});
|
||||||
|
|
||||||
|
const overrides: Ref<Record<string, string | boolean>> = ref({});
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
window.posthog?.reset?.();
|
window.posthog?.reset?.();
|
||||||
featureFlags.value = null;
|
featureFlags.value = null;
|
||||||
@@ -37,6 +43,33 @@ export const usePostHog = defineStore('posthog', () => {
|
|||||||
return getVariant(experiment) === variant;
|
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 identify = () => {
|
||||||
const instanceId = rootStore.instanceId;
|
const instanceId = rootStore.instanceId;
|
||||||
const user = usersStore.currentUser;
|
const user = usersStore.currentUser;
|
||||||
@@ -51,6 +84,13 @@ export const usePostHog = defineStore('posthog', () => {
|
|||||||
window.posthog?.identify?.(id, traits);
|
window.posthog?.identify?.(id, traits);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addExperimentOverrides = () => {
|
||||||
|
featureFlags.value = {
|
||||||
|
...featureFlags.value,
|
||||||
|
...overrides.value,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const init = (evaluatedFeatureFlags?: FeatureFlags) => {
|
const init = (evaluatedFeatureFlags?: FeatureFlags) => {
|
||||||
if (!window.posthog) {
|
if (!window.posthog) {
|
||||||
return;
|
return;
|
||||||
@@ -86,10 +126,12 @@ export const usePostHog = defineStore('posthog', () => {
|
|||||||
featureFlags: evaluatedFeatureFlags,
|
featureFlags: evaluatedFeatureFlags,
|
||||||
};
|
};
|
||||||
trackExperiments(evaluatedFeatureFlags);
|
trackExperiments(evaluatedFeatureFlags);
|
||||||
|
addExperimentOverrides();
|
||||||
} else {
|
} else {
|
||||||
// depend on client side evaluation if serverside evaluation fails
|
// depend on client side evaluation if serverside evaluation fails
|
||||||
window.posthog?.onFeatureFlags?.((keys: string[], map: FeatureFlags) => {
|
window.posthog?.onFeatureFlags?.((keys: string[], map: FeatureFlags) => {
|
||||||
featureFlags.value = map;
|
featureFlags.value = map;
|
||||||
|
addExperimentOverrides();
|
||||||
trackExperiments(map);
|
trackExperiments(map);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -101,6 +143,10 @@ export const usePostHog = defineStore('posthog', () => {
|
|||||||
|
|
||||||
const trackExperiment = (featureFlags: FeatureFlags, name: string) => {
|
const trackExperiment = (featureFlags: FeatureFlags, name: string) => {
|
||||||
const variant = featureFlags[name];
|
const variant = featureFlags[name];
|
||||||
|
if (!variant || trackedDemoExp.value[name] === variant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
telemetryStore.track(EVENTS.IS_PART_OF_EXPERIMENT, {
|
telemetryStore.track(EVENTS.IS_PART_OF_EXPERIMENT, {
|
||||||
name,
|
name,
|
||||||
variant,
|
variant,
|
||||||
|
|||||||
@@ -179,13 +179,19 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
|||||||
setSettings(settings: IN8nUISettings): void {
|
setSettings(settings: IN8nUISettings): void {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.userManagement = settings.userManagement;
|
this.userManagement = settings.userManagement;
|
||||||
this.userManagement.showSetupOnFirstLoad = !!settings.userManagement.showSetupOnFirstLoad;
|
if (this.userManagement) {
|
||||||
|
this.userManagement.showSetupOnFirstLoad = !!settings.userManagement.showSetupOnFirstLoad;
|
||||||
|
}
|
||||||
this.api = settings.publicApi;
|
this.api = settings.publicApi;
|
||||||
this.onboardingCallPromptEnabled = settings.onboardingCallPromptEnabled;
|
this.onboardingCallPromptEnabled = settings.onboardingCallPromptEnabled;
|
||||||
this.ldap.loginEnabled = settings.sso.ldap.loginEnabled;
|
if (settings.sso?.ldap) {
|
||||||
this.ldap.loginLabel = settings.sso.ldap.loginLabel;
|
this.ldap.loginEnabled = settings.sso.ldap.loginEnabled;
|
||||||
this.saml.loginEnabled = settings.sso.saml.loginEnabled;
|
this.ldap.loginLabel = settings.sso.ldap.loginLabel;
|
||||||
this.saml.loginLabel = settings.sso.saml.loginLabel;
|
}
|
||||||
|
if (settings.sso?.saml) {
|
||||||
|
this.saml.loginEnabled = settings.sso.saml.loginEnabled;
|
||||||
|
this.saml.loginLabel = settings.sso.saml.loginLabel;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async getSettings(): Promise<void> {
|
async getSettings(): Promise<void> {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
|
|||||||
Reference in New Issue
Block a user