mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(studio): flag for unavailable regions (#50473)
## 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? Adds feature flag for controlling region unavailability. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Region options now display availability badges, tooltips, and status-specific notices. * Restricted regions remain selectable so users can review their availability status. * Project creation provides a clear field-level message when a selected region is unavailable and prompts users to choose another region. * **Bug Fixes** * Region availability messaging now consistently reflects platform status and configured restrictions. * Availability warnings clear after selecting an eligible region. * Region checks now cover both dynamic and static provider regions. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
7b4e3aba01
commit
24e8333c54
6 files changed
+383
-10
No files matched your search
@@ -30,6 +30,7 @@ import {
|
||||
} from './ProjectCreation.constants'
|
||||
import { FormSchema } from './ProjectCreation.schema'
|
||||
import {
|
||||
getAvailableRegions,
|
||||
getHighAvailabilityRegionCode,
|
||||
instanceLabel,
|
||||
monthlyInstancePrice,
|
||||
@@ -39,7 +40,9 @@ import {
|
||||
import { ProjectCreationFooter } from './ProjectCreationFooter'
|
||||
import { ProjectNameInput } from './ProjectNameInput'
|
||||
import { RegionSelector } from './RegionSelector'
|
||||
import { getRegionRestrictionMessage } from './RegionSelector.utils'
|
||||
import { SecurityOptions } from './SecurityOptions'
|
||||
import { useRegionRestriction } from './useRegionRestriction'
|
||||
import { AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL } from '@/components/interfaces/Database/Triggers/EventTriggersList/EventTriggers.constants'
|
||||
import {
|
||||
GitHubRepositoryField,
|
||||
@@ -136,6 +139,7 @@ export const ProjectCreationForm = ({
|
||||
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
const showInternalOnlyConfiguration =
|
||||
useFlag('newProjectInternalOnlyConfiguration') && !isVercelIntegrationFlow
|
||||
const { getRegionRestriction } = useRegionRestriction()
|
||||
|
||||
// Read the raw flag for telemetry — coerce-undefined-to-false would record false for
|
||||
// users whose flags haven't loaded yet. The raw value preserves undefined (omitted from
|
||||
@@ -181,7 +185,7 @@ export const ProjectCreationForm = ({
|
||||
shouldRunMigrations: true,
|
||||
},
|
||||
})
|
||||
const { getFieldState, resetField, setValue } = form
|
||||
const { getFieldState, resetField, setError, setValue } = form
|
||||
const {
|
||||
instanceSize: watchedInstanceSize,
|
||||
cloudProvider,
|
||||
@@ -470,6 +474,29 @@ export const ProjectCreationForm = ({
|
||||
`High Availability projects are not available in the required region (${highAvailabilityRegionCode})`
|
||||
)
|
||||
}
|
||||
|
||||
const selectedSpecificRegion = specific.find((x) => x.name === dbRegion)
|
||||
const selectedStaticRegion = smartRegionEnabled
|
||||
? undefined
|
||||
: Object.values(getAvailableRegions(cloudProvider as CloudProvider)).find(
|
||||
(region) => region.displayName === dbRegion
|
||||
)
|
||||
const selectedRegionRestriction = getRegionRestriction(
|
||||
selectedSpecificRegion ?? selectedStaticRegion
|
||||
)
|
||||
if (selectedRegionRestriction !== undefined) {
|
||||
setError(
|
||||
'dbRegion',
|
||||
{ type: 'manual', message: getRegionRestrictionMessage(selectedRegionRestriction) },
|
||||
{ shouldFocus: true }
|
||||
)
|
||||
trackFunnelError(
|
||||
'project_creation',
|
||||
{ errorCategory: 'validation', errorReason: 'region_unavailable' },
|
||||
'form'
|
||||
)
|
||||
return
|
||||
}
|
||||
const parsedGitHubRepositoryId =
|
||||
githubRepositoryId.length > 0 ? Number(githubRepositoryId) : undefined
|
||||
const shouldIncludeGitHubFields =
|
||||
|
||||
@@ -30,6 +30,8 @@ import {
|
||||
getAvailableRegions,
|
||||
getHighAvailabilityRegionCode,
|
||||
} from './ProjectCreation.utils'
|
||||
import { getRegionRestrictionCopy, SELECT_DIFFERENT_REGION } from './RegionSelector.utils'
|
||||
import { useRegionRestriction } from './useRegionRestriction'
|
||||
import { AlertError } from '@/components/ui/AlertError'
|
||||
import { InlineLink } from '@/components/ui/InlineLink'
|
||||
import Panel from '@/components/ui/Panel'
|
||||
@@ -85,6 +87,8 @@ export const RegionSelector = ({
|
||||
const { hasLoaded: flagsLoaded } = useFeatureFlags()
|
||||
const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS'
|
||||
|
||||
const { getRegionRestriction } = useRegionRestriction()
|
||||
|
||||
const { data: statusData } = useIncidentStatusQuery()
|
||||
const { incidents = [] } = statusData ?? {}
|
||||
|
||||
@@ -131,6 +135,10 @@ export const RegionSelector = ({
|
||||
[...unfilteredRegionOptions],
|
||||
highAvailability
|
||||
)
|
||||
const regionOptionsWithRestriction = regionOptions.map((region) => ({
|
||||
...region,
|
||||
restriction: getRegionRestriction(region),
|
||||
}))
|
||||
const isLoading = smartRegionEnabled ? isLoadingAvailableRegions : isLoadingDefaultRegion
|
||||
|
||||
const isLocalEnvironment = process.env.NEXT_PUBLIC_ENVIRONMENT === 'local'
|
||||
@@ -181,6 +189,13 @@ export const RegionSelector = ({
|
||||
const selectedRegionLabel = selectedRegion?.name
|
||||
? getDisplayNameForSmartRegion(selectedRegion.name)
|
||||
: dbRegion
|
||||
const selectedRestriction = regionOptionsWithRestriction.find(
|
||||
(region) => region.name === dbRegion
|
||||
)?.restriction
|
||||
const selectedRestrictionCopy =
|
||||
selectedRestriction !== undefined
|
||||
? getRegionRestrictionCopy(selectedRestriction)
|
||||
: undefined
|
||||
const triggerLabel = isLoading ? 'Loading available regions...' : selectedRegionLabel
|
||||
|
||||
const affectingIncidents = incidents.filter((incident) => {
|
||||
@@ -237,6 +252,7 @@ export const RegionSelector = ({
|
||||
disabled={isLoading}
|
||||
>
|
||||
<SelectTrigger
|
||||
ref={field.ref}
|
||||
id="region"
|
||||
className="[&>:nth-child(1)]:w-full [&>:nth-child(1)]:flex [&>:nth-child(1)]:items-start"
|
||||
>
|
||||
@@ -298,16 +314,19 @@ export const RegionSelector = ({
|
||||
<SelectLabel>
|
||||
{highAvailability ? 'High Availability Regions' : 'Specific regions'}
|
||||
</SelectLabel>
|
||||
{regionOptions.map((value) => {
|
||||
{regionOptionsWithRestriction.map((value) => {
|
||||
const restrictionCopy =
|
||||
value.restriction !== undefined
|
||||
? getRegionRestrictionCopy(value.restriction)
|
||||
: undefined
|
||||
return (
|
||||
<SelectItem
|
||||
key={value.code}
|
||||
value={value.name}
|
||||
className={cn(
|
||||
'w-full [&>:nth-child(2)]:w-full',
|
||||
value.status !== undefined && 'pointer-events-auto!'
|
||||
restrictionCopy !== undefined && 'pointer-events-auto!'
|
||||
)}
|
||||
disabled={value.status !== undefined}
|
||||
>
|
||||
<div className="flex flex-row items-center justify-between w-full gap-x-2">
|
||||
<div className="flex items-center gap-x-3">
|
||||
@@ -325,16 +344,14 @@ export const RegionSelector = ({
|
||||
Recommended
|
||||
</Badge>
|
||||
)}
|
||||
{value.status !== undefined && value.status === 'capacity' && (
|
||||
{restrictionCopy !== undefined && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Badge variant="warning" className="mr-1">
|
||||
Unavailable
|
||||
{restrictionCopy.badge}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Temporarily unavailable due to this region being at capacity.
|
||||
</TooltipContent>
|
||||
<TooltipContent>{restrictionCopy.tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
@@ -366,6 +383,17 @@ export const RegionSelector = ({
|
||||
/>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
|
||||
{selectedRestrictionCopy !== undefined && (
|
||||
<FormItemLayout layout="horizontal" isReactForm={false}>
|
||||
<Admonition
|
||||
type="warning"
|
||||
title={selectedRestrictionCopy.title}
|
||||
description={`${selectedRestrictionCopy.notice} ${SELECT_DIFFERENT_REGION}`}
|
||||
className="mt-3"
|
||||
/>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
getRegionRestrictionCopy,
|
||||
getRegionRestrictionMessage,
|
||||
parseRestrictedRegions,
|
||||
REGION_RESTRICTION_COPY,
|
||||
resolveRegionRestriction,
|
||||
SELECT_DIFFERENT_REGION,
|
||||
} from './RegionSelector.utils'
|
||||
|
||||
vi.mock('@/lib/error-reporting', () => ({ captureCriticalError: vi.fn() }))
|
||||
|
||||
describe('parseRestrictedRegions', () => {
|
||||
it.each([
|
||||
['the false that useFlag returns when unresolved or errored', false],
|
||||
['undefined', undefined],
|
||||
['an empty string', ''],
|
||||
['an empty object', '{}'],
|
||||
['invalid JSON', 'not json'],
|
||||
['an unknown status value', '{"sa-east-1":"bogus"}'],
|
||||
['a status value the flag no longer supports', '{"sa-east-1":"paid_only"}'],
|
||||
['an array instead of a record', '["sa-east-1"]'],
|
||||
['a non-string value', 42],
|
||||
])('fails open to no restrictions for %s', (_label, flagValue) => {
|
||||
expect(parseRestrictedRegions(flagValue)).toEqual({})
|
||||
})
|
||||
|
||||
it('returns the restrictions keyed by region code for a valid payload', () => {
|
||||
expect(
|
||||
parseRestrictedRegions('{"sa-east-1":"unavailable","eu-central-1":"unavailable"}')
|
||||
).toEqual({ 'sa-east-1': 'unavailable', 'eu-central-1': 'unavailable' })
|
||||
})
|
||||
|
||||
it('drops every restriction when any entry is invalid', () => {
|
||||
expect(parseRestrictedRegions('{"sa-east-1":"unavailable","eu-central-1":"bogus"}')).toEqual({})
|
||||
})
|
||||
|
||||
it('reports a present-but-invalid payload without reporting the unresolved flag value', async () => {
|
||||
const { captureCriticalError } = await import('@/lib/error-reporting')
|
||||
vi.mocked(captureCriticalError).mockClear()
|
||||
|
||||
parseRestrictedRegions(false)
|
||||
parseRestrictedRegions('')
|
||||
expect(captureCriticalError).not.toHaveBeenCalled()
|
||||
|
||||
parseRestrictedRegions('not json')
|
||||
parseRestrictedRegions('{"sa-east-1":"bogus"}')
|
||||
expect(captureCriticalError).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveRegionRestriction', () => {
|
||||
it('lets the platform status win over the flag', () => {
|
||||
expect(
|
||||
resolveRegionRestriction({ platformStatus: 'capacity', flagRestriction: 'unavailable' })
|
||||
).toBe('capacity')
|
||||
expect(
|
||||
resolveRegionRestriction({ platformStatus: 'other', flagRestriction: 'unavailable' })
|
||||
).toBe('other')
|
||||
})
|
||||
|
||||
it('applies the flag only where the platform reports no status', () => {
|
||||
expect(
|
||||
resolveRegionRestriction({ platformStatus: undefined, flagRestriction: 'unavailable' })
|
||||
).toBe('unavailable')
|
||||
})
|
||||
|
||||
it('passes a platform status it does not recognise through unchanged', () => {
|
||||
expect(
|
||||
resolveRegionRestriction({ platformStatus: 'brand_new', flagRestriction: undefined })
|
||||
).toBe('brand_new')
|
||||
})
|
||||
|
||||
it('returns no restriction when neither source restricts the region', () => {
|
||||
expect(
|
||||
resolveRegionRestriction({ platformStatus: undefined, flagRestriction: undefined })
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getRegionRestrictionCopy', () => {
|
||||
it('uses the capacity copy for a capacity status', () => {
|
||||
expect(getRegionRestrictionCopy('capacity')).toBe(REGION_RESTRICTION_COPY.capacity)
|
||||
expect(getRegionRestrictionCopy('capacity').tooltip).toBe(
|
||||
'Temporarily unavailable due to this region being at capacity.'
|
||||
)
|
||||
})
|
||||
|
||||
it.each(['other', 'unavailable', 'brand_new', 'toString'])(
|
||||
'falls back to the generic copy for %s',
|
||||
(restriction) => {
|
||||
expect(getRegionRestrictionCopy(restriction)).toBe(REGION_RESTRICTION_COPY.other)
|
||||
}
|
||||
)
|
||||
|
||||
it('builds the submit message from the title and the shared call to action', () => {
|
||||
expect(getRegionRestrictionMessage('capacity')).toBe(
|
||||
`Selected region is at capacity. ${SELECT_DIFFERENT_REGION}`
|
||||
)
|
||||
expect(getRegionRestrictionMessage('brand_new')).toBe(
|
||||
`Selected region is unavailable. ${SELECT_DIFFERENT_REGION}`
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
import { captureCriticalError } from '@/lib/error-reporting'
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useFlag } from 'common'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import {
|
||||
parseRestrictedRegions,
|
||||
resolveRegionRestriction,
|
||||
RESTRICTED_REGIONS_FLAG_KEY,
|
||||
} from './RegionSelector.utils'
|
||||
|
||||
type RegionLike = {
|
||||
code: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export function useRegionRestriction() {
|
||||
const restrictedRegionsFlag = useFlag<string | boolean>(RESTRICTED_REGIONS_FLAG_KEY)
|
||||
const restrictedRegions = useMemo(
|
||||
() => parseRestrictedRegions(restrictedRegionsFlag),
|
||||
[restrictedRegionsFlag]
|
||||
)
|
||||
|
||||
const getRegionRestriction = (region: RegionLike | undefined) =>
|
||||
region === undefined
|
||||
? undefined
|
||||
: resolveRegionRestriction({
|
||||
platformStatus: region.status,
|
||||
flagRestriction: restrictedRegions[region.code],
|
||||
})
|
||||
|
||||
return { getRegionRestriction }
|
||||
}
|
||||
@@ -103,6 +103,18 @@ const DEFAULT_AVAILABLE_REGIONS: RegionsInfo = {
|
||||
}
|
||||
|
||||
const FRANKFURT = 'Central EU (Frankfurt)'
|
||||
const SAO_PAULO = 'South America (São Paulo)'
|
||||
|
||||
const availableRegionsWithSaoPaulo = (status?: 'capacity' | 'other'): RegionsInfo => ({
|
||||
...DEFAULT_AVAILABLE_REGIONS,
|
||||
all: {
|
||||
...DEFAULT_AVAILABLE_REGIONS.all,
|
||||
specific: [
|
||||
...DEFAULT_AVAILABLE_REGIONS.all.specific,
|
||||
{ code: 'sa-east-1', name: SAO_PAULO, provider: 'AWS', type: 'specific', status },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const AVAILABLE_REGIONS_WITH_FRANKFURT: RegionsInfo = {
|
||||
...DEFAULT_AVAILABLE_REGIONS,
|
||||
@@ -262,9 +274,10 @@ const DEFAULT_FLAGS = {
|
||||
newProjectInternalOnlyConfiguration: false,
|
||||
disableOrioleProjectCreation: false,
|
||||
defaultRegionRestrictedPool: false,
|
||||
projectCreationRestrictedRegions: false,
|
||||
}
|
||||
|
||||
async function renderWizard(options: { flags?: Partial<typeof DEFAULT_FLAGS> } = {}) {
|
||||
async function renderWizard(options: { flags?: Record<string, boolean | string> } = {}) {
|
||||
const { default: Wizard } = await import('@/pages/new/[slug]')
|
||||
return customRender(
|
||||
<FeatureFlagContext.Provider
|
||||
@@ -479,6 +492,80 @@ describe('project creation wizard', () => {
|
||||
|
||||
await screen.findByText('Error loading available regions')
|
||||
})
|
||||
|
||||
describe('restricted regions', () => {
|
||||
const CAPACITY_COPY = {
|
||||
title: 'Selected region is at capacity',
|
||||
notice:
|
||||
'This region currently has capacity for Micro compute and above. Free plan projects run on Nano compute.',
|
||||
}
|
||||
const GENERIC_COPY = {
|
||||
title: 'Selected region is unavailable',
|
||||
notice: 'This region is temporarily unavailable for new projects.',
|
||||
}
|
||||
|
||||
const expectSelectableRestrictedRegion = async (
|
||||
onRequest: ReturnType<typeof vi.fn>,
|
||||
copy: { title: string; notice: string }
|
||||
) => {
|
||||
await fillProjectName('Restricted Region Project')
|
||||
await generateAndWaitForStrongPassword()
|
||||
await user.click(getSelectTriggerByLabel('Region'))
|
||||
|
||||
const saoPaulo = await screen.findByRole('option', { name: /São Paulo/ })
|
||||
expect(saoPaulo).not.toHaveAttribute('aria-disabled', 'true')
|
||||
expect(within(saoPaulo).getByText('Unavailable')).toBeInTheDocument()
|
||||
expect(
|
||||
within(screen.getByRole('option', { name: /North Virginia/ })).queryByText('Unavailable')
|
||||
).not.toBeInTheDocument()
|
||||
await user.click(saoPaulo)
|
||||
|
||||
expect(await screen.findByText(copy.title)).toBeInTheDocument()
|
||||
expect(screen.getByText(copy.notice, { exact: false })).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Create new project' }))
|
||||
|
||||
const message = `${copy.title}. Select a different region to continue.`
|
||||
expect(await screen.findByText(message)).toBeInTheDocument()
|
||||
expect(onRequest).not.toHaveBeenCalled()
|
||||
|
||||
await selectRegion(/Americas/)
|
||||
await waitFor(() => expect(screen.queryByText(message)).not.toBeInTheDocument())
|
||||
expect(screen.queryByText(copy.title)).not.toBeInTheDocument()
|
||||
}
|
||||
|
||||
test('keeps a capacity-restricted region selectable, explains it, and blocks submission', async () => {
|
||||
mockWizardEndpoints({ availableRegions: availableRegionsWithSaoPaulo('capacity') })
|
||||
const onRequest = vi.fn()
|
||||
mockCreateProject(onRequest)
|
||||
|
||||
await renderWizard()
|
||||
|
||||
await expectSelectableRestrictedRegion(onRequest, CAPACITY_COPY)
|
||||
})
|
||||
|
||||
test('labels an "other" platform status with generic copy instead of greying it out silently', async () => {
|
||||
mockWizardEndpoints({ availableRegions: availableRegionsWithSaoPaulo('other') })
|
||||
const onRequest = vi.fn()
|
||||
mockCreateProject(onRequest)
|
||||
|
||||
await renderWizard()
|
||||
|
||||
await expectSelectableRestrictedRegion(onRequest, GENERIC_COPY)
|
||||
})
|
||||
|
||||
test('applies a flag-only restriction the same way when the platform reports no status', async () => {
|
||||
mockWizardEndpoints({ availableRegions: availableRegionsWithSaoPaulo() })
|
||||
const onRequest = vi.fn()
|
||||
mockCreateProject(onRequest)
|
||||
|
||||
await renderWizard({
|
||||
flags: { projectCreationRestrictedRegions: '{"sa-east-1":"unavailable"}' },
|
||||
})
|
||||
|
||||
await expectSelectableRestrictedRegion(onRequest, GENERIC_COPY)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('compute size and spend confirmation', () => {
|
||||
|
||||
Reference in new issue
Block a user