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, cn, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Form, FormControl, FormField, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetContent, SheetFooter, SheetHeader, SheetSection, SheetTitle, } from 'ui' import { Admonition } from 'ui-patterns/Admonition' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { MultiSelector, MultiSelectorContent, MultiSelectorItem, MultiSelectorList, 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' import { DocsButton } from '@/components/ui/DocsButton' import { useDatabaseIndexCreateMutation } from '@/data/database-indexes/index-create-mutation' import { useSchemasQuery } from '@/data/database/schemas-query' import { useTableColumnsQuery } from '@/data/database/table-columns-query' import { useEntityTypesQuery } from '@/data/entity-types/entity-types-infinite-query' import { useSchemasFilteredForHighAvailability } from '@/hooks/misc/useHighAvailability' import { useIsOrioleDb, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' interface CreateIndexSidePanelProps { visible: boolean 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 export const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelProps) => { const { data: project } = useSelectedProjectQuery() const isOrioleDb = useIsOrioleDb() const form = useForm({ 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('') const [searchTerm, setSearchTerm] = useState('') const { data: allSchemas } = useSchemasQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const schemas = useSchemasFilteredForHighAvailability(allSchemas) const { data: entities, isPending: isLoadingEntities } = useEntityTypesQuery({ schemas: [selectedSchema], sort: 'alphabetical', search: searchTerm, projectRef: project?.ref, connectionString: project?.connectionString, }) const { data: tableColumns, isPending: isLoadingTableColumns, isSuccess: isSuccessTableColumns, } = useTableColumnsQuery({ schema: selectedSchema, table: selectedEntity, projectRef: project?.ref, connectionString: project?.connectionString, }) const { mutate: createIndex, isPending: isExecuting } = useDatabaseIndexCreateMutation({ onSuccess: () => { onClose() toast.success(`Successfully created index`) }, }) const entityTypes = useMemo( () => entities?.pages.flatMap((page) => page.data.entities) || [], [entities?.pages] ) function handleSearchChange(value: string) { setSearchTerm(value) } const columns = tableColumns?.[0]?.columns ?? [] const columnOptions = columns .filter((column): column is NonNullable => column !== null) .map((column) => ({ id: column.attname, value: column.attname, name: column.attname, disabled: false, })) const generatedSQL = ` CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexType} (${selectedColumns .map((column) => `"${column}"`) .join(', ')}); `.trim() const { reset } = form useEffect(() => { if (visible) { reset() setSchemaSearchTerm('') setSearchTerm('') } }, [visible, reset]) useEffect(() => { if (!schemaDropdownOpen) setSchemaSearchTerm('') }, [schemaDropdownOpen]) const isSelectEntityDisabled = entityTypes.length === 0 && searchTerm.trim().length === 0 function onSubmit(values: z.infer) { 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 ( onClose()}> Create new index
( 7 && 'max-h-[210px]! overflow-y-auto' )} onWheel={(event) => event.stopPropagation()} > No schemas found {(schemas ?? []).map((schema) => ( { field.onChange(schema.name) form.setValue('table', '') form.setValue('columns', []) form.setValue('type', INDEX_TYPES[0].value) setSearchTerm('') }} > {schema.name} ))} )} /> ( {/* [Terry] shouldFilter context: https://github.com/pacocoursey/cmdk/issues/267#issuecomment-2252717107 */} 7 && 'max-h-[210px]! overflow-y-auto' )} onWheel={(event) => event.stopPropagation()} > {isLoadingEntities ? (
Loading...
) : ( 'No tables found' )}
{entityTypes.map((entity) => ( { field.onChange(entity.name) setTableDropdownOpen(false) form.setValue('columns', []) form.setValue('type', INDEX_TYPES[0].value) }} > {entity.name} ))}
)} />
{selectedEntity && ( ( {isLoadingTableColumns && } {isSuccessTableColumns && (
{columnOptions.map((option) => ( {option.name} ))}
)}
)} />
)} {selectedColumns.length > 0 && ( <> ( <> {isOrioleDb && ( {/* [Joshen Oriole] Hook up proper docs URL */} )} )} />

Preview of SQL statement

)}
) }