Files
supabase/apps/studio/components/interfaces/ProjectCreation/HighAvailabilityInput.tsx
T
d2a3162bf1 Add high availability project creation controls (#48375)
## Summary

- Move High Availability into the standard project creation settings
above Compute, gated by the `instances.high_availability` entitlement.
- Mark the option as Alpha and explain that it is free during Alpha for
up to two projects.
- Enforce the supported HA configuration: `AWS_K8S`, Postgres 17 on the
`ga` release channel (no custom version is sent — the API resolves the
image), and the environment-specific local/staging region restrictions.
- Show eligible locations in a dedicated **High Availability Regions**
group.
- Preserve the existing Advanced Configuration availability rules and
additionally hide the section while HA is enabled.
- Restore the previous provider and Postgres settings when HA is
switched off.

## How to test
1. Go to create a new project
2. Ensure you have access to high availability (e.g. on local)
3. Toggle high availability on and note how the project form restricts
settings listed above

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added High Availability to project creation with Alpha warning
labeling and improved switch accessibility.
* Constrains region selection to compatible High Availability regions
and enforces HA-specific engine/release settings.
* Disables/hides custom PostgreSQL version selection when High
Availability is enabled (and omits HA custom request payloads).

* **Bug Fixes**
* Improved persistence of selected PostgreSQL version and region across
data reloads and configuration panel reopen/toggle.
* Restores region when form state temporarily drops values during
remounts.

* **Tests**
* Expanded end-to-end coverage for HA UI, region grouping, and
submit/payload restoration behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-07-30 16:36:54 +08:00

128 lines
4.8 KiB
TypeScript

import { useEffect, useRef } from 'react'
import { UseFormReturn } from 'react-hook-form'
import { type CloudProvider } from 'shared-data'
import { Badge, FormControl, FormField, Switch, useWatch } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateProjectForm } from './ProjectCreation.schema'
import Panel from '@/components/ui/Panel'
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
interface HighAvailabilityInputProps {
form: UseFormReturn<CreateProjectForm>
// Derived from an org-wide regions query owned by the parent form, since fetching it
// here would mean duplicating that query's slug/cloud-provider/instance-size context.
highAvailabilityRegionName: string | undefined
}
export const HighAvailabilityInput = ({
form,
highAvailabilityRegionName,
}: HighAvailabilityInputProps) => {
const { getValues, setValue } = form
const { hasAccess } = useCheckEntitlements('instances.high_availability')
const highAvailability = useWatch({ control: form.control, name: 'highAvailability' })
// Fields to revert to when toggling off HA, so previously selected values aren't lost.
const beforeHighAvailability = useRef<{
cloudProvider: CloudProvider | undefined
postgresVersionSelection: string | undefined
dbRegion: string | null
}>({
cloudProvider: undefined,
postgresVersionSelection: undefined,
dbRegion: null,
})
const handleHighAvailabilityChange = (checked: boolean) => {
if (checked) {
const currentCloudProvider = getValues('cloudProvider') as CloudProvider
if (currentCloudProvider !== 'AWS_K8S') {
beforeHighAvailability.current.cloudProvider = currentCloudProvider
setValue('cloudProvider', 'AWS_K8S')
}
beforeHighAvailability.current.postgresVersionSelection = getValues(
'postgresVersionSelection'
)
setValue('useOrioleDb', false)
const currentRegion = getValues('dbRegion')
if (
highAvailabilityRegionName !== undefined &&
currentRegion !== highAvailabilityRegionName
) {
beforeHighAvailability.current.dbRegion = currentRegion ?? null
setValue('dbRegion', highAvailabilityRegionName)
}
} else {
if (beforeHighAvailability.current.cloudProvider !== undefined) {
setValue('cloudProvider', beforeHighAvailability.current.cloudProvider)
beforeHighAvailability.current.cloudProvider = undefined
}
if (beforeHighAvailability.current.postgresVersionSelection !== undefined) {
setValue(
'postgresVersionSelection',
beforeHighAvailability.current.postgresVersionSelection
)
beforeHighAvailability.current.postgresVersionSelection = undefined
}
if (beforeHighAvailability.current.dbRegion !== null) {
setValue('dbRegion', beforeHighAvailability.current.dbRegion)
beforeHighAvailability.current.dbRegion = null
}
}
}
// Catches the case where highAvailabilityRegionName wasn't loaded yet at the moment the
// toggle fired above (the org's available-regions query for AWS_K8S may still be in
// flight). The region auto-fill effect in the parent form skips dirty fields, so a
// manually chosen region would otherwise keep showing in the trigger — force it over
// explicitly once the HA region becomes known.
useEffect(() => {
if (!highAvailability || highAvailabilityRegionName === undefined) return
const currentRegion = getValues('dbRegion')
if (currentRegion === highAvailabilityRegionName) return
if (beforeHighAvailability.current.dbRegion === null) {
beforeHighAvailability.current.dbRegion = currentRegion ?? null
}
setValue('dbRegion', highAvailabilityRegionName)
}, [highAvailability, highAvailabilityRegionName, getValues, setValue])
if (!hasAccess) return null
return (
<Panel.Content>
<FormField
control={form.control}
name="highAvailability"
render={({ field }) => (
<FormItemLayout
label={
<div className="flex items-center gap-x-2">
<span>High availability</span>
<Badge variant="warning">Alpha</Badge>
</div>
}
description="Horizontally scalable Postgres for highly available deployments. Free during Alpha for up to 2 projects."
layout="horizontal"
>
<FormControl>
<Switch
aria-label="Enable high availability"
checked={field.value}
onCheckedChange={(checked) => {
handleHighAvailabilityChange(checked)
field.onChange(checked)
}}
/>
</FormControl>
</FormItemLayout>
)}
/>
</Panel.Content>
)
}