UpdateForeignSchemaDialog.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { zodResolver } from '@hookform/resolvers/zod'
  2. import { useParams } from 'common'
  3. import { useState } from 'react'
  4. import { SubmitHandler, useForm } from 'react-hook-form'
  5. import { toast } from 'sonner'
  6. import {
  7. Button,
  8. Dialog,
  9. DialogContent,
  10. DialogFooter,
  11. DialogHeader,
  12. DialogSection,
  13. DialogSectionSeparator,
  14. DialogTitle,
  15. DialogTrigger,
  16. Form,
  17. FormField,
  18. Select,
  19. SelectContent,
  20. SelectItem,
  21. SelectTrigger,
  22. SelectValue,
  23. Tooltip,
  24. TooltipContent,
  25. TooltipTrigger,
  26. } from 'ui'
  27. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  28. import z from 'zod'
  29. import { getAnalyticsBucketFDWServerName } from './AnalyticsBucketDetails.utils'
  30. import { useAnalyticsBucketAssociatedEntities } from './useAnalyticsBucketAssociatedEntities'
  31. import { DocsButton } from '@/components/ui/DocsButton'
  32. import { InlineLinkClassName } from '@/components/ui/InlineLink'
  33. import { useFDWImportForeignSchemaMutation } from '@/data/fdw/fdw-import-foreign-schema-mutation'
  34. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  35. import { DOCS_URL } from '@/lib/constants'
  36. export const UpdateForeignSchemaDialog = ({
  37. namespace,
  38. tables,
  39. }: {
  40. namespace: string
  41. tables: string[]
  42. }) => {
  43. const { ref: projectRef, bucketId } = useParams()
  44. const { data: project } = useSelectedProjectQuery()
  45. const [isOpen, setIsOpen] = useState(false)
  46. const { icebergWrapper } = useAnalyticsBucketAssociatedEntities({ bucketId })
  47. const connectedForeignTablesForNamespace = (icebergWrapper?.tables ?? []).filter((x) =>
  48. x.options[0].startsWith(`table=${namespace}.`)
  49. )
  50. const schemasAssociatedWithNamespace = [
  51. ...new Set(connectedForeignTablesForNamespace.map((x) => x.schema)),
  52. ]
  53. const serverName = getAnalyticsBucketFDWServerName(bucketId ?? '')
  54. const FormSchema = z.object({
  55. schema: z.string().trim().min(1, 'Schema name is required'),
  56. })
  57. const form = useForm<z.infer<typeof FormSchema>>({
  58. resolver: zodResolver(FormSchema as any),
  59. defaultValues: { schema: schemasAssociatedWithNamespace[0] },
  60. values: { schema: schemasAssociatedWithNamespace[0] },
  61. })
  62. const { mutateAsync: importForeignSchema, isPending: isUpdating } =
  63. useFDWImportForeignSchemaMutation()
  64. const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
  65. if (!projectRef) return console.error('Project ref is required')
  66. try {
  67. await importForeignSchema({
  68. projectRef,
  69. connectionString: project?.connectionString,
  70. serverName: serverName,
  71. sourceSchema: namespace,
  72. targetSchema: values.schema,
  73. })
  74. toast.success(`Successfully updated "${values.schema}" schema!`)
  75. setIsOpen(false)
  76. } catch (error: any) {
  77. toast.error(`Failed to update schema: ${error.message}`)
  78. }
  79. }
  80. return (
  81. <Dialog open={isOpen} onOpenChange={setIsOpen}>
  82. <DialogTrigger asChild>
  83. <Button type="default">Update schema tables</Button>
  84. </DialogTrigger>
  85. <DialogContent size="medium" aria-describedby={undefined}>
  86. <Form {...form}>
  87. <form onSubmit={form.handleSubmit(onSubmit)}>
  88. <DialogHeader>
  89. <DialogTitle>Update schema to expose foreign tables</DialogTitle>
  90. </DialogHeader>
  91. <DialogSectionSeparator />
  92. <DialogSection className="flex flex-col gap-y-4">
  93. <p className="text-sm">
  94. {tables.length > 1 ? (
  95. <>
  96. There are{' '}
  97. <Tooltip>
  98. <TooltipTrigger>
  99. <span className={InlineLinkClassName}>{tables.length} tables</span>
  100. </TooltipTrigger>
  101. <TooltipContent className="max-w-64 text-center" side="bottom">
  102. {tables.join(', ')}
  103. </TooltipContent>
  104. </Tooltip>{' '}
  105. that aren't included in the Iceberg Foreign Data Wrapper. Update the wrapper to
  106. create foreign tables for all unexposed tables. This will let you query the
  107. tables from Postgres.
  108. </>
  109. ) : (
  110. `The table "${tables[0]}" in the "${namespace}" namespace is not yet included in the Iceberg Foreign Data Wrapper. The schema can be updated to expose this table as a foreign table.`
  111. )}
  112. </p>
  113. {schemasAssociatedWithNamespace.length > 1 ? (
  114. <FormField
  115. control={form.control}
  116. name="schema"
  117. render={({ field }) => (
  118. <FormItemLayout
  119. layout="vertical"
  120. label="Select which Postgres schema to update"
  121. >
  122. <Select value={field.value} onValueChange={(val) => field.onChange(val)}>
  123. <SelectTrigger>
  124. <SelectValue />
  125. </SelectTrigger>
  126. <SelectContent>
  127. {schemasAssociatedWithNamespace.map((x) => (
  128. <SelectItem key={x} value={x}>
  129. {x}
  130. </SelectItem>
  131. ))}
  132. </SelectContent>
  133. </Select>
  134. </FormItemLayout>
  135. )}
  136. />
  137. ) : (
  138. <p className="text-sm">
  139. Confirm to update foreign schema on the "{namespace}" namespace?
  140. </p>
  141. )}
  142. </DialogSection>
  143. <DialogFooter className="justify-between!">
  144. <DocsButton href={`${DOCS_URL}/guides/storage/analytics/query-with-postgres`} />
  145. <div className="flex items-center gap-x-2">
  146. <Button type="default" disabled={isUpdating} onClick={() => setIsOpen(false)}>
  147. Cancel
  148. </Button>
  149. <Button htmlType="submit" type="primary" loading={isUpdating}>
  150. Update schema
  151. </Button>
  152. </div>
  153. </DialogFooter>
  154. </form>
  155. </Form>
  156. </DialogContent>
  157. </Dialog>
  158. )
  159. }