QueryInsightsTableRow.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import { Loader2 } from 'lucide-react'
  2. import { AiIconAnimation, Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
  3. import type { ClassifiedQuery } from '../QueryInsightsHealth/QueryInsightsHealth.types'
  4. import { ISSUE_DOT_COLORS, ISSUE_ICONS } from './QueryInsightsTable.constants'
  5. import { formatDuration, getColumnName, getTableName } from './QueryInsightsTable.utils'
  6. interface QueryInsightsTableRowProps {
  7. item: ClassifiedQuery
  8. onRowClick?: () => void
  9. onGoToLogs?: () => void
  10. onCreateIndex?: () => void
  11. onExplain?: () => void
  12. onAiSuggestedFix?: () => void
  13. isExplainLoading?: boolean
  14. }
  15. export const QueryInsightsTableRow = ({
  16. item,
  17. onRowClick,
  18. onGoToLogs,
  19. onCreateIndex,
  20. onExplain,
  21. onAiSuggestedFix,
  22. isExplainLoading,
  23. }: QueryInsightsTableRowProps) => {
  24. const IssueIcon = item.issueType ? ISSUE_ICONS[item.issueType] : null
  25. return (
  26. <div
  27. className="flex items-center gap-4 px-6 py-4 border-b hover:bg-surface-100 cursor-pointer group"
  28. onClick={onRowClick}
  29. >
  30. {item.issueType && IssueIcon && (
  31. <div
  32. className={cn(
  33. 'h-6 w-6 rounded-full shrink-0 border flex items-center justify-center',
  34. ISSUE_DOT_COLORS[item.issueType]?.border,
  35. ISSUE_DOT_COLORS[item.issueType]?.background
  36. )}
  37. >
  38. <IssueIcon size={14} className={ISSUE_DOT_COLORS[item.issueType].color} />
  39. </div>
  40. )}
  41. <div className="flex-1 min-w-0">
  42. <p className="text-xs font-mono text-foreground line-clamp-1">
  43. <span className="text-foreground">{item.queryType ?? '–'}</span>
  44. {getTableName(item.query) && (
  45. <>
  46. {' '}
  47. <span className="text-foreground-lighter">in</span> {getTableName(item.query)}
  48. </>
  49. )}
  50. {getColumnName(item.query) && (
  51. <>
  52. <span className="text-foreground-lighter">,</span> {getColumnName(item.query)}
  53. </>
  54. )}
  55. </p>
  56. <p
  57. className={cn(
  58. 'text-xs mt-0.5 font-mono line-clamp-1',
  59. item.issueType === 'error' && 'text-destructive-600',
  60. item.issueType === 'index' && 'text-warning-600',
  61. item.issueType === 'slow' && 'text-foreground-lighter'
  62. )}
  63. >
  64. {item.hint}
  65. </p>
  66. </div>
  67. <div className="flex items-stretch divide-x divide-border shrink-0 tabular-nums">
  68. <Tooltip>
  69. <TooltipTrigger asChild>
  70. <div className="flex flex-col items-end pr-4 cursor-default">
  71. <span
  72. className={cn(
  73. 'text-sm font-mono leading-snug',
  74. item.mean_time >= 1000 && 'text-destructive-600'
  75. )}
  76. >
  77. {formatDuration(item.mean_time)}
  78. </span>
  79. <span className="text-[10px] text-foreground-muted uppercase tracking-wide leading-snug">
  80. avg
  81. </span>
  82. </div>
  83. </TooltipTrigger>
  84. <TooltipContent side="top" className="max-w-[220px] text-center">
  85. Average execution time per call. High mean time means individual runs are slow —
  86. directly felt by users.
  87. </TooltipContent>
  88. </Tooltip>
  89. <Tooltip>
  90. <TooltipTrigger asChild>
  91. <div className="flex flex-col items-end px-4 cursor-default">
  92. <span className="text-sm font-mono leading-snug">
  93. {item.prop_total_time.toFixed(1)}%
  94. </span>
  95. <span className="text-[10px] text-foreground-muted uppercase tracking-wide leading-snug">
  96. of db
  97. </span>
  98. </div>
  99. </TooltipTrigger>
  100. <TooltipContent side="top" className="max-w-[220px] text-center">
  101. Percentage of total database execution time. Fixing high-impact queries has the biggest
  102. overall effect on your database.
  103. </TooltipContent>
  104. </Tooltip>
  105. <Tooltip>
  106. <TooltipTrigger asChild>
  107. <div className="flex flex-col items-end pl-4 cursor-default">
  108. <span className="text-sm font-mono leading-snug">{item.calls.toLocaleString()}</span>
  109. <span className="text-[10px] text-foreground-muted uppercase tracking-wide leading-snug">
  110. calls
  111. </span>
  112. </div>
  113. </TooltipTrigger>
  114. <TooltipContent side="top" className="max-w-[220px] text-center">
  115. Number of times this query ran in the selected time window.
  116. </TooltipContent>
  117. </Tooltip>
  118. </div>
  119. <div className="flex items-center gap-2 shrink-0 justify-end w-[260px]">
  120. <Button
  121. type="default"
  122. size="tiny"
  123. onClick={(e) => {
  124. e.stopPropagation()
  125. onGoToLogs?.()
  126. }}
  127. >
  128. Go to Logs
  129. </Button>
  130. {(item.issueType === 'index' || item.issueType === 'slow') && (
  131. <Button
  132. type="default"
  133. size="tiny"
  134. icon={isExplainLoading ? <Loader2 size={12} className="animate-spin" /> : undefined}
  135. onClick={(e) => {
  136. e.stopPropagation()
  137. onExplain?.()
  138. }}
  139. >
  140. Explain
  141. </Button>
  142. )}
  143. {item.issueType === 'index' && (
  144. <Button
  145. type="primary"
  146. size="tiny"
  147. onClick={(e) => {
  148. e.stopPropagation()
  149. onCreateIndex?.()
  150. }}
  151. >
  152. Create Index
  153. </Button>
  154. )}
  155. {(item.issueType === 'error' || item.issueType === 'slow') && (
  156. <Button
  157. type="default"
  158. size="tiny"
  159. icon={<AiIconAnimation size={14} />}
  160. onClick={(e) => {
  161. e.stopPropagation()
  162. onAiSuggestedFix?.()
  163. }}
  164. >
  165. Fix with AI
  166. </Button>
  167. )}
  168. </div>
  169. </div>
  170. )
  171. }