From dfb0603a36069abde8e410963669e75dc0689aec Mon Sep 17 00:00:00 2001
From: Joshen Lim
Date: Mon, 17 Aug 2026 16:11:07 +0800
Subject: [PATCH] Joshenlim/fe 4063 set up incremental default opt in for
database connections (#49132)
## Context
As per PR title - sets up incremental default opt in for the Database
Connections feature preview
Database Connections preview banner should still only show up if it's
never been dismissed before, but the CTA's changed to "Explore" rather
than "Enable" if the user's default opted in
Related discussion here:
https://github.com/orgs/supabase/discussions/48639
## Summary by CodeRabbit
* **New Features**
* Database Connections preview is now enabled by default.
* Added clearer handling for preview state and initialization.
* Banner actions open Database Connections when enabled, or the feature
preview when disabled.
* **Bug Fixes**
* Improved banner and menu visibility while preview settings initialize.
* Preserved banner dismissal behavior after a previous preference
change.
* Improved navigation consistency across Database Connections entry
points.
---
.../FeaturePreview/FeaturePreviewContext.tsx | 16 +++++--
.../App/FeaturePreview/useFeaturePreviews.ts | 4 +-
.../ObservabilityLayout.tsx | 13 +++---
.../ObservabilityMenu.utils.test.tsx | 2 +-
.../ObservabilityMenu.utils.tsx | 2 +-
.../layouts/SQLEditorLayout/SQLEditorMenu.tsx | 2 +-
.../Banners/BannerDatabaseConnections.tsx | 45 ++++++++++++++-----
.../[ref]/observability/connections.tsx | 2 +-
apps/studio/tests/lib/custom-render.tsx | 2 +-
9 files changed, 60 insertions(+), 28 deletions(-)
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
index 557c0e1327e..03691172a71 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
@@ -13,6 +13,7 @@ import {
} from 'react'
import { useFeaturePreviews } from './useFeaturePreviews'
+import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { IS_PLATFORM } from '@/lib/constants'
import { EMPTY_OBJ } from '@/lib/void'
@@ -152,9 +153,18 @@ export const useIsMarketplaceEnabled = () => {
}
export const useIsDatabaseConnectionsEnabled = () => {
- const { flags } = useFeaturePreviewContext()
- const isDatabaseConnectionsEnabled = useFlag('topForPostgres')
- return isDatabaseConnectionsEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS]
+ const { flags, isInitialized } = useFeaturePreviewContext()
+ const [localStorageFlag] = useLocalStorageQuery(
+ LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS,
+ null
+ )
+ const previouslyToggled = localStorageFlag !== null
+
+ return {
+ enabled: flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS],
+ isInitialized,
+ previouslyToggled,
+ }
}
export const useFeaturePreviewModal = () => {
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts
index 3587e6b175b..a30be45c941 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts
+++ b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts
@@ -133,8 +133,8 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
discussionsUrl: 'https://github.com/orgs/supabase/discussions/48639',
isNew: true,
isPlatformOnly: false,
- isDefaultOptIn: false,
- enabled: isDatabaseConnectionsEnabled,
+ isDefaultOptIn: isDatabaseConnectionsEnabled,
+ enabled: true,
getRoute: (ref?: string) => `/project/${ref}/observability/connections`,
bannerId: 'database-connections-banner',
},
diff --git a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityLayout.tsx b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityLayout.tsx
index c350faa2332..acdd6ac0b98 100644
--- a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityLayout.tsx
+++ b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityLayout.tsx
@@ -1,4 +1,4 @@
-import { LOCAL_STORAGE_KEYS, useFeatureFlags, useParams } from 'common'
+import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { usePathname } from 'next/navigation'
import { PropsWithChildren, useEffect, useRef } from 'react'
@@ -24,7 +24,6 @@ const ObservabilityLayoutContent = ({
}: PropsWithChildren) => {
const { ref } = useParams()
const pathname = usePathname()
- const { hasLoaded } = useFeatureFlags()
const { addBanner, dismissBanner } = useBannerStack()
const { isIndexAdvisorAvailable, isIndexAdvisorEnabled } = useIndexAdvisorStatus()
@@ -33,7 +32,7 @@ const ObservabilityLayoutContent = ({
false
)
- const isDatabaseConnectionsEnabled = useIsDatabaseConnectionsEnabled()
+ const { isInitialized, previouslyToggled } = useIsDatabaseConnectionsEnabled()
const [isDatabaseConnectionsBannerDismissed, , { isSuccess: isLocalStorageReady }] =
useLocalStorageQuery(LOCAL_STORAGE_KEYS.DATABASE_CONNECTIONS_BANNER_DISMISSED(ref ?? ''), false)
@@ -42,10 +41,10 @@ const ObservabilityLayoutContent = ({
useEffect(() => {
if (
- !hasLoaded ||
+ !isInitialized ||
!isLocalStorageReady ||
isDatabaseConnectionsBannerDismissed ||
- isDatabaseConnectionsEnabled
+ previouslyToggled
)
return
@@ -56,11 +55,11 @@ const ObservabilityLayoutContent = ({
content: ,
})
}, [
- hasLoaded,
addBanner,
dismissBanner,
+ isInitialized,
isDatabaseConnectionsBannerDismissed,
- isDatabaseConnectionsEnabled,
+ previouslyToggled,
isLocalStorageReady,
])
diff --git a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.test.tsx b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.test.tsx
index f63bdd9f116..a61aa7a3b8e 100644
--- a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.test.tsx
+++ b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.test.tsx
@@ -1,4 +1,3 @@
-import { renderHook } from '@testing-library/react'
import { useFlag, useParams } from 'common'
import { beforeEach, describe, expect, it, vi } from 'vitest'
@@ -9,6 +8,7 @@ import {
import { useSupamonitorStatus } from '@/components/interfaces/QueryPerformance/hooks/useSupamonitorStatus'
import { useContentQuery } from '@/data/content/content-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
+import { customRenderHook as renderHook } from '@/tests/lib/custom-render'
import { routerMock } from '@/tests/lib/route-mock'
const { REF, mockIsPlatform } = vi.hoisted(() => ({
diff --git a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.tsx b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.tsx
index 44ff90b6442..7301943bd0a 100644
--- a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.tsx
+++ b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.tsx
@@ -49,7 +49,7 @@ export const useGenerateObservabilityMenu = () => {
const { isSupamonitorEnabled } = useSupamonitorStatus()
const showOverview = useFlag('observabilityOverview')
- const isDatabaseConnectionsEnabled = useIsDatabaseConnectionsEnabled()
+ const { enabled: isDatabaseConnectionsEnabled } = useIsDatabaseConnectionsEnabled()
const storageSupported = useIsFeatureEnabled('project_storage:all')
const baseUrl = `/project/${ref}/observability`
diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx
index 3c3d66e8355..6938b6ce9f8 100644
--- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx
+++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx
@@ -41,7 +41,7 @@ export const SQLEditorMenu = () => {
const { data: project } = useSelectedProjectQuery()
const snapV2 = useSqlEditorV2StateSnapshot()
- const isDatabaseConnectionsEnabled = useIsDatabaseConnectionsEnabled()
+ const { enabled: isDatabaseConnectionsEnabled } = useIsDatabaseConnectionsEnabled()
const sqlEditorLogsSource = useFlag('sqlEditorLogsSource')
const otelLegacyLogs = useFlag('otelLegacyLogs')
const canCreateLogsSnippet = sqlEditorLogsSource && otelLegacyLogs
diff --git a/apps/studio/components/ui/BannerStack/Banners/BannerDatabaseConnections.tsx b/apps/studio/components/ui/BannerStack/Banners/BannerDatabaseConnections.tsx
index 4ca36cb90f9..c80f67615ea 100644
--- a/apps/studio/components/ui/BannerStack/Banners/BannerDatabaseConnections.tsx
+++ b/apps/studio/components/ui/BannerStack/Banners/BannerDatabaseConnections.tsx
@@ -1,10 +1,14 @@
import { LOCAL_STORAGE_KEYS } from 'common'
import { useParams } from 'common/hooks'
+import Link from 'next/link'
import { Badge, Button, WarningIcon } from 'ui'
import { BannerCard } from '../BannerCard'
import { useBannerStack } from '../BannerStackProvider'
-import { useFeaturePreviewModal } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
+import {
+ useFeaturePreviewModal,
+ useIsDatabaseConnectionsEnabled,
+} from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useTrack } from '@/lib/telemetry/track'
@@ -19,6 +23,8 @@ export const BannerDatabaseConnections = () => {
false
)
+ const { enabled: isEnabled } = useIsDatabaseConnectionsEnabled()
+
return (
{
@@ -72,16 +78,33 @@ export const BannerDatabaseConnections = () => {
-
+ {isEnabled ? (
+
+ ) : (
+
+ )}
diff --git a/apps/studio/pages/project/[ref]/observability/connections.tsx b/apps/studio/pages/project/[ref]/observability/connections.tsx
index a49cb5178cc..10cc40a4caf 100644
--- a/apps/studio/pages/project/[ref]/observability/connections.tsx
+++ b/apps/studio/pages/project/[ref]/observability/connections.tsx
@@ -33,7 +33,7 @@ export const DatabaseConnections: NextPageWithLayout = () => {
const { data: project } = useSelectedProjectQuery()
const { openSidebar } = useSidebarManagerSnapshot()
const aiSnap = useAiAssistantStateSnapshot()
- const isDatabaseConnectionsEnabled = useIsDatabaseConnectionsEnabled()
+ const { enabled: isDatabaseConnectionsEnabled } = useIsDatabaseConnectionsEnabled()
const { selectFeaturePreview } = useFeaturePreviewModal()
const [live, setLive] = useState(true)
diff --git a/apps/studio/tests/lib/custom-render.tsx b/apps/studio/tests/lib/custom-render.tsx
index f182c109a3e..d23ace9f00c 100644
--- a/apps/studio/tests/lib/custom-render.tsx
+++ b/apps/studio/tests/lib/custom-render.tsx
@@ -66,7 +66,7 @@ export const customRender = (component: React.ReactElement, renderOptions?: Cust
})
}
-export const customRenderHook = (hook: () => any, renderOptions?: CustomRenderOpts) => {
+export const customRenderHook = (hook: () => T, renderOptions?: CustomRenderOpts) => {
return renderHook(hook, {
wrapper: ({ children }) =>
CustomWrapper({