import { Check, ChevronsUpDown, Loader2 } from 'lucide-react' import Link from 'next/link' import { Fragment, useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import CodeEditor from 'components/ui/CodeEditor/CodeEditor' import { DocsButton } from 'components/ui/DocsButton' import ShimmeringLoader from 'components/ui/ShimmeringLoader' 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 { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { DOCS_URL } from 'lib/constants' import { Button, CommandEmpty_Shadcn_, CommandGroup_Shadcn_, CommandInput_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_, Command_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_, ScrollArea, SelectContent_Shadcn_, SelectItem_Shadcn_, SelectSeparator_Shadcn_, SelectTrigger_Shadcn_, SelectValue_Shadcn_, Select_Shadcn_, SidePanel, cn, } from 'ui' import { Admonition } from 'ui-patterns' import { MultiSelectOption } from 'ui-patterns/MultiSelectDeprecated' import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { INDEX_TYPES } from './Indexes.constants' interface CreateIndexSidePanelProps { visible: boolean onClose: () => void } const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelProps) => { const { data: project } = useSelectedProjectQuery() const isOrioleDb = useIsOrioleDb() const [selectedSchema, setSelectedSchema] = useState('public') const [selectedEntity, setSelectedEntity] = useState(undefined) const [selectedColumns, setSelectedColumns] = useState([]) const [selectedIndexType, setSelectedIndexType] = useState(INDEX_TYPES[0].value) const [schemaDropdownOpen, setSchemaDropdownOpen] = useState(false) const [tableDropdownOpen, setTableDropdownOpen] = useState(false) const [searchTerm, setSearchTerm] = useState('') const { data: schemas } = useSchemasQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const { data: entities, isLoading: isLoadingEntities } = useEntityTypesQuery({ schemas: [selectedSchema], sort: 'alphabetical', search: searchTerm, projectRef: project?.ref, connectionString: project?.connectionString, }) const { data: tableColumns, isLoading: 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: MultiSelectOption[] = 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 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, }, }) } useEffect(() => { if (visible) { setSelectedSchema('public') setSelectedEntity('') setSelectedColumns([]) setSelectedIndexType(INDEX_TYPES[0].value) } }, [visible]) useEffect(() => { setSelectedEntity('') setSelectedColumns([]) setSelectedIndexType(INDEX_TYPES[0].value) }, [selectedSchema]) useEffect(() => { setSelectedColumns([]) setSelectedIndexType(INDEX_TYPES[0].value) }, [selectedEntity]) const isSelectEntityDisabled = entityTypes.length === 0 return ( onSaveIndex()} loading={isExecuting} confirmText="Create index" >
No schemas found 7 ? 'h-[210px]' : ''}> {(schemas ?? []).map((schema) => ( { setSelectedSchema(schema.name) setSchemaDropdownOpen(false) }} onClick={() => { setSelectedSchema(schema.name) setSchemaDropdownOpen(false) }} > {schema.name} {selectedEntity === schema.name && ( )} ))} {/* [Terry] shouldFilter context: https://github.com/pacocoursey/cmdk/issues/267#issuecomment-2252717107 */} {isLoadingEntities ? (
Loading...
) : ( 'No tables found' )}
7 ? 'h-[210px]' : ''}> {(entityTypes ?? []).map((entity) => ( { setSelectedEntity(entity.name) setTableDropdownOpen(false) }} onClick={() => { setSelectedEntity(entity.name) setTableDropdownOpen(false) }} > {entity.name} {selectedEntity === entity.name && ( )} ))}
{selectedEntity && ( {isLoadingTableColumns && } {isSuccessTableColumns && ( )} )}
{selectedColumns.length > 0 && ( <> {selectedIndexType} {INDEX_TYPES.map((index, i) => (
{index.name} {index.description.split('\n').map((x, idx) => ( {x} ))}
{i < INDEX_TYPES.length - 1 && }
))}
{isOrioleDb && ( {/* [Joshen Oriole] Hook up proper docs URL */} )}

Preview of SQL statement

)}
) } export default CreateIndexSidePanel