mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 19:35:06 +03:00
## Context
Realised that we were calling both `useDefaultRegionQuery` and
`useOrganizationAvailableRegionsQuery` in the new project page.
`smartRegionEnabled` defaults to `false` at the beginning while the
flags are still being loaded, to this calls `useDefaultRegionQuery`. But
once the flags are loaded and `smartRegionEnabled` becomes `true`, then
the other hook is called
## Changes involved
- Checks that the flags are loaded first before calling either hooks to
prevent unnecessarily triggering both
- Adjust `defaultRegion` to remove hardcode
- Check smart region first, then default back to default specific region
- Renamed variables to be clearer:
- `autoDefaultRegion` (check based on location)
- `fixedDefaultRegion` (hardcoded in repo)
- Update `useEffect` on `regionError` to only reset the region value if
the default value is not undefined
- Also just a tiny nit to re-arrange to group the `useEffects` together
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Region selection now waits for feature flags to load before fetching
default and “smart” region options, preventing premature or incorrect
region choices.
* New project setup now updates the database region and smart
recommendations more consistently, with improved fallback behavior when
region lookup fails.
* AWS Nimbus default region is now environment-aware: non-prod uses
Southeast Asia, while prod uses East US.
* Default privilege settings now stay in sync more reliably during
setup, updating only when appropriate.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
317 lines
13 KiB
TypeScript
317 lines
13 KiB
TypeScript
import { useFeatureFlags, useFlag, useParams } from 'common'
|
|
import { UseFormReturn } from 'react-hook-form'
|
|
import type { CloudProvider } from 'shared-data'
|
|
import {
|
|
Badge,
|
|
cn,
|
|
FormField,
|
|
Select,
|
|
SelectContent,
|
|
SelectGroup,
|
|
SelectItem,
|
|
SelectLabel,
|
|
SelectSeparator,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from 'ui'
|
|
import { Admonition } from 'ui-patterns/admonition'
|
|
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
|
|
|
import { CreateProjectForm } from './ProjectCreation.schema'
|
|
import { getAvailableRegions } from './ProjectCreation.utils'
|
|
import AlertError from '@/components/ui/AlertError'
|
|
import { InlineLink } from '@/components/ui/InlineLink'
|
|
import Panel from '@/components/ui/Panel'
|
|
import { useDefaultRegionQuery } from '@/data/misc/get-default-region-query'
|
|
import { useOrganizationAvailableRegionsQuery } from '@/data/organizations/organization-available-regions-query'
|
|
import { useIncidentStatusQuery } from '@/data/platform/incident-status-query'
|
|
import type { DesiredInstanceSize } from '@/data/projects/new-project.constants'
|
|
import { BASE_PATH, PROVIDERS } from '@/lib/constants'
|
|
|
|
interface RegionSelectorProps {
|
|
form: UseFormReturn<CreateProjectForm>
|
|
instanceSize?: DesiredInstanceSize
|
|
layout?: 'vertical' | 'horizontal'
|
|
}
|
|
|
|
// [Joshen] Let's use a library to maintain the flag SVGs in the future
|
|
// I tried using https://flagpack.xyz/docs/development/react/ but couldn't get it to render
|
|
// ^ can try again next time
|
|
|
|
// Maps smart region group codes to the specific-region code prefixes they contain.
|
|
// Used to check whether an incident affecting specific regions also affects a smart region selection.
|
|
const SMART_REGION_PREFIXES: Record<string, Array<string>> = {
|
|
americas: ['us-', 'ca-', 'sa-'],
|
|
emea: ['eu-', 'me-', 'af-'],
|
|
apac: ['ap-'],
|
|
}
|
|
|
|
function smartRegionMatchesSpecific(smartCode: string, specificCode: string): boolean {
|
|
return (SMART_REGION_PREFIXES[smartCode] ?? []).some((prefix) => specificCode.startsWith(prefix))
|
|
}
|
|
|
|
// Map backend region names to user-friendly display names
|
|
const getDisplayNameForSmartRegion = (name: string): string => {
|
|
if (name === 'APAC') {
|
|
return 'Asia-Pacific'
|
|
}
|
|
return name
|
|
}
|
|
|
|
export const RegionSelector = ({
|
|
form,
|
|
instanceSize,
|
|
layout = 'horizontal',
|
|
}: RegionSelectorProps) => {
|
|
const { slug } = useParams()
|
|
const cloudProvider = form.getValues('cloudProvider') as CloudProvider
|
|
|
|
const { hasLoaded: flagsLoaded } = useFeatureFlags()
|
|
const smartRegionEnabled = useFlag('enableSmartRegion')
|
|
|
|
const { data: statusData } = useIncidentStatusQuery()
|
|
const { incidents = [] } = statusData ?? {}
|
|
|
|
const { isPending: isLoadingDefaultRegion } = useDefaultRegionQuery(
|
|
{ cloudProvider },
|
|
{ enabled: flagsLoaded && !smartRegionEnabled }
|
|
)
|
|
|
|
const {
|
|
data: availableRegionsData,
|
|
isPending: isLoadingAvailableRegions,
|
|
isError: isErrorAvailableRegions,
|
|
error: errorAvailableRegions,
|
|
} = useOrganizationAvailableRegionsQuery(
|
|
{ slug, cloudProvider, desiredInstanceSize: instanceSize },
|
|
{ enabled: smartRegionEnabled, staleTime: 1000 * 60 * 5 } // 5 minutes
|
|
)
|
|
|
|
const smartRegions = availableRegionsData?.all.smartGroup ?? []
|
|
const allRegions = availableRegionsData?.all.specific ?? []
|
|
|
|
const recommendedSmartRegions = new Set(
|
|
[availableRegionsData?.recommendations.smartGroup.code].filter(Boolean)
|
|
)
|
|
const recommendedSpecificRegions = new Set(
|
|
availableRegionsData?.recommendations.specific.map((region) => region.code)
|
|
)
|
|
|
|
const availableRegions = getAvailableRegions(PROVIDERS[cloudProvider].id)
|
|
const regionsArray = Object.entries(availableRegions).map(([_key, value]) => {
|
|
return {
|
|
code: value.code,
|
|
name: value.displayName,
|
|
provider: cloudProvider,
|
|
status: undefined,
|
|
}
|
|
})
|
|
|
|
const regionOptions = smartRegionEnabled ? allRegions : regionsArray
|
|
const isLoading = smartRegionEnabled ? isLoadingAvailableRegions : isLoadingDefaultRegion
|
|
|
|
const showNonProdFields =
|
|
process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' ||
|
|
process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
|
|
|
|
const allSelectableRegions = [...smartRegions, ...regionOptions]
|
|
|
|
if (isErrorAvailableRegions) {
|
|
return <AlertError subject="Error loading available regions" error={errorAvailableRegions} />
|
|
}
|
|
|
|
return (
|
|
<Panel.Content>
|
|
<FormField
|
|
control={form.control}
|
|
name="dbRegion"
|
|
render={({ field }) => {
|
|
const selectedRegion = allSelectableRegions.find((region) => {
|
|
return !!region.name && region.name === field.value
|
|
})
|
|
|
|
const affectingIncidents = incidents.filter((incident) => {
|
|
const affectedRegions = incident.cache?.affected_regions ?? []
|
|
if (affectedRegions.length === 0 || selectedRegion?.code === undefined) return false
|
|
|
|
// Specific region: direct code match
|
|
if (affectedRegions.includes(selectedRegion.code)) return true
|
|
|
|
// Smart region: match if any affected region falls within the smart group
|
|
return affectedRegions.some((specificCode) =>
|
|
smartRegionMatchesSpecific(selectedRegion.code, specificCode)
|
|
)
|
|
})
|
|
|
|
return (
|
|
<>
|
|
<FormItemLayout
|
|
layout={layout}
|
|
label="Region"
|
|
description={
|
|
<>
|
|
<p>Select the region closest to your users for the best performance.</p>
|
|
{showNonProdFields && (
|
|
<div className="mt-2 text-warning">
|
|
<p>Only these regions are supported for local/staging projects:</p>
|
|
<ul className="list-disc list-inside mt-1">
|
|
<li>East US (North Virginia)</li>
|
|
<li>Central EU (Frankfurt)</li>
|
|
<li>Southeast Asia (Singapore)</li>
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</>
|
|
}
|
|
>
|
|
<Select value={field.value} onValueChange={field.onChange} disabled={isLoading}>
|
|
<SelectTrigger className="[&>:nth-child(1)]:w-full [&>:nth-child(1)]:flex [&>:nth-child(1)]:items-start">
|
|
<SelectValue
|
|
placeholder={
|
|
isLoading
|
|
? 'Loading available regions...'
|
|
: 'Select a region for your project..'
|
|
}
|
|
>
|
|
{field.value !== undefined && (
|
|
<div className="flex items-center gap-x-3">
|
|
{selectedRegion?.code && (
|
|
<img
|
|
alt="region icon"
|
|
className="w-5 rounded-xs"
|
|
src={`${BASE_PATH}/img/regions/${selectedRegion.code}.svg`}
|
|
/>
|
|
)}
|
|
<span className="text-foreground">
|
|
{selectedRegion?.name
|
|
? getDisplayNameForSmartRegion(selectedRegion.name)
|
|
: field.value}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</SelectValue>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{smartRegionEnabled && (
|
|
<>
|
|
<SelectGroup>
|
|
<SelectLabel>General regions</SelectLabel>
|
|
{smartRegions.map((value) => {
|
|
return (
|
|
<SelectItem
|
|
key={value.code}
|
|
value={value.name}
|
|
className="w-full [&>:nth-child(2)]:w-full"
|
|
>
|
|
<div className="flex flex-row items-center justify-between w-full">
|
|
<div className="flex items-center gap-x-3">
|
|
<img
|
|
alt="region icon"
|
|
className="w-5 rounded-xs"
|
|
src={`${BASE_PATH}/img/regions/${value.code}.svg`}
|
|
/>
|
|
<span className="text-foreground">
|
|
{getDisplayNameForSmartRegion(value.name)}
|
|
</span>
|
|
</div>
|
|
|
|
<div>
|
|
{recommendedSmartRegions.has(value.code) && (
|
|
<Badge variant="success" className="mr-1">
|
|
Recommended
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</SelectItem>
|
|
)
|
|
})}
|
|
</SelectGroup>
|
|
<SelectSeparator />
|
|
</>
|
|
)}
|
|
|
|
<SelectGroup>
|
|
<SelectLabel>Specific regions</SelectLabel>
|
|
{regionOptions.map((value) => {
|
|
return (
|
|
<SelectItem
|
|
key={value.code}
|
|
value={value.name}
|
|
className={cn(
|
|
'w-full [&>:nth-child(2)]:w-full',
|
|
value.status !== 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">
|
|
<img
|
|
alt="region icon"
|
|
className="w-5 rounded-xs"
|
|
src={`${BASE_PATH}/img/regions/${value.code}.svg`}
|
|
/>
|
|
<div className="flex items-center gap-x-2">
|
|
<span className="text-foreground">{value.name}</span>
|
|
<span className="text-xs text-foreground-lighter font-mono">
|
|
{value.code}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{recommendedSpecificRegions.has(value.code) && (
|
|
<Badge variant="success" className="mr-1">
|
|
Recommended
|
|
</Badge>
|
|
)}
|
|
{value.status !== undefined && value.status === 'capacity' && (
|
|
<Tooltip>
|
|
<TooltipTrigger>
|
|
<Badge variant="warning" className="mr-1">
|
|
Unavailable
|
|
</Badge>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
Temporarily unavailable due to this region being at capacity.
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
)}
|
|
</div>
|
|
</SelectItem>
|
|
)
|
|
})}
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</FormItemLayout>
|
|
|
|
{affectingIncidents.length > 0 && (
|
|
<FormItemLayout layout="horizontal">
|
|
<Admonition
|
|
type="warning"
|
|
title="Incident in progress for this region"
|
|
description={
|
|
<>
|
|
We're currently investigating an issue that may impact projects in this
|
|
region. Follow updates on{' '}
|
|
<InlineLink href="https://status.supabase.com">
|
|
status.supabase.com
|
|
</InlineLink>
|
|
.
|
|
</>
|
|
}
|
|
className="mt-3"
|
|
/>
|
|
</FormItemLayout>
|
|
)}
|
|
</>
|
|
)
|
|
}}
|
|
/>
|
|
</Panel.Content>
|
|
)
|
|
}
|