// @ts-nocheck import { zodResolver } from '@hookform/resolvers/zod' import { useEffect, useRef } from 'react' import { SubmitHandler, useForm, useWatch } from 'react-hook-form' import { toast } from 'sonner' import { Button, Card, CardContent, Form, FormControl, FormField, Input, RadioGroupStacked, RadioGroupStackedItem, SheetFooter, SheetHeader, SheetSection, SheetTitle, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { PageSection, PageSectionContent, PageSectionDescription, PageSectionMeta, PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' import * as z from 'zod' import { CreateWrapperSheetProps } from './CreateWrapperSheet' import InputField from './InputField' 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' const FORM_ID = 'create-wrapper-form' const S3TableSchema = z.object({ target: z.literal('S3Tables'), source_schema: z.string().min(1, 'Please provide a namespace name'), wrapper_name: z.string().min(1, 'Please provide a name for your wrapper'), target_schema: z.string().min(1, 'Please provide an unique target schema'), vault_aws_access_key_id: z.string().min(1, 'Required'), vault_aws_secret_access_key: z.string().min(1, 'Required'), region_name: z.string().min(1, 'Required'), vault_aws_s3table_bucket_arn: z.string().min(1, 'Required'), }) const R2CatalogSchema = z.object({ target: z.literal('R2Catalog'), source_schema: z.string().min(1, 'Please provide a namespace name'), wrapper_name: z.string().min(1, 'Please provide a name for your wrapper'), target_schema: z.string().min(1, 'Please provide an unique target schema'), vault_aws_access_key_id: z.string().min(1, 'Required'), vault_aws_secret_access_key: z.string().min(1, 'Required'), vault_token: z.string().min(1, 'Required'), warehouse: z.string().min(1, 'Required'), s3: z.object({ endpoint: z.string().min(1, 'Required') }), catalog_uri: z.string().min(1, 'Required'), }) const IcebergRestCatalogSchema = z.object({ target: z.literal('IcebergRestCatalog'), source_schema: z.string().min(1, 'Please provide a namespace name'), wrapper_name: z.string().min(1, 'Please provide a name for your wrapper'), target_schema: z.string().min(1, 'Please provide an unique target schema'), vault_aws_access_key_id: z.string().optional(), vault_aws_secret_access_key: z.string().optional(), region_name: z.string().optional(), vault_aws_s3table_bucket_arn: z.string().optional(), vault_token: z.string().optional(), warehouse: z.string().optional(), s3: z.object({ endpoint: z.string().min(1, 'Required') }), catalog_uri: z.string().optional(), }) const formSchema = z.discriminatedUnion('target', [ S3TableSchema, R2CatalogSchema, IcebergRestCatalogSchema, ]) type FormSchema = z.infer const targetFields: 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' const INITIAL_VALUES = { wrapper_name: '', source_schema: '', target_schema: '', target: 'S3Tables', vault_aws_access_key_id: '', vault_aws_s3table_bucket_arn: '', vault_aws_secret_access_key: '', region_name: '', } satisfies FormSchema export const CreateIcebergWrapperSheet = ({ wrapperMeta, onDirty, onClose, onCloseWithConfirmation, }: CreateWrapperSheetProps) => { const { data: project } = useSelectedProjectQuery() const { data: org } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() const { mutateAsync: createFDW, isPending: isCreatingWrapper } = useFDWCreateMutation({ onSuccess: () => { toast.success(`Successfully created ${wrapperMeta?.label} foreign data wrapper`) onClose() }, }) const { data: schemas } = useSchemasQuery({ projectRef: project?.ref!, connectionString: project?.connectionString, }) const { mutateAsync: createSchema } = useSchemaCreateMutation() const form = useForm({ resolver: zodResolver(formSchema as any), defaultValues: INITIAL_VALUES, }) const { resetField, formState, setError, watch } = form const { isDirty, isSubmitting } = formState useEffect(() => { onDirty(isDirty) }, [onDirty, isDirty]) const currentTarget = useRef(INITIAL_VALUES.target) useEffect(() => { const subscription = watch((values) => { if (!values.target || values.target === currentTarget.current) return currentTarget.current = values.target const fields = targetFields[values.target] if (!fields) return wrapperMeta.server.options.forEach((option) => { // @ts-expect-error Can't reconcile with form schema resetField(option.name, { defaultValue: option.defaultValue ?? '' }) }) }) return () => subscription.unsubscribe() }, [resetField, watch, wrapperMeta]) const onSubmit: SubmitHandler = async (values) => { const foundSchema = schemas?.find((s) => s.name === values.target_schema) if (foundSchema) { setError('target_schema', { type: 'validate', message: 'This schema already exists. Please specify a unique schema name.', }) return } let formValues: Record = {} if (values.target === 'R2Catalog' || values.target === 'IcebergRestCatalog') { const { s3, ...otherFormValues } = values formValues = otherFormValues formValues['s3.endpoint'] = s3.endpoint } else { formValues = values } try { await createSchema({ projectRef: project?.ref, connectionString: project?.connectionString, name: values.target_schema, }) await createFDW({ projectRef: project?.ref, connectionString: project?.connectionString, wrapperMeta, formState: { ...formValues, server_name: `${values.wrapper_name}_server`, briven_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 || isSubmitting const wrapperName = useWatch({ name: 'wrapper_name', control: form.control }) const target = useWatch({ name: 'target', control: form.control }) const targetOptions = wrapperMeta.server.options .filter((option) => targetFields[target].find((field) => field.name === option.name)) .map((option) => { return { ...option, required: !!targetFields[target].find((field) => field.name === option.name)?.required, } }) return ( <>
Create a {wrapperMeta.label} wrapper Wrapper Configuration ( 0 ? ( <> Your wrapper's server name will be{' '} {wrapperName}_server ) : ( '' ) } > )} /> Data 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 {targetOptions.map((option) => option.hidden ? ( ) : ( ) )} Foreign Schema You can query your data from the foreign tables in the specified schema after the wrapper is created. {wrapperMeta.sourceSchemaOption && ( )}
) }