Files
supabase/apps/studio/components/interfaces/Explorer/QueryEditor/QueryResultChart.tsx
T
Saxon Fletcher 4ee43f3585 chore(studio): refine Explorer query UI (#49895)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

UI refinements for Explorer query surfaces.

## What is the current behavior?

- The assistant chat textarea uses a tighter radius than the Run SQL /
Create a notebook cards on Explorer home.
- Chart results sit unevenly in the results pane because axis gutters
stack on card padding, and long Y labels can clip.
- Selecting SQL in the editor changes the primary Run button to Run
selected, which is easy to trigger by accident.
- The Prettify SQL icon in notebook query cells uses Lucide's default
size, so it doesn't match other toolbar actions.

## What is the new behavior?

- Assistant chat form uses `rounded-lg` so it matches the home action
cards everywhere the form is used.
- Query result charts collapse unused axis space, add a little padding
when labels are on, and size the Y axis from formatted ticks so longer
labels fit.
- Run is a default split button that always executes the full query. Run
selected is a secondary menu item, disabled until SQL is selected.
- Notebook cell Prettify SQL icons use `size={16}` and `strokeWidth={2}`
like the rest of the Explorer toolbar.

## Additional context

Cmd+Enter in the editor still runs the current selection when there is
one.

## Test plan

- [ ] Open Explorer home and confirm the assistant chat radius matches
the Run SQL and Create a notebook cards.
- [ ] Run a query, switch to chart view, and check spacing with labels
off and on, including large Y values.
- [ ] With no selection, click Run and confirm the full query runs. Open
the split menu and confirm Run selected is disabled.
- [ ] Select SQL, click Run, and confirm the full query still runs. Use
Run selected from the menu to run only the selection.
- [ ] In a notebook query cell, confirm Prettify SQL matches the size
and stroke of nearby toolbar icons.


Made with [Cursor](https://cursor.com)

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

## Summary by CodeRabbit

- **New Features**
- Added a split Run control in the query editor, with separate actions
for running all content or only selected text.
  - Added support for customizing chart X-axis display settings.
- Improved chart Y-axis sizing, scaling, and tick formatting for clearer
results.

- **Bug Fixes**
  - The “Run selected” action is unavailable when no text is selected.

- **Style**
- Updated toolbar icon sizing and added rounded corners to the assistant
chat input.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-03 09:44:45 +10:00

139 lines
4.1 KiB
TypeScript

import { useMemo } from 'react'
import { cn, type ChartConfig as ChartSeriesConfig } from 'ui'
import { Chart, ChartBar, ChartCard, ChartContent, ChartLine } from 'ui-patterns/Chart'
import { type QueryResult } from '../types'
import NoDataPlaceholder from '@/components/ui/Charts/NoDataPlaceholder'
import {
computeYAxisWidth,
formatLogTick,
formatYAxisTick,
getCumulativeResults,
} from '@/components/ui/QueryBlock/QueryBlock.utils'
import { type ChartConfig } from '@/data/content/notebooks/notebook-schema'
interface QueryResultChartProps {
chart?: ChartConfig
result?: QueryResult
}
const Y_SERIES_COLORS = [
'hsl(var(--brand-default))',
'hsl(var(--chart-blue))',
'hsl(var(--chart-3))',
]
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_series = [], cumulative, show_labels, scale } = chart ?? {}
const hasConfig = !!x_column && y_series.length > 0
// Logarithmic scale only makes sense for a single series — DisplaySettingsButton
// resets `scale` to linear once a second Y column is added
const effectiveScale = y_series.length > 1 ? 'linear' : scale
const chartConfig: ChartSeriesConfig = useMemo(
() =>
y_series.reduce((acc, key, index) => {
acc[key] = { label: key, color: Y_SERIES_COLORS[index] }
return acc
}, {} as ChartSeriesConfig),
[y_series]
)
const chartRows = useMemo(() => {
const xKey = x_column ?? ''
return (result?.rows ?? []).map((row) => {
const chartRow: Record<string, string | number> = { [xKey]: toChartValue(row[xKey]) }
y_series.forEach((yKey) => {
chartRow[yKey] = toChartValue(row[yKey])
})
return chartRow
})
}, [result, x_column, y_series])
const cumulativeResults = useMemo(
() => getCumulativeResults({ rows: chartRows }, { yKey: y_series }),
[chartRows, y_series]
)
const resultToRender = cumulative ? cumulativeResults : chartRows
const yAxisWidth = Math.max(
36,
...y_series.map((key) =>
computeYAxisWidth(resultToRender, key, { isLogScale: effectiveScale === 'log' })
)
)
const yAxisProps = {
...(show_labels ? { width: yAxisWidth } : {}),
scale: effectiveScale === 'log' ? 'log' : 'auto',
domain: effectiveScale === 'log' ? ([1, 'auto'] as const) : undefined,
tickFormatter: effectiveScale === 'log' ? formatLogTick : formatYAxisTick,
}
if (!result || (result?.rows && result.rows.length === 0)) {
return (
<NoDataPlaceholder
isFullHeight
className="border-0"
size="normal"
message="No results"
description="Your query returned no rows"
/>
)
}
if (!hasConfig) {
return (
<NoDataPlaceholder
isFullHeight
className="border-0"
size="normal"
message="Configure your chart"
description="Select your X and Y axis in the display settings"
/>
)
}
return (
<Chart className="flex flex-grow min-h-0">
<ChartCard className="flex flex-grow rounded-none border-0 min-h-0">
<ChartContent className={cn('min-h-0 h-full w-full', show_labels && 'pl-2 pb-2')}>
{type === 'bar' && (
<ChartBar
isFullHeight
xKey={x_column}
dataKey={y_series[0]}
dataKeys={y_series}
config={chartConfig}
showXAxis={show_labels}
showYAxis={show_labels}
data={resultToRender}
YAxisProps={yAxisProps}
/>
)}
{type === 'line' && (
<ChartLine
isFullHeight
xKey={x_column}
dataKey={y_series[0]}
dataKeys={y_series}
config={chartConfig}
showXAxis={show_labels}
showYAxis={show_labels}
data={resultToRender}
YAxisProps={yAxisProps}
/>
)}
</ChartContent>
</ChartCard>
</Chart>
)
}