From 30eb3ac4ef08b04018b15e3608ffad10feb3bca3 Mon Sep 17 00:00:00 2001
From: Ali Waseem
Date: Mon, 30 Mar 2026 11:40:07 -0600
Subject: [PATCH] wip: refactored create queue sheet
---
.../Integrations/Queues/CreateQueueSheet.tsx | 365 ------------------
.../CreateQueueSheet.schema.ts | 30 ++
.../CreateQueueSheet/CreateQueueSheet.tsx | 149 +++++++
.../PartitionConfigFields.tsx | 47 +++
.../CreateQueueSheet/PgPartmanCallout.tsx | 39 ++
.../CreateQueueSheet/QueueNameField.tsx | 26 ++
.../CreateQueueSheet/QueueTypeSelector.tsx | 71 ++++
.../Queues/CreateQueueSheet/RlsSection.tsx | 64 +++
.../Queues/CreateQueueSheet/index.ts | 3 +
9 files changed, 429 insertions(+), 365 deletions(-)
delete mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet.tsx
create mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.schema.ts
create mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.tsx
create mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/PartitionConfigFields.tsx
create mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/PgPartmanCallout.tsx
create mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/QueueNameField.tsx
create mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/QueueTypeSelector.tsx
create mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/RlsSection.tsx
create mode 100644 apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/index.ts
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet.tsx b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet.tsx
deleted file mode 100644
index e877dc3243f..00000000000
--- a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet.tsx
+++ /dev/null
@@ -1,365 +0,0 @@
-import { zodResolver } from '@hookform/resolvers/zod'
-import { EnableExtensionModal } from 'components/interfaces/Database/Extensions/EnableExtensionModal'
-import { Markdown } from 'components/interfaces/Markdown'
-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, useState } from 'react'
-import { SubmitHandler, useForm } from 'react-hook-form'
-import { toast } from 'sonner'
-import {
- Badge,
- Button,
- Checkbox_Shadcn_,
- Form_Shadcn_,
- FormControl_Shadcn_,
- FormField_Shadcn_,
- Input,
- Input_Shadcn_,
- RadioGroupStacked,
- RadioGroupStackedItem,
- Separator,
- Sheet,
- SheetContent,
- SheetFooter,
- SheetHeader,
- SheetSection,
- SheetTitle,
-} from 'ui'
-import { Admonition } from 'ui-patterns'
-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
- onClose: () => void
-}
-
-const normalQueueSchema = z.object({
- type: z.literal('basic'),
-})
-
-const partitionedQueueSchema = z.object({
- type: z.literal('partitioned'),
- partitionInterval: z.coerce.number(),
- retentionInterval: z.coerce.number(),
-})
-
-const unloggedQueueSchema = z.object({
- type: z.literal('unlogged'),
-})
-
-const FormSchema = z.object({
- name: QueryNameSchema,
- enableRls: z.boolean(),
- values: z.discriminatedUnion('type', [
- normalQueueSchema,
- partitionedQueueSchema,
- unloggedQueueSchema,
- ]),
-})
-
-export type CreateQueueForm = z.infer
-export type QueueType = CreateQueueForm['values']
-
-const FORM_ID = 'create-queue-sidepanel'
-
-export const CreateQueueSheet = ({ visible, onClose }: CreateQueueSheetProps) => {
- const router = useRouter()
- const { data: project } = useSelectedProjectQuery()
-
- const { data: isExposed } = useQueuesExposePostgrestStatusQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
-
- 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,
- })
-
- useEffect(() => {
- if (visible) {
- form.reset(defaultValues)
- }
- }, [
- form,
- pgPartmanInstalled,
- // end of stable references
- visible,
- ])
-
- const checkIsDirty = () => form.formState.isDirty
-
- const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
- checkIsDirty,
- onClose,
- })
-
- const onSubmit: SubmitHandler = async ({ name, enableRls, values }) => {
- createQueue(
- {
- projectRef: project!.ref,
- connectionString: project?.connectionString,
- name,
- enableRls,
- type: values.type,
- configuration:
- values.type === 'partitioned'
- ? {
- partitionInterval: values.partitionInterval,
- retentionInterval: values.retentionInterval,
- }
- : undefined,
- },
- {
- onSuccess: () => {
- toast.success(`Successfully created queue ${name}`)
- router.push(`/project/${project?.ref}/integrations/queues/queues/${name}`)
- onClose()
- },
- }
- )
- }
-
- const [showEnableExtensionModal, setShowEnableExtensionModal] = useState(false)
-
- const queueType = form.watch('values.type')
-
- return (
-
-
-
-
- Create a new queue
-
-
-
- }
- description="Restrict access to your queue by enabling RLS and writing Postgres policies to control access for each role."
- >
-
-
-
-
- )}
- />
- {!isExposed ? (
-
-
-
- ) : (
-
- )}
-
-
-
-
-
-
- Cancel
-
-
- Create queue
-
-
-
-
- {pgPartmanExtension && !pgPartmanInstalled && (
- setShowEnableExtensionModal(false)}
- />
- )}
-
-
- )
-}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.schema.ts b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.schema.ts
new file mode 100644
index 00000000000..393c244061b
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.schema.ts
@@ -0,0 +1,30 @@
+import z from 'zod'
+
+import { QueryNameSchema } from '../Queues.utils'
+
+const normalQueueSchema = z.object({
+ type: z.literal('basic'),
+})
+
+const partitionedQueueSchema = z.object({
+ type: z.literal('partitioned'),
+ partitionInterval: z.coerce.number(),
+ retentionInterval: z.coerce.number(),
+})
+
+const unloggedQueueSchema = z.object({
+ type: z.literal('unlogged'),
+})
+
+export const FormSchema = z.object({
+ name: QueryNameSchema,
+ enableRls: z.boolean(),
+ values: z.discriminatedUnion('type', [
+ normalQueueSchema,
+ partitionedQueueSchema,
+ unloggedQueueSchema,
+ ]),
+})
+
+export type CreateQueueForm = z.infer
+export type QueueType = CreateQueueForm['values']
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.tsx b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.tsx
new file mode 100644
index 00000000000..ca82965422c
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/CreateQueueSheet.tsx
@@ -0,0 +1,149 @@
+import { zodResolver } from '@hookform/resolvers/zod'
+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, useMemo } from 'react'
+import { SubmitHandler, useForm } from 'react-hook-form'
+import { toast } from 'sonner'
+import {
+ Button,
+ Form_Shadcn_,
+ Separator,
+ Sheet,
+ SheetContent,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+} from 'ui'
+
+import { usePgPartmanStatus } from '../usePgPartmanStatus'
+import { CreateQueueForm, FormSchema } from './CreateQueueSheet.schema'
+import { PartitionConfigFields } from './PartitionConfigFields'
+import { PgPartmanCallout } from './PgPartmanCallout'
+import { QueueNameField } from './QueueNameField'
+import { QueueTypeSelector } from './QueueTypeSelector'
+import { RlsSection } from './RlsSection'
+
+export interface CreateQueueSheetProps {
+ visible: boolean
+ onClose: () => void
+}
+
+const FORM_ID = 'create-queue-sidepanel'
+
+export const CreateQueueSheet = ({ visible, onClose }: CreateQueueSheetProps) => {
+ const router = useRouter()
+ const { data: project } = useSelectedProjectQuery()
+
+ const { data: isExposed } = useQueuesExposePostgrestStatusQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ })
+
+ const { mutate: createQueue, isPending } = useDatabaseQueueCreateMutation()
+ const { isInstalled: pgPartmanInstalled } = usePgPartmanStatus()
+
+ const defaultValues: CreateQueueForm = useMemo(
+ () =>
+ pgPartmanInstalled
+ ? {
+ name: '',
+ enableRls: true,
+ values: { type: 'partitioned', partitionInterval: 0, retentionInterval: 0 },
+ }
+ : { name: '', enableRls: true, values: { type: 'basic' } },
+ [pgPartmanInstalled]
+ )
+
+ const form = useForm({
+ resolver: zodResolver(FormSchema),
+ defaultValues,
+ })
+
+ useEffect(() => {
+ if (visible) {
+ form.reset(defaultValues)
+ }
+ }, [form, defaultValues, visible])
+
+ const checkIsDirty = () => form.formState.isDirty
+
+ const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
+ checkIsDirty,
+ onClose,
+ })
+
+ const onSubmit: SubmitHandler = async ({ name, enableRls, values }) => {
+ createQueue(
+ {
+ projectRef: project!.ref,
+ connectionString: project?.connectionString,
+ name,
+ enableRls,
+ type: values.type,
+ configuration:
+ values.type === 'partitioned'
+ ? {
+ partitionInterval: values.partitionInterval,
+ retentionInterval: values.retentionInterval,
+ }
+ : undefined,
+ },
+ {
+ onSuccess: () => {
+ toast.success(`Successfully created queue ${name}`)
+ router.push(`/project/${project?.ref}/integrations/queues/queues/${name}`)
+ onClose()
+ },
+ }
+ )
+ }
+
+ return (
+
+
+
+
+ Create a new queue
+
+
+
+
+
+ Cancel
+
+
+ Create queue
+
+
+
+
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/PartitionConfigFields.tsx b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/PartitionConfigFields.tsx
new file mode 100644
index 00000000000..21b2314ecea
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/PartitionConfigFields.tsx
@@ -0,0 +1,47 @@
+import { UseFormReturn } from 'react-hook-form'
+import { FormField_Shadcn_, Input, Separator, SheetSection } from 'ui'
+import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+
+import { CreateQueueForm } from './CreateQueueSheet.schema'
+
+export function PartitionConfigFields({ form }: { form: UseFormReturn }) {
+ const queueType = form.watch('values.type')
+
+ if (queueType !== 'partitioned') return null
+
+ return (
+ <>
+
+ (
+
+ ms
}
+ />
+
+ )}
+ />
+ (
+
+ ms}
+ />
+
+ )}
+ />
+
+
+ >
+ )
+}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/PgPartmanCallout.tsx b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/PgPartmanCallout.tsx
new file mode 100644
index 00000000000..4e7e54f35d8
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/PgPartmanCallout.tsx
@@ -0,0 +1,39 @@
+import { EnableExtensionModal } from 'components/interfaces/Database/Extensions/EnableExtensionModal'
+import { useState } from 'react'
+import { Button } from 'ui'
+import { Admonition } from 'ui-patterns'
+
+import { usePgPartmanStatus } from '../usePgPartmanStatus'
+
+export function PgPartmanCallout() {
+ const { pgPartmanExtension, isAvailable, isInstalled } = usePgPartmanStatus()
+ const [showEnableModal, setShowEnableModal] = useState(false)
+
+ if (!isAvailable || isInstalled) return null
+
+ return (
+
+
+ setShowEnableModal(true)}
+ >
+ Enable pg_partman
+
+
+ {pgPartmanExtension && (
+
setShowEnableModal(false)}
+ />
+ )}
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/QueueNameField.tsx b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/QueueNameField.tsx
new file mode 100644
index 00000000000..8743be1e433
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/QueueNameField.tsx
@@ -0,0 +1,26 @@
+import { UseFormReturn } from 'react-hook-form'
+import { FormControl_Shadcn_, FormField_Shadcn_, Input_Shadcn_, SheetSection } from 'ui'
+import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+
+import { CreateQueueForm } from './CreateQueueSheet.schema'
+
+export function QueueNameField({ form }: { form: UseFormReturn }) {
+ return (
+
+ (
+
+
+
+
+
+ Must be all lowercase letters
+
+
+ )}
+ />
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/QueueTypeSelector.tsx b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/QueueTypeSelector.tsx
new file mode 100644
index 00000000000..2af655d10f4
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/QueueTypeSelector.tsx
@@ -0,0 +1,71 @@
+import { UseFormReturn } from 'react-hook-form'
+import {
+ Badge,
+ FormControl_Shadcn_,
+ FormField_Shadcn_,
+ RadioGroupStacked,
+ RadioGroupStackedItem,
+ SheetSection,
+} from 'ui'
+import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+
+import { QUEUE_TYPES } from '../Queues.constants'
+import { usePgPartmanStatus } from '../usePgPartmanStatus'
+import { CreateQueueForm } from './CreateQueueSheet.schema'
+
+export function QueueTypeSelector({ form }: { form: UseFormReturn }) {
+ const { isInstalled } = usePgPartmanStatus()
+
+ return (
+
+ (
+
+
+
+ {QUEUE_TYPES.filter(
+ (definition) => definition.value !== 'partitioned' || isInstalled
+ ).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}
+
+
+
+
+ )
+ })}
+
+
+
+ )}
+ />
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/RlsSection.tsx b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/RlsSection.tsx
new file mode 100644
index 00000000000..5039b804726
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/RlsSection.tsx
@@ -0,0 +1,64 @@
+import { Markdown } from 'components/interfaces/Markdown'
+import { UseFormReturn } from 'react-hook-form'
+import { Badge, Checkbox_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, SheetSection } from 'ui'
+import { Admonition } from 'ui-patterns'
+import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+
+import { CreateQueueForm } from './CreateQueueSheet.schema'
+
+export function RlsSection({
+ form,
+ isExposed,
+ projectRef,
+}: {
+ form: UseFormReturn
+ isExposed: boolean | undefined
+ projectRef: string | undefined
+}) {
+ return (
+
+ (
+
+ Enable Row Level Security (RLS)
+ Recommended
+
+ }
+ description="Restrict access to your queue by enabling RLS and writing Postgres policies to control access for each role."
+ >
+
+
+
+
+ )}
+ />
+ {!isExposed ? (
+
+
+
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/index.ts b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/index.ts
new file mode 100644
index 00000000000..7ce513a3e00
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Queues/CreateQueueSheet/index.ts
@@ -0,0 +1,3 @@
+export { CreateQueueSheet } from './CreateQueueSheet'
+export type { CreateQueueSheetProps } from './CreateQueueSheet'
+export type { CreateQueueForm, QueueType } from './CreateQueueSheet.schema'