diff --git a/apps/studio/hooks/misc/__tests__/useEnabledIdentityProviders.test.ts b/apps/studio/hooks/misc/__tests__/useEnabledIdentityProviders.test.ts index 38ef4892467..8c42bd61283 100644 --- a/apps/studio/hooks/misc/__tests__/useEnabledIdentityProviders.test.ts +++ b/apps/studio/hooks/misc/__tests__/useEnabledIdentityProviders.test.ts @@ -24,15 +24,20 @@ vi.mock('common', async (importOriginal) => ({ useFlag: mockUseFlag, })) +function mockFeatures({ github = false, chatgpt = true }: { github?: boolean; chatgpt?: boolean }) { + mockIsFeatureEnabled.mockReturnValue({ + dashboardAuthSignInWithGithub: github, + dashboardAuthSignInWithChatgpt: chatgpt, + }) +} + describe('useEnabledIdentityProviders', () => { beforeEach(() => { mockUseFlag.mockReset() }) it('returns every provider when all flags are enabled', () => { - mockIsFeatureEnabled.mockReturnValue({ - dashboardAuthSignInWithGithub: true, - }) + mockFeatures({ github: true, chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([true]) mockUseFlag.mockReturnValue(true) @@ -42,9 +47,7 @@ describe('useEnabledIdentityProviders', () => { }) it('returns no providers when all flags are disabled', () => { - mockIsFeatureEnabled.mockReturnValue({ - dashboardAuthSignInWithGithub: false, - }) + mockFeatures({ github: false, chatgpt: false }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(false) @@ -54,7 +57,7 @@ describe('useEnabledIdentityProviders', () => { }) it('includes ChatGPT when localStorage is true and configcat is true', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([true]) mockUseFlag.mockReturnValue(true) @@ -64,7 +67,7 @@ describe('useEnabledIdentityProviders', () => { }) it('includes ChatGPT when localStorage is true and configcat is false', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([true]) mockUseFlag.mockReturnValue(false) @@ -74,7 +77,7 @@ describe('useEnabledIdentityProviders', () => { }) it('includes ChatGPT when localStorage is false and configcat is true', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(true) @@ -84,7 +87,7 @@ describe('useEnabledIdentityProviders', () => { }) it('excludes ChatGPT when localStorage is false and configcat is false', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(false) @@ -93,8 +96,38 @@ describe('useEnabledIdentityProviders', () => { expect(result.current).toEqual([]) }) + it('excludes ChatGPT when its feature flag is disabled but configcat is true', () => { + mockFeatures({ chatgpt: false }) + mockUseLocalStorageQuery.mockReturnValue([false]) + mockUseFlag.mockReturnValue(true) + + const { result } = renderHook(() => useEnabledIdentityProviders()) + + expect(result.current).toEqual([]) + }) + + it('excludes ChatGPT when its feature flag is disabled but localStorage is opted in', () => { + mockFeatures({ chatgpt: false }) + mockUseLocalStorageQuery.mockReturnValue([true]) + mockUseFlag.mockReturnValue(false) + + const { result } = renderHook(() => useEnabledIdentityProviders()) + + expect(result.current).toEqual([]) + }) + + it('excludes ChatGPT when its feature flag is disabled and both rollout gates are on', () => { + mockFeatures({ github: true, chatgpt: false }) + mockUseLocalStorageQuery.mockReturnValue([true]) + mockUseFlag.mockReturnValue(true) + + const { result } = renderHook(() => useEnabledIdentityProviders()) + + expect(result.current).toEqual([GITHUB_IDENTITY_PROVIDER]) + }) + it('includes GitHub when its feature flag is enabled', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: true }) + mockFeatures({ github: true, chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(false) @@ -104,7 +137,7 @@ describe('useEnabledIdentityProviders', () => { }) it('excludes GitHub when its feature flag is disabled', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ github: false, chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(false) diff --git a/apps/studio/hooks/misc/useEnabledIdentityProviders.ts b/apps/studio/hooks/misc/useEnabledIdentityProviders.ts index 939a7e0e9ff..681bedce42b 100644 --- a/apps/studio/hooks/misc/useEnabledIdentityProviders.ts +++ b/apps/studio/hooks/misc/useEnabledIdentityProviders.ts @@ -14,14 +14,19 @@ import { * To add a provider: declare its config in `lib/external-identity-providers.ts`, add a * `dashboard_auth:sign_in_with_*` flag, and gate it here. * - * ChatGPT is a deliberate exception: it's rolled out via the `ShowSignInWithChatGptButton` - * ConfigCat flag OR'd with a manual, localStorage-only opt-in switch - * (`LOCAL_STORAGE_KEYS.SIGN_IN_CHATGPT_ENABLED`, flippable via the `?siwc-enabled=1` query param — - * see `useSiwcQueryParamOptIn`), instead of the static `dashboard_auth:sign_in_with_*` pattern. + * ChatGPT carries an extra rollout gate on top of its `dashboard_auth:sign_in_with_chatgpt` flag: + * the feature flag must be enabled AND either the `ShowSignInWithChatGptButton` ConfigCat flag or a + * manual, localStorage-only opt-in switch (`LOCAL_STORAGE_KEYS.SIGN_IN_CHATGPT_ENABLED`, flippable + * via the `?siwc-enabled=1` query param — see `useSiwcQueryParamOptIn`) must be on. The feature flag + * is the static kill switch; the OR'd pair is the progressive rollout mechanism. */ export function useEnabledIdentityProviders(): ExternalIdentityProviderConfig[] { - const { dashboardAuthSignInWithGithub: githubEnabled } = useIsFeatureEnabled([ + const { + dashboardAuthSignInWithGithub: githubEnabled, + dashboardAuthSignInWithChatgpt: chatgptFeatureEnabled, + } = useIsFeatureEnabled([ 'dashboard_auth:sign_in_with_github', + 'dashboard_auth:sign_in_with_chatgpt', ]) const [chatgptLocalStorageEnabled] = useLocalStorageQuery( @@ -30,12 +35,15 @@ export function useEnabledIdentityProviders(): ExternalIdentityProviderConfig[] ) const chatGptConfigCatFlagEnabled = useFlag('ShowSignInWithChatGptButton') + const isChatGptEnabled = + chatgptFeatureEnabled && (chatgptLocalStorageEnabled || chatGptConfigCatFlagEnabled) + return useMemo( () => [ githubEnabled && GITHUB_IDENTITY_PROVIDER, - (chatgptLocalStorageEnabled || chatGptConfigCatFlagEnabled) && CHATGPT_IDENTITY_PROVIDER, + isChatGptEnabled && CHATGPT_IDENTITY_PROVIDER, ].filter((p): p is ExternalIdentityProviderConfig => Boolean(p)), - [githubEnabled, chatgptLocalStorageEnabled, chatGptConfigCatFlagEnabled] + [githubEnabled, isChatGptEnabled] ) } diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json index 7e4fcb40075..bdf512b1e13 100644 --- a/packages/common/enabled-features/enabled-features.json +++ b/packages/common/enabled-features/enabled-features.json @@ -34,6 +34,7 @@ "dashboard_auth:sign_up": true, "dashboard_auth:sign_in_with_github": true, + "dashboard_auth:sign_in_with_chatgpt": true, "dashboard_auth:sign_in_with_sso": true, "dashboard_auth:sign_in_with_email": true, "dashboard_auth:show_testimonial": true, diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json index 0a63c3e2b63..4ca4fda54b8 100644 --- a/packages/common/enabled-features/enabled-features.schema.json +++ b/packages/common/enabled-features/enabled-features.schema.json @@ -115,6 +115,10 @@ "type": "boolean", "description": "Enable the sign in with github provider" }, + "dashboard_auth:sign_in_with_chatgpt": { + "type": "boolean", + "description": "Enable the sign in with ChatGPT provider" + }, "dashboard_auth:sign_in_with_sso": { "type": "boolean", "description": "Enable the sign in with sso provider" @@ -468,6 +472,7 @@ "billing:all", "dashboard_auth:sign_up", "dashboard_auth:sign_in_with_github", + "dashboard_auth:sign_in_with_chatgpt", "dashboard_auth:sign_in_with_sso", "dashboard_auth:sign_in_with_email", "dashboard_auth:show_tos",