mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## Summary - Adds a new project-creation-form incident warning path behind the `incidentIoStatusPage` ConfigCat flag (default off — no visible behavior change until the flag is enabled) - When the flag is on, reads the new `/api/status-page` endpoint (added earlier in this stack) instead of the legacy `/api/incident-status` endpoint, and only fetches the legacy endpoint when the flag is off - Extracts region-matching logic into `RegionSelector.utils.ts` (`regionMatches`, `getItemsAffectingProjectCreation`) with unit test coverage See Linear FE-4057 for full context and design. ## 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 components/interfaces/ProjectCreation/RegionSelector.utils.test.ts` (35/35 passing) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Project creation displays a warning and links to the status page when an incident or maintenance event may affect the selected region. * Status notices match exact regions and broader smart-region groupings; global project-creation notices are also shown. * The AI assistant can report visible, active incidents and in-progress maintenance, including affected components and update details. When there are no active events, it reports that status. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
129 lines
4.2 KiB
TypeScript
129 lines
4.2 KiB
TypeScript
import { z } from 'zod'
|
|
|
|
import { captureCriticalError } from '@/lib/error-reporting'
|
|
import { normalizeRegion, type StatusItem } from '@/lib/status-page/status-page.utils'
|
|
|
|
export const RESTRICTED_REGIONS_FLAG_KEY = 'projectCreationRestrictedRegions'
|
|
|
|
export type PlatformRegionStatus = 'capacity' | 'other'
|
|
|
|
const FLAG_RESTRICTIONS = ['unavailable'] as const
|
|
export type FlagRestriction = (typeof FLAG_RESTRICTIONS)[number]
|
|
|
|
export type RegionRestriction = PlatformRegionStatus | FlagRestriction
|
|
|
|
export type RegionRestrictionCopy = {
|
|
badge: string
|
|
title: string
|
|
tooltip: string
|
|
notice: string
|
|
}
|
|
|
|
const GENERIC_RESTRICTION_COPY: RegionRestrictionCopy = {
|
|
badge: 'Unavailable',
|
|
title: 'Selected region is unavailable',
|
|
tooltip: 'Temporarily unavailable for new projects.',
|
|
notice: 'This region is temporarily unavailable for new projects.',
|
|
}
|
|
|
|
export const REGION_RESTRICTION_COPY: Record<RegionRestriction, RegionRestrictionCopy> = {
|
|
capacity: {
|
|
badge: 'Unavailable',
|
|
title: 'Selected region is at capacity',
|
|
tooltip: 'Temporarily unavailable due to this region being at capacity.',
|
|
notice:
|
|
'This region currently has capacity for Micro compute and above. Free plan projects run on Nano compute.',
|
|
},
|
|
other: GENERIC_RESTRICTION_COPY,
|
|
unavailable: GENERIC_RESTRICTION_COPY,
|
|
}
|
|
|
|
export const SELECT_DIFFERENT_REGION = 'Select a different region to continue.'
|
|
|
|
function isKnownRestriction(value: string): value is RegionRestriction {
|
|
return Object.prototype.hasOwnProperty.call(REGION_RESTRICTION_COPY, value)
|
|
}
|
|
|
|
export function getRegionRestrictionCopy(restriction: string): RegionRestrictionCopy {
|
|
return isKnownRestriction(restriction)
|
|
? REGION_RESTRICTION_COPY[restriction]
|
|
: GENERIC_RESTRICTION_COPY
|
|
}
|
|
|
|
export function getRegionRestrictionMessage(restriction: string) {
|
|
return `${getRegionRestrictionCopy(restriction).title}. ${SELECT_DIFFERENT_REGION}`
|
|
}
|
|
|
|
const RestrictedRegionsSchema = z.record(z.string(), z.enum(FLAG_RESTRICTIONS))
|
|
|
|
export function parseRestrictedRegions(flagValue: unknown): Record<string, FlagRestriction> {
|
|
if (typeof flagValue !== 'string' || flagValue.trim() === '') return {}
|
|
|
|
let payload: unknown
|
|
try {
|
|
payload = JSON.parse(flagValue)
|
|
} catch {
|
|
reportInvalidPayload(flagValue, 'invalid JSON')
|
|
return {}
|
|
}
|
|
|
|
const result = RestrictedRegionsSchema.safeParse(payload)
|
|
if (!result.success) {
|
|
reportInvalidPayload(flagValue, result.error.issues[0]?.message ?? 'invalid shape')
|
|
return {}
|
|
}
|
|
|
|
return result.data
|
|
}
|
|
|
|
function reportInvalidPayload(flagValue: string, reason: string) {
|
|
captureCriticalError(
|
|
new Error(`Ignoring ${RESTRICTED_REGIONS_FLAG_KEY} flag (${reason}): ${flagValue}`),
|
|
'parse restricted regions flag'
|
|
)
|
|
}
|
|
|
|
type ResolveRegionRestrictionArgs = {
|
|
platformStatus: string | undefined
|
|
flagRestriction: FlagRestriction | undefined
|
|
}
|
|
|
|
export function resolveRegionRestriction({
|
|
platformStatus,
|
|
flagRestriction,
|
|
}: ResolveRegionRestrictionArgs): string | undefined {
|
|
return platformStatus ?? flagRestriction
|
|
}
|
|
|
|
// Maps smart region group codes to the specific-region code prefixes they contain.
|
|
// Used to check whether a status item affecting specific regions also affects a smart region selection.
|
|
export const SMART_REGION_PREFIXES: Record<string, Array<string>> = {
|
|
americas: ['us-', 'ca-', 'sa-'],
|
|
emea: ['eu-', 'me-', 'af-'],
|
|
apac: ['ap-'],
|
|
}
|
|
|
|
export function regionMatches(selectedCode: string, affectedRegion: string): boolean {
|
|
const normalizedSelected = normalizeRegion(selectedCode)
|
|
const normalizedAffected = normalizeRegion(affectedRegion)
|
|
if (normalizedSelected === normalizedAffected) return true
|
|
return (SMART_REGION_PREFIXES[normalizedSelected] ?? []).some((prefix) =>
|
|
normalizedAffected.startsWith(prefix)
|
|
)
|
|
}
|
|
|
|
export function getItemsAffectingProjectCreation(
|
|
items: Array<StatusItem>,
|
|
selectedRegionCode: string | undefined
|
|
): Array<StatusItem> {
|
|
return items.filter((item) => {
|
|
if (item.kind !== 'incident' && item.kind !== 'maintenance') return false
|
|
if (item.projectCreationScope === null) return false
|
|
if (item.projectCreationScope.type === 'global') return true
|
|
return (
|
|
selectedRegionCode !== undefined &&
|
|
item.projectCreationScope.regions.some((region) => regionMatches(selectedRegionCode, region))
|
|
)
|
|
})
|
|
}
|