mirror of
https://github.com/supabase/supabase.git
synced 2026-06-13 01:39:53 +08:00
## Problem
The observability overview page fetched service health data by making
six separate calls to the generic \`logs.all\` endpoint with
hand-crafted SQL (via \`genChartQuery\`). This coupled the overview to
SQL internals and missed out on the purpose-built \`service-health\`
endpoint that accepts structured \`lql\` filters and a \`granularity\`
parameter.
## Fix
- Added \`/platform/projects/{ref}/analytics/endpoints/service-health\`
to \`platform.d.ts\`, including the \`ProjectServiceHealthResponse\`
schema and \`UsageApiController_getProjectServiceHealth\` operation.
- Created \`apps/studio/data/analytics/service-health-query.ts\` with a
\`getServiceHealth\` fetch function and \`useServiceHealthQuery\` hook
following the same pattern as other analytics query files.
- Added a \`serviceHealth\` key factory to
\`apps/studio/data/analytics/keys.ts\`.
- Rewrote \`useServiceHealthMetrics.ts\` to call the new endpoint per
service using \`lql\` selectors (\`s:postgres_logs\`, \`s:auth_logs\`,
etc.) and a \`granularity\` value derived from the selected interval
(\`1hr\` -> \`minute\`, \`1day\` -> \`hour\`, \`7day\` -> \`day\`). The
timeseries normalisation and chart data pipeline is unchanged.
- Updated the refresh handler in \`ObservabilityOverview.tsx\` to
invalidate the new query key prefix and removed the now-unused
\`postgrest-overview-metrics\` invalidation.
## How to test
- Navigate to a project's Observability > Overview page.
- Verify that the Service Health table loads data for all six services
(Database, Auth, Edge Functions, Realtime, Storage, Data API).
- Switch between the 1hr, 1day, and 7day interval selectors and confirm
the charts update.
- Click the Refresh button and confirm the charts reload.
- Click a bar in any chart and confirm navigation to the corresponding
logs page scoped to that time window.
- Confirm no regressions in the Database Infrastructure section (CPU,
RAM, disk, connections).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Centralized service‑health fetching for consistent cross‑service
metrics and improved charting.
* New analytics key and backend endpoint for project service‑health; API
schemas added.
* Backend support for an additional log‑drain type (hidden from the UI).
* **Bug Fixes**
* Improved refresh behavior for service‑health data.
* Clear "No requests in this period" fallback and correct charts when
totals are zero.
* **Tests**
* Added unit tests for service‑health data extraction and
transformation.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46100?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 -->
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { extractServiceRows } from './useServiceHealthMetrics'
|
|
import type { ServiceHealthResultRow } from '@/data/analytics/service-health-query'
|
|
|
|
const makeRow = (overrides?: Partial<ServiceHealthResultRow>): ServiceHealthResultRow => ({
|
|
timestamp: '2026-05-20T10:00:00',
|
|
postgres_logs: { ok: 10, warning: 1, error: 2, total: 13 },
|
|
auth_logs: { ok: 5, warning: 0, error: 1, total: 6 },
|
|
function_edge_logs: { ok: 20, warning: 2, error: 0, total: 22 },
|
|
storage_logs: { ok: 3, warning: 0, error: 0, total: 3 },
|
|
realtime_logs: { ok: 0, warning: 0, error: 0, total: 0 },
|
|
postgrest_logs: { ok: 7, warning: 1, error: 1, total: 9 },
|
|
edge_logs: { ok: 100, warning: 5, error: 3, total: 108 },
|
|
supavisor_logs: { ok: 0, warning: 0, error: 0, total: 0 },
|
|
function_logs: { ok: 15, warning: 0, error: 0, total: 15 },
|
|
etl_replication_logs: { ok: 0, warning: 0, error: 0, total: 0 },
|
|
...overrides,
|
|
})
|
|
|
|
describe('extractServiceRows', () => {
|
|
it('maps postgres_logs fields to ok_count/warning_count/error_count for db', () => {
|
|
const rows = [makeRow()]
|
|
const result = extractServiceRows(rows, 'db')
|
|
expect(result).toEqual([
|
|
{ timestamp: '2026-05-20T10:00:00', ok_count: 10, warning_count: 1, error_count: 2 },
|
|
])
|
|
})
|
|
|
|
it('maps the correct service field for each service key', () => {
|
|
const row = makeRow()
|
|
expect(extractServiceRows([row], 'auth')[0]).toMatchObject({
|
|
ok_count: 5,
|
|
error_count: 1,
|
|
})
|
|
expect(extractServiceRows([row], 'functions')[0]).toMatchObject({
|
|
ok_count: 20,
|
|
error_count: 0,
|
|
})
|
|
expect(extractServiceRows([row], 'storage')[0]).toMatchObject({ ok_count: 3 })
|
|
expect(extractServiceRows([row], 'postgrest')[0]).toMatchObject({
|
|
ok_count: 7,
|
|
warning_count: 1,
|
|
error_count: 1,
|
|
})
|
|
})
|
|
|
|
it('returns an empty array when given no rows', () => {
|
|
expect(extractServiceRows([], 'db')).toEqual([])
|
|
})
|
|
|
|
it('preserves the timestamp from each row', () => {
|
|
const rows = [
|
|
makeRow({ timestamp: '2026-05-20T09:00:00' }),
|
|
makeRow({ timestamp: '2026-05-20T10:00:00' }),
|
|
]
|
|
const result = extractServiceRows(rows, 'db')
|
|
expect(result.map((r) => r.timestamp)).toEqual(['2026-05-20T09:00:00', '2026-05-20T10:00:00'])
|
|
})
|
|
|
|
it('handles multiple rows and maps each independently', () => {
|
|
const rows = [
|
|
makeRow({ postgres_logs: { ok: 100, warning: 0, error: 0, total: 100 } }),
|
|
makeRow({ postgres_logs: { ok: 50, warning: 5, error: 10, total: 65 } }),
|
|
]
|
|
const result = extractServiceRows(rows, 'db')
|
|
expect(result[0]).toMatchObject({ ok_count: 100, warning_count: 0, error_count: 0 })
|
|
expect(result[1]).toMatchObject({ ok_count: 50, warning_count: 5, error_count: 10 })
|
|
})
|
|
|
|
it('defaults to 0 when the service field is missing from the row', () => {
|
|
const row = makeRow()
|
|
// Simulate a backend response that omits a service field
|
|
const partial = { ...row } as Partial<ServiceHealthResultRow> & { timestamp: string }
|
|
delete (partial as any).realtime_logs
|
|
const result = extractServiceRows([partial as ServiceHealthResultRow], 'realtime')
|
|
expect(result[0]).toEqual({
|
|
timestamp: row.timestamp,
|
|
ok_count: 0,
|
|
warning_count: 0,
|
|
error_count: 0,
|
|
})
|
|
})
|
|
})
|