import { Check, ChevronsUpDown } from 'lucide-react' import { useMemo, useState } from 'react' import { Button, cn, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, ScrollArea, } from 'ui' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { getExposedSchemaCounts } from './ExposedSchemaSelector.utils' import { useSchemasQuery } from '@/data/database/schemas-query' import { MULTIGRES_SCHEMA_NAME, useHighAvailability, useSchemasFilteredForHighAvailability, } from '@/hooks/misc/useHighAvailability' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { INTERNAL_SCHEMAS } from '@/hooks/useProtectedSchemas' import { pluralize } from '@/lib/helpers' /** * [Joshen] This would only affect graphql_public and pgmq_public, given that they're intended * to be public, we can let users expose them via the API, but not let them adjust the schema via the dashboard * */ export const internalSchemasCannotExpose = new Set( INTERNAL_SCHEMAS.filter((x) => !x.endsWith('_public')) ) interface ExposedSchemaSelectorProps { /** * When true the dropdown can still be opened to inspect which schemas are exposed (e.g. * self-hosted, where schemas are managed via PGRST_DB_SCHEMAS), but schemas can't be toggled. */ readOnly?: boolean selectedSchemas: string[] onToggleSchema: (schema: string) => void } export const ExposedSchemaSelector = ({ readOnly = false, selectedSchemas, onToggleSchema, }: ExposedSchemaSelectorProps) => { const [open, setOpen] = useState(false) const { data: project } = useSelectedProjectQuery() const { isHighAvailability } = useHighAvailability() const { data: allSchemas, isPending, isError, isSuccess, } = useSchemasQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const visibleSchemas = useSchemasFilteredForHighAvailability(allSchemas) const schemas = useMemo( () => visibleSchemas .filter((s) => !internalSchemasCannotExpose.has(s.name)) .sort((a, b) => a.name.localeCompare(b.name)), [visibleSchemas] ) // Persisted selections go through the same HA filtering as the schema list, so a // multigres schema exposed in the config doesn't render as a "missing" schema row. const visibleSelectedSchemas = useMemo( () => isHighAvailability ? selectedSchemas.filter((schema) => schema !== MULTIGRES_SCHEMA_NAME) : selectedSchemas, [selectedSchemas, isHighAvailability] ) const missingExposedSchema = useMemo( () => visibleSelectedSchemas.filter((schema) => !schemas.some((s) => s.name === schema)), [schemas, visibleSelectedSchemas] ) const selectedSet = useMemo(() => new Set(visibleSelectedSchemas), [visibleSelectedSchemas]) const { selectedCount, totalCount } = getExposedSchemaCounts({ visibleSchemas: schemas.map((s) => s.name), selectedSchemas: visibleSelectedSchemas, protectedSchemas: internalSchemasCannotExpose, }) return ( {isPending ? ( <>
) : isError ? (

Failed to retrieve schemas

) : ( <>

No schemas found

7 ? 'h-[210px]' : ''}> {missingExposedSchema.map((schema) => ( { if (readOnly) return onToggleSchema(schema) }} >
{schema}
{internalSchemasCannotExpose.has(schema) ? ( This schema is protected and should not be exposed ) : ( This schema does not exist and can be safely removed )}
))} {schemas.map((schema) => { const isExposed = selectedSet.has(schema.name) return ( { if (readOnly) return onToggleSchema(schema.name) }} >
{isExposed && } {schema.name}
) })}
)}
) }