Files
supabase/apps/studio/components/interfaces/ProjectCreation/PostgresVersionSelector.tsx
T
ec2657e4f7 fix: Filter the version selector in the new project form by the chosen project type (#51490)
This pull request enhances the project creation flow by introducing
support for filtering Postgres versions based on the OrioleDB engine.
The main changes involve updating the `PostgresVersionSelector`
component to allow filtering for OrioleDB-specific versions, and
integrating this logic into the internal-only configuration step of
project creation. This provides a clearer and more flexible experience
when selecting Postgres versions, especially for internal or advanced
users.

**Postgres Version Selection Enhancements:**

* Added a new `useOrioleDbForCreate` prop to `PostgresVersionSelector`,
allowing the component to filter available Postgres versions to only
OrioleDB-based options or exclude them, based on user selection. The
prop defaults to `undefined` for no filtering.
[[1]](diffhunk://#diff-1842d78cb2a4be2c2b6e1581981d8111304fd9aaf0ad45df273d8c001938bcd9R41-R50)
[[2]](diffhunk://#diff-1842d78cb2a4be2c2b6e1581981d8111304fd9aaf0ad45df273d8c001938bcd9R82-R85)
[[3]](diffhunk://#diff-1842d78cb2a4be2c2b6e1581981d8111304fd9aaf0ad45df273d8c001938bcd9L92-R114)
* Added a `description` prop to `PostgresVersionSelector` for improved
UI guidance, and passed this prop through the form.
[[1]](diffhunk://#diff-1842d78cb2a4be2c2b6e1581981d8111304fd9aaf0ad45df273d8c001938bcd9R41-R50)
[[2]](diffhunk://#diff-1842d78cb2a4be2c2b6e1581981d8111304fd9aaf0ad45df273d8c001938bcd9R82-R85)
[[3]](diffhunk://#diff-1842d78cb2a4be2c2b6e1581981d8111304fd9aaf0ad45df273d8c001938bcd9L139-R161)

**Integration with Project Creation Flow:**

* Updated `InternalOnlyConfiguration` to watch the `useOrioleDb` form
field and pass its value to `PostgresVersionSelector`, ensuring the
version list is filtered appropriately during project creation. Also,
conditionally set the description to guide users toward advanced
configuration for OrioleDB.
[[1]](diffhunk://#diff-f0fa51a1cc67bb1a2c38d2c595eb3cab3a45813a027a04942d684914f2c3de93R33)
[[2]](diffhunk://#diff-f0fa51a1cc67bb1a2c38d2c595eb3cab3a45813a027a04942d684914f2c3de93R60-R65)
* Adjusted the render order in `ProjectCreationForm` to ensure advanced
configuration and internal-only configuration are displayed in a logical
sequence.

**Other Improvements:**

* Imported the `isSuccess` utility from `effect/Result` in
`PostgresVersionSelector.tsx` to support the updated logic.

---------

Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-10-13 12:15:51 +02:00

194 lines
6.6 KiB
TypeScript

import { useEffect } from 'react'
import { ControllerRenderProps, UseFormReturn, useWatch } from 'react-hook-form'
import type { CloudProvider } from 'shared-data'
import {
Badge,
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { smartRegionToExactRegion } from './ProjectCreation.utils'
import { useProjectCreationPostgresVersionsQuery } from '@/data/config/project-creation-postgres-versions-query'
import { useProjectUnpausePostgresVersionsQuery } from '@/data/config/project-unpause-postgres-versions-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
interface PostgresVersionSelectorProps {
cloudProvider: CloudProvider
dbRegion: string
disabled?: boolean
organizationSlug?: string
highAvailability?: boolean
field: ControllerRenderProps<any, 'postgresVersionSelection'>
form: UseFormReturn<any>
/**
* Owned by the form owner (not this component) so the last valid selection
* survives this selector unmounting, e.g. when its collapsible section
* closes. Create it with useRef('') alongside the form.
*/
lastValidSelectionRef: { current: string }
/**
* Used when creating a project. If true, it will filter all versions to only OrioleDB versions (or exclude them if false).
*
* Defaults to undefined, which is not filtering.
*/
useOrioleDbForCreate?: boolean
type?: 'create' | 'unpause'
layout?: 'vertical' | 'horizontal'
label?: string
description?: string
}
const formatValue = ({
postgres_engine,
release_channel,
}: {
postgres_engine: string
release_channel: string
}) => {
return `${postgres_engine}|${release_channel}`
}
export const PostgresVersionSelector = ({
cloudProvider,
dbRegion,
disabled = false,
organizationSlug,
highAvailability,
field,
form,
lastValidSelectionRef,
type = 'create',
useOrioleDbForCreate = undefined,
layout = 'horizontal',
label = 'Postgres version',
description,
}: PostgresVersionSelectorProps) => {
const { data: project } = useSelectedProjectQuery()
const dbRegionExact = smartRegionToExactRegion(dbRegion)
const {
data: createVersions,
isPending: isLoadingProjectCreateVersions,
isSuccess,
} = useProjectCreationPostgresVersionsQuery(
{
cloudProvider,
dbRegion: dbRegionExact,
organizationSlug,
highAvailability,
},
{
enabled: type === 'create',
select: (data) => {
if (useOrioleDbForCreate === undefined) return data
return {
available_versions: data?.available_versions.filter((version) =>
useOrioleDbForCreate
? version.postgres_engine.endsWith('oriole')
: !version.postgres_engine.endsWith('oriole')
),
}
},
}
)
const { data: unpauseVersions, isPending: isLoadingProjectUnpauseVersions } =
useProjectUnpausePostgresVersionsQuery(
{ projectRef: project?.ref },
{ enabled: type === 'unpause' }
)
const versions =
type === 'create'
? (createVersions?.available_versions ?? [])
: (unpauseVersions?.available_versions ?? [])
const availableVersions = versions.sort((a, b) => a.version.localeCompare(b.version)).reverse()
const postgresVersionSelection = useWatch({
control: form.control,
name: 'postgresVersionSelection',
})
// react-hook-form intermittently drops this field's value when its Controller
// remounts, so a one-shot "set the default once versions load" effect leaves
// the select stuck empty. Instead this effect re-asserts off the watched value:
// a selection present in the current list is kept (and remembered in the
// owner-held lastValidSelectionRef), and when the value is missing or cleared
// out from under us it restores the last valid selection, falling back to the
// GA default.
useEffect(() => {
if (availableVersions.length === 0) return
const isSelectionAvailable = (selection: string) =>
availableVersions.some((version) => formatValue(version) === selection)
if (postgresVersionSelection && isSelectionAvailable(postgresVersionSelection)) {
lastValidSelectionRef.current = postgresVersionSelection
return
}
if (isSelectionAvailable(lastValidSelectionRef.current)) {
form.setValue('postgresVersionSelection', lastValidSelectionRef.current)
return
}
const gaVersion = availableVersions.find((x) => x.release_channel === 'ga')
const defaultValue = gaVersion ? formatValue(gaVersion) : formatValue(availableVersions[0])
form.setValue('postgresVersionSelection', defaultValue)
}, [isSuccess, availableVersions, postgresVersionSelection, lastValidSelectionRef, form])
return (
<FormItemLayout id={field.name} label={label} layout={layout} description={description}>
<Select
value={postgresVersionSelection}
onValueChange={field.onChange}
disabled={
disabled ||
availableVersions.length === 0 ||
(type === 'create' && isLoadingProjectCreateVersions) ||
(type === 'unpause' && isLoadingProjectUnpauseVersions)
}
>
<SelectTrigger
id={field.name}
className="[&>:nth-child(1)]:w-full [&>:nth-child(1)]:flex [&>:nth-child(1)]:items-start"
>
<SelectValue placeholder="Select a Postgres version for your project" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{availableVersions.map((value) => {
const postgresVersion = value.version
.split('supabase-postgres-')[1]
.replace('-orioledb', '')
return (
<SelectItem
key={formatValue(value)}
value={formatValue(value)}
className="w-full [&>:nth-child(2)]:w-full"
>
<div className="flex flex-row items-center justify-between w-full">
<span className="text-foreground">{postgresVersion}</span>
<div className="flex flex-row gap-x-2">
{value.release_channel !== 'ga' && (
<Badge variant="warning">{value.release_channel}</Badge>
)}
{value.postgres_engine.includes('oriole') && (
<Badge variant="default">OrioleDB</Badge>
)}
</div>
</div>
</SelectItem>
)
})}
</SelectGroup>
</SelectContent>
</Select>
</FormItemLayout>
)
}