mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## Summary * Adds the client query layer for `/api/status-page` (`statusPageQueryOptions` in `data/platform/status-page-query.ts`) and a shared normalization module (`lib/status-page/status-page.utils.ts`) that maps the endpoint response into region- and project-creation-aware `StatusItem`s — later PRs in this stack (assistant tool, project-creation admonition, global banner) build on this same module. * Wires the support form's status pill and incident admonition to the new data behind the `incidentIoStatusPage` ConfigCat flag. `useSupportStatus` branches between the new endpoint and the existing `useIncidentStatusQuery`/`processIncidentData` path, both mapped to the same `SupportStatus` shape. * `IncidentAdmonition` becomes purely presentational; the status link now points at the `pageUrl` returned by the endpoint instead of a hardcoded URL. Part of [FE-4057](https://linear.app/supabase/issue/FE-4057/frontend-bannerbot-reconfigured) — see Linear for full design context. ## Test plan - [X] `pnpm --filter studio run typecheck` - [X] `pnpm --filter studio run lint:ratchet` - [X] `pnpm knip --workspace apps/studio` - [X] `pnpm test:prettier` - [X] `pnpm --filter studio exec vitest run` (touched files) — 70 tests passing, including a flag-on case for `SupportFormPage` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Support pages now display current incident and maintenance information, including relevant status descriptions and links to the status page. * Status details can reflect items affecting a user’s region or project-creation services. Upcoming maintenance is excluded from active alerts. * **Bug Fixes** * Status labels and alerts now account for loading, errors, incidents, and maintenance consistently across support pages. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
176 lines
5.6 KiB
TypeScript
176 lines
5.6 KiB
TypeScript
import { describe, expect, test } from 'vitest'
|
|
|
|
import {
|
|
getStatusPillLabel,
|
|
getSupportAdmonitionCopy,
|
|
getSupportStatusLabel,
|
|
prioritizeActiveItems,
|
|
type SupportStatus,
|
|
} from './SupportStatus.utils'
|
|
import type { StatusItem } from '@/lib/status-page/status-page.utils'
|
|
|
|
function incident(overrides: Partial<Extract<StatusItem, { kind: 'incident' }>> = {}): StatusItem {
|
|
return {
|
|
kind: 'incident',
|
|
id: 'inc-1',
|
|
name: 'Elevated errors',
|
|
url: 'https://status.supabase.com/incidents/inc-1',
|
|
lastUpdateAt: '2026-01-01T00:00:00Z',
|
|
lastUpdateMessage: null,
|
|
components: [],
|
|
scope: { type: 'global' },
|
|
projectCreationScope: null,
|
|
isProjectCreationOnly: false,
|
|
showBanner: true,
|
|
status: 'investigating',
|
|
impact: 'full_outage',
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function maintenance(
|
|
overrides: Partial<Extract<StatusItem, { kind: 'maintenance' }>> = {}
|
|
): StatusItem {
|
|
return {
|
|
kind: 'maintenance',
|
|
id: 'maint-1',
|
|
name: 'Database maintenance',
|
|
url: 'https://status.supabase.com/incidents/maint-1',
|
|
lastUpdateAt: '2026-01-01T00:00:00Z',
|
|
lastUpdateMessage: null,
|
|
components: [],
|
|
scope: { type: 'global' },
|
|
projectCreationScope: null,
|
|
isProjectCreationOnly: false,
|
|
showBanner: true,
|
|
startedAt: '2026-01-01T00:00:00Z',
|
|
scheduledEndAt: null,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function upcomingMaintenance(
|
|
overrides: Partial<Extract<StatusItem, { kind: 'upcoming_maintenance' }>> = {}
|
|
): StatusItem {
|
|
return {
|
|
kind: 'upcoming_maintenance',
|
|
id: 'upcoming-1',
|
|
name: 'Upcoming maintenance',
|
|
url: 'https://status.supabase.com/incidents/upcoming-1',
|
|
lastUpdateAt: '2026-01-01T00:00:00Z',
|
|
lastUpdateMessage: null,
|
|
components: [],
|
|
scope: { type: 'global' },
|
|
projectCreationScope: null,
|
|
isProjectCreationOnly: false,
|
|
showBanner: true,
|
|
startsAt: '2026-02-01T00:00:00Z',
|
|
endsAt: '2026-02-01T02:00:00Z',
|
|
leadDays: 7,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
describe('prioritizeActiveItems', () => {
|
|
test('ranks incidents above maintenance by impact', () => {
|
|
const items = [
|
|
maintenance({ id: 'm', lastUpdateAt: '2026-01-01T00:00:00Z' }),
|
|
incident({
|
|
id: 'degraded',
|
|
impact: 'degraded_performance',
|
|
lastUpdateAt: '2026-01-01T00:00:00Z',
|
|
}),
|
|
incident({ id: 'full', impact: 'full_outage', lastUpdateAt: '2026-01-01T00:00:00Z' }),
|
|
]
|
|
|
|
expect(prioritizeActiveItems(items).map((item) => item.id)).toEqual(['full', 'degraded', 'm'])
|
|
})
|
|
|
|
test('breaks ties by lastUpdateAt descending', () => {
|
|
const items = [
|
|
incident({ id: 'older', impact: 'full_outage', lastUpdateAt: '2026-01-01T00:00:00Z' }),
|
|
incident({ id: 'newer', impact: 'full_outage', lastUpdateAt: '2026-01-02T00:00:00Z' }),
|
|
]
|
|
|
|
expect(prioritizeActiveItems(items).map((item) => item.id)).toEqual(['newer', 'older'])
|
|
})
|
|
|
|
test('ignores upcoming maintenance entirely', () => {
|
|
const items = [upcomingMaintenance(), incident()]
|
|
expect(prioritizeActiveItems(items).map((item) => item.id)).toEqual(['inc-1'])
|
|
})
|
|
|
|
test('includes items regardless of showBanner', () => {
|
|
const items = [incident({ showBanner: false })]
|
|
expect(prioritizeActiveItems(items)).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('getSupportAdmonitionCopy', () => {
|
|
test('returns null when there is nothing active', () => {
|
|
expect(getSupportAdmonitionCopy([upcomingMaintenance()])).toBeNull()
|
|
})
|
|
|
|
test('singular title for one active item', () => {
|
|
const copy = getSupportAdmonitionCopy([incident({ name: 'Elevated errors' })])
|
|
expect(copy?.title).toBe('Elevated errors')
|
|
})
|
|
|
|
test('plural title for two active items', () => {
|
|
const copy = getSupportAdmonitionCopy([
|
|
incident({ id: 'a', name: 'Elevated errors', impact: 'full_outage' }),
|
|
incident({ id: 'b', name: 'Slow queries', impact: 'degraded_performance' }),
|
|
])
|
|
expect(copy?.title).toBe('Elevated errors and 1 other issue')
|
|
})
|
|
|
|
test('plural title uses plural "issues" for three or more', () => {
|
|
const copy = getSupportAdmonitionCopy([
|
|
incident({ id: 'a', impact: 'full_outage' }),
|
|
incident({ id: 'b', impact: 'degraded_performance' }),
|
|
incident({ id: 'c', impact: 'degraded_performance' }),
|
|
])
|
|
expect(copy?.title).toBe('Elevated errors and 2 other issues')
|
|
})
|
|
|
|
test('describes an in-progress maintenance', () => {
|
|
const copy = getSupportAdmonitionCopy([maintenance()])
|
|
expect(copy?.description).toContain('Scheduled maintenance is in progress')
|
|
})
|
|
})
|
|
|
|
describe('getStatusPillLabel', () => {
|
|
test('all systems operational when nothing is active', () => {
|
|
expect(getStatusPillLabel([upcomingMaintenance()])).toBe('All systems operational')
|
|
})
|
|
|
|
test('active incident ongoing when any incident is active', () => {
|
|
expect(getStatusPillLabel([incident(), maintenance()])).toBe('Active incident ongoing')
|
|
})
|
|
|
|
test('scheduled maintenance when only maintenance is active', () => {
|
|
expect(getStatusPillLabel([maintenance()])).toBe('Scheduled maintenance')
|
|
})
|
|
})
|
|
|
|
describe('getSupportStatusLabel', () => {
|
|
test('pending', () => {
|
|
expect(getSupportStatusLabel({ status: 'pending' })).toBe('Checking status')
|
|
})
|
|
|
|
test('error', () => {
|
|
expect(getSupportStatusLabel({ status: 'error' })).toBe('Failed to check status')
|
|
})
|
|
|
|
test('success uses the computed pill label', () => {
|
|
const status: SupportStatus = {
|
|
status: 'success',
|
|
hasActiveIncidents: true,
|
|
pillLabel: 'Active incident ongoing',
|
|
admonition: null,
|
|
pageUrl: 'https://status.supabase.com/',
|
|
}
|
|
expect(getSupportStatusLabel(status)).toBe('Active incident ongoing')
|
|
})
|
|
})
|