mirror of
https://github.com/supabase/supabase.git
synced 2026-09-09 03:19:36 +08:00
## Summary Resolves [GROWTH-865](https://linear.app/supabase/issue/GROWTH-865) on the Studio side. Companion backend PR: [supabase/platform#33086](https://github.com/supabase/platform/pull/33086). Resource-exhaustion advisories (CPU, Disk IO, Memory) currently give users a list of identical-looking messages with no project context. This PR makes the affected project visible in the advisor panel and hardens the "Check consumption" deep-link. ### Changes **Advisor list view** — `AdvisorPanel.utils.ts`, `AdvisorPanel.types.ts`, `AdvisorPanel.tsx`, `AdvisorPanelBody.tsx` - `AdvisorNotificationItem` now carries `project_ref`. - `getAdvisorItemSecondaryText` accepts an optional `projectNameByRef` map and returns the resolved project name (falling back to the ref if the lookup hasn't loaded). Falls through to the existing date string for notifications without a project. - `AdvisorPanel.tsx` builds the map from `useProjectsInfiniteQuery` and threads it through `AdvisorPanelBody`. **NotificationDetail** — `NotificationDetail.tsx` - `[ref]` / `[slug]` substitution in action URLs falls back to `data.project_ref` / `data.org_slug` before the `_` literal. This fixes the universal-link bug Tim reported where "Check consumption" sometimes resolved to `/project/_/...` while `useProjectDetailQuery` was still loading. The companion backend PR updates the notification copy so the title/message also name the project. Both PRs degrade gracefully if landed independently. ## Test plan - [x] `pnpm test:studio -- AdvisorPanel.utils.test.ts` — 6/6 pass (4 new tests cover the notification branch of `getAdvisorItemSecondaryText`) - [x] `pnpm typecheck` passes for apps/studio - [x] `pnpm exec eslint components/ui/AdvisorPanel/` passes - [ ] Local Studio smoke test: open Advisor → Messages, confirm project name shows under each notification and "Check consumption" deep-links to the correct project even if the project detail query is slow <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Advisor Panel notifications now display resolved project names when available for clearer context. * **Bug Fixes** * Notification action URLs now prefer stored project and organization refs/slugs with improved fallbacks, making action links more reliable. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46203?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 -->
115 lines
3.8 KiB
TypeScript
115 lines
3.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import type { AdvisorSignalItem } from './AdvisorPanel.types'
|
|
import {
|
|
createAdvisorLintItems,
|
|
createAdvisorNotificationItems,
|
|
getAdvisorItemSecondaryText,
|
|
sortAdvisorItems,
|
|
} from './AdvisorPanel.utils'
|
|
import type { Lint } from '@/data/lint/lint-query'
|
|
import type { Notification } from '@/data/notifications/notifications-v2-query'
|
|
|
|
const createLint = (overrides: Partial<Lint> = {}): Lint =>
|
|
({
|
|
cache_key: 'lint-1',
|
|
name: 'unknown_lint',
|
|
detail: 'Critical lint detail',
|
|
level: 'ERROR',
|
|
categories: ['SECURITY'],
|
|
metadata: {},
|
|
...overrides,
|
|
}) as Lint
|
|
|
|
const createNotification = (overrides: Partial<Notification> = {}): Notification =>
|
|
({
|
|
id: 'notification-1',
|
|
inserted_at: '2026-03-01T00:00:00.000Z',
|
|
priority: 'Info',
|
|
status: 'seen',
|
|
data: {
|
|
title: 'Notification title',
|
|
message: 'Notification body',
|
|
actions: [],
|
|
},
|
|
...overrides,
|
|
}) as Notification
|
|
|
|
const createBannedIPSignalItem = (ip: string): AdvisorSignalItem => ({
|
|
id: `signal:banned-ip:${ip}:v1`,
|
|
dismissalKey: `signal:banned-ip:${ip}:v1`,
|
|
source: 'signal',
|
|
type: 'banned-ip',
|
|
severity: 'warning',
|
|
tab: 'security',
|
|
title: 'Banned IP address',
|
|
summary: `The IP address \`${ip}\` is temporarily blocked.`,
|
|
docsUrl: 'https://supabase.com/docs/reference/cli/supabase-network-bans',
|
|
actions: [],
|
|
sourceData: { type: 'banned-ip', ip },
|
|
})
|
|
|
|
describe('AdvisorPanel.utils', () => {
|
|
it('orders mixed lint, signal and notification items by severity and recency', () => {
|
|
const lintItems = createAdvisorLintItems([
|
|
createLint({ cache_key: 'lint-critical', detail: 'Critical lint detail' }),
|
|
])
|
|
const signalItems = [createBannedIPSignalItem('203.0.113.10')]
|
|
const notificationItems = createAdvisorNotificationItems([
|
|
createNotification({
|
|
id: 'notification-info',
|
|
data: { title: 'Notification title', message: 'Body', actions: [] },
|
|
}),
|
|
])
|
|
|
|
const sorted = sortAdvisorItems([...notificationItems, ...signalItems, ...lintItems])
|
|
|
|
expect(sorted.map((item) => item.source)).toEqual(['lint', 'signal', 'notification'])
|
|
})
|
|
|
|
it('uses database surface-area metadata and the IP address for banned IP signals', () => {
|
|
const bannedIpSignal = createBannedIPSignalItem('203.0.113.10')
|
|
expect(getAdvisorItemSecondaryText(bannedIpSignal)).toBe('Database · 203.0.113.10')
|
|
})
|
|
|
|
describe('notification secondary text', () => {
|
|
const [notificationWithProject] = createAdvisorNotificationItems([
|
|
createNotification({
|
|
id: 'notification-with-project',
|
|
data: {
|
|
title: 'CPU usage is high on my-project.',
|
|
message: 'Project my-project has high CPU usage.',
|
|
project_ref: 'abcd1234',
|
|
actions: [],
|
|
},
|
|
}),
|
|
])
|
|
|
|
const [notificationWithoutProject] = createAdvisorNotificationItems([
|
|
createNotification({
|
|
id: 'notification-without-project',
|
|
data: { title: 'Generic notification', message: 'Body', actions: [] },
|
|
}),
|
|
])
|
|
|
|
it('returns the resolved project name when available in the map', () => {
|
|
const projectNameByRef = new Map([['abcd1234', 'my-production-db']])
|
|
expect(getAdvisorItemSecondaryText(notificationWithProject, projectNameByRef)).toBe(
|
|
'my-production-db'
|
|
)
|
|
})
|
|
|
|
it('falls back to the project ref when the name is missing from the map', () => {
|
|
expect(getAdvisorItemSecondaryText(notificationWithProject, new Map())).toBe('abcd1234')
|
|
})
|
|
|
|
it('falls back to the project ref when no map is provided', () => {
|
|
expect(getAdvisorItemSecondaryText(notificationWithProject)).toBe('abcd1234')
|
|
})
|
|
|
|
it('returns undefined for notifications without a project_ref', () => {
|
|
expect(getAdvisorItemSecondaryText(notificationWithoutProject)).toBeUndefined()
|
|
})
|
|
})
|
|
})
|