Joshenlim/fe 4304 bring database connections out of feature preview (#50107)

## Context

As per PR title - we're bringing Database Connections out of feature
preview and it'll live on the dashboard by default 🙂
Also deprecating the existing Ongoing queries panel which Database
Connections now supercedes.

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

* **New Features**
* Database Connections is now available without feature-preview
activation.
* The SQL editor’s “View running queries” option now links directly to
Database Connections.

* **Bug Fixes**
* Query cancellation and session termination now refresh database
activity data.

* **Removed**
* Removed the in-editor ongoing queries panel and its termination
controls.
* Removed the Database Connections promotional banner, preview
messaging, settings, and related telemetry.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-09-08 17:26:24 +08:00
1 parent e5f2b29625
commit db0e6b761b
22 files changed
+47 -602

No files matched your search

@@ -1,41 +0,0 @@
import { useParams } from 'common'
import Image from 'next/image'
import { InlineLink } from '@/components/ui/InlineLink'
import { BASE_PATH } from '@/lib/constants'
export const DatabaseConnectionsPreview = () => {
const { ref = '_' } = useParams()
return (
<div className="space-y-4">
<p className="text-sm text-foreground-light">
See every session on your database in real time, spot the ones that are slow, idle in a
transaction, or blocked, and terminate the one holding things up - all from{' '}
<InlineLink href={`/project/${ref}/observability/connections`}>
Database Connections
</InlineLink>
.
</p>
<Image
alt="database-connections-preview"
src={`${BASE_PATH}/img/previews/database-connections-preview.png`}
width={1200}
height={626}
className="rounded-sm border mb-4"
/>
<div className="space-y-2">
<p className="text-sm">Enabling this preview will:</p>
<ul className="list-disc pl-6 text-sm text-foreground-light space-y-1">
<li>Show live connection, query, and locking activity for your database</li>
<li>Flag queries that are idle in transaction, long-running, or blocked</li>
<li>Trace a blocked query back to the session actually holding the lock</li>
<li>Let you terminate a stuck session directly from the table</li>
<li>Summarize current activity on demand using the AI Assistant</li>
</ul>
</div>
</div>
)
}
@@ -13,7 +13,6 @@ 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'
@@ -150,21 +149,6 @@ export const useIsMarketplaceEnabled = () => {
return isMarketplaceEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_MARKETPLACE]
}
export const useIsDatabaseConnectionsEnabled = () => {
const { flags, isInitialized } = useFeaturePreviewContext()
const [localStorageFlag] = useLocalStorageQuery<boolean | null>(
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 useIsExplorerEnabled = () => {
const { flags } = useFeaturePreviewContext()
const isExplorerEnabled = useFlag('explorer')
@@ -31,7 +31,6 @@ import {
import { AdvisorRulesPreview } from './AdvisorRulesPreview'
import { CLSPreview } from './CLSPreview'
import { DatabaseConnectionsPreview } from './DatabaseConnectionsPreview'
import { ExplorerPreview } from './ExplorerPreview'
import { useFeaturePreviewContext, useFeaturePreviewModal } from './FeaturePreviewContext'
import { IntegrationsLayoutPreview } from './IntegrationsLayoutPreview'
@@ -58,7 +57,6 @@ const FEATURE_PREVIEW_KEY_TO_CONTENT: {
[LOCAL_STORAGE_KEYS.UI_PREVIEW_JIT_DB_ACCESS]: <JitDbAccessPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE]: <SqlEditorManualSavePreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_MARKETPLACE]: <IntegrationsLayoutPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS]: <DatabaseConnectionsPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_EXPLORER]: <ExplorerPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_STORAGE_VERSIONING]: <StorageVersioningPreview />,
}
@@ -34,7 +34,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
const isPlatformWebhooksEnabled = useFlag('platformWebhooks')
const jitDbAccessEnabled = useFlag('jitDbAccess')
const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
const isDatabaseConnectionsEnabled = useFlag('topForPostgres')
const isExplorerEnabled = useFlag('explorer')
const isStorageVersioningEnabled = useFlag('storageVersioningPrivateAlpha')
@@ -139,18 +138,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
// before the rollout reaches them.
isForced: isSqlEditorManualSaveForced,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS,
name: 'Diagnose blocked queries',
category: 'observability',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/48639',
isNew: true,
isPlatformOnly: false,
isDefaultOptIn: isDatabaseConnectionsEnabled,
enabled: true,
getRoute: (ref?: string) => `/project/${ref}/observability/connections`,
bannerId: 'database-connections-banner',
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_STORAGE_VERSIONING,
name: 'Storage versioning',
@@ -169,7 +156,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
isPlatformWebhooksEnabled,
jitDbAccessEnabled,
isMarketplaceEnabled,
isDatabaseConnectionsEnabled,
isExplorerEnabled,
isStorageVersioningEnabled,
])
@@ -1,195 +0,0 @@
import { useParams } from 'common'
import dayjs from 'dayjs'
import { RefreshCw, StopCircle } from 'lucide-react'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'
import {
Button,
cn,
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetSection,
SheetTitle,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { AlertError } from '@/components/ui/AlertError'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { useOngoingQueriesQuery } from '@/data/sql/ongoing-queries-query'
import { useSessionTerminateMutation } from '@/data/sql/terminate-session-mutation'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { useUrlState } from '@/hooks/ui/useUrlState'
import { IS_PLATFORM } from '@/lib/constants'
import { useAppStateSnapshot } from '@/state/app-state'
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
import type { ResponseError } from '@/types'
export const OngoingQueriesPanel = () => {
const [_, setParams] = useUrlState({ replace: true })
const { viewOngoingQueries } = useParams()
const { data: project } = useSelectedProjectQuery()
const state = useDatabaseSelectorStateSnapshot()
const appState = useAppStateSnapshot()
const [selectedId, setSelectedId] = useState<number>()
const { data: databases } = useReadReplicasQuery({ projectRef: project?.ref })
const database = (databases ?? []).find((db) => db.identifier === state.selectedDatabaseId)
const {
data,
error,
isError,
isPending: isLoadingOngoingQueries,
isFetching: isFetchingOngoingQueries,
refetch,
} = useOngoingQueriesQuery(
{
projectRef: project?.ref,
connectionString: database?.connectionString,
},
{
enabled: !IS_PLATFORM || (IS_PLATFORM && database?.connectionString !== undefined),
staleTime: 5000,
}
)
const queries = data ?? []
useEffect(() => {
if (viewOngoingQueries) {
appState.setOnGoingQueriesPanelOpen(true)
setParams({ viewOngoingQueries: undefined })
}
}, [viewOngoingQueries])
const { mutate: terminateSession, isPending } = useSessionTerminateMutation({
onSuccess: () => {
toast.success(`Successfully terminated session (ID: ${selectedId})`)
setSelectedId(undefined)
},
})
const closePanel = () => {
setParams({ viewOngoingQueries: undefined })
appState.setOnGoingQueriesPanelOpen(false)
}
return (
<>
<Sheet open={appState.ongoingQueriesPanelOpen} onOpenChange={() => closePanel()}>
<SheetContent size="lg">
<SheetHeader>
<SheetTitle className="flex items-center gap-x-2">
Running queries on{' '}
{database?.identifier === project?.ref ? 'primary database' : 'read replica'}
<Button
variant="default"
className="px-1.5"
loading={isLoadingOngoingQueries || isFetchingOngoingQueries}
icon={<RefreshCw />}
onClick={() => refetch()}
/>
</SheetTitle>
<SheetDescription>
There {queries.length === 1 ? 'is' : 'are'}{' '}
<span className="text-foreground-light">{queries.length}</span> quer
{queries.length === 1 ? 'y' : 'ies'} currently running{' '}
{database?.identifier !== project?.ref ? `on replica ${database?.identifier}` : ''}
</SheetDescription>
</SheetHeader>
<div className="max-h-full h-full divide-y overflow-y-auto">
{isError && (
<div className="flex items-center justify-center h-full px-16">
<AlertError
subject="Failed to retrieve ongoing queries"
error={error as ResponseError}
/>
</div>
)}
{queries.length === 0 && (
<div className="flex flex-col gap-y-2 items-center justify-center h-full text-foreground-light text-sm">
<span>
No queries are currently running on the{' '}
{database?.identifier !== project?.ref
? `read replica ${database?.identifier}`
: (databases ?? []).length > 1
? 'primary database'
: 'database'}
</span>
<Button
variant="default"
loading={isLoadingOngoingQueries || isFetchingOngoingQueries}
icon={<RefreshCw />}
onClick={() => refetch()}
>
Refresh
</Button>
</div>
)}
{queries.map((query) => (
<SheetSection key={query.pid} className="flex justify-between gap-x-4">
<div className="flex flex-col gap-y-2 w-full">
<CodeBlock
hideLineNumbers
value={query.query}
language="sql"
className={cn(
'max-w-none max-h-52 w-full',
'bg-transparent! py-3! px-3.5! prose dark:prose-dark',
'[&>code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap'
)}
/>
<div className="flex items-center gap-x-2">
<p className="text-foreground-light text-xs">PID: {query.pid}</p>
<p className="text-foreground-light text-xs">•</p>
<p className="text-foreground-light text-xs">
Started since: {dayjs(query.query_start).format('DD MMM YYYY HH:mm (ZZ)')}
</p>
</div>
</div>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="warning"
className="px-1.5"
icon={<StopCircle />}
onClick={() => setSelectedId(query.pid)}
/>
</TooltipTrigger>
<TooltipContent side="bottom">Abort query</TooltipContent>
</Tooltip>
</SheetSection>
))}
</div>
</SheetContent>
</Sheet>
<ConfirmationModal
loading={isPending}
variant="warning"
title={`Confirm to terminate this session? (ID: ${selectedId})`}
visible={selectedId !== undefined}
onCancel={() => setSelectedId(undefined)}
onConfirm={() => {
if (selectedId !== undefined)
terminateSession({
pid: selectedId,
projectRef: project?.ref,
connectionString: database?.connectionString,
})
}}
>
<p className="text-sm">
Terminating this session will close its connection and roll back any open transaction. The
application will need to reconnect.
</p>
</ConfirmationModal>
</>
)
}
@@ -4,9 +4,7 @@ import { PropsWithChildren, useEffect, useRef } from 'react'
import { ProjectLayout } from '../ProjectLayout'
import { ObservabilityMenu } from './ObservabilityMenu'
import { useIsDatabaseConnectionsEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useIndexAdvisorStatus } from '@/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus'
import { BannerDatabaseConnections } from '@/components/ui/BannerStack/Banners/BannerDatabaseConnections'
import { BannerIndexAdvisor } from '@/components/ui/BannerStack/Banners/BannerIndexAdvisor'
import { useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider'
import { UnknownInterface } from '@/components/ui/UnknownInterface'
@@ -32,36 +30,8 @@ const ObservabilityLayoutContent = ({
false
)
const { isInitialized, previouslyToggled } = useIsDatabaseConnectionsEnabled()
const [isDatabaseConnectionsBannerDismissed, , { isSuccess: isLocalStorageReady }] =
useLocalStorageQuery(LOCAL_STORAGE_KEYS.DATABASE_CONNECTIONS_BANNER_DISMISSED(ref ?? ''), false)
const prevPathnameRef = useRef(pathname)
useEffect(() => {
if (
!isInitialized ||
!isLocalStorageReady ||
isDatabaseConnectionsBannerDismissed ||
previouslyToggled
)
return
addBanner({
id: 'database-connections-banner',
priority: 2,
isDismissed: false,
content: <BannerDatabaseConnections />,
})
}, [
addBanner,
isInitialized,
isDatabaseConnectionsBannerDismissed,
previouslyToggled,
isLocalStorageReady,
])
useEffect(() => {
const isQueryPerformancePage = pathname?.includes('/query-performance')
@@ -2,7 +2,6 @@ import { useFlag, useParams } from 'common'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { useIsDatabaseConnectionsEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useSupamonitorStatus } from '@/components/interfaces/QueryPerformance/hooks/useSupamonitorStatus'
import { useContentQuery, type Content, type ContentBase } from '@/data/content/content-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
@@ -49,7 +48,6 @@ export const useGenerateObservabilityMenu = () => {
const { isSupamonitorEnabled } = useSupamonitorStatus()
const showOverview = useFlag('observabilityOverview')
const { enabled: isDatabaseConnectionsEnabled } = useIsDatabaseConnectionsEnabled()
const storageSupported = useIsFeatureEnabled('project_storage:all')
const baseUrl = `/project/${ref}/observability`
@@ -92,16 +90,12 @@ export const useGenerateObservabilityMenu = () => {
},
]
: []),
...(isDatabaseConnectionsEnabled
? [
{
name: 'Database Connections',
key: 'connections',
url: `${baseUrl}/connections`,
shortcutId: SHORTCUT_IDS.NAV_OBSERVABILITY_API_GATEWAY,
},
]
: []),
{
name: 'Database Connections',
key: 'connections',
url: `${baseUrl}/connections`,
shortcutId: SHORTCUT_IDS.NAV_OBSERVABILITY_CONNECTIONS,
},
]
const productItems: ObservabilityMenuItem[] = [
@@ -1,6 +1,5 @@
import { ReactNode } from 'react'
import { OngoingQueriesPanel } from '@/components/interfaces/SQLEditor/OngoingQueriesPanel'
import { SqlEditorManualSaveNoticeDialog } from '@/components/interfaces/SQLEditor/SqlEditorManualSaveNoticeDialog'
import { withAuth } from '@/hooks/misc/withAuth'
@@ -12,7 +11,6 @@ const SQLEditorLayout = ({ children }: SQLEditorLayoutProps) => {
return (
<>
{children}
<OngoingQueriesPanel />
<SqlEditorManualSaveNoticeDialog />
</>
)
@@ -25,13 +25,12 @@ import {
import { SearchList } from './SQLEditorNavV2/SearchList'
import { SQLEditorNav } from './SQLEditorNavV2/SQLEditorNav'
import { useIsDatabaseConnectionsEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { type SqlSnippetSource } from '@/components/interfaces/SQLEditor/querySource'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useLocalStorage } from '@/hooks/misc/useLocalStorage'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { getErrorMessage } from '@/lib/get-error-message'
import { useProfile } from '@/lib/profile'
import { getAppStateSnapshot } from '@/state/app-state'
import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor/sql-editor-state'
export const SQLEditorMenu = () => {
@@ -41,7 +40,6 @@ export const SQLEditorMenu = () => {
const { data: project } = useSelectedProjectQuery()
const snapV2 = useSqlEditorV2StateSnapshot()
const { enabled: isDatabaseConnectionsEnabled } = useIsDatabaseConnectionsEnabled()
const sqlEditorLogsSource = useFlag('sqlEditorLogsSource')
const otelLegacyLogs = useFlag('otelLegacyLogs')
const canCreateLogsSnippet = sqlEditorLogsSource && otelLegacyLogs
@@ -53,7 +51,6 @@ export const SQLEditorMenu = () => {
'inserted_at'
)
const appState = getAppStateSnapshot()
const debouncedSearch = useDebounce(search, 500)
const { can: canCreateSQLSnippet } = useAsyncCheckPermissions(
@@ -84,8 +81,8 @@ export const SQLEditorMenu = () => {
router.push(`/project/${ref}/sql/new?skip=true${suffix}`)
setSearch('')
setShowSearch(false)
} catch (error: any) {
toast.error(`Failed to create new query: ${error.message}`)
} catch (error) {
toast.error(`Failed to create new query: ${getErrorMessage(error)}`)
}
}
@@ -131,7 +128,7 @@ export const SQLEditorMenu = () => {
) : (
<InnerSideBarFilterSortDropdown
value={sort}
onValueChange={(value: any) => setSort(value)}
onValueChange={(value) => setSort(value as 'name' | 'inserted_at')}
>
<InnerSideBarFilterSortDropdownItem key="name" value="name">
Alphabetical
@@ -181,15 +178,9 @@ export const SQLEditorMenu = () => {
</div>
<div className="p-4 border-t sticky bottom-0 bg-studio">
{isDatabaseConnectionsEnabled ? (
<Button asChild block variant="default">
<Link href={`/project/${ref}/observability/connections`}>View running queries</Link>
</Button>
) : (
<Button block variant="default" onClick={() => appState.setOnGoingQueriesPanelOpen(true)}>
View running queries
</Button>
)}
<Button asChild block variant="default">
<Link href={`/project/${ref}/observability/connections`}>View running queries</Link>
</Button>
</div>
</div>
)
@@ -1,7 +1,6 @@
import { createContext, useCallback, useContext, useState } from 'react'
export const BANNER_ID = {
DATABASE_CONNECTIONS: 'database-connections-banner',
INDEX_ADVISOR: 'index-advisor-banner',
TABLE_EDITOR_QUEUE_OPERATIONS: 'table-editor-queue-operations-banner',
RLS_EVENT_TRIGGER: 'rls-event-trigger-banner',
@@ -1,112 +0,0 @@
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,
useIsDatabaseConnectionsEnabled,
} from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useTrack } from '@/lib/telemetry/track'
export const BannerDatabaseConnections = () => {
const track = useTrack()
const { ref } = useParams()
const { dismissBanner } = useBannerStack()
const { selectFeaturePreview } = useFeaturePreviewModal()
const [, setIsDismissed] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.DATABASE_CONNECTIONS_BANNER_DISMISSED(ref ?? ''),
false
)
const { enabled: isEnabled } = useIsDatabaseConnectionsEnabled()
return (
<BannerCard
onDismiss={() => {
setIsDismissed(true)
dismissBanner('database-connections-banner')
track('database_connections_banner_dismiss_button_clicked')
}}
>
<div className="flex flex-col gap-y-4">
<div className="flex flex-col gap-y-2 items-start w-full">
<Badge variant="success" className="-ml-0.5 uppercase inline-flex items-center">
Preview
</Badge>
<div className="-mx-6 w-[calc(100%+3rem)] bg-linear-to-t from-background to-transparent px-6 py-2 border-b">
<div className="flex items-center gap-x-2">
<div>
<WarningIcon />
</div>
<p className="text-xs font-mono uppercase tracking-tigher text-warning">Blocking</p>
<p className="truncate text-xs font-mono tracking-tighter">
update orders set status = $1 where id = $2;
</p>
</div>
<div className="pl-[7.5px]">
<div className="relative flex items-center gap-x-2 pl-4 pt-2 pb-1">
<div className="absolute left-0 top-0 h-full border-l border-stronger" />
<div className="absolute left-0 top-1/2 w-3 border-b border-stronger" />
<p className="text-xs font-mono uppercase tracking-tigher text-foreground-light">
Waiting
</p>
<p className="truncate text-xs font-mono tracking-tighter text-foreground-lighter">
select * from orders where id = $1 for update
</p>
</div>
<div className="relative flex items-center gap-x-2 pl-4 py-1">
<div className="absolute left-0 top-0 h-1/2 w-3 border-l border-b border-stronger rounded-bl-md" />
<p className="text-xs font-mono uppercase tracking-tigher text-foreground-light">
Waiting
</p>
<p className="truncate text-xs font-mono tracking-tighter text-foreground-lighter">
update orders set status = $1 where id = $2;
</p>
</div>
</div>
</div>
</div>
<div className="flex flex-col gap-y-1 mb-2">
<p className="text-sm font-medium">Diagnose blocked queries</p>
<p className="text-xs text-foreground-lighter text-balance">
See what's blocking your database and terminate the session causing it.
</p>
</div>
<div className="flex gap-2">
{isEnabled ? (
<Button
asChild
variant="default"
size="tiny"
onClick={() => {
setIsDismissed(true)
dismissBanner('database-connections-banner')
track('database_connections_banner_cta_button_clicked', { isEnabled })
}}
>
<Link href={`/project/${ref}/observability/connections`}>
Explore Database Connections
</Link>
</Button>
) : (
<Button
variant="default"
size="tiny"
onClick={() => {
selectFeaturePreview(LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS)
track('database_connections_banner_cta_button_clicked', { isEnabled: false })
}}
>
Enable Database Connections
</Button>
)}
</div>
</div>
</BannerCard>
)
}
@@ -2,7 +2,7 @@ import { getCancelQuerySQL } from '@supabase/pg-meta'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { sqlKeys } from './keys'
import { databaseKeys } from '@/data/database/keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import { ResponseError, type UseCustomMutationOptions } from '@/types'
@@ -50,7 +50,7 @@ export const useQueryCancelMutation = ({
mutationFn: (vars) => cancelQuery(vars),
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries({ queryKey: sqlKeys.ongoingQueries(projectRef) })
await queryClient.invalidateQueries({ queryKey: databaseKeys.databaseActivity(projectRef) })
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
-2
View File
@@ -3,6 +3,4 @@ import type { QueryKey } from '@tanstack/react-query'
export const sqlKeys = {
query: (projectRef: string | undefined, queryKey: QueryKey) =>
['projects', projectRef, 'query', ...queryKey] as const,
ongoingQueries: (projectRef: string | undefined) =>
['projects', projectRef, 'ongoing-queries'] as const,
}
@@ -1,48 +0,0 @@
import { getOngoingQueriesSql } from '@supabase/pg-meta'
import { useQuery } from '@tanstack/react-query'
import { sqlKeys } from './keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import { ResponseError, UseCustomQueryOptions } from '@/types'
type OngoingQuery = {
pid: number
query: string
query_start: string
}
export type OngoingQueriesVariables = {
projectRef?: string
connectionString?: string | null
}
export async function getOngoingQueries(
{ projectRef, connectionString }: OngoingQueriesVariables,
signal?: AbortSignal
) {
const sql = getOngoingQueriesSql()
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['ongoing-queries'] },
signal
)
return (result ?? []).filter((x: OngoingQuery) => !x.query.startsWith(sql)) as OngoingQuery[]
}
export type OngoingQueriesData = Awaited<ReturnType<typeof getOngoingQueries>>
export type OngoingQueriesError = ResponseError
export const useOngoingQueriesQuery = <TData = OngoingQueriesData>(
{ projectRef, connectionString }: OngoingQueriesVariables,
{
enabled = true,
...options
}: UseCustomQueryOptions<OngoingQueriesData, OngoingQueriesError, TData> = {}
) =>
useQuery<OngoingQueriesData, OngoingQueriesError, TData>({
queryKey: sqlKeys.ongoingQueries(projectRef),
queryFn: ({ signal }) => getOngoingQueries({ projectRef, connectionString }, signal),
enabled: enabled && typeof projectRef !== 'undefined',
...options,
})
@@ -2,7 +2,7 @@ import { getTerminateSessionSQL } from '@supabase/pg-meta'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { sqlKeys } from './keys'
import { databaseKeys } from '@/data/database/keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import { ResponseError, type UseCustomMutationOptions } from '@/types'
@@ -50,7 +50,7 @@ export const useSessionTerminateMutation = ({
mutationFn: (vars) => terminateSession(vars),
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries({ queryKey: sqlKeys.ongoingQueries(projectRef) })
await queryClient.invalidateQueries({ queryKey: databaseKeys.databaseActivity(projectRef) })
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
@@ -1,14 +1,8 @@
import { LOCAL_STORAGE_KEYS } from 'common'
import dayjs from 'dayjs'
import { CirclePause, CirclePlay, TextSearch } from 'lucide-react'
import { CirclePause, CirclePlay } from 'lucide-react'
import { useEffect, useState } from 'react'
import { Badge, Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational'
import {
useFeaturePreviewModal,
useIsDatabaseConnectionsEnabled,
} from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { Activity } from '@/components/interfaces/Observability/DatabaseConnections/Activity'
import { buildDatabaseConnectionsSummaryPrompt } from '@/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.ai'
import { Overview } from '@/components/interfaces/Observability/DatabaseConnections/Overview'
@@ -17,7 +11,6 @@ import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { AiAssistantDropdown } from '@/components/ui/AiAssistantDropdown'
import { FeaturePreviewBadge } from '@/components/ui/FeaturePreviewBadge'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { useDatabaseActivityQuery } from '@/data/database/activity-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
@@ -33,14 +26,11 @@ export const DatabaseConnections: NextPageWithLayout = () => {
const { data: project } = useSelectedProjectQuery()
const { openSidebar } = useSidebarManagerSnapshot()
const aiSnap = useAiAssistantStateSnapshot()
const { enabled: isDatabaseConnectionsEnabled } = useIsDatabaseConnectionsEnabled()
const { selectFeaturePreview } = useFeaturePreviewModal()
const [live, setLive] = useState(true)
const [now, setNow] = useState(() => dayjs.utc())
useShortcut(SHORTCUT_IDS.DATA_TABLE_TOGGLE_LIVE, handleToggleLive, {
enabled: isDatabaseConnectionsEnabled,
registerInCommandMenu: false,
})
@@ -54,7 +44,6 @@ export const DatabaseConnections: NextPageWithLayout = () => {
connectionString: project?.connectionString,
},
{
enabled: isDatabaseConnectionsEnabled,
refetchOnWindowFocus: live,
refetchInterval: live ? 3000 : false,
}
@@ -92,18 +81,17 @@ export const DatabaseConnections: NextPageWithLayout = () => {
// [Joshen] Just to trigger a UI re-render for the duration to be "live"
useEffect(() => {
if (!live || !isDatabaseConnectionsEnabled) return
if (!live) return
const interval = setInterval(() => setNow(dayjs.utc()), 1000)
return () => clearInterval(interval)
}, [live, isDatabaseConnectionsEnabled])
}, [live])
return (
<ReportPadding className="gap-y-12">
<div className="flex items-center justify-between gap-x-4">
<div className="flex items-center gap-x-2">
<h1 className="w-max">Database Connections</h1>
<FeaturePreviewBadge featureKey={LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS} />
{isDatabaseConnectionsEnabled && live && (
{live && (
<Tooltip>
<TooltipTrigger className="flex items-center">
<Badge variant="success">
@@ -116,56 +104,35 @@ export const DatabaseConnections: NextPageWithLayout = () => {
)}
</div>
{isDatabaseConnectionsEnabled && (
<div className="flex items-center gap-x-2">
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.DATA_TABLE_TOGGLE_LIVE}
label={live ? 'Pause live mode' : 'Refresh data every 3 seconds'}
side="bottom"
<div className="flex items-center gap-x-2">
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.DATA_TABLE_TOGGLE_LIVE}
label={live ? 'Pause live mode' : 'Refresh data every 3 seconds'}
side="bottom"
>
<Button
variant={live ? 'default' : 'primary'}
onClick={handleToggleLive}
icon={live ? <CirclePause /> : <CirclePlay />}
>
<Button
variant={live ? 'default' : 'primary'}
onClick={handleToggleLive}
icon={live ? <CirclePause /> : <CirclePlay />}
>
{live ? 'Pause' : 'Live'}
</Button>
</ShortcutTooltip>
<AiAssistantDropdown
label="Summarize activity"
buildPrompt={buildPrompt}
onOpenAssistant={handleSummarizeActivity}
disabled={isLoadingActivity}
loading={isLoadingActivity}
telemetrySource="database_connections"
size="tiny"
variant="default"
/>
</div>
)}
{live ? 'Pause' : 'Live'}
</Button>
</ShortcutTooltip>
<AiAssistantDropdown
label="Summarize activity"
buildPrompt={buildPrompt}
onOpenAssistant={handleSummarizeActivity}
disabled={isLoadingActivity}
loading={isLoadingActivity}
telemetrySource="database_connections"
size="tiny"
variant="default"
/>
</div>
</div>
{!isDatabaseConnectionsEnabled && (
<EmptyStatePresentational
icon={<TextSearch />}
title="Diagnose stuck and blocked queries"
description="See every session on your database in real time, spot the ones that are slow, idle in a transaction, or blocked, and terminate the one holding things up"
>
<Button
variant="default"
onClick={() => selectFeaturePreview(LOCAL_STORAGE_KEYS.UI_PREVIEW_DATABASE_CONNECTIONS)}
>
Enable feature preview
</Button>
</EmptyStatePresentational>
)}
{isDatabaseConnectionsEnabled && (
<>
<Overview live={live} />
<Activity live={live} />
</>
)}
<Overview live={live} />
<Activity live={live} />
</ReportPadding>
)
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

-6
View File
@@ -9,7 +9,6 @@ const getInitialState = () => {
showProjectApiDocs: false,
showCreateBranchModal: false,
showAiSettingsModal: false,
ongoingQueriesPanelOpen: false,
mobileMenuOpen: false,
showSidebar: true,
showEditorPanel: false,
@@ -61,11 +60,6 @@ export const appState = proxy({
appState.showSidebar = value
},
showOngoingQueriesPanelOpen: false,
setOnGoingQueriesPanelOpen: (value: boolean) => {
appState.ongoingQueriesPanelOpen = value
},
mobileMenuOpen: false,
setMobileMenuOpen: (value: boolean) => {
appState.mobileMenuOpen = value
@@ -27,7 +27,6 @@ export const LOCAL_STORAGE_KEYS = {
UI_PREVIEW_JIT_DB_ACCESS: 'supabase-ui-jit-db-access',
UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE: 'supabase-ui-sql-editor-manual-save',
UI_PREVIEW_MARKETPLACE: 'supabase-ui-marketplace',
UI_PREVIEW_DATABASE_CONNECTIONS: 'preview-database-connections',
UI_PREVIEW_EXPLORER: 'preview-explorer',
UI_PREVIEW_STORAGE_VERSIONING: 'supabase-ui-storage-versioning',
@@ -114,8 +113,6 @@ export const LOCAL_STORAGE_KEYS = {
PROJECT_SECURITY_DISMISSED_AT: (ref: string) => `project-security-dismissed-at-${ref}`,
DATABASE_CONNECTIONS_BANNER_DISMISSED: (ref: string) =>
`database-connections-banner-dismissed-${ref}`,
ORGANIZATION_MARKETPLACE_BANNER_DISMISSED: (orgSlug: string, managedBy: string) =>
`organization-marketplace-banner-dismissed-${orgSlug}-${managedBy}`,
PROJECT_INTEGRATION_BANNER_DISMISSED: (ref: string, integrationSource: string) =>
-29
View File
@@ -1525,33 +1525,6 @@ export interface DatabaseConnectionsLiveModeClickedEvent {
groups: TelemetryGroups
}
/**
* User clicked the dismiss button on the Database Connections banner in studio project pages.
*
* @group Events
* @source studio
* @page /dashboard/project/{ref}/observability/connections
*/
export interface DatabaseConnectionsBannerDismissButtonClickedEvent {
action: 'database_connections_banner_dismiss_button_clicked'
groups: TelemetryGroups
}
/**
* User clicked the CTA button on the Database Connections banner in studio project pages.
*
* @group Events
* @source studio
* @page /dashboard/project/{ref}/observability/connections
*/
export interface DatabaseConnectionsBannerCtaButtonClickedEvent {
action: 'database_connections_banner_cta_button_clicked'
properties: {
isEnabled: boolean
}
groups: TelemetryGroups
}
/**
* The Explorer feature preview banner was rendered in studio project pages, fired at most once
* per page load. Acts as the denominator for the banner's dismiss and CTA rates; dedupe per
@@ -3985,8 +3958,6 @@ export type TelemetryEvent =
| DatabaseConnectionsOverviewMetricCardClickedEvent
| DatabaseConnectionsFilterUpdatedEvent
| DatabaseConnectionsBlockerViewClickedEvent
| DatabaseConnectionsBannerDismissButtonClickedEvent
| DatabaseConnectionsBannerCtaButtonClickedEvent
| ExplorerBannerExposedEvent
| ExplorerBannerDismissButtonClickedEvent
| ExplorerBannerCtaButtonClickedEvent
@@ -1,3 +1,2 @@
export * from './abort-query'
export * from './keywords'
export * from './ongoing-queries'
@@ -1,5 +0,0 @@
import { safeSql, type SafeSqlFragment } from '../../../pg-format'
export const getOngoingQueriesSql = (): SafeSqlFragment => {
return safeSql`select pid, query, query_start from pg_stat_activity where state = 'active' and datname = 'postgres';`
}