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({