HighQueryCost.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import {
  2. Button,
  3. Dialog,
  4. DialogClose,
  5. DialogContent,
  6. DialogDescription,
  7. DialogFooter,
  8. DialogHeader,
  9. DialogSection,
  10. DialogSectionSeparator,
  11. DialogTitle,
  12. DialogTrigger,
  13. Tooltip,
  14. TooltipContent,
  15. TooltipTrigger,
  16. } from 'ui'
  17. import { Admonition } from 'ui-patterns'
  18. import { DocsButton } from './DocsButton'
  19. import { InlineLinkClassName } from './InlineLink'
  20. import { DOCS_URL } from '@/lib/constants'
  21. import { ResponseError } from '@/types'
  22. interface HighQueryCostErrorProps {
  23. error: ResponseError
  24. suggestions?: string[]
  25. onSelectLoadData?: () => void
  26. }
  27. export const HighCostError = ({
  28. error,
  29. suggestions,
  30. onSelectLoadData,
  31. }: HighQueryCostErrorProps) => {
  32. return (
  33. <Admonition
  34. type="default"
  35. title="Data not loaded to protect database performance"
  36. description="The query to retrieve the data was not run as it could place heavy load on the database and impact performance"
  37. >
  38. <div className="mt-2 flex items-center gap-x-2 items-center">
  39. {!!onSelectLoadData && (
  40. <LoadDataWarningDialog error={error} onSelectLoadData={onSelectLoadData} />
  41. )}
  42. <HighQueryCostDialog error={error} suggestions={suggestions} />
  43. </div>
  44. </Admonition>
  45. )
  46. }
  47. const HighQueryCostDialog = ({ error, suggestions = [] }: HighQueryCostErrorProps) => {
  48. const metadata = error.metadata
  49. return (
  50. <Dialog>
  51. <DialogTrigger asChild>
  52. <Button type="outline">Learn more</Button>
  53. </DialogTrigger>
  54. <DialogContent onOpenAutoFocus={(event) => event.preventDefault()}>
  55. <DialogHeader>
  56. <DialogTitle>Estimated query cost exceeds safety thresholds</DialogTitle>
  57. <DialogDescription>
  58. Preventive measure to mitigate impacting the database
  59. </DialogDescription>
  60. </DialogHeader>
  61. <DialogSectionSeparator />
  62. <DialogSection className="flex flex-col gap-y-2 text-sm">
  63. <p>
  64. The dashboard runs optimized SQL queries on your project’s database to load data for
  65. this interface.
  66. </p>
  67. <p>
  68. However, the query was skipped as its{' '}
  69. <Tooltip>
  70. <TooltipTrigger className={InlineLinkClassName}>estimated cost</TooltipTrigger>
  71. <TooltipContent side="bottom" className="flex flex-col gap-y-1">
  72. <p>Estimated cost: {metadata?.cost.toLocaleString()}</p>
  73. <p className="text-foreground-light">
  74. Determined via the <code className="text-code-inline">EXPLAIN</code> command
  75. </p>
  76. </TooltipContent>
  77. </Tooltip>{' '}
  78. is high and could place significant load on the database with high disk I/O or CPU
  79. usage.
  80. </p>
  81. </DialogSection>
  82. {suggestions.length > 0 && (
  83. <>
  84. <DialogSectionSeparator />
  85. <DialogSection className="flex flex-col gap-y-4 text-sm">
  86. <p className="font-mono text-foreground-lighter uppercase tracking-tight text-sm">
  87. Suggested steps
  88. </p>
  89. {suggestions.length > 0 && (
  90. <div className="flex flex-col gap-y-1">
  91. <p>You may check the following to lower the cost of the query</p>
  92. <ul className="list-disc pl-6">
  93. {suggestions.map((x) => (
  94. <li key={x}>{x}</li>
  95. ))}
  96. </ul>
  97. </div>
  98. )}
  99. </DialogSection>
  100. </>
  101. )}
  102. <DialogFooter>
  103. <DocsButton
  104. href={`${DOCS_URL}/guides/troubleshooting/understanding-postgresql-explain-output-Un9dqX`}
  105. />
  106. <DialogClose asChild>
  107. <Button type="default" className="opacity-100">
  108. Understood
  109. </Button>
  110. </DialogClose>
  111. </DialogFooter>
  112. </DialogContent>
  113. </Dialog>
  114. )
  115. }
  116. const LoadDataWarningDialog = ({
  117. error,
  118. onSelectLoadData,
  119. }: {
  120. error: ResponseError
  121. onSelectLoadData: () => void
  122. }) => {
  123. const metadata = error.metadata
  124. return (
  125. <Dialog>
  126. <DialogTrigger asChild>
  127. <Button type="default">Load data</Button>
  128. </DialogTrigger>
  129. <DialogContent onOpenAutoFocus={(event) => event.preventDefault()}>
  130. <DialogHeader>
  131. <DialogTitle>Confirm to proceed loading data</DialogTitle>
  132. <DialogDescription>
  133. Preventive measure to mitigate impacting the database
  134. </DialogDescription>
  135. </DialogHeader>
  136. <DialogSectionSeparator />
  137. <DialogSection className="flex flex-col gap-y-2 text-sm">
  138. <p>
  139. The query to load your table's data was initially skipped as its{' '}
  140. <Tooltip>
  141. <TooltipTrigger className={InlineLinkClassName}>estimated cost</TooltipTrigger>
  142. <TooltipContent side="bottom" className="flex flex-col gap-y-1">
  143. <p>Estimated cost: {metadata?.cost.toLocaleString()}</p>
  144. <p className="text-foreground-light">
  145. Determined via the <code className="text-code-inline">EXPLAIN</code> command
  146. </p>
  147. </TooltipContent>
  148. </Tooltip>{' '}
  149. is high and could place significant load on the database with high disk I/O or CPU
  150. usage.
  151. </p>
  152. <p>
  153. You may proceed to run the query, and we'll suppress this warning for this table for the
  154. rest of this browser session.
  155. </p>
  156. </DialogSection>
  157. <DialogFooter>
  158. <DialogClose asChild>
  159. <Button type="default" className="opacity-100">
  160. Cancel
  161. </Button>
  162. </DialogClose>
  163. <DialogClose asChild>
  164. <Button type="warning" onClick={() => onSelectLoadData()}>
  165. I understand, proceed
  166. </Button>
  167. </DialogClose>
  168. </DialogFooter>
  169. </DialogContent>
  170. </Dialog>
  171. )
  172. }