Files
supabase/apps/studio/components/interfaces/Explorer/QueryResultChart.tsx
T
Joshen Lim ebb8e2336e Centralize empty state + error handling for query results (#49096)
## Context

Related to Explorer/Notebook - currently with the chart view, if the
query has any errors, there's no error UI being shown
Mainly because the error UI handlers are all within the table view

Changes here hence opt to extract the empty state + error UI into a
centralized renderer

<img width="936" height="366" alt="image"
src="https://github.com/user-attachments/assets/437891dc-241e-4c43-97a6-6eef52472ee7"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added unified query result display for prompts, errors, empty results,
tables, and charts.
  * Query results now switch consistently between table and chart views.

* **Bug Fixes**
  * Improved empty-result layout centering across views.
  * Expanded error display to use the available width.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-14 19:57:36 +07:00

104 lines
3.3 KiB
TypeScript

import { useMemo } from 'react'
import { Chart, ChartBar, ChartCard, ChartContent, ChartLine } from 'ui-patterns/Chart'
import { type QueryChartConfig, type QueryResult } from './types'
import NoDataPlaceholder from '@/components/ui/Charts/NoDataPlaceholder'
import { formatLogTick, getCumulativeResults } from '@/components/ui/QueryBlock/QueryBlock.utils'
interface QueryResultChartProps {
chart?: QueryChartConfig
result?: QueryResult
}
// [Joshen] Will need to implement log scale - refer to QueryBlock.tsx `effectiveLogScale`
// [Joshen] Will also need to implement error handling where appropriate (e.g if query errors)
const toChartValue = (value: unknown): string | number => {
if (typeof value === 'number' || typeof value === 'string') return value
if (value === null || value === undefined) return ''
return String(value)
}
export const QueryResultChart = ({ chart, result }: QueryResultChartProps) => {
const { type, x_column, y_columns = [], cumulative, show_labels, scale } = chart ?? {}
const hasConfig = !!x_column && y_columns.length > 0
const chartRows = useMemo(() => {
const xKey = x_column ?? ''
const yKey = y_columns[0] ?? ''
return (result?.rows ?? []).map((row) => ({
[xKey]: toChartValue(row[xKey]),
[yKey]: toChartValue(row[yKey]),
}))
}, [result, x_column, y_columns])
const cumulativeResults = useMemo(
() => getCumulativeResults({ rows: chartRows }, { yKey: y_columns[0] ?? '' }),
[chartRows, y_columns]
)
const resultToRender = cumulative ? cumulativeResults : chartRows
if (!result || (result?.rows && result.rows.length === 0)) {
return (
<NoDataPlaceholder
className="bg border-0"
size="normal"
message="No results"
description="Your query returned no rows"
/>
)
}
if (!hasConfig) {
return (
<NoDataPlaceholder
className="bg border-0"
size="normal"
message="Configure your chart"
description="Select your X and Y axis in the display settings"
/>
)
}
return (
<Chart>
<ChartCard className="rounded-none border-0">
<ChartContent>
<div className="h-40">
{type === 'bar' && (
<ChartBar
isFullHeight
xKey={x_column}
dataKey={y_columns[0]}
showXAxis={show_labels}
showYAxis={show_labels}
data={resultToRender}
YAxisProps={{
scale: scale === 'log' ? 'log' : 'auto',
domain: scale === 'log' ? [1, 'auto'] : undefined,
tickFormatter: scale === 'log' ? formatLogTick : undefined,
}}
/>
)}
{type === 'line' && (
<ChartLine
isFullHeight
xKey={x_column}
dataKey={y_columns[0]}
showXAxis={show_labels}
showYAxis={show_labels}
data={resultToRender}
YAxisProps={{
scale: scale === 'log' ? 'log' : 'auto',
domain: scale === 'log' ? [1, 'auto'] : undefined,
tickFormatter: scale === 'log' ? formatLogTick : undefined,
}}
/>
)}
</div>
</ChartContent>
</ChartCard>
</Chart>
)
}