Files
supabase/apps/studio/components/interfaces/Storage/ImportForeignSchemaDialog.tsx
Ivan VasilovandAlaister Young 89c803551b feat: Analytic storage bucket (#37003)
* Migrate the CreateBucketModal to use RHF.

* Minor fixes for the create bucket modal.

* Use the bucket type from the API everywhere.

* Improve the types on some functions. Add "skip" mode to FDWCreateMutation.

* Expand the CreateBucketModal to explain what will happen when creating an iceberg bucket.

* Add a page for iceberg buckets.

* Add error states to the iceberg explorer.

* Fix the names for the FDWs.

* Add a dialog for setting up namespace.

* Restructure the new files.

* Add a download env button.

* Add warning to install the wrappers extension when creating an iceberg bucket.

* Add failover details when the FDW is not setup.

* Fix a lint error.

* Fix a type error.

* Minor fixes.

* Fix the server name.

* Add an icon for iceberg buckets.

* Fix the import foreign schema dialog.

* Make the setup wrapper button functional.

* Fix a bad useMemo dependency.

* Small changes to the iceberg bucket page.

* Minor fix for the edit wrapper sheet.

* Rename the files from kebab-case to PascalCase.

* Rename the files again to include Analytic instead of Iceberg.

* Rename Iceberg type to Analytic.

* Add a switch for creating namespace in the import foreign schema dialog.

* Fix the CreateBucketModal.

* Fix the delete modal feature.

* Fix the S3 keys in the FDW.

* Only create a namespace if the switch is true.

* Regenerate and fix the types.

* Fix the FDW create mutation to handle numbers in the FDW names.

* Make the icon smaller.

* Check whether the namespace exists, if it doesn't create it.

* Hide action from the analytic bucket which don't work.

* Invalidate namespaces when creating them.

* Add small explanation for the creation of namespaces.

* Minor fixes.

* Tons of changes to make the namespace feature work.

* Fix type errors.

* Fix bad import.

* Minor copy fixes.

* Replace the multiple cards with a table of namespaces.

* update copy icon

* tiny copy update

* Fix the empty state for foreign tables.

* Hide the analytics bucket option for self-hosted.

* Minor copy fixes.

* Expand the CTA on no namespaces state.

* More minor fixes.

* More small fixes.

---------

Co-authored-by: Alaister Young <a@alaisteryoung.com>
2025-07-15 14:18:38 +02:00

168 lines
5.1 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { snakeCase } from 'lodash'
import { useEffect, useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import z from 'zod'
import { useParams } from 'common'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import SchemaSelector from 'components/ui/SchemaSelector'
import { useFDWImportForeignSchemaMutation } from 'data/fdw/fdw-import-foreign-schema-mutation'
import {
Button,
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
Input_Shadcn_,
Modal,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import SchemaEditor from '../TableGridEditor/SidePanelEditor/SchemaEditor'
export interface ImportForeignSchemaDialogProps {
bucketName: string
namespace: string
excludedSchemas: string[]
wrapperValues: Record<string, string>
visible: boolean
onClose: () => void
}
const FormSchema = z.object({
bucketName: z.string().trim(),
sourceNamespace: z.string().trim(),
targetSchema: z.string().trim(),
})
export type ImportForeignSchemaForm = z.infer<typeof FormSchema>
export const ImportForeignSchemaDialog = ({
bucketName,
namespace,
excludedSchemas,
wrapperValues,
visible,
onClose,
}: ImportForeignSchemaDialogProps) => {
const { project } = useProjectContext()
const { ref } = useParams()
const [loading, setLoading] = useState(false)
const [createSchemaSheetOpen, setCreateSchemaSheetOpen] = useState(false)
const { mutateAsync: importForeignSchema } = useFDWImportForeignSchemaMutation({
onSuccess: () => {
toast.success(`Successfully connected ${bucketName} to the database.`)
onClose()
},
})
const form = useForm<ImportForeignSchemaForm>({
resolver: zodResolver(FormSchema),
defaultValues: {
bucketName,
sourceNamespace: namespace,
targetSchema: '',
},
})
const onSubmit: SubmitHandler<ImportForeignSchemaForm> = async (values) => {
if (!ref) return console.error('Project ref is required')
setLoading(true)
try {
await importForeignSchema({
projectRef: ref,
connectionString: project?.connectionString,
serverName: `${snakeCase(values.bucketName)}_fdw_server`,
sourceSchema: values.sourceNamespace,
targetSchema: values.targetSchema,
})
} catch (error: any) {
// error will be handled by the mutation onError callback
} finally {
setLoading(false)
}
}
useEffect(() => {
if (visible) {
form.reset({
bucketName,
sourceNamespace: namespace,
targetSchema: '',
})
}
}, [visible, form, bucketName, namespace])
return (
<Modal
hideFooter
visible={visible}
size="medium"
header={
<span>
Connect namespace <span className="text-brand">{namespace}</span>
</span>
}
onCancel={() => onClose()}
>
<Form_Shadcn_ {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<Modal.Content className="flex flex-col gap-y-4">
<FormField_Shadcn_
control={form.control}
name="sourceNamespace"
render={({ field }) => (
<FormItemLayout label="Namespace" layout="vertical">
<FormControl_Shadcn_>
<Input_Shadcn_ {...field} placeholder="Enter namespace name" disabled />
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
<FormField_Shadcn_
control={form.control}
name="targetSchema"
render={({ field }) => (
<FormItemLayout
label="Target Schema"
description="Select the database schema where the Iceberg data will be accessible. Each schema can only be connected to one namespace."
layout="vertical"
>
<SchemaSelector
portal={false}
size="small"
selectedSchemaName={field.value}
excludedSchemas={excludedSchemas}
onSelectSchema={(schema) => field.onChange(schema)}
onSelectCreateSchema={() => setCreateSchemaSheetOpen(true)}
/>
</FormItemLayout>
)}
/>
</Modal.Content>
<Modal.Separator />
<Modal.Content className="flex items-center space-x-2 justify-end">
<Button type="default" htmlType="button" disabled={loading} onClick={onClose}>
Cancel
</Button>
<Button type="primary" htmlType="submit" loading={loading}>
Save
</Button>
</Modal.Content>
</form>
</Form_Shadcn_>
<SchemaEditor
visible={createSchemaSheetOpen}
closePanel={() => setCreateSchemaSheetOpen(false)}
onSuccess={(schema) => {
form.setValue('targetSchema', schema)
setCreateSchemaSheetOpen(false)
}}
/>
</Modal>
)
}