import { isEmpty } from 'lodash' import { useMemo, useState } from 'react' import { toast } from 'sonner' import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection' import { useSchemaCreateMutation } from 'data/database/schema-create-mutation' import { useSchemasQuery } from 'data/database/schemas-query' import { useFDWCreateMutation } from 'data/fdw/fdw-create-mutation' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, Form, Input, RadioGroupStacked, RadioGroupStackedItem, Separator, SheetFooter, SheetHeader, SheetTitle, } from 'ui' import { CreateWrapperSheetProps } from './CreateWrapperSheet' import InputField from './InputField' import { makeValidateRequired } from './Wrappers.utils' const FORM_ID = 'create-wrapper-form' const requiredFields: Record = { S3Tables: [ { name: 'vault_aws_access_key_id', required: true }, { name: 'vault_aws_secret_access_key', required: true }, { name: 'region_name', required: true }, { name: 'vault_aws_s3table_bucket_arn', required: true }, ], R2Catalog: [ { name: 'vault_aws_access_key_id', required: true }, { name: 'vault_aws_secret_access_key', required: true }, { name: 'vault_token', required: true }, { name: 'warehouse', required: true }, { name: 's3.endpoint', required: true }, { name: 'catalog_uri', required: true }, ], IcebergRestCatalog: [ { name: 'vault_aws_access_key_id', required: false }, { name: 'vault_aws_secret_access_key', required: false }, { name: 'region_name', required: false }, { name: 'vault_aws_s3table_bucket_arn', required: false }, { name: 'vault_token', required: false }, { name: 'warehouse', required: false }, { name: 's3.endpoint', required: false }, { name: 'catalog_uri', required: false }, ], } as const type Target = 'S3Tables' | 'R2Catalog' | 'IcebergRestCatalog' export const CreateIcebergWrapperSheet = ({ wrapperMeta: wrapperMetaOriginal, onDirty, onClose, onCloseWithConfirmation, }: CreateWrapperSheetProps) => { const { data: project } = useSelectedProjectQuery() const { data: org } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() const [selectedTarget, setSelectedTarget] = useState('S3Tables') const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({}) const { mutateAsync: createFDW, isPending: isCreatingWrapper } = useFDWCreateMutation({ onSuccess: () => { toast.success(`Successfully created ${wrapperMeta?.label} foreign data wrapper`) onClose() }, }) const wrapperMeta = useMemo(() => { const fields = requiredFields[selectedTarget] return { ...wrapperMetaOriginal, server: { options: wrapperMetaOriginal.server.options // when the target changes, change the wrapperMeta field .filter((option) => fields.find((field) => field.name === option.name)) .map((option) => { const field = fields.find((field) => field.name === option.name) return { ...option, required: field?.required ?? false, } }), }, } }, [wrapperMetaOriginal, selectedTarget]) const { data: schemas } = useSchemasQuery({ projectRef: project?.ref!, connectionString: project?.connectionString, }) const initialValues = { wrapper_name: '', server_name: '', source_schema: wrapperMeta.sourceSchemaOption?.defaultValue ?? '', target_schema: '', ...Object.fromEntries( wrapperMeta.server.options.map((option) => [option.name, option.defaultValue ?? '']) ), } const { mutateAsync: createSchema, isPending: isCreatingSchema } = useSchemaCreateMutation() const onSubmit = async (values: any) => { const validate = makeValidateRequired(wrapperMeta.server.options) const errors: any = validate(values) if (values.source_schema.length === 0) { errors.source_schema = 'Please provide a namespace name' } if (values.wrapper_name.length === 0) { errors.wrapper_name = 'Please provide a name for your wrapper' } if (values.target_schema.length === 0) { errors.target_schema = 'Please provide an unique target schema' } const foundSchema = schemas?.find((s) => s.name === values.target_schema) if (foundSchema) { errors.target_schema = 'This schema already exists. Please specify a unique schema name.' } setFormErrors(errors) if (!isEmpty(errors)) { return } try { await createSchema({ projectRef: project?.ref, connectionString: project?.connectionString, name: values.target_schema, }) await createFDW({ projectRef: project?.ref, connectionString: project?.connectionString, wrapperMeta, formState: { ...values, server_name: `${values.wrapper_name}_server`, supabase_target_schema: values.target_schema, }, mode: 'schema', tables: [], sourceSchema: values.source_schema, targetSchema: values.target_schema, }) sendEvent({ action: 'foreign_data_wrapper_created', properties: { wrapperType: wrapperMeta.label, }, groups: { project: project?.ref ?? 'Unknown', organization: org?.slug ?? 'Unknown', }, }) } catch (error) { console.error(error) // The error will be handled by the mutation onError callback (toast.error) } } const isLoading = isCreatingWrapper || isCreatingSchema return ( <>
{({ values, initialValues }: any) => { const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) onDirty(hasChanges) return ( <> Create a {wrapperMeta.label} wrapper
Wrapper Configuration}> 0 ? ( <> Your wrapper's server name will be{' '} {values.wrapper_name}_server ) : ( '' ) } /> Data target}> setSelectedTarget(value as Target)} >

AWS S3 storage that's optimized for analytics workloads.

Managed Apache Iceberg built directly into your R2 bucket.

Can be used with any S3-compatible storage.

{wrapperMeta.label} Configuration} > {wrapperMeta.server.options .filter((option) => !option.hidden) .map((option) => ( ))}

Foreign Schema

You can query your data from the foreign tables in the specified schema after the wrapper is created.

} > {wrapperMeta.sourceSchemaOption && (

{wrapperMeta.sourceSchemaOption.description}

)}

A new schema will be created. For security purposes, the wrapper tables from the foreign schema cannot be created within an existing schema.

) }}
) }