feat(studio): add dashboard_auth:sign_in_with_chatgpt enabled feature (#48677)

Adds a `dashboard_auth:sign_in_with_chatgpt` enabled-features flag so
deployments can disable the sign in with ChatGPT button via
`disabled_features`, the same way `dashboard_auth:sign_in_with_github`
works. Previously the button was only gated by the ConfigCat rollout
flag / localStorage opt-in, so white-labeled deployments with custom
auth providers had no way to turn it off.

**Added:**
- `dashboard_auth:sign_in_with_chatgpt` (default `true`) in
`enabled-features.json` + schema
- Tests covering the feature-disabled state

**Changed:**
- `useEnabledIdentityProviders` now gates ChatGPT as `featureEnabled &&
(localStorageOptIn || configCatFlag)` — the feature flag is the static
kill switch, the existing OR'd pair remains the rollout mechanism

## To test

- Sign-in and sign-up pages behave exactly as before by default (flag
defaults to `true`, ConfigCat/localStorage rollout gate unchanged)
- With `dashboard_auth:sign_in_with_chatgpt` in a profile's
`disabled_features`, the ChatGPT button no longer renders even with
`?siwc-enabled=1` or the ConfigCat flag on
- GitHub button gating unaffected

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
  - Added a feature flag to control ChatGPT sign-in availability.
- ChatGPT sign-in is now available only when the feature is enabled and
an applicable rollout or opt-in condition is met.

- **Tests**
- Expanded coverage for ChatGPT and GitHub sign-in provider availability
under different feature-flag and rollout conditions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
Alaister YoungandAlaister Young authored and GitHub committed 2026-08-04 13:58:43 +07:00
1 parent 1123a74813
commit 270925b680
4 files changed
+66 -19

No files matched your search

@@ -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)
@@ -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]
)
}
@@ -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,
@@ -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",