Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/composables/useFeatureFlags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@/composables/useFeatureFlags'
import * as distributionTypes from '@/platform/distribution/types'
import {
cachedBillingControlEnabled,
cachedConsolidatedBillingEnabled,
cachedTeamWorkspacesEnabled,
remoteConfig,
Expand Down Expand Up @@ -226,6 +227,14 @@ describe('useFeatureFlags', () => {
expect(flags.teamWorkspacesEnabled).toBe(true)
})

it('billingControlEnabled override bypasses isCloud and isAuthenticatedConfigLoaded guards', () => {
vi.mocked(distributionTypes).isCloud = false
localStorage.setItem('ff:billing_control_enabled', 'true')

const { flags } = useFeatureFlags()
expect(flags.billingControlEnabled).toBe(true)
})

it('consolidatedBillingEnabled override bypasses isCloud and isAuthenticatedConfigLoaded guards', () => {
vi.mocked(distributionTypes).isCloud = false
localStorage.setItem('ff:consolidated_billing_enabled', 'true')
Expand All @@ -234,10 +243,11 @@ describe('useFeatureFlags', () => {
expect(flags.consolidatedBillingEnabled).toBe(true)
})

it('consolidatedBillingEnabled is false off-cloud even without an override', () => {
it('billingControlEnabled is false off-cloud even without an override', () => {
vi.mocked(distributionTypes).isCloud = false

const { flags } = useFeatureFlags()
expect(flags.billingControlEnabled).toBe(false)
expect(flags.consolidatedBillingEnabled).toBe(false)
})
})
Expand All @@ -249,6 +259,7 @@ describe('useFeatureFlags', () => {
remoteConfig.value = {}
cachedTeamWorkspacesEnabled.value = undefined
cachedConsolidatedBillingEnabled.value = undefined
cachedBillingControlEnabled.value = undefined
localStorage.clear()
})

Expand All @@ -258,35 +269,41 @@ describe('useFeatureFlags', () => {
remoteConfig.value = {}
cachedTeamWorkspacesEnabled.value = undefined
cachedConsolidatedBillingEnabled.value = undefined
cachedBillingControlEnabled.value = undefined
localStorage.clear()
})

it('returns the cached session value during the auth window', () => {
cachedTeamWorkspacesEnabled.value = false
cachedConsolidatedBillingEnabled.value = true
cachedBillingControlEnabled.value = true

const { flags } = useFeatureFlags()
expect(flags.teamWorkspacesEnabled).toBe(false)
expect(flags.consolidatedBillingEnabled).toBe(true)
expect(flags.billingControlEnabled).toBe(true)
})

it('defaults to false during the auth window when nothing is cached', () => {
const { flags } = useFeatureFlags()
expect(flags.teamWorkspacesEnabled).toBe(false)
expect(flags.consolidatedBillingEnabled).toBe(false)
expect(flags.billingControlEnabled).toBe(false)
})

it('prefers authenticated remoteConfig over the server feature fallback', () => {
remoteConfigState.value = 'authenticated'
remoteConfig.value = {
team_workspaces_enabled: true,
consolidated_billing_enabled: true
consolidated_billing_enabled: true,
billing_control_enabled: false
}
vi.mocked(api.getServerFeature).mockReturnValue(false)

const { flags } = useFeatureFlags()
expect(flags.teamWorkspacesEnabled).toBe(true)
expect(flags.consolidatedBillingEnabled).toBe(true)
expect(flags.billingControlEnabled).toBe(false)
})

it('falls back to api.getServerFeature when authenticated config omits the flag', () => {
Expand All @@ -297,13 +314,15 @@ describe('useFeatureFlags', () => {
if (path === ServerFeatureFlag.TEAM_WORKSPACES_ENABLED) return true
if (path === ServerFeatureFlag.CONSOLIDATED_BILLING_ENABLED)
return true
if (path === ServerFeatureFlag.BILLING_CONTROL_ENABLED) return true
return defaultValue
}
)

const { flags } = useFeatureFlags()
expect(flags.teamWorkspacesEnabled).toBe(true)
expect(flags.consolidatedBillingEnabled).toBe(true)
expect(flags.billingControlEnabled).toBe(true)
})
})

Expand Down
9 changes: 9 additions & 0 deletions src/composables/useFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Ref } from 'vue'

