Files
supabase/apps/studio/hooks/analytics/useComputeMetrics.ts
Jordi Enric dfd3eec8e9 feat(studio): compute metrics on project diagram Primary Database card (#45274)
## Problem

The Primary Database card in the project homepage diagram showed region
and instance size, but no live health data. Users had no quick way to
spot a high-disk or high-CPU situation without navigating to the
database report.

## Fix

Added a clickable metrics row at the bottom of the Primary Database card
showing CPU, Disk, and RAM as percentages, plus active/max connections
when available. Each metric is color-coded (warning at 80%, destructive
at 90%). Clicking the row navigates to the database observability
report.

The metrics are powered by a new \`useComputeMetrics\` hook that wraps
the existing \`useInfraMonitoringAttributesQuery\` and
\`useMaxConnectionsQuery\`, reusing the parse utilities already used by
the database infrastructure section. The \`metricColor\` threshold logic
is extracted into a separate util with unit tests.

## How to test

- Open the project homepage for a running project
- The Primary Database card should show a new bottom row: "CPU X% · Disk
X% · RAM X% · Y/Z conns"
- Values above 80% should appear in amber, above 90% in red
- Click the metrics row and confirm it navigates to
\`/project/<ref>/observability/database\`
- While metrics are loading, a spinner should appear in the row
- If the infra monitoring API is unavailable, the row should show
"Metrics unavailable" instead of zeroes

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

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Infrastructure configuration page now displays real-time compute
metrics (CPU, disk, memory usage) with color-coded usage indicators
based on thresholds.
  * Connection information is displayed when available.
  * Includes loading states and error handling for metric retrieval.

* **Tests**
  * Added test coverage for metric color-coding logic.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 14:15:11 +02:00

75 lines
2.1 KiB
TypeScript

import dayjs from 'dayjs'
import { useMemo } from 'react'
import {
parseConnectionsData,
parseInfrastructureMetrics,
} from '@/components/interfaces/Observability/DatabaseInfrastructureSection.utils'
import { useInfraMonitoringAttributesQuery } from '@/data/analytics/infra-monitoring-query'
import { useMaxConnectionsQuery } from '@/data/database/max-connections-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
export type ComputeMetrics = {
cpu: number
disk: number
memory: number
connections: { current: number; max: number }
isLoading: boolean
isError: boolean
}
export function useComputeMetrics({ projectRef }: { projectRef?: string }): ComputeMetrics {
const { data: project } = useSelectedProjectQuery()
// Intentionally anchored to mount time so the query key stays stable across re-renders.
// React Query's staleTime handles background refresh without shifting the window.
// eslint-disable-next-line react-hooks/exhaustive-deps
const { startDate, endDate } = useMemo(() => {
const now = dayjs()
return {
startDate: now.subtract(1, 'hour').toISOString(),
endDate: now.toISOString(),
}
}, [])
const {
data: infraData,
isLoading: infraLoading,
isError,
} = useInfraMonitoringAttributesQuery({
projectRef,
attributes: [
'avg_cpu_usage',
'ram_usage',
'disk_fs_used_system',
'disk_fs_used_wal',
'pg_database_size',
'disk_fs_size',
'pg_stat_database_num_backends',
],
startDate,
endDate,
interval: '1h',
})
const { data: maxConnectionsData, isLoading: connectionsLoading } = useMaxConnectionsQuery({
projectRef,
connectionString: project?.connectionString,
})
const metrics = useMemo(() => parseInfrastructureMetrics(infraData), [infraData])
const connections = useMemo(
() => parseConnectionsData(infraData, maxConnectionsData),
[infraData, maxConnectionsData]
)
return {
cpu: metrics?.cpu.current ?? 0,
disk: metrics?.disk.current ?? 0,
memory: metrics?.ram.current ?? 0,
connections,
isLoading: infraLoading || connectionsLoading,
isError,
}
}