diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index c40d5aa13a..0919176281 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -363,7 +363,7 @@ const _ProjectController = { user: (async () => { const user = await User.findById( userId, - 'email first_name last_name referal_id signUpDate featureSwitches features featuresEpoch refProviders alphaProgram betaProgram isAdmin ace labsProgram completedTutorials writefull aiErrorAssistant' + 'email first_name last_name referal_id signUpDate featureSwitches features featuresEpoch refProviders alphaProgram betaProgram isAdmin ace labsProgram labsExperiments completedTutorials writefull aiErrorAssistant' ).exec() // Handle case of deleted user if (!user) { @@ -838,6 +838,7 @@ const _ProjectController = { referencesSearchMode: user.ace.referencesSearchMode, enableNewEditor: user.ace.enableNewEditor ?? true, }, + labsExperiments: user.labsExperiments ?? [], privilegeLevel, anonymous, isTokenMember, diff --git a/services/web/app/src/Features/User/UserPagesController.mjs b/services/web/app/src/Features/User/UserPagesController.mjs index f95a2e6b4e..6f7bb7802d 100644 --- a/services/web/app/src/Features/User/UserPagesController.mjs +++ b/services/web/app/src/Features/User/UserPagesController.mjs @@ -149,6 +149,7 @@ async function settingsPage(req, res) { enabled: Boolean(user.aiErrorAssistant?.enabled), }, }, + labsExperiments: user.labsExperiments ?? [], hasPassword: !!user.hashedPassword, shouldAllowEditingDetails, oauthProviders: UserPagesController._translateProviderDescriptions( diff --git a/services/web/app/src/models/User.js b/services/web/app/src/models/User.js index 73506f161c..c63647e914 100644 --- a/services/web/app/src/models/User.js +++ b/services/web/app/src/models/User.js @@ -203,6 +203,7 @@ const UserSchema = new Schema( alphaProgram: { type: Boolean, default: false }, // experimental features betaProgram: { type: Boolean, default: false }, labsProgram: { type: Boolean, default: false }, + labsExperiments: { type: Array, default: [] }, overleaf: { id: { type: Number }, accessToken: { type: String }, diff --git a/services/web/app/views/project/editor/_meta.pug b/services/web/app/views/project/editor/_meta.pug index 20e90576c9..61b7dd7d67 100644 --- a/services/web/app/views/project/editor/_meta.pug +++ b/services/web/app/views/project/editor/_meta.pug @@ -2,6 +2,7 @@ meta(name="ol-project_id" content=project_id) meta(name="ol-projectName" content=projectName) meta(name="ol-userSettings" data-type="json" content=userSettings) meta(name="ol-user" data-type="json" content=user) +meta(name="ol-labsExperiments" data-type="json" content=labsExperiments) meta(name="ol-learnedWords" data-type="json" content=learnedWords) meta(name="ol-anonymous" data-type="boolean" content=anonymous) meta(name="ol-brandVariation" data-type="json" content=brandVariation) diff --git a/services/web/app/views/user/settings.pug b/services/web/app/views/user/settings.pug index 57eef44c27..4f939a41ca 100644 --- a/services/web/app/views/user/settings.pug +++ b/services/web/app/views/user/settings.pug @@ -22,6 +22,7 @@ block append meta meta(name="ol-passwordStrengthOptions", data-type="json", content=settings.passwordStrengthOptions || {}) meta(name="ol-isExternalAuthenticationSystemUsed" data-type="boolean" content=externalAuthenticationSystemUsed()) meta(name="ol-user" data-type="json" content=user) + meta(name="ol-labsExperiments" data-type="json" content=labsExperiments) meta(name="ol-dropbox" data-type="json" content=dropbox) meta(name="ol-github" data-type="json" content=github) meta(name="ol-projectSyncSuccessMessage", content=projectSyncSuccessMessage) diff --git a/services/web/frontend/js/utils/labs-utils.ts b/services/web/frontend/js/utils/labs-utils.ts new file mode 100644 index 0000000000..9ac34e588c --- /dev/null +++ b/services/web/frontend/js/utils/labs-utils.ts @@ -0,0 +1,10 @@ +import getMeta from './meta' + +// Should be `never` when no experiments are active. Otherwise it should be a +// union of active experiment names e.g. `'experiment1' | 'experiment2'` +export type ActiveExperiment = never + +export const isInExperiment = (experiment: ActiveExperiment): boolean => { + const experiments = getMeta('ol-labsExperiments') + return Boolean(experiments?.includes(experiment)) +} diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 0955d98cd1..c45a17948f 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -52,6 +52,7 @@ import { SubscriptionChangePreview } from '../../../types/subscription/subscript import { DefaultNavbarMetadata } from '@/features/ui/components/types/default-navbar-metadata' import { FooterMetadata } from '@/features/ui/components/types/footer-metadata' import type { ScriptLogType } from '../../../modules/admin-panel/frontend/js/features/script-logs/script-log' +import { ActiveExperiment } from './labs-utils' export interface Meta { 'ol-ExposedSettings': ExposedSettings 'ol-addonPrices': Record @@ -139,6 +140,7 @@ export interface Meta { 'ol-itm_content': string 'ol-itm_referrer': string 'ol-labs': boolean + 'ol-labsExperiments': ActiveExperiment[] | undefined 'ol-languages': SpellCheckLanguage[] 'ol-learnedWords': string[] 'ol-legacyEditorThemes': string[]