import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Badge, Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogSection, DialogSectionSeparator, DialogTitle, Form, FormControl, FormField, Input, Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue, } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import * as z from 'zod' import { extensionsWithRecommendedSchemas } from './Extensions.constants' import { DocsButton } from '@/components/ui/DocsButton' import { useDatabaseExtensionEnableMutation } from '@/data/database-extensions/database-extension-enable-mutation' import { type DatabaseExtension } from '@/data/database-extensions/database-extensions-query' import { useSchemasQuery } from '@/data/database/schemas-query' import { useIsOrioleDb, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useProtectedSchemas } from '@/hooks/useProtectedSchemas' import { DOCS_URL } from '@/lib/constants' const orioleExtCallOuts = ['vector', 'postgis'] const FormSchema = z.object({ name: z.string(), schema: z.string() }).superRefine((val, ctx) => { if (val.schema === 'custom' && val.name.length === 0) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['name'], message: 'Please provide a name for the schema', }) } }) interface EnableExtensionModalProps { visible: boolean extension: DatabaseExtension onCancel: () => void } export const EnableExtensionModal = ({ visible, extension, onCancel, }: EnableExtensionModalProps) => { const isOrioleDb = useIsOrioleDb() const { data: project } = useSelectedProjectQuery() const { data: protectedSchemas } = useProtectedSchemas({ excludeSchemas: ['extensions'] }) const recommendedSchema = extensionsWithRecommendedSchemas[extension.name] const { data: schemas = [], isPending: isLoading } = useSchemasQuery( { projectRef: project?.ref, connectionString: project?.connectionString, }, { enabled: visible } ) const availableSchemas = schemas.filter( (schema) => schema.name === recommendedSchema || !protectedSchemas.some((protectedSchema) => protectedSchema.name === schema.name) ) // [Joshen] Hard-coding pg_cron here as this is enforced on our end (Not via pg_available_extension_versions) const defaultSchema = extension.name === 'pg_cron' ? 'pg_catalog' : extension.default_version_schema const { mutate: enableExtension, isPending: isEnabling } = useDatabaseExtensionEnableMutation({ onSuccess: () => { toast.success(`Extension "${extension.name}" is now enabled`) onCancel() }, onError: (error) => { toast.error(`Failed to enable ${extension.name}: ${error.message}`) }, }) const defaultValues = { name: extension.name, schema: recommendedSchema ?? 'extensions' } const form = useForm>({ mode: 'onBlur', reValidateMode: 'onBlur', resolver: zodResolver(FormSchema as any), defaultValues, }) const { schema } = form.watch() const onSubmit = async (values: z.infer) => { if (project === undefined) return console.error('Project is required') const schema = defaultSchema !== undefined && defaultSchema !== null ? defaultSchema : values.schema === 'custom' ? values.name : values.schema enableExtension({ projectRef: project.ref, connectionString: project?.connectionString, schema, name: extension.name, version: extension.default_version, cascade: true, createSchema: !schema.startsWith('pg_'), }) } return ( { if (!open) onCancel() }} > Enable {extension.name} {isOrioleDb && orioleExtCallOuts.includes(extension.name) && ( {extension.name} cannot be accelerated by indexes on tables that are using the OrioleDB access method )} {extension.name === 'pg_cron' && project?.cloud_provider === 'FLY' && (

You can still enable the extension, but pg_cron jobs may not run due to the behavior of Fly projects.

)}
{isLoading ? (
) : !!defaultSchema ? (

Extension must be installed in the "{defaultSchema}" schema.

) : (
( )} /> {!!recommendedSchema && (

Use the "{recommendedSchema}" schema for full compatibility with related features.

)} {schema === 'custom' && ( ( )} /> )}
)}
) }