Files
supabase/apps/studio/components/interfaces/Integrations/NewQueues/CreateQueueSheet.tsx
T
590ed362ab feat: Redesign for the integrations pages (#30476)
* Initial commit.

* Minor type fixes.

* Add a integration for Queues. Refactor some of the integration layout.

* Migrate the Cron integration to the new style.

* Add useInstalledIntegrations hook.

* Add an integration entry for vault.

* Add an integration entry for GraphiQL.

* Add supabase webhooks.

* Feat/integrations get layout (#30538)

* scroll based icon

* Update header.tsx

* remove dep from overview

* moar

* more table stuff

* moar

* alt nav put in

* fix MotionNumber issues

* more

* trying both layouts

* Fix bunch of type errors.

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>

* Migrate Vercel and Github files to their own folders.

* Switch all integrations with the new designs.

* More fixes for links, pages structure and other random stuff.

* Remove unneeded file.

* Another set of fixes. Migrated most of the extension integrations.

* Migrated Vault and webhooks to the new style.

* Various fixes to make the animation work.

* Remove extra code.

* Tiny fixes 😬 i swear its tiny

* Refactor IntegrationOverviewTab

* chore/ update integrations routes (#30585)

* init

* add child support in tabs

* add webhooks

* Update IntegrationPageHandler.tsx

* fix id issues

* use messageId instead

* animation tweaks

* Move the description to the wrappers array.

* The useInstalledIntegrations now provides integrations which could be installed.

* Add static content for the various integrations.

* Move the page handler logic into the integrations definitions.

* Clean up some extra code.

* Add logic to make the overview tab the default tab.

* Don't show the header until the integration id has been checked.

* Add logic to the integration pages to avoid weird loading bugs, deselecting tabs if the integration hasn't been installed etc.

* Fix the webhooks overview tab.

* Fix the buttons for enabling extensions.

* Add padding to all custom tab contents.

* Small fixes

* Prettier lint

* Fix icon color + add empty state for when available integrations are all installed

* Fix ts errors

* Fiox

* Add enable webhooks cta

* Fix key

* Fix all lints

* Fix the queues create sheet.

* Fix the deletion of wrappers.

* Fix the minimum version alert for the wrappers extension.

* Make the queues table fit the whole container.

* Fix an issue which reset the tab when installing an extension.

* Address comments

* Add loading state for installed integrations in side nav

* Fix edit secret not rendering value in input field after subsequent openings

* Fix vault keys auto filling search input with vault

* Fix search input placeholder for cron

* Minor fix in install database extension copy

* Fix a bad redirect when reloading.

* Fix bad url redirects.

* Fix scrolling in create new/edit wrapper sheet.

* Add y padding to the wrappers rows.

* Fix merge errors.

* More merge fixes.

* Fix bad imports during the merge.

---------

Co-authored-by: Jonathan Summers-Muir <MildTomato@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2024-11-25 13:26:22 +01:00

338 lines
12 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import z from 'zod'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useDatabaseQueueCreateMutation } from 'data/database-queues/database-queues-create-mutation'
import {
Badge,
Button,
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
Input,
Input_Shadcn_,
RadioGroupStacked,
RadioGroupStackedItem,
Separator,
SheetFooter,
SheetHeader,
SheetSection,
SheetTitle,
} from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { QUEUE_TYPES } from './Queues.constants'
export interface CreateQueueSheetProps {
isClosing: boolean
setIsClosing: (v: boolean) => void
onClose: () => void
}
const normalQueueSchema = z.object({
type: z.literal('basic'),
})
const partitionedQueueSchema = z.object({
type: z.literal('partitioned'),
partitionInterval: z.number(),
retentionInterval: z.number(),
})
const unloggedQueueSchema = z.object({
type: z.literal('unlogged'),
})
const FormSchema = z.object({
name: z
.string()
.trim()
.min(1, 'Please provide a name for your queue')
.max(47, "The name can't be longer than 47 characters"),
values: z.discriminatedUnion('type', [
normalQueueSchema,
partitionedQueueSchema,
unloggedQueueSchema,
]),
})
export type CreateQueueForm = z.infer<typeof FormSchema>
export type QueueType = CreateQueueForm['values']
const FORM_ID = 'create-queue-sidepanel'
export const CreateQueueSheet = ({ isClosing, setIsClosing, onClose }: CreateQueueSheetProps) => {
// const [showEnableExtensionModal, setShowEnableExtensionModal] = useState(false)
const { mutate: createQueue, isLoading } = useDatabaseQueueCreateMutation()
// const canToggleExtensions = useCheckPermissions(
// PermissionAction.TENANT_SQL_ADMIN_WRITE,
// 'extensions'
// )
const form = useForm<CreateQueueForm>({
resolver: zodResolver(FormSchema),
defaultValues: {
name: '',
values: {
type: 'basic',
},
},
})
const { project } = useProjectContext()
const isEdited = form.formState.isDirty
// if the form hasn't been touched and the user clicked esc or the backdrop, close the sheet
if (!isEdited && isClosing) {
onClose()
}
const onClosePanel = () => {
if (isEdited) {
setIsClosing(true)
} else {
onClose()
}
}
const onSubmit: SubmitHandler<CreateQueueForm> = async ({ name, values }) => {
let query = `SELECT pgmq.create('${name}');`
if (values.type === 'partitioned') {
query = `select from pgmq.create_partitioned('${name}', '${values.partitionInterval}', '${values.retentionInterval}');`
}
if (values.type === 'unlogged') {
query = `SELECT pgmq.create_unlogged('${name}');`
}
createQueue(
{
projectRef: project!.ref,
connectionString: project?.connectionString,
query,
},
{
onSuccess: () => {
toast.success(`Successfully created queue ${name}`)
onClose()
},
}
)
}
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 queueType = form.watch('values.type')
return (
<>
<div className="flex flex-col h-full" tabIndex={-1}>
<SheetHeader>
<SheetTitle>Create a new queue</SheetTitle>
</SheetHeader>
<div className="overflow-auto flex-grow">
<Form_Shadcn_ {...form}>
<form
id={FORM_ID}
className="flex-grow overflow-auto"
onSubmit={form.handleSubmit(onSubmit)}
>
<SheetSection>
<FormField_Shadcn_
control={form.control}
name="name"
render={({ field }) => (
<FormItemLayout label="Name" layout="vertical" className="gap-1">
<FormControl_Shadcn_>
<Input_Shadcn_ {...field} />
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</SheetSection>
<Separator />
<SheetSection>
<FormField_Shadcn_
control={form.control}
name="values.type"
render={({ field }) => (
<FormItemLayout label="Type" layout="vertical" className="gap-1">
<FormControl_Shadcn_>
<RadioGroupStacked
id="queue_type"
name="queue_type"
value={field.value}
disabled={field.disabled}
onValueChange={field.onChange}
>
{QUEUE_TYPES.map((definition) => (
<RadioGroupStackedItem
key={definition.value}
id={definition.value}
value={definition.value}
label=""
disabled={
!pgPartmanExtensionInstalled && definition.value === 'partitioned'
}
showIndicator={false}
>
<div className="flex items-center gap-x-5">
<div className="text-foreground">{definition.icon}</div>
<div className="flex flex-col">
<div className="flex gap-x-2">
<p className="text-foreground">{definition.label}</p>
</div>
<p className="text-foreground-light text-left">
{definition.description}
</p>
</div>
</div>
{definition.value === 'partitioned' ? (
<div className="pt-2 pl-10">
<Badge variant="warning">COMING SOON</Badge>
</div>
) : null}
{/* {!pgPartmanExtensionInstalled &&
definition.value === 'partitioned' ? (
<div className="w-full flex gap-x-2 pl-11 py-2 items-center">
<WarningIcon />
<span className="text-xs">
<code>pg_partman</code> needs to be installed to use this type
</span>
</div>
) : null} */}
</RadioGroupStackedItem>
))}
</RadioGroupStacked>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
{/* {!pgPartmanExtensionInstalled && (
<Admonition
type="note"
// @ts-ignore
title={
<span>
Enable <code className="text-xs w-min">pg_partman</code> for partitioned
queues
</span>
}
description={
<div className="flex flex-col gap-y-2">
<span>
This will allow you to create partitioned queues which can handle a large
amount of messages
</span>
<ButtonTooltip
type="default"
className="w-min"
disabled={!canToggleExtensions}
onClick={() => setShowEnableExtensionModal(true)}
tooltip={{
content: {
side: 'bottom',
text: 'You need additional permissions to enable database extensions',
},
}}
>
Install pg_partman extension
</ButtonTooltip>
</div>
}
/>
)} */}
</SheetSection>
<Separator />
{queueType === 'partitioned' && (
<>
<SheetSection className="flex flex-col gap-3">
<FormField_Shadcn_
control={form.control}
name="values.partitionInterval"
render={({ field: { ref, ...rest } }) => (
<FormItemLayout label="Partition interval" className="gap-1">
<Input
{...rest}
type="number"
placeholder="1000"
actions={<p className="text-foreground-light pr-2">ms</p>}
/>
</FormItemLayout>
)}
/>
<FormField_Shadcn_
control={form.control}
name="values.retentionInterval"
render={({ field: { ref, ...rest } }) => (
<FormItemLayout label="Retention interval" className="gap-1">
<Input
{...rest}
type="number"
placeholder="1000"
actions={<p className="text-foreground-light pr-2">ms</p>}
/>
</FormItemLayout>
)}
/>
</SheetSection>
</>
)}
</form>
</Form_Shadcn_>
</div>
<SheetFooter>
<Button
size="tiny"
type="default"
htmlType="button"
onClick={onClosePanel}
disabled={isLoading}
>
Cancel
</Button>
<Button
size="tiny"
type="primary"
form={FORM_ID}
htmlType="submit"
disabled={isLoading}
loading={isLoading}
>
Create queue
</Button>
</SheetFooter>
</div>
<ConfirmationModal
visible={isClosing}
title="Discard changes"
confirmLabel="Discard"
onCancel={() => setIsClosing(false)}
onConfirm={() => onClose()}
>
<p className="text-sm text-foreground-light">
There are unsaved changes. Are you sure you want to close the panel? Your changes will be
lost.
</p>
</ConfirmationModal>
{/* {pgPartmanExtension && (
<EnableExtensionModal
visible={showEnableExtensionModal}
extension={pgPartmanExtension}
onCancel={() => setShowEnableExtensionModal(false)}
/>
)} */}
</>
)
}