Files
supabase/apps/studio/data/lint/health-lints-query.test.ts
Saxon FletcherandCursor ad33b16f8c feat(studio): show health advisors on the project home (#49661)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Feature

## Summary

- Add a `useProjectHealthLintsQuery` that runs the live health checks
(database down, unreachable, connection limit, service error rate,
infrastructure alerts)
- Surface those results on the project home advisor row alongside
security and performance errors
- Register health lint metadata (titles, docs links, entity icon) so
homepage cards can render them

Bottom of the stack. The advisor sidebar still uses tabs; health items
show under All until #49662.

## To test

1. Open any project home in Studio.
2. Find the Advisor row (the cards under “Advisor found N issues”).
3. If the project has a real health problem, you should see a **HEALTH**
card (for example “Database process is down” or “Database connection
limit reached”), not only SECURITY / PERFORMANCE.
4. If the project is healthy, you should **not** see a HEALTH card.
Existing security and performance cards should still appear as before.
5. Click a HEALTH card (or any advisor card). Advisor Center should open
on that item.
6. In Advisor Center on this PR, health items only show under the
**All** tab — Health is not its own tab yet.

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

- **New Features**
- Added a Health category to Advisor, with a dedicated tab and activity
icon.
- Added health checks for database availability, connection limits,
service errors, and infrastructure alerts.
- Health issues now appear alongside security and performance
recommendations with relevant troubleshooting links.

- **Bug Fixes**
- Health-related advisor findings are now correctly categorized and
displayed.

- **Tests**
- Added coverage for health checks, categorization, filtering, and
project health query behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 11:05:14 +02:00

82 lines
2.8 KiB
TypeScript

import { HttpResponse } from 'msw'
import { describe, expect, it } from 'vitest'
import { getProjectHealthLints, HEALTH_LINT_NAMES } from './health-lints-query'
import type { components } from '@/data/api'
import { addAPIMock } from '@/tests/lib/msw'
type AdvisorsResponse = components['schemas']['V2ProjectAdvisorsResponse_Output']
type AdvisorsRequestBody = components['schemas']['V2RunProjectAdvisorsBody']
type AdvisorLint = AdvisorsResponse['data']['attributes']['lints'][number]
const createLint = (overrides: Partial<AdvisorLint> = {}): AdvisorLint =>
({
cache_key: 'instance_db_down',
name: 'instance_db_down',
title: 'Database process is down',
level: 'ERROR',
facing: 'EXTERNAL',
categories: ['HEALTH'],
description: 'The instance is running but Postgres is not accepting connections locally.',
detail: 'The on-host check of Postgres is failing.',
remediation: 'https://supabase.com/docs/guides/platform/troubleshooting',
metadata: { type: 'health', entity: 'Database' },
...overrides,
}) as AdvisorLint
const mockAdvisorsRun = (lints: AdvisorLint[], onRequest?: (body: AdvisorsRequestBody) => void) => {
addAPIMock({
method: 'post',
path: '/v2/projects/:ref/advisors/run',
response: async ({ request }) => {
onRequest?.((await request.json()) as AdvisorsRequestBody)
return HttpResponse.json<AdvisorsResponse>({
data: { type: 'project_advisors', attributes: { lints } },
})
},
})
}
describe('getProjectHealthLints', () => {
it('asks the endpoint for the health lints Studio surfaces', async () => {
let requestBody: AdvisorsRequestBody | undefined
mockAdvisorsRun([], (body) => {
requestBody = body
})
await getProjectHealthLints({ projectRef: 'default' })
expect(requestBody?.data.attributes.lints.map((lint) => lint.name)).toEqual([
...HEALTH_LINT_NAMES,
])
})
it('returns the health issues the project actually has', async () => {
mockAdvisorsRun([createLint()])
const result = await getProjectHealthLints({ projectRef: 'default' })
expect(result.map((lint) => lint.name)).toEqual(['instance_db_down'])
})
it('drops results that report on the check rather than on the project', async () => {
mockAdvisorsRun([
createLint(),
createLint({
cache_key: 'advisor_check_unavailable:metrics_unavailable',
name: 'advisor_check_unavailable',
level: 'INFO',
}),
createLint({ cache_key: 'project_not_active', name: 'project_not_active' }),
])
const result = await getProjectHealthLints({ projectRef: 'default' })
expect(result.map((lint) => lint.name)).toEqual(['instance_db_down'])
})
it('throws when no project ref is given', async () => {
await expect(getProjectHealthLints({})).rejects.toThrow('Project ref is required')
})
})