ExplainVisualizer.RowCountIndicator.tsx94 lines · main
| 1 | import { ArrowRight } from 'lucide-react' |
| 2 | import { Tooltip, TooltipContent, TooltipTrigger } from 'ui' |
| 3 | |
| 4 | interface RowCountIndicatorProps { |
| 5 | actualRows?: number |
| 6 | estimatedRows?: number |
| 7 | rowsRemovedByFilter?: number |
| 8 | } |
| 9 | |
| 10 | function formatRowCount(rows: number | undefined): string { |
| 11 | if (rows === undefined) return '-' |
| 12 | return rows.toLocaleString() |
| 13 | } |
| 14 | |
| 15 | export function RowCountIndicator({ |
| 16 | actualRows, |
| 17 | estimatedRows, |
| 18 | rowsRemovedByFilter, |
| 19 | }: RowCountIndicatorProps) { |
| 20 | const hasActualRows = actualRows !== undefined |
| 21 | const hasEstimatedRows = estimatedRows !== undefined |
| 22 | const hasFilterData = rowsRemovedByFilter !== undefined && hasActualRows |
| 23 | const totalRowsScanned = hasFilterData ? actualRows + rowsRemovedByFilter : undefined |
| 24 | |
| 25 | // Show filter flow: scanned → filtered out → remaining |
| 26 | if (hasFilterData && totalRowsScanned !== undefined) { |
| 27 | return ( |
| 28 | <Tooltip> |
| 29 | <TooltipTrigger asChild> |
| 30 | <div className="flex items-center gap-1.5 cursor-help"> |
| 31 | <span className="text-foreground-light">{formatRowCount(totalRowsScanned)}</span> |
| 32 | <ArrowRight size={10} className="text-foreground-muted" /> |
| 33 | <span className="text-destructive-600 font-medium"> |
| 34 | -{formatRowCount(rowsRemovedByFilter)} |
| 35 | </span> |
| 36 | <ArrowRight size={10} className="text-foreground-muted" /> |
| 37 | <span className="text-brand font-medium"> |
| 38 | {formatRowCount(actualRows)} {actualRows === 1 ? 'row' : 'rows'} |
| 39 | </span> |
| 40 | </div> |
| 41 | </TooltipTrigger> |
| 42 | <TooltipContent side="top" className="max-w-xs font-sans"> |
| 43 | <p className="font-medium">Filter applied</p> |
| 44 | <p className="text-foreground-lighter text-xs mt-1"> |
| 45 | {formatRowCount(totalRowsScanned)} rows were scanned,{' '} |
| 46 | {formatRowCount(rowsRemovedByFilter)} were filtered out, leaving{' '} |
| 47 | {formatRowCount(actualRows)} rows. Consider adding an index to reduce rows scanned. |
| 48 | </p> |
| 49 | </TooltipContent> |
| 50 | </Tooltip> |
| 51 | ) |
| 52 | } |
| 53 | |
| 54 | // Simple row count with actual rows |
| 55 | if (hasActualRows) { |
| 56 | return ( |
| 57 | <Tooltip> |
| 58 | <TooltipTrigger asChild> |
| 59 | <span className="text-foreground-light cursor-help"> |
| 60 | {formatRowCount(actualRows)} {actualRows === 1 ? 'row' : 'rows'} |
| 61 | </span> |
| 62 | </TooltipTrigger> |
| 63 | <TooltipContent side="top" className="max-w-xs font-sans"> |
| 64 | <p className="font-medium">Rows returned</p> |
| 65 | <p className="text-foreground-lighter text-xs mt-1"> |
| 66 | This operation processed and returned {formatRowCount(actualRows)} rows. |
| 67 | </p> |
| 68 | </TooltipContent> |
| 69 | </Tooltip> |
| 70 | ) |
| 71 | } |
| 72 | |
| 73 | // Only estimated rows available (EXPLAIN without ANALYZE) |
| 74 | if (hasEstimatedRows) { |
| 75 | return ( |
| 76 | <Tooltip> |
| 77 | <TooltipTrigger asChild> |
| 78 | <span className="text-foreground-muted cursor-help"> |
| 79 | est. {formatRowCount(estimatedRows)} {estimatedRows === 1 ? 'row' : 'rows'} |
| 80 | </span> |
| 81 | </TooltipTrigger> |
| 82 | <TooltipContent side="top" className="max-w-xs font-sans"> |
| 83 | <p className="font-medium">Estimated rows</p> |
| 84 | <p className="text-foreground-lighter text-xs mt-1"> |
| 85 | The planner estimates this operation will return {formatRowCount(estimatedRows)} rows. |
| 86 | Run with EXPLAIN ANALYZE to see actual row counts. |
| 87 | </p> |
| 88 | </TooltipContent> |
| 89 | </Tooltip> |
| 90 | ) |
| 91 | } |
| 92 | |
| 93 | return <span className="text-foreground-muted">-</span> |
| 94 | } |