// @ts-nocheck import { zodResolver } from '@hookform/resolvers/zod' import { useParams } from 'common' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, cn, DialogFooter, DialogSection, Form, FormControl, FormField, Input, SheetFooter, SheetSection, } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import z from 'zod' import { useIcebergWrapperExtension } from './AnalyticsBucketDetails/useIcebergWrapper' import { reservedPrefixes, reservedSuffixes, validBucketNameRegex, } from './CreateAnalyticsBucketForm.utils' import { InlineLink } from '@/components/ui/InlineLink' import { useDatabaseExtensionEnableMutation } from '@/data/database-extensions/database-extension-enable-mutation' import { useAnalyticsBucketCreateMutation } from '@/data/storage/analytics-bucket-create-mutation' import { useAnalyticsBucketsQuery } from '@/data/storage/analytics-buckets-query' import { useIcebergWrapperCreateMutation } from '@/data/storage/iceberg-wrapper-create-mutation' import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' const FormSchema = z .object({ name: z .string() .trim() .min(3, 'Bucket name should be at least 3 characters') .max(63, 'Bucket name should be up to 63 characters') .refine( (value) => !value.endsWith(' '), 'The name of the bucket cannot end with a whitespace' ) .refine( (value) => value !== 'public', '"public" is a reserved name. Please choose another name' ), }) .superRefine((data, ctx) => { if (reservedPrefixes.test(data.name)) { const [match] = data.name.match(reservedPrefixes) ?? [] return ctx.addIssue({ path: ['name'], code: z.ZodIssueCode.custom, message: `Bucket name cannot start with "${match}"`, }) } if (reservedSuffixes.test(data.name)) { const [match] = data.name.match(reservedSuffixes) ?? [] return ctx.addIssue({ path: ['name'], code: z.ZodIssueCode.custom, message: `Bucket name cannot end with "${match}"`, }) } if (/[A-Z]/.test(data.name)) { return ctx.addIssue({ path: ['name'], code: z.ZodIssueCode.custom, message: 'Bucket name can only be lowercase characters', }) } if (!validBucketNameRegex.test(data.name)) { if (!/^[a-z0-9]/.test(data.name)) { return ctx.addIssue({ path: ['name'], code: z.ZodIssueCode.custom, message: 'Bucket name must start with a lowercase letter or number.', }) } if (!/[a-z0-9]$/.test(data.name)) { return ctx.addIssue({ path: ['name'], code: z.ZodIssueCode.custom, message: 'Bucket name must end with a lowercase letter or number.', }) } const [match] = data.name.match(/[^a-z0-9-]/) ?? [] return ctx.addIssue({ path: ['name'], code: z.ZodIssueCode.custom, message: !!match ? `Bucket name cannot contain the "${match}" character` : 'Bucket name contains an invalid special character', }) } }) const formId = 'create-analytics-storage-bucket-form' export type CreateAnalyticsBucketForm = z.infer interface CreateAnalyticsBucketFormProps { type?: 'dialog' | 'sheet' onOpenChange: (value: boolean) => void } export const CreateAnalyticsBucketForm = ({ type = 'dialog', onOpenChange, }: CreateAnalyticsBucketFormProps) => { const { ref } = useParams() const { data: org } = useSelectedOrganizationQuery() const { data: project } = useSelectedProjectQuery() const { extension: wrappersExtension, state: wrappersExtensionState } = useIcebergWrapperExtension() const { data: buckets = [] } = useAnalyticsBucketsQuery({ projectRef: ref }) const wrappersExtensionNeedsUpgrading = wrappersExtensionState === 'needs-upgrade' const { mutate: sendEvent } = useSendEventMutation() const { mutateAsync: createAnalyticsBucket, isPending: isCreatingAnalyticsBucket } = useAnalyticsBucketCreateMutation({ // [Joshen] Silencing the error here as it's being handled in onSubmit onError: () => {}, }) const { mutateAsync: createIcebergWrapper, isPending: isCreatingIcebergWrapper } = useIcebergWrapperCreateMutation() const { mutateAsync: enableExtension, isPending: isEnablingExtension } = useDatabaseExtensionEnableMutation() const isCreating = isEnablingExtension || isCreatingIcebergWrapper || isCreatingAnalyticsBucket const form = useForm({ resolver: zodResolver(FormSchema as any), defaultValues: { name: '' }, }) const onSubmit: SubmitHandler = async (values) => { if (!ref) return console.error('Project ref is required') if (!project) return console.error('Project details is required') if (!wrappersExtension) return console.error('Unable to find wrappers extension') const hasExistingBucket = buckets.some((x) => x.name === values.name) if (hasExistingBucket) return toast.error('Bucket name already exists') try { await createAnalyticsBucket({ projectRef: ref, bucketName: values.name, }) if (wrappersExtensionState === 'not-installed') { await enableExtension({ projectRef: project?.ref, connectionString: project?.connectionString, name: wrappersExtension.name, schema: wrappersExtension.schema ?? 'extensions', version: wrappersExtension.default_version, }) } await createIcebergWrapper({ bucketName: values.name }) sendEvent({ action: 'storage_bucket_created', properties: { bucketType: 'analytics' }, groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' }, }) form.reset() toast.success(`Created bucket “${values.name}”`) onOpenChange(false) } catch (error: any) { toast.error(`Failed to create bucket: ${error.message}`) } } const Section = type === 'dialog' ? DialogSection : SheetSection const Footer = type === 'dialog' ? DialogFooter : SheetFooter return ( <>
( )} /> {wrappersExtensionNeedsUpgrading ? (

Update the wrappers extension by upgrading your project from your{' '} project settings {' '} before creating an Analytics bucket.{' '} Learn more .

) : (

Briven will install the{' '} {wrappersExtensionState !== 'installed' ? 'Wrappers extension and ' : ''} Iceberg Wrapper integration on your behalf.{' '} Learn more .

)}
) }