Files
supabase/apps/studio/components/interfaces/Explorer/QueryEditor/QueryResultChart.tsx
T
CharisandJoshen Lim 8bdfe03fe7 refactor(studio): drop notebook type widening now that the API supports it (#49272)
## Summary

- Regenerates `packages/api-types` for the content endpoints now that
the Platform API's `notebook` content type has landed (list/get/upsert
`type` enums, plus `UpsertContentBody`'s notebook cell shape with
`_id`/`y_series`). Unrelated schema drift from the same regen
(Warehouse, SSO, notification exceptions, etc.) is excluded — only the
content-endpoint hunks are applied.
- Removes every local widening cast added while the API support was
pending (`content-query.ts`, `content-infinite-query.ts`,
`notebook-query.ts`, `notebook-upsert-mutation.ts`,
`sql-folders-query.ts`).
- What remains is scoped and renamed to match: draft ids
(`generateDraftId`/`isDraftId`), used only for cells created client-side
in the editor before their first save, dropped before they'd ever reach
the backend as a fake `_id`.

## Test plan

- [x] `pnpm typecheck` — clean
- [x] `pnpm --filter studio test` — full suite passes (518 files / 5471
tests)
- [x] `pnpm --filter studio run lint:ratchet` — no new warnings
- [x] `pnpm format` / prettier — clean

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

* **Bug Fixes**
* Improved notebook cell tracking during editing, reordering, insertion,
and deletion.
* Preserved existing cell identifiers while removing temporary draft
identifiers before saving.
* Improved chart configuration for selecting and displaying multiple
Y-axis series.
  * Strengthened notebook validation and content persistence behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-08-20 13:06:41 +08:00

128 lines
4.1 KiB
TypeScript

import { useMemo } from 'react'
import { 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 { formatLogTick, 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
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_series[0]}
dataKeys={y_series}
config={chartConfig}
showXAxis={show_labels}
showYAxis={show_labels}
data={resultToRender}
YAxisProps={{
scale: effectiveScale === 'log' ? 'log' : 'auto',
domain: effectiveScale === 'log' ? [1, 'auto'] : undefined,
tickFormatter: effectiveScale === 'log' ? formatLogTick : undefined,
}}
/>
)}
{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={{
scale: effectiveScale === 'log' ? 'log' : 'auto',
domain: effectiveScale === 'log' ? [1, 'auto'] : undefined,
tickFormatter: effectiveScale === 'log' ? formatLogTick : undefined,
}}
/>
)}
</div>
</ChartContent>
</ChartCard>
</Chart>
)
}