mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore: create index form (#46288)
## 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? Refactored the Database Create Index side panel to align with the recommended Studio form patterns. ## What is the current behavior? The side panel was using a legacy layout that did not follow the common Studio form patterns. It's also not a real form. ## What is the new behavior? The Create Index side panel in the Database section is now consistent with the Studio form patterns. It's now a real form using `react-hook-form` ## Additional context Before: <img width="668" height="855" alt="image" src="https://github.com/user-attachments/assets/d15ee63c-8d03-4780-8945-1792f0493133" /> After: <img width="606" height="856" alt="image" src="https://github.com/user-attachments/assets/78e0269c-e79c-42d1-a2c2-9465c0b0c46a" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **UI Improvements** * Reworked index-creation panel into a validated, form-driven workflow with clearer field interactions, multi-column selector, live read-only SQL preview, and updated submit/cancel behavior. * Footer actions: Cancel resets the form and closes the panel; Create triggers form submission and reflects loading state. * **Tests** * Updated end-to-end flow to click "Select a table" (replacing "Choose a table") during index creation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com>
This commit is contained in:
1 parent
ab93452cdf
commit
d745036c9e
2 files changed
+440
-307
No files matched your search
@@ -1,6 +1,8 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Check, ChevronsUpDown, Loader2 } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||
import { useForm, useWatch } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Button,
|
||||
@@ -11,6 +13,9 @@ import {
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
@@ -20,7 +25,13 @@ import {
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SidePanel,
|
||||
Separator,
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetSection,
|
||||
SheetTitle,
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
@@ -32,6 +43,7 @@ import {
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { INDEX_TYPES } from './Indexes.constants'
|
||||
import CodeEditor from '@/components/ui/CodeEditor/CodeEditor'
|
||||
@@ -48,14 +60,39 @@ interface CreateIndexSidePanelProps {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const formSchema = z.object({
|
||||
schema: z.string().min(1, 'Please provide a name for your schema'),
|
||||
table: z.string().min(1, 'Please provide a name for your table'),
|
||||
columns: z
|
||||
.array(z.string())
|
||||
.min(1, 'Please select at least one column')
|
||||
.max(32, 'You can select up to 32 columns'),
|
||||
type: z.string().min(1, 'Please select an index type'),
|
||||
})
|
||||
|
||||
type FormSchema = z.infer<typeof formSchema>
|
||||
|
||||
export const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelProps) => {
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const isOrioleDb = useIsOrioleDb()
|
||||
|
||||
const [selectedSchema, setSelectedSchema] = useState('public')
|
||||
const [selectedEntity, setSelectedEntity] = useState<string | undefined>(undefined)
|
||||
const [selectedColumns, setSelectedColumns] = useState<string[]>([])
|
||||
const [selectedIndexType, setSelectedIndexType] = useState<string>(INDEX_TYPES[0].value)
|
||||
const form = useForm<FormSchema>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
schema: 'public',
|
||||
table: '',
|
||||
columns: [],
|
||||
type: INDEX_TYPES[0].value,
|
||||
},
|
||||
})
|
||||
|
||||
const formId = 'schema-form'
|
||||
|
||||
const selectedSchema = useWatch({ name: 'schema', control: form.control })
|
||||
const selectedEntity = useWatch({ name: 'table', control: form.control })
|
||||
const selectedColumns = useWatch({ name: 'columns', control: form.control }) ?? []
|
||||
const selectedIndexType = useWatch({ name: 'type', control: form.control })
|
||||
|
||||
const [schemaDropdownOpen, setSchemaDropdownOpen] = useState(false)
|
||||
const [tableDropdownOpen, setTableDropdownOpen] = useState(false)
|
||||
const [schemaSearchTerm, setSchemaSearchTerm] = useState('')
|
||||
@@ -98,13 +135,6 @@ export const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelP
|
||||
setSearchTerm(value)
|
||||
}
|
||||
|
||||
function handleSchemaSelect(schema: string) {
|
||||
setSelectedSchema(schema)
|
||||
setSearchTerm('')
|
||||
setSchemaSearchTerm('')
|
||||
setSchemaDropdownOpen(false)
|
||||
}
|
||||
|
||||
const columns = tableColumns?.[0]?.columns ?? []
|
||||
const columnOptions = columns
|
||||
.filter((column): column is NonNullable<typeof column> => column !== null)
|
||||
@@ -121,44 +151,14 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp
|
||||
.join(', ')});
|
||||
`.trim()
|
||||
|
||||
const onSaveIndex = () => {
|
||||
if (!project) return console.error('Project is required')
|
||||
if (!selectedEntity) return console.error('Entity is required')
|
||||
|
||||
createIndex({
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
payload: {
|
||||
schema: selectedSchema,
|
||||
entity: selectedEntity,
|
||||
type: selectedIndexType,
|
||||
columns: selectedColumns,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const { reset } = form
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setSelectedSchema('public')
|
||||
setSelectedEntity('')
|
||||
setSelectedColumns([])
|
||||
setSelectedIndexType(INDEX_TYPES[0].value)
|
||||
reset()
|
||||
setSchemaSearchTerm('')
|
||||
setSearchTerm('')
|
||||
}
|
||||
}, [visible])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedEntity('')
|
||||
setSelectedColumns([])
|
||||
setSelectedIndexType(INDEX_TYPES[0].value)
|
||||
setSearchTerm('')
|
||||
}, [selectedSchema])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedColumns([])
|
||||
setSelectedIndexType(INDEX_TYPES[0].value)
|
||||
}, [selectedEntity])
|
||||
}, [visible, reset])
|
||||
|
||||
useEffect(() => {
|
||||
if (!schemaDropdownOpen) setSchemaSearchTerm('')
|
||||
@@ -166,272 +166,405 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp
|
||||
|
||||
const isSelectEntityDisabled = entityTypes.length === 0 && searchTerm.trim().length === 0
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
if (!project) return console.error('Project is required')
|
||||
if (!selectedEntity) return console.error('Entity is required')
|
||||
|
||||
createIndex({
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
payload: {
|
||||
schema: values.schema,
|
||||
entity: values.table,
|
||||
type: values.type,
|
||||
columns: values.columns,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<SidePanel
|
||||
size="large"
|
||||
header="Create new index"
|
||||
visible={visible}
|
||||
onCancel={onClose}
|
||||
onConfirm={() => onSaveIndex()}
|
||||
loading={isExecuting}
|
||||
confirmText="Create index"
|
||||
>
|
||||
<div className="py-6 space-y-6">
|
||||
<SidePanel.Content className="space-y-6">
|
||||
<FormItemLayout label="Select a schema" name="select-schema" isReactForm={false}>
|
||||
<Popover modal={false} open={schemaDropdownOpen} onOpenChange={setSchemaDropdownOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="default"
|
||||
size={'medium'}
|
||||
className={`w-full [&>span]:w-full text-left`}
|
||||
iconRight={
|
||||
<ChevronsUpDown className="text-foreground-muted" strokeWidth={2} size={14} />
|
||||
}
|
||||
>
|
||||
{selectedSchema !== undefined && selectedSchema !== ''
|
||||
? selectedSchema
|
||||
: 'Choose a schema'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" side="bottom" align="start" sameWidthAsTrigger>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Find schema..."
|
||||
value={schemaSearchTerm}
|
||||
onValueChange={setSchemaSearchTerm}
|
||||
/>
|
||||
<CommandList
|
||||
className={cn((schemas ?? []).length > 7 && 'max-h-[210px]! overflow-y-auto')}
|
||||
onWheel={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CommandEmpty>No schemas found</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{(schemas ?? []).map((schema) => (
|
||||
<CommandItem
|
||||
key={schema.name}
|
||||
className="cursor-pointer flex items-center justify-between space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
handleSchemaSelect(schema.name)
|
||||
}}
|
||||
onClick={() => {
|
||||
handleSchemaSelect(schema.name)
|
||||
}}
|
||||
>
|
||||
<span>{schema.name}</span>
|
||||
{selectedSchema === schema.name && (
|
||||
<Check className="text-brand" strokeWidth={2} size={16} />
|
||||
)}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormItemLayout>
|
||||
|
||||
<FormItemLayout
|
||||
label="Select a table"
|
||||
name="select-table"
|
||||
description={
|
||||
isSelectEntityDisabled &&
|
||||
!isLoadingEntities &&
|
||||
'Create a table in this schema via the Table or SQL editor first'
|
||||
}
|
||||
isReactForm={false}
|
||||
<Sheet open={visible} onOpenChange={() => onClose()}>
|
||||
<SheetContent size="lg" className="flex flex-col gap-0">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Create new index</SheetTitle>
|
||||
</SheetHeader>
|
||||
<Form {...form}>
|
||||
<form
|
||||
id={formId}
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="overflow-auto grow px-0"
|
||||
>
|
||||
<Popover modal={false} open={tableDropdownOpen} onOpenChange={setTableDropdownOpen}>
|
||||
<PopoverTrigger asChild disabled={isSelectEntityDisabled || isLoadingEntities}>
|
||||
<Button
|
||||
type="default"
|
||||
size="medium"
|
||||
className={cn(
|
||||
'w-full [&>span]:w-full text-left',
|
||||
selectedEntity === '' && 'text-foreground-lighter'
|
||||
)}
|
||||
iconRight={
|
||||
<ChevronsUpDown className="text-foreground-muted" strokeWidth={2} size={14} />
|
||||
}
|
||||
>
|
||||
{selectedEntity !== undefined && selectedEntity !== ''
|
||||
? selectedEntity
|
||||
: isSelectEntityDisabled
|
||||
? 'No tables available in schema'
|
||||
: 'Choose a table'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" side="bottom" align="start" sameWidthAsTrigger>
|
||||
{/* [Terry] shouldFilter context:
|
||||
https://github.com/pacocoursey/cmdk/issues/267#issuecomment-2252717107 */}
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Find table..."
|
||||
value={searchTerm}
|
||||
onValueChange={handleSearchChange}
|
||||
/>
|
||||
<CommandList
|
||||
className={cn(entityTypes.length > 7 && 'max-h-[210px]! overflow-y-auto')}
|
||||
onWheel={(event) => event.stopPropagation()}
|
||||
<SheetSection>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="schema"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="horizontal" label="Select a schema" id="schema">
|
||||
<FormControl className="col-span-6">
|
||||
<Popover
|
||||
modal={false}
|
||||
open={schemaDropdownOpen}
|
||||
onOpenChange={setSchemaDropdownOpen}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="schema"
|
||||
type="default"
|
||||
size={'medium'}
|
||||
className={`w-full [&>span]:w-full text-left`}
|
||||
iconRight={
|
||||
<ChevronsUpDown
|
||||
className="text-foreground-muted"
|
||||
strokeWidth={2}
|
||||
size={14}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{field.value || 'Choose a schema'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
side="bottom"
|
||||
align="start"
|
||||
sameWidthAsTrigger
|
||||
>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Find schema..."
|
||||
value={schemaSearchTerm}
|
||||
onValueChange={setSchemaSearchTerm}
|
||||
/>
|
||||
<CommandList
|
||||
className={cn(
|
||||
(schemas ?? []).length > 7 && 'max-h-[210px]! overflow-y-auto'
|
||||
)}
|
||||
onWheel={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CommandEmpty>No schemas found</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{(schemas ?? []).map((schema) => (
|
||||
<CommandItem
|
||||
key={schema.name}
|
||||
value={schema.name}
|
||||
className="cursor-pointer flex items-center space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
field.onChange(schema.name)
|
||||
form.setValue('table', '')
|
||||
form.setValue('columns', [])
|
||||
form.setValue('type', INDEX_TYPES[0].value)
|
||||
setSearchTerm('')
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'text-brand',
|
||||
schema.name === field.value ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
strokeWidth={2}
|
||||
size={16}
|
||||
/>
|
||||
<span>{schema.name}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</SheetSection>
|
||||
|
||||
<Separator className="w-full" />
|
||||
|
||||
<SheetSection>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="table"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="horizontal"
|
||||
label="Select a table"
|
||||
id="table"
|
||||
description={
|
||||
isSelectEntityDisabled &&
|
||||
!isLoadingEntities &&
|
||||
'Create a table in this schema via the Table or SQL editor first'
|
||||
}
|
||||
>
|
||||
<CommandEmpty>
|
||||
{isLoadingEntities ? (
|
||||
<div className="flex items-center gap-2 text-center justify-center">
|
||||
<Loader2 size={12} className="animate-spin" />
|
||||
Loading...
|
||||
<FormControl className="col-span-6">
|
||||
<Popover
|
||||
modal={false}
|
||||
open={tableDropdownOpen}
|
||||
onOpenChange={setTableDropdownOpen}
|
||||
>
|
||||
<PopoverTrigger
|
||||
asChild
|
||||
disabled={isSelectEntityDisabled || isLoadingEntities}
|
||||
>
|
||||
<Button
|
||||
id="table"
|
||||
type="default"
|
||||
size="medium"
|
||||
className={cn(
|
||||
'w-full [&>span]:w-full text-left',
|
||||
selectedEntity === '' && 'text-foreground-lighter'
|
||||
)}
|
||||
iconRight={
|
||||
<ChevronsUpDown
|
||||
className="text-foreground-muted"
|
||||
strokeWidth={2}
|
||||
size={14}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{field.value
|
||||
? field.value
|
||||
: isSelectEntityDisabled
|
||||
? 'No tables available in schema'
|
||||
: 'Choose a table'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
side="bottom"
|
||||
align="start"
|
||||
sameWidthAsTrigger
|
||||
>
|
||||
{/* [Terry] shouldFilter context:
|
||||
https://github.com/pacocoursey/cmdk/issues/267#issuecomment-2252717107 */}
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Find table..."
|
||||
value={searchTerm}
|
||||
onValueChange={handleSearchChange}
|
||||
/>
|
||||
<CommandList
|
||||
className={cn(
|
||||
entityTypes.length > 7 && 'max-h-[210px]! overflow-y-auto'
|
||||
)}
|
||||
onWheel={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CommandEmpty>
|
||||
{isLoadingEntities ? (
|
||||
<div className="flex items-center gap-2 text-center justify-center">
|
||||
<Loader2 size={12} className="animate-spin" />
|
||||
Loading...
|
||||
</div>
|
||||
) : (
|
||||
'No tables found'
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{entityTypes.map((entity) => (
|
||||
<CommandItem
|
||||
key={entity.name}
|
||||
className="cursor-pointer flex items-center space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
field.onChange(entity.name)
|
||||
setTableDropdownOpen(false)
|
||||
form.setValue('columns', [])
|
||||
form.setValue('type', INDEX_TYPES[0].value)
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'text-brand',
|
||||
entity.name === field.value ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
strokeWidth={2}
|
||||
size={16}
|
||||
/>
|
||||
<span>{entity.name}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</SheetSection>
|
||||
|
||||
<Separator className="w-full" />
|
||||
|
||||
{selectedEntity && (
|
||||
<SheetSection>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="columns"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="horizontal"
|
||||
label="Select up to 32 columns"
|
||||
id="columns"
|
||||
>
|
||||
{isLoadingTableColumns && <ShimmeringLoader className="py-4" />}
|
||||
{isSuccessTableColumns && (
|
||||
<div className="col-span-6">
|
||||
<MultiSelector
|
||||
onValuesChange={field.onChange}
|
||||
values={field.value}
|
||||
size="small"
|
||||
className="w-full"
|
||||
>
|
||||
<MultiSelectorTrigger
|
||||
id="columns"
|
||||
mode="inline-combobox"
|
||||
label={
|
||||
field.value.length === 0
|
||||
? 'Choose which columns to create an index on'
|
||||
: 'Search for a column'
|
||||
}
|
||||
deletableBadge
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
className="w-full"
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
{columnOptions.map((option) => (
|
||||
<MultiSelectorItem
|
||||
key={option.id}
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
{option.name}
|
||||
</MultiSelectorItem>
|
||||
))}
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
</div>
|
||||
) : (
|
||||
'No tables found'
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{entityTypes.map((entity) => (
|
||||
<CommandItem
|
||||
key={entity.name}
|
||||
className="cursor-pointer flex items-center justify-between space-x-2 w-full"
|
||||
onSelect={() => {
|
||||
setSelectedEntity(entity.name)
|
||||
setTableDropdownOpen(false)
|
||||
}}
|
||||
onClick={() => {
|
||||
setSelectedEntity(entity.name)
|
||||
setTableDropdownOpen(false)
|
||||
}}
|
||||
>
|
||||
<span>{entity.name}</span>
|
||||
{selectedEntity === entity.name && (
|
||||
<Check className="text-brand" strokeWidth={2} size={16} />
|
||||
)}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormItemLayout>
|
||||
|
||||
{selectedEntity && (
|
||||
<FormItemLayout id="columns" label="Select up to 32 columns" isReactForm={false}>
|
||||
{isLoadingTableColumns && <ShimmeringLoader className="py-4" />}
|
||||
{isSuccessTableColumns && (
|
||||
<MultiSelector values={selectedColumns} onValuesChange={setSelectedColumns}>
|
||||
<MultiSelectorTrigger
|
||||
id="columns"
|
||||
mode="inline-combobox"
|
||||
label={
|
||||
selectedColumns.length === 0
|
||||
? 'Choose which columns to create an index on'
|
||||
: 'Search for a column'
|
||||
}
|
||||
deletableBadge
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
{columnOptions.map((option) => (
|
||||
<MultiSelectorItem
|
||||
key={option.id}
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
{option.name}
|
||||
</MultiSelectorItem>
|
||||
))}
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)}
|
||||
</FormItemLayout>
|
||||
)}
|
||||
</SidePanel.Content>
|
||||
|
||||
{selectedColumns.length > 0 && (
|
||||
<>
|
||||
<SidePanel.Separator />
|
||||
<SidePanel.Content className="space-y-6">
|
||||
<FormItemLayout
|
||||
label="Select an index type"
|
||||
name="selected-index-type"
|
||||
isReactForm={false}
|
||||
>
|
||||
<Select
|
||||
disabled={isOrioleDb}
|
||||
value={selectedIndexType}
|
||||
onValueChange={setSelectedIndexType}
|
||||
name="selected-index-type"
|
||||
>
|
||||
<SelectTrigger size={'small'}>
|
||||
<SelectValue className="font-mono">{selectedIndexType}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INDEX_TYPES.map((index, i) => (
|
||||
<Fragment key={index.name}>
|
||||
<SelectItem value={index.value}>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span>{index.name}</span>
|
||||
{index.description.split('\n').map((x, idx) => (
|
||||
<span
|
||||
className="text-foreground-lighter group-focus:text-foreground-light group-data-checked:text-foreground-light"
|
||||
key={`${index.value}-description-${idx}`}
|
||||
>
|
||||
{x}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</SelectItem>
|
||||
{i < INDEX_TYPES.length - 1 && <SelectSeparator />}
|
||||
</Fragment>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItemLayout>
|
||||
{isOrioleDb && (
|
||||
<Admonition
|
||||
type="default"
|
||||
className="mt-2!"
|
||||
title="OrioleDB currently only supports the B-tree index type"
|
||||
description="More index types may be supported when OrioleDB is no longer in preview"
|
||||
>
|
||||
{/* [Joshen Oriole] Hook up proper docs URL */}
|
||||
<DocsButton className="mt-2" abbrev={false} href={`${DOCS_URL}`} />
|
||||
</Admonition>
|
||||
)}
|
||||
</SidePanel.Content>
|
||||
<SidePanel.Separator />
|
||||
<SidePanel.Content>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm">Preview of SQL statement</p>
|
||||
<Button asChild type="default">
|
||||
<Link
|
||||
href={
|
||||
project !== undefined
|
||||
? `/project/${project.ref}/sql/new?content=${generatedSQL}`
|
||||
: '/'
|
||||
}
|
||||
>
|
||||
Open in SQL Editor
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</SidePanel.Content>
|
||||
<div className="h-[200px] mt-2!">
|
||||
<div className="relative h-full">
|
||||
<CodeEditor
|
||||
isReadOnly
|
||||
autofocus={false}
|
||||
id={`${selectedSchema}-${selectedEntity}-${selectedColumns.join(
|
||||
','
|
||||
)}-${selectedIndexType}`}
|
||||
language="pgsql"
|
||||
defaultValue={generatedSQL}
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</SidePanel>
|
||||
</SheetSection>
|
||||
)}
|
||||
|
||||
<Separator className="w-full" />
|
||||
|
||||
{selectedColumns.length > 0 && (
|
||||
<>
|
||||
<SheetSection>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<FormItemLayout layout="horizontal" label="Select an index type">
|
||||
<FormControl className="col-span-6">
|
||||
<Select
|
||||
disabled={isOrioleDb}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue className="font-mono">{selectedIndexType}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{INDEX_TYPES.map((index, i) => (
|
||||
<Fragment key={index.name}>
|
||||
<SelectItem value={index.value}>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span>{index.name}</span>
|
||||
{index.description.split('\n').map((x, idx) => (
|
||||
<span
|
||||
className="text-foreground-lighter group-focus:text-foreground-light group-data-checked:text-foreground-light"
|
||||
key={`${index.value}-description-${idx}`}
|
||||
>
|
||||
{x}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</SelectItem>
|
||||
{i < INDEX_TYPES.length - 1 && <SelectSeparator />}
|
||||
</Fragment>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
|
||||
{isOrioleDb && (
|
||||
<Admonition
|
||||
type="default"
|
||||
className="mt-2!"
|
||||
title="OrioleDB currently only supports the B-tree index type"
|
||||
description="More index types may be supported when OrioleDB is no longer in preview"
|
||||
>
|
||||
{/* [Joshen Oriole] Hook up proper docs URL */}
|
||||
<DocsButton className="mt-2" abbrev={false} href={`${DOCS_URL}`} />
|
||||
</Admonition>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</SheetSection>
|
||||
|
||||
<Separator className="w-full" />
|
||||
<SheetSection>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm">Preview of SQL statement</p>
|
||||
<Button asChild type="default">
|
||||
<Link
|
||||
href={
|
||||
project !== undefined
|
||||
? `/project/${project.ref}/sql/new?content=${generatedSQL}`
|
||||
: '/'
|
||||
}
|
||||
>
|
||||
Open in SQL Editor
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</SheetSection>
|
||||
<div className="h-[200px] mt-2!">
|
||||
<div className="relative h-full">
|
||||
<CodeEditor
|
||||
isReadOnly
|
||||
autofocus={false}
|
||||
id={`${selectedSchema}-${selectedEntity}-${selectedColumns.join(
|
||||
','
|
||||
)}-${selectedIndexType}`}
|
||||
language="pgsql"
|
||||
defaultValue={generatedSQL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
<SheetFooter>
|
||||
<Button
|
||||
type="default"
|
||||
disabled={isExecuting}
|
||||
onClick={() => {
|
||||
form.reset()
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
form={formId}
|
||||
htmlType="submit"
|
||||
disabled={isExecuting}
|
||||
loading={isExecuting}
|
||||
>
|
||||
Create index
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
@@ -730,7 +730,7 @@ test.describe('Database', () => {
|
||||
|
||||
// create new index
|
||||
await page.getByRole('button', { name: 'Create index' }).click()
|
||||
await page.getByRole('button', { name: 'Choose a table' }).click()
|
||||
await page.getByRole('button', { name: 'Select a table' }).click()
|
||||
|
||||
const columnsWait = waitForApiResponse(
|
||||
page,
|
||||
|
||||
Reference in new issue
Block a user