Files
supabase/apps/studio/components/interfaces/QueryPerformance/WithStatements/WithStatements.tsx
Ali Waseem 42c0cb7171 feat(studio): keyboard shortcuts for observability pages (#46277)
## Summary

Wires Linear-style keyboard shortcuts across all observability pages —
refresh, time picker, filters, and sub-page navigation — with hover
tooltips surfacing each binding.

| Page | Shortcut | Action |
| --- | --- | --- |
| Overview | `Shift+R` | Refresh report |
| Overview | `Shift+P` | Open time picker |
| Query Performance | `Shift+R` | Refresh report |
| Query Performance | `R` then `C` | Reset report
(`pg_stat_statements_reset`) |
| Query Performance | `Shift+F` | Search queries |
| Query Performance | `F` then `C` | Reset filters |
| API Gateway | `Shift+R` | Refresh report |
| API Gateway | `Shift+P` | Open time picker |
| API Gateway | `Shift+F` | Add filter |
| API Gateway | `F` then `C` | Reset filters |
| API Gateway | `Shift+S` | Filter requests by service |
| Database | `Shift+R` | Refresh report |
| Database | `Shift+P` | Open time picker |
| Auth | `Shift+R` | Refresh report |
| Auth | `Shift+P` | Open time picker |
| Data API | `Shift+R` | Refresh report |
| Data API | `Shift+P` | Open time picker |
| Storage | `Shift+R` | Refresh report |
| Storage | `Shift+P` | Open time picker |
| Realtime | `Shift+R` | Refresh report |
| Realtime | `Shift+P` | Open time picker |
| Edge Functions | `Shift+R` | Refresh report |
| Edge Functions | `Shift+P` | Open time picker |
| All observability pages | `U` then `O/Q/G/D/P/A/F/S/L` | Jump to
sub-page |

## Test plan

- [ ] Each shortcut fires on its page; tooltip on hover shows the
binding
- [ ] Picker shortcut toggles the popover open/closed without leaving
the tooltip visible
- [ ] Reset-report on Query Performance opens the confirm modal
- [ ] `Escape` on the query search clears the value, then blurs
- [ ] No "Shift+R already registered" / Tooltip controlled-uncontrolled
warnings in the console

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

* **New Features**
* Keyboard shortcuts to navigate Observability pages and perform common
actions (refresh, toggle date picker/interval, focus search, reset
filters, create reports).
* Shortcut hints shown on relevant buttons and controls; date pickers
and interval dropdowns can be controlled via shortcuts.
* Global shortcut groups/registries added for Observability navigation
and page actions.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46277?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-25 07:37:16 -06:00

341 lines
12 KiB
TypeScript

import { safeSql } from '@supabase/pg-meta/src/pg-format'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { RefreshCw, RotateCcw, X } from 'lucide-react'
import { parseAsString, useQueryStates } from 'nuqs'
import { useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { Button, cn, LoadingLine } from 'ui'
import { Admonition } from 'ui-patterns'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { Markdown } from '../../Markdown'
import { captureQueryPerformanceError } from '../QueryPerformance.utils'
import { QueryPerformanceFilterBar } from '../QueryPerformanceFilterBar'
import { QueryPerformanceGrid } from '../QueryPerformanceGrid'
import { QueryPerformanceMetrics } from '../QueryPerformanceMetrics'
import { QueryPerformanceInfiniteHook } from '../useQueryPerformanceQuery'
import { transformStatementDataToRows } from './WithStatements.utils'
import { PresetHookResult } from '@/components/interfaces/Reports/Reports.utils'
import { DownloadResultsButton } from '@/components/ui/DownloadResultsButton'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { formatDatabaseID } from '@/data/read-replicas/replicas.utils'
import { executeSql } from '@/data/sql/execute-sql-query'
import { useInfiniteScroll } from '@/hooks/misc/useInfiniteScroll'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { DOCS_URL, IS_PLATFORM } from '@/lib/constants'
import { getErrorMessage } from '@/lib/get-error-message'
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface WithStatementsProps {
queryHitRate: PresetHookResult
queryPerformanceQuery: QueryPerformanceInfiniteHook
queryMetrics: PresetHookResult
}
export const WithStatements = ({
queryHitRate,
queryPerformanceQuery,
queryMetrics,
}: WithStatementsProps) => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const state = useDatabaseSelectorStateSnapshot()
const {
data,
isLoading,
isRefetching,
isFetchingNextPage,
hasNextPage,
error: queryError,
fetchNextPage,
refetch: runQuery,
} = queryPerformanceQuery
const isPrimaryDatabase = state.selectedDatabaseId === ref
const formattedDatabaseId = formatDatabaseID(state.selectedDatabaseId ?? '')
const hitRateError = 'error' in queryHitRate ? queryHitRate.error : null
const metricsError = 'error' in queryMetrics ? queryMetrics.error : null
const mainQueryError = queryError || null
const [showResetgPgStatStatements, setShowResetgPgStatStatements] = useState(false)
const [showBottomSection, setShowBottomSection] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.QUERY_PERF_SHOW_BOTTOM_SECTION,
true
)
const [{ indexAdvisor }] = useQueryStates({
indexAdvisor: parseAsString.withDefault('false'),
})
const handleRefresh = () => {
runQuery()
queryHitRate.runQuery()
queryMetrics.runQuery()
}
useShortcut(SHORTCUT_IDS.OBSERVABILITY_REFRESH, handleRefresh, {
enabled: !isRefetching,
})
useShortcut(SHORTCUT_IDS.OBSERVABILITY_RESET_REPORT, () => {
setShowResetgPgStatStatements(true)
})
const processedData = useMemo(() => {
return transformStatementDataToRows(data || [], indexAdvisor === 'true')
}, [data, indexAdvisor])
const { data: databases } = useReadReplicasQuery({ projectRef: ref })
const handleScroll = useInfiniteScroll({
isLoading,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
})
useEffect(() => {
state.setSelectedDatabaseId(ref)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref])
useEffect(() => {
if (mainQueryError) {
const errorMessage = getErrorMessage(mainQueryError)
const isNotInstalled =
typeof errorMessage === 'string' &&
errorMessage.includes('pg_stat_statements') &&
errorMessage.includes('does not exist')
if (!isNotInstalled) {
captureQueryPerformanceError(mainQueryError, {
projectRef: ref,
databaseIdentifier: state.selectedDatabaseId,
queryPreset: 'unified',
queryType: 'mainQuery',
postgresVersion: project?.dbVersion,
databaseType: isPrimaryDatabase ? 'primary' : 'read-replica',
sql: queryPerformanceQuery.resolvedSql,
errorMessage: errorMessage || undefined,
})
}
}
}, [
mainQueryError,
ref,
state.selectedDatabaseId,
project?.dbVersion,
isPrimaryDatabase,
queryPerformanceQuery.resolvedSql,
])
useEffect(() => {
if (hitRateError) {
const errorMessage = getErrorMessage(hitRateError)
captureQueryPerformanceError(hitRateError, {
projectRef: ref,
databaseIdentifier: state.selectedDatabaseId,
queryPreset: 'queryHitRate',
queryType: 'hitRate',
postgresVersion: project?.dbVersion,
databaseType: isPrimaryDatabase ? 'primary' : 'read-replica',
errorMessage: errorMessage || undefined,
})
}
}, [hitRateError, ref, state.selectedDatabaseId, project?.dbVersion, isPrimaryDatabase])
useEffect(() => {
if (metricsError) {
const errorMessage = getErrorMessage(metricsError)
captureQueryPerformanceError(metricsError, {
projectRef: ref,
databaseIdentifier: state.selectedDatabaseId,
queryPreset: 'queryMetrics',
queryType: 'metrics',
postgresVersion: project?.dbVersion,
databaseType: isPrimaryDatabase ? 'primary' : 'read-replica',
errorMessage: errorMessage || undefined,
})
}
}, [metricsError, ref, state.selectedDatabaseId, project?.dbVersion, isPrimaryDatabase])
const hasError = mainQueryError || hitRateError || metricsError
const errorMessage = mainQueryError
? getErrorMessage(mainQueryError) || 'Failed to load query performance data'
: hitRateError
? getErrorMessage(hitRateError) || 'Failed to load cache hit rate data'
: metricsError
? getErrorMessage(metricsError) || 'Failed to load query metrics'
: null
const isPgStatStatementsNotInstalled =
typeof errorMessage === 'string' &&
errorMessage.includes('pg_stat_statements') &&
errorMessage.includes('does not exist')
return (
<>
{hasError && (
<div className="px-6 pt-4">
{isPgStatStatementsNotInstalled ? (
<Admonition
type="warning"
title="pg_stat_statements extension is not enabled"
description="Query Performance requires the pg_stat_statements extension. Enable it in Database → Extensions."
/>
) : (
<Admonition
type="destructive"
title="Error loading query performance data"
description={
errorMessage ||
'An error occurred while loading query performance data. Please try refreshing the page.'
}
/>
)}
</div>
)}
<QueryPerformanceMetrics />
<QueryPerformanceFilterBar
showRolesFilter
showSourceFilter
actions={
<>
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
label="Refresh"
side="top"
>
<Button
type="default"
size="tiny"
icon={<RefreshCw />}
onClick={handleRefresh}
className="w-[26px]"
/>
</ShortcutTooltip>
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_RESET_REPORT}
label="Reset report"
side="top"
>
<Button
type="default"
size="tiny"
icon={<RotateCcw />}
onClick={() => setShowResetgPgStatStatements(true)}
className="w-[26px]"
/>
</ShortcutTooltip>
<DownloadResultsButton
results={processedData}
fileName={`Supabase Query Performance Statements (${ref})`}
align="end"
/>
</>
}
/>
<LoadingLine loading={isLoading || isRefetching || isFetchingNextPage} />
<QueryPerformanceGrid
aggregatedData={processedData}
isLoading={isLoading}
error={
mainQueryError
? getErrorMessage(mainQueryError) || 'Failed to load query performance data'
: null
}
onRetry={handleRefresh}
onScroll={handleScroll}
/>
<div
className={cn('px-6 py-6 flex gap-x-4 border-t relative', {
hidden: showBottomSection === false,
})}
>
<Button
className="absolute top-1.5 right-3 px-1.5"
type="text"
size="tiny"
onClick={() => setShowBottomSection(false)}
>
<X size="14" />
</Button>
<div className="w-[33%] flex flex-col gap-y-1 text-sm">
<p>Reset report</p>
<p className="text-xs text-foreground-light">
Consider resetting the analysis after optimizing any queries
</p>
<Button
type="default"
className="mt-3! w-min"
onClick={() => setShowResetgPgStatStatements(true)}
>
Reset report
</Button>
</div>
<div className="w-[33%] flex flex-col gap-y-1 text-sm">
<p>How is this report generated?</p>
<Markdown
className="text-xs"
content={`This report uses the pg_stat_statements table, and pg_stat_statements extension. [Learn more here](${DOCS_URL}/guides/platform/performance#examining-query-performance).`}
/>
</div>
<div className="w-[33%] flex flex-col gap-y-1 text-sm">
<p>Inspect your database for potential issues</p>
<Markdown
className="text-xs"
content={`The Supabase CLI comes with a range of tools to help inspect your Postgres instances for
potential issues. [Learn more here](${DOCS_URL}/guides/database/inspect).`}
/>
</div>
</div>
<ConfirmationModal
visible={showResetgPgStatStatements}
size="medium"
variant="destructive"
title="Reset query performance analysis"
confirmLabel="Reset report"
confirmLabelLoading="Resetting report"
onCancel={() => setShowResetgPgStatStatements(false)}
onConfirm={async () => {
const connectionString = databases?.find(
(db) => db.identifier === state.selectedDatabaseId
)?.connectionString
if (IS_PLATFORM && !connectionString) {
return toast.error('Unable to run query: Connection string is missing')
}
try {
await executeSql({
projectRef: project?.ref,
connectionString,
sql: safeSql`SELECT pg_stat_statements_reset();`,
})
handleRefresh()
setShowResetgPgStatStatements(false)
} catch (error: any) {
toast.error(`Failed to reset analysis: ${error.message}`)
}
}}
>
<p className="text-foreground-light text-sm">
This will reset the pg_stat_statements table in the extensions schema on your{' '}
<span className="text-foreground">
{isPrimaryDatabase ? 'primary database' : `read replica (ID: ${formattedDatabaseId})`}
</span>
, which is used to calculate query performance. This data will repopulate immediately
after.
</p>
</ConfirmationModal>
</>
)
}