import { isCloud, isNightly } from '@/platform/distribution/types'
import {
cachedBillingControlEnabled,
cachedConsolidatedBillingEnabled,
cachedTeamWorkspacesEnabled,
isAuthenticatedConfigLoaded,
Expand Down Expand Up @@ -33,6 +34,7 @@ export enum ServerFeatureFlag {
SHOW_SIGNIN_BUTTON = 'show_signin_button',
UNIFIED_CLOUD_AUTH = 'unified_cloud_auth',
CONSOLIDATED_BILLING_ENABLED = 'consolidated_billing_enabled',
BILLING_CONTROL_ENABLED = 'billing_control_enabled',
FREE_TIER_JOB_ALLOWANCE_ENABLED = 'free_tier_job_allowance_enabled',
SIGNUP_TURNSTILE = 'signup_turnstile'
}
Expand Down Expand Up @@ -203,6 +205,13 @@ export function useFeatureFlags() {
cachedConsolidatedBillingEnabled
)
},
get billingControlEnabled() {
return resolveAuthGatedFlag(
ServerFeatureFlag.BILLING_CONTROL_ENABLED,
remoteConfig.value.billing_control_enabled,
cachedBillingControlEnabled
)
},
get freeTierJobAllowanceEnabled() {
const config = remoteConfig.value as typeof remoteConfig.value & {
free_tier_job_allowance_enabled?: boolean
Expand Down
4 changes: 4 additions & 0 deletions src/platform/remoteConfig/refreshRemoteConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
cachedBillingControlEnabled,
cachedConsolidatedBillingEnabled,
cachedTeamWorkspacesEnabled,
remoteConfig,
Expand Down Expand Up @@ -63,6 +64,9 @@ export async function refreshRemoteConfig(
cachedConsolidatedBillingEnabled.value = Boolean(
config.consolidated_billing_enabled
)
cachedBillingControlEnabled.value = Boolean(
config.billing_control_enabled
)
}
return
}
Expand Down
5 changes: 5 additions & 0 deletions src/platform/remoteConfig/remoteConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@ export const cachedConsolidatedBillingEnabled = useStorage<boolean | undefined>(
'consolidated_billing_enabled' satisfies `${ServerFeatureFlag.CONSOLIDATED_BILLING_ENABLED}`,
undefined
)

export const cachedBillingControlEnabled = useStorage<boolean | undefined>(
'billing_control_enabled' satisfies `${ServerFeatureFlag.BILLING_CONTROL_ENABLED}`,
undefined
)
1 change: 1 addition & 0 deletions src/platform/remoteConfig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export type RemoteConfig = {
comfyhub_profile_gate_enabled?: boolean
unified_cloud_auth?: boolean
consolidated_billing_enabled?: boolean
billing_control_enabled?: boolean
sentry_dsn?: string
turnstile_sitekey?: string
// Raw, unvalidated wire value (a server typo like 'enfroce' is possible).
Expand Down
6 changes: 4 additions & 2 deletions src/storybook/mocks/useFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ export enum ServerFeatureFlag {
COMFYHUB_PROFILE_GATE_ENABLED = 'comfyhub_profile_gate_enabled',
SHOW_SIGNIN_BUTTON = 'show_signin_button',
UNIFIED_CLOUD_AUTH = 'unified_cloud_auth',
CONSOLIDATED_BILLING_ENABLED = 'consolidated_billing_enabled'
CONSOLIDATED_BILLING_ENABLED = 'consolidated_billing_enabled',
BILLING_CONTROL_ENABLED = 'billing_control_enabled'
}

export function useFeatureFlags() {
return {
flags: {
teamWorkspacesEnabled: true,
consolidatedBillingEnabled: true
consolidatedBillingEnabled: true,
billingControlEnabled: true
}
}
}
Loading