Files
supabase/apps/studio/components/interfaces/Explorer/QueryCell/QueryResultChart.tsx
T
Saxon Fletcher ace9422bfd refactor(studio): share Explorer query editor (#49041)
## Stack

Depends on #49027. Followed by #49038.

## Summary

- extract a controlled `QueryEditor` from the existing notebook query
cell
- reuse it from `QueryCell`, leaving notebook persistence and
sortable-block behavior in the adapter
- make table/chart result settings controlled so other query surfaces
can share them
- persist notebook SQL on editor blur and query execution

## To test

1. Open a notebook query cell, edit and rename it, then run the query
and confirm results appear.
2. Switch between table and chart results and confirm notebook
move/delete actions still work.

## Why

Notebooks, query tabs, and future chat tabs need consistent query
actions and result rendering without duplicating the notebook
implementation.

## Impact

This is primarily a refactor of the existing notebook query experience.
It introduces no new query-tab routes or source-selection behavior.

## Validation

- fresh non-incremental Studio TypeScript check
- focused NotebookEditor component tests

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

* **New Features**
* Added a shared query editor with SQL editing, execution, validation,
visibility controls, editable titles, row limits, and loading/error
states.
* Added table and chart result views, including customizable bar and
line charts.
* Added support for switching display modes and updating chart settings.

* **Improvements**
  * Improved query result handling and display-setting updates.
  * Repositioned the logarithmic-scale tooltip for better visibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-08-13 17:59:30 +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>
)
}