diff --git a/apps/studio/components/interfaces/Database/Extensions/Extensions.constants.ts b/apps/studio/components/interfaces/Database/Extensions/Extensions.constants.ts index 065adf1b7dc..0bb9c07e8f5 100644 --- a/apps/studio/components/interfaces/Database/Extensions/Extensions.constants.ts +++ b/apps/studio/components/interfaces/Database/Extensions/Extensions.constants.ts @@ -19,10 +19,13 @@ export const HIDDEN_EXTENSIONS = [ export const SEARCH_TERMS: Record = { vector: ['pgvector', 'pg_vector'], + pg_partman: ['partman', 'partition', 'partitioned'], } export const EXTENSION_DISABLE_WARNINGS: Record = { pg_cron: 'Disabling this extension will delete all scheduled jobs. This cannot be undone.', + pg_partman: + 'Disabling this extension will stop automatic partition management for any partitioned queues. New partitions will no longer be created and retention policies will no longer be enforced.', } // Extensions that have recommended schemas (rather than required schemas) diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet.tsx b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet.tsx index b01387918a1..e877dc3243f 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet.tsx @@ -1,16 +1,15 @@ import { zodResolver } from '@hookform/resolvers/zod' -import { SubmitHandler, useForm } from 'react-hook-form' -import { toast } from 'sonner' -import z from 'zod' - +import { EnableExtensionModal } from 'components/interfaces/Database/Extensions/EnableExtensionModal' import { Markdown } from 'components/interfaces/Markdown' -import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' +import { DiscardChangesConfirmationDialog } from 'components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog' import { useDatabaseQueueCreateMutation } from 'data/database-queues/database-queues-create-mutation' import { useQueuesExposePostgrestStatusQuery } from 'data/database-queues/database-queues-expose-postgrest-status-query' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useConfirmOnClose } from 'hooks/ui/useConfirmOnClose' import { useRouter } from 'next/router' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { toast } from 'sonner' import { Badge, Button, @@ -31,10 +30,12 @@ import { SheetTitle, } from 'ui' import { Admonition } from 'ui-patterns' -import { DiscardChangesConfirmationDialog } from 'components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import z from 'zod' + import { QUEUE_TYPES } from './Queues.constants' import { QueryNameSchema } from './Queues.utils' +import { usePgPartmanStatus } from './usePgPartmanStatus' export interface CreateQueueSheetProps { visible: boolean @@ -47,8 +48,8 @@ const normalQueueSchema = z.object({ const partitionedQueueSchema = z.object({ type: z.literal('partitioned'), - partitionInterval: z.number(), - retentionInterval: z.number(), + partitionInterval: z.coerce.number(), + retentionInterval: z.coerce.number(), }) const unloggedQueueSchema = z.object({ @@ -81,21 +82,32 @@ export const CreateQueueSheet = ({ visible, onClose }: CreateQueueSheetProps) => const { mutate: createQueue, isPending } = useDatabaseQueueCreateMutation() + const { + pgPartmanExtension, + isAvailable: pgPartmanAvailable, + isInstalled: pgPartmanInstalled, + } = usePgPartmanStatus() + + const defaultValues: CreateQueueForm = pgPartmanInstalled + ? { + name: '', + enableRls: true, + values: { type: 'partitioned', partitionInterval: 0, retentionInterval: 0 }, + } + : { name: '', enableRls: true, values: { type: 'basic' } } + const form = useForm({ resolver: zodResolver(FormSchema), - defaultValues: { - name: '', - enableRls: true, - values: { type: 'basic' }, - }, + defaultValues, }) useEffect(() => { if (visible) { - form.reset() + form.reset(defaultValues) } }, [ form, + pgPartmanInstalled, // end of stable references visible, ]) @@ -133,13 +145,7 @@ export const CreateQueueSheet = ({ visible, onClose }: CreateQueueSheetProps) => ) } - const { data } = useDatabaseExtensionsQuery({ - projectRef: project?.ref, - connectionString: project?.connectionString, - }) - - const pgPartmanExtension = (data ?? []).find((ext) => ext.name === 'pg_partman') - const pgPartmanExtensionInstalled = pgPartmanExtension?.installed_version != undefined + const [showEnableExtensionModal, setShowEnableExtensionModal] = useState(false) const queueType = form.watch('values.type') @@ -189,40 +195,63 @@ export const CreateQueueSheet = ({ visible, onClose }: CreateQueueSheetProps) => disabled={field.disabled} onValueChange={field.onChange} > - {QUEUE_TYPES.map((definition) => ( - -
-
{definition.icon}
-
-
-

- {definition.label} + {QUEUE_TYPES.filter( + (definition) => + definition.value !== 'partitioned' || pgPartmanInstalled + ).map((definition) => { + const isPartitioned = definition.value === 'partitioned' + + return ( + +

+
{definition.icon}
+
+
+

+ {definition.label} +

+ {isPartitioned && ( + Recommended + )} +
+

+ {isPartitioned + ? 'Automatically manages data retention and improves performance for high-volume queues via pg_partman.' + : definition.description}

- {definition.value === 'partitioned' && ( - Coming soon - )}
-

- {definition.description} -

-
- - ))} + + ) + })} )} /> + {pgPartmanAvailable && !pgPartmanInstalled && ( + + + + )} {queueType === 'partitioned' && ( @@ -323,6 +352,13 @@ export const CreateQueueSheet = ({ visible, onClose }: CreateQueueSheetProps) =>
+ {pgPartmanExtension && !pgPartmanInstalled && ( + setShowEnableExtensionModal(false)} + /> + )} ) diff --git a/apps/studio/components/interfaces/Integrations/Queues/usePgPartmanStatus.ts b/apps/studio/components/interfaces/Integrations/Queues/usePgPartmanStatus.ts new file mode 100644 index 00000000000..22d2aaf6dfb --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/Queues/usePgPartmanStatus.ts @@ -0,0 +1,17 @@ +import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' +import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' + +export function usePgPartmanStatus() { + const { data: project } = useSelectedProjectQuery() + + const { data: extensions } = useDatabaseExtensionsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + + const pgPartmanExtension = (extensions ?? []).find((ext) => ext.name === 'pg_partman') + const isAvailable = pgPartmanExtension !== undefined + const isInstalled = pgPartmanExtension?.installed_version != undefined + + return { pgPartmanExtension, isAvailable, isInstalled } +}