From 32f7d5d273f979e24bf4c221fd9d2777e90ad7d0 Mon Sep 17 00:00:00 2001 From: wadii Date: Mon, 15 Jun 2026 15:39:14 +0200 Subject: [PATCH 1/3] feat: set organisation_plan flagsmith trait on login and org switch --- frontend/common/stores/account-store.js | 1 + frontend/common/types/responses.ts | 8 +++++++ frontend/common/utils/utils.tsx | 28 +++++++++++++++++++++++++ frontend/web/project/api.ts | 11 ++++++++++ 4 files changed, 48 insertions(+) diff --git a/frontend/common/stores/account-store.js b/frontend/common/stores/account-store.js index 2deb58f93073..839e6a49f1fa 100644 --- a/frontend/common/stores/account-store.js +++ b/frontend/common/stores/account-store.js @@ -314,6 +314,7 @@ const controller = { getStore().dispatch(setSelectedOrganisationId(id)) store.changed() identifyChatUser() + API.setFlagsmithOrganisationPlanTrait() }, setToken: (token) => { diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index 73b6c5607024..6ba10cbc7939 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -490,6 +490,14 @@ export type AuditLogDetail = AuditLogItem & { new: FlagsmithValue }[] } +// Mirrors API plans, to be updated in case of new family plan +export enum SubscriptionPlan { + FREE = 'free', + STARTUP = 'startup', + SCALE_UP = 'scale-up', + ENTERPRISE = 'enterprise', +} + export type Subscription = { id: number uuid: string diff --git a/frontend/common/utils/utils.tsx b/frontend/common/utils/utils.tsx index db241e4457f6..27e5565b96cd 100644 --- a/frontend/common/utils/utils.tsx +++ b/frontend/common/utils/utils.tsx @@ -12,6 +12,7 @@ import { Project as ProjectType, ProjectFlag, SegmentCondition, + SubscriptionPlan, Tag, UserPermissions, } from 'common/types/responses' @@ -467,6 +468,7 @@ const Utils = Object.assign({}, BaseUtils, { } } }, + getPlanName: (_plan: string) => { const plan = (_plan || '')?.toLowerCase() if (plan.includes('free')) { @@ -489,6 +491,7 @@ const Utils = Object.assign({}, BaseUtils, { } return planNames.free }, + getPlanPermission: (plan: string, feature: PaidFeature) => { const planName = Utils.getPlanName(plan) if (!plan || planName === planNames.free) { @@ -508,6 +511,7 @@ const Utils = Object.assign({}, BaseUtils, { } return true }, + getPlansPermission: (feature: PaidFeature) => { const isOrgPermission = feature !== '2FA' let plans @@ -527,6 +531,7 @@ const Utils = Object.assign({}, BaseUtils, { ) return !!found }, + getProjectColour(index: number) { return Constants.projectColors[index % (Constants.projectColors.length - 1)] }, @@ -621,6 +626,29 @@ const Utils = Object.assign({}, BaseUtils, { return false }, + // Collapse a raw subscription plan id (e.g. 'scale-up-v4-monthly', 'startup-v2') + // into its plan family, mirroring SubscriptionPlanFamily.get_by_plan_id in + // api/organisations/subscriptions/constants.py. An unrecognised (new) plan is + // surfaced as its raw id rather than silently bucketed as free, so it stays + // visible and segmentable until added as a family. An empty plan is free. + getSubscriptionPlanFamily: (plan?: string | null): string => { + const raw = (plan || '').toLowerCase() + if (!raw) { + return SubscriptionPlan.FREE + } + const normalised = raw.replace(/-/g, '') + if (normalised.startsWith('scaleup')) { + return SubscriptionPlan.SCALE_UP + } + if (normalised.startsWith('startup')) { + return SubscriptionPlan.STARTUP + } + if (normalised.startsWith('enterprise')) { + return SubscriptionPlan.ENTERPRISE + } + return raw + }, + getTagColour(index: number) { return Constants.tagColors[index % (Constants.tagColors.length - 1)] }, diff --git a/frontend/web/project/api.ts b/frontend/web/project/api.ts index bf1535b36666..40ea9cebcdb0 100644 --- a/frontend/web/project/api.ts +++ b/frontend/web/project/api.ts @@ -137,6 +137,9 @@ const API = { return flagsmith .identify(`${user.id}`, { email: user.email, + organisation_plan: Utils.getSubscriptionPlanFamily( + AccountStore.getOrganisation()?.subscription?.plan, + ), organisations: user.organisations ? user.organisations.map((o) => String(o.id)).join(',') : '', @@ -285,6 +288,14 @@ const API = { API.setCookie('event', v) }, + setFlagsmithOrganisationPlanTrait: () => { + const organisation = AccountStore.getOrganisation() + return flagsmith.setTrait( + 'organisation_plan', + Utils.getSubscriptionPlanFamily(organisation?.subscription?.plan), + ) + }, + setInvite(id: string): void { Cookies.set('invite', id) }, From 006414580ba7f247e282ed786a2175d071bcbdc9 Mon Sep 17 00:00:00 2001 From: wadii Date: Tue, 16 Jun 2026 14:41:50 +0200 Subject: [PATCH 2/3] feat: add docs link to experiments banner --- .../ExperimentsFakeDoor/ExperimentsFakeDoor.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/web/components/experiments/ExperimentsFakeDoor/ExperimentsFakeDoor.tsx b/frontend/web/components/experiments/ExperimentsFakeDoor/ExperimentsFakeDoor.tsx index 02864130387b..2b74d202057a 100644 --- a/frontend/web/components/experiments/ExperimentsFakeDoor/ExperimentsFakeDoor.tsx +++ b/frontend/web/components/experiments/ExperimentsFakeDoor/ExperimentsFakeDoor.tsx @@ -45,7 +45,14 @@ const ExperimentsFakeDoor: FC = () => { I would like to participate in beta testing - Get early access to Experiments and help shape the feature + Get early access to Experiments and help shape the feature.{' '} + + Learn more + {signedUp ? ( From 48026dfdf691224965361eb0f5bce9350b10c346 Mon Sep 17 00:00:00 2001 From: wadii Date: Tue, 16 Jun 2026 18:17:49 +0200 Subject: [PATCH 3/3] feat: mimicked api using raw subscription trait --- frontend/common/stores/account-store.js | 2 +- frontend/common/types/responses.ts | 8 -------- frontend/common/utils/utils.tsx | 24 ------------------------ frontend/web/project/api.ts | 11 +++++------ 4 files changed, 6 insertions(+), 39 deletions(-) diff --git a/frontend/common/stores/account-store.js b/frontend/common/stores/account-store.js index 839e6a49f1fa..d57110e0b9a5 100644 --- a/frontend/common/stores/account-store.js +++ b/frontend/common/stores/account-store.js @@ -314,7 +314,7 @@ const controller = { getStore().dispatch(setSelectedOrganisationId(id)) store.changed() identifyChatUser() - API.setFlagsmithOrganisationPlanTrait() + API.setFlagsmithSubscriptionPlanTrait() }, setToken: (token) => { diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index 6ba10cbc7939..73b6c5607024 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -490,14 +490,6 @@ export type AuditLogDetail = AuditLogItem & { new: FlagsmithValue }[] } -// Mirrors API plans, to be updated in case of new family plan -export enum SubscriptionPlan { - FREE = 'free', - STARTUP = 'startup', - SCALE_UP = 'scale-up', - ENTERPRISE = 'enterprise', -} - export type Subscription = { id: number uuid: string diff --git a/frontend/common/utils/utils.tsx b/frontend/common/utils/utils.tsx index 27e5565b96cd..be6dd74ddbab 100644 --- a/frontend/common/utils/utils.tsx +++ b/frontend/common/utils/utils.tsx @@ -12,7 +12,6 @@ import { Project as ProjectType, ProjectFlag, SegmentCondition, - SubscriptionPlan, Tag, UserPermissions, } from 'common/types/responses' @@ -626,29 +625,6 @@ const Utils = Object.assign({}, BaseUtils, { return false }, - // Collapse a raw subscription plan id (e.g. 'scale-up-v4-monthly', 'startup-v2') - // into its plan family, mirroring SubscriptionPlanFamily.get_by_plan_id in - // api/organisations/subscriptions/constants.py. An unrecognised (new) plan is - // surfaced as its raw id rather than silently bucketed as free, so it stays - // visible and segmentable until added as a family. An empty plan is free. - getSubscriptionPlanFamily: (plan?: string | null): string => { - const raw = (plan || '').toLowerCase() - if (!raw) { - return SubscriptionPlan.FREE - } - const normalised = raw.replace(/-/g, '') - if (normalised.startsWith('scaleup')) { - return SubscriptionPlan.SCALE_UP - } - if (normalised.startsWith('startup')) { - return SubscriptionPlan.STARTUP - } - if (normalised.startsWith('enterprise')) { - return SubscriptionPlan.ENTERPRISE - } - return raw - }, - getTagColour(index: number) { return Constants.tagColors[index % (Constants.tagColors.length - 1)] }, diff --git a/frontend/web/project/api.ts b/frontend/web/project/api.ts index 40ea9cebcdb0..6782be510782 100644 --- a/frontend/web/project/api.ts +++ b/frontend/web/project/api.ts @@ -137,12 +137,11 @@ const API = { return flagsmith .identify(`${user.id}`, { email: user.email, - organisation_plan: Utils.getSubscriptionPlanFamily( - AccountStore.getOrganisation()?.subscription?.plan, - ), organisations: user.organisations ? user.organisations.map((o) => String(o.id)).join(',') : '', + 'subscription.plan': + AccountStore.getOrganisation()?.subscription?.plan || '', }) .then(() => flagsmith.setTrait( @@ -288,11 +287,11 @@ const API = { API.setCookie('event', v) }, - setFlagsmithOrganisationPlanTrait: () => { + setFlagsmithSubscriptionPlanTrait: () => { const organisation = AccountStore.getOrganisation() return flagsmith.setTrait( - 'organisation_plan', - Utils.getSubscriptionPlanFamily(organisation?.subscription?.plan), + 'subscription.plan', + organisation?.subscription?.plan || '', ) },