Home / Function/ CreateWrapperSheet() — supabase Function Reference

CreateWrapperSheet() — supabase Function Reference

Architecture documentation for the CreateWrapperSheet() function in CreateWrapperSheet.tsx from the supabase codebase.

Entity Profile

Dependency Diagram

graph TD
  a07b5e79_2b1c_182b_80b9_d4e85041defc["CreateWrapperSheet()"]
  f17778f0_0474_c6f9_6004_6763d2604934["makeValidateRequired()"]
  a07b5e79_2b1c_182b_80b9_d4e85041defc -->|calls| f17778f0_0474_c6f9_6004_6763d2604934
  style a07b5e79_2b1c_182b_80b9_d4e85041defc fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

apps/studio/components/interfaces/Integrations/Wrappers/CreateWrapperSheet.tsx lines 41–480

export const CreateWrapperSheet = ({
  wrapperMeta,
  onDirty,
  onClose,
  onCloseWithConfirmation,
}: CreateWrapperSheetProps) => {
  const queryClient = useQueryClient()

  const { data: project } = useSelectedProjectQuery()
  const { data: org } = useSelectedOrganizationQuery()
  const { mutate: sendEvent } = useSendEventMutation()

  const [newTables, setNewTables] = useState<any[]>([])
  const [isEditingTable, setIsEditingTable] = useState(false)
  const [selectedTableToEdit, setSelectedTableToEdit] = useState()
  const [selectedMode, setSelectedMode] = useState<'tables' | 'schema'>(
    wrapperMeta.tables.length > 0 ? 'tables' : 'schema'
  )

  const { data: extensions } = useDatabaseExtensionsQuery({
    projectRef: project?.ref,
    connectionString: project?.connectionString,
  })

  const wrappersExtension = extensions?.find((ext) => ext.name === 'wrappers')
  // The import foreign schema requires a minimum extension version of 0.5.0
  const hasRequiredVersionForeignSchema = wrappersExtension?.installed_version
    ? wrappersExtension?.installed_version >= '0.5.0'
    : false

  const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({})

  const { mutateAsync: createFDW, isPending: isCreatingWrapper } = useFDWCreateMutation({
    onSuccess: () => {
      toast.success(`Successfully created ${wrapperMeta?.label} foreign data wrapper`)
      setNewTables([])

      const hasNewSchema = newTables.some((table) => table.is_new_schema)
      if (hasNewSchema) invalidateSchemasQuery(queryClient, project?.ref)

      onClose()
    },
  })

  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 onUpdateTable = (values: any) => {
    setNewTables((prev) => {
      // if the new values have tableIndex, we are editing an existing table
      if (values.tableIndex !== undefined) {
        const tableIndex = values.tableIndex
        const newTables = [...prev]
        delete values.tableIndex
        newTables[tableIndex] = values
        return newTables
      }
      return [...prev, values]
    })
    setIsEditingTable(false)
    setSelectedTableToEdit(undefined)
  }

  const onSubmit = async (values: any) => {
    const validate = makeValidateRequired(wrapperMeta.server.options)
    const errors: any = validate(values)

    if (values.wrapper_name.length === 0) {
      errors.wrapper_name = 'Please provide a name for your wrapper'
    }

    if (selectedMode === 'tables') {
      if (newTables.length === 0) {
        errors.tables = 'Please provide at least one table'
      }
    }
    if (selectedMode === 'schema') {
      if (wrapperMeta.sourceSchemaOption && values.source_schema.length === 0) {
        errors.source_schema = 'Please provide a source schema'
      }
      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 {
      if (selectedMode === 'schema') {
        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: selectedMode === 'schema' ? values.target_schema : undefined,
        },
        mode:
          selectedMode === 'schema'
            ? wrapperMeta.sourceSchemaOption
              ? 'schema'
              : 'skip'
            : 'tables',
        tables: newTables,
        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 (
    <>
      <div className="h-full" tabIndex={-1}>
        <Form
          id={FORM_ID}
          initialValues={initialValues}
          onSubmit={onSubmit}
          className="flex-grow flex flex-col h-full"
        >
          {({ values, initialValues }: any) => {
            const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues)
            onDirty(hasChanges)

            return (
              <>
                <SheetHeader>
                  <SheetTitle>Create a {wrapperMeta.label} wrapper</SheetTitle>
                </SheetHeader>
                <div className="flex-grow overflow-y-auto">
                  <FormSection header={<FormSectionLabel>Wrapper Configuration</FormSectionLabel>}>
                    <FormSectionContent loading={false}>
                      <Input
                        id="wrapper_name"
                        label="Wrapper Name"
                        error={formErrors.wrapper_name}
                        descriptionText={
                          (values?.wrapper_name ?? '').length > 0 ? (
                            <>
                              Your wrapper's server name will be{' '}
                              <code className="text-code-inline">{values.wrapper_name}_server</code>
                            </>
                          ) : (
                            ''
                          )
                        }
                      />
                    </FormSectionContent>
                  </FormSection>
                  <Separator />
                  <FormSection
                    header={<FormSectionLabel>{wrapperMeta.label} Configuration</FormSectionLabel>}
                  >
                    <FormSectionContent loading={false}>
                      {wrapperMeta.server.options
                        .filter((option) => !option.hidden)
                        .map((option) => (
                          <InputField
                            key={option.name}
                            option={option}
                            loading={false}
                            error={formErrors[option.name]}
                          />
                        ))}
                    </FormSectionContent>
                  </FormSection>
                  <Separator />
                  <FormSection header={<FormSectionLabel>Data target</FormSectionLabel>}>
                    <FormSectionContent loading={false} className="text-sm">
                      <RadioGroupStacked
                        value={selectedMode}
                        onValueChange={(value) => setSelectedMode(value as 'tables' | 'schema')}
                      >
                        <RadioGroupStackedItem
                          key="tables"
                          value="tables"
                          disabled={wrapperMeta.tables.length === 0}
                          label="Tables"
                          showIndicator={false}
                        >
                          <div className="flex  gap-x-5">
                            <div className="flex flex-col">
                              <p className="text-foreground-light text-left">
                                Create foreign tables to query data from {wrapperMeta.label}.
                              </p>
                            </div>
                          </div>
                          {wrapperMeta.tables.length === 0 ? (
                            <div className="w-full flex gap-x-2 py-2 items-center">
                              <WarningIcon />
                              <span className="text-xs">
                                This wrapper doesn't support using foreign tables.
                              </span>
                            </div>
                          ) : null}
                        </RadioGroupStackedItem>
                        <RadioGroupStackedItem
                          key="schema"
                          value="schema"
                          disabled={
                            !wrapperMeta.canTargetSchema || !hasRequiredVersionForeignSchema
                          }
                          label="Schema"
                          showIndicator={false}
                        >
                          <div className="flex  gap-x-5">
                            <div className="flex flex-col">
                              <p className="text-foreground-light text-left">
                                Create all foreign tables from {wrapperMeta.label} in a specified
                                schema.
                              </p>
                            </div>
                          </div>
                          {wrapperMeta.canTargetSchema ? (
                            hasRequiredVersionForeignSchema ? null : (
                              <div className="w-full flex gap-x-2 py-2 items-center">
                                <WarningIcon />
                                <span className="text-xs text-left">
                                  This feature requires the{' '}
                                  <span className="text-brand">wrappers</span> extension to be of
                                  minimum version of 0.5.0.
                                </span>
                              </div>
                            )
                          ) : (
                            <div className="w-full flex gap-x-2 py-2 items-center">
                              <WarningIcon />
                              <span className="text-xs">
                                This wrapper doesn't support using a foreign schema.
                              </span>
                            </div>
                          )}
                        </RadioGroupStackedItem>
                      </RadioGroupStacked>
                    </FormSectionContent>
                  </FormSection>
                  <Separator />
                  {selectedMode === 'tables' && (
                    <FormSection
                      header={
                        <FormSectionLabel>
                          <p>Foreign Tables</p>
                          <p className="text-foreground-light mt-2 w-[90%]">
                            You can query your data from these foreign tables after the wrapper is
                            created
                          </p>
                        </FormSectionLabel>
                      }
                    >
                      <FormSectionContent loading={false}>
                        {newTables.length === 0 ? (
                          <div className="flex justify-end translate-y-4">
                            <Button type="default" onClick={() => setIsEditingTable(true)}>
                              Add foreign table
                            </Button>
                          </div>
                        ) : (
                          <div className="space-y-2">
                            {newTables.map((table, i) => (
                              <div
                                key={`${table.schema_name}.${table.table_name}`}
                                className="flex items-center justify-between px-4 py-2 border rounded-md border-control"
                              >
                                <div>
                                  <p className="text-sm">
                                    {table.schema_name}.{table.table_name}
                                  </p>
                                  <p className="text-sm text-foreground-light">
                                    Columns:{' '}
                                    {table.columns.map((column: any) => column.name).join(', ')}
                                  </p>
                                </div>
                                <div className="flex items-center space-x-2">
                                  <Button
                                    type="default"
                                    className="px-1"
                                    icon={<Edit />}
                                    onClick={() => {
                                      setIsEditingTable(true)
                                      setSelectedTableToEdit({ ...table, tableIndex: i })
                                    }}
                                  />
                                  <Button
                                    type="default"
                                    className="px-1"
                                    icon={<Trash />}
                                    onClick={() => {
                                      setNewTables((prev) => prev.filter((_, j) => j !== i))
                                    }}
                                  />
                                </div>
                              </div>
                            ))}
                          </div>
                        )}
                        {newTables.length > 0 && (
                          <div className="flex justify-end">
                            <Button type="default" onClick={() => setIsEditingTable(true)}>
                              Add foreign table
                            </Button>
                          </div>
                        )}
                        {newTables.length === 0 && formErrors.tables && (
                          <p className="text-sm text-right text-red-900">{formErrors.tables}</p>
                        )}
                      </FormSectionContent>
                    </FormSection>
                  )}
                  {selectedMode === 'schema' && (
                    <FormSection
                      header={
                        <FormSectionLabel>
                          <p>Foreign Schema</p>
                          <p className="text-foreground-light mt-2 w-[90%]">
                            You can query your data from the foreign tables in the specified schema
                            after the wrapper is created.
                          </p>
                        </FormSectionLabel>
                      }
                    >
                      <FormSectionContent loading={false}>
                        {wrapperMeta.sourceSchemaOption &&
                          !wrapperMeta.sourceSchemaOption?.readOnly && (
                            // Hide the field if the source schema is read-only
                            <div>
                              <InputField
                                key="source_schema"
                                option={wrapperMeta.sourceSchemaOption}
                                loading={false}
                                error={formErrors['source_schema']}
                              />
                              <p className="text-foreground-lighter text-sm">
                                {wrapperMeta.sourceSchemaOption.description}
                              </p>
                            </div>
                          )}
                        <div className="flex flex-col gap-2">
                          <InputField
                            key="target_schema"
                            option={{
                              name: 'target_schema',
                              label: 'Specify a new schema to create all wrapper tables in',
                              required: true,
                              encrypted: false,
                              secureEntry: false,
                            }}
                            loading={false}
                            error={formErrors['target_schema']}
                          />
                          <p className="text-foreground-lighter text-sm">
                            A new schema will be created. For security purposes, the wrapper tables
                            from the foreign schema cannot be created within an existing schema.
                          </p>
                        </div>
                      </FormSectionContent>
                    </FormSection>
                  )}
                </div>

                <SheetFooter>
                  <Button
                    size="tiny"
                    type="default"
                    htmlType="button"
                    onClick={onCloseWithConfirmation}
                    disabled={isLoading}
                  >
                    Cancel
                  </Button>
                  <Button
                    size="tiny"
                    type="primary"
                    form={FORM_ID}
                    htmlType="submit"
                    loading={isLoading}
                  >
                    Create wrapper
                  </Button>
                </SheetFooter>
              </>
            )
          }}
        </Form>
      </div>

      <WrapperTableEditor
        visible={isEditingTable}
        tables={wrapperMeta.tables}
        onCancel={() => {
          setSelectedTableToEdit(undefined)
          setIsEditingTable(false)
        }}
        onSave={onUpdateTable}
        initialData={selectedTableToEdit}
      />
    </>
  )
}

Subdomains

Frequently Asked Questions

What does CreateWrapperSheet() do?
CreateWrapperSheet() is a function in the supabase codebase.
What does CreateWrapperSheet() call?
CreateWrapperSheet() calls 1 function(s): makeValidateRequired.

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free