Files
supabase/apps/studio/components/interfaces/Explorer/QueryCell/DisplaySettingsButton.tsx
T
Joshen Lim d434c63bad joshen/fe 4150 explorer query cells result display settings (#49003)
## Context

Related to Explorer / Notebooks - this adds chart functionality for the
Query cells
<img width="250" alt="image"
src="https://github.com/user-attachments/assets/4ea37c14-87dc-4c43-ba7f-cb9436085c81"
/>

Query results can be rendered as either bar or line chart - using the
chart packages from `ui-patterns`
[NOTE]: For design team reviewers - am patching the chart packages to be
agnostic to the `timestamp` property within the provided data set. Would
love to use this component from a consistency POV instead of the old
`BarChart` component we have.

Have intentionally omitted log scale functionality from this PR - will
have that separately 🙏

<img width="999" height="483" alt="image"
src="https://github.com/user-attachments/assets/14356ee4-c658-4fd1-90e0-17c38dac4822"
/>
<img width="988" height="478" alt="image"
src="https://github.com/user-attachments/assets/cd0ca088-9a03-4aa3-9884-17bc36d3cabf"
/>



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

- **New Features**
- Added chart views for notebook query results, including bar and line
charts.
- Added display settings for selecting X/Y columns, chart type, scale,
cumulative values, and label visibility.
  - Added configurable X-axis support for charts.
  - Display preferences are saved with each notebook cell.

- **Improvements**
  - New database cells default to table view.
  - Chart results better handle varied data types.
- Empty results and incomplete chart settings now display clear
placeholders.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-13 10:09:54 +07:00

226 lines
7.5 KiB
TypeScript

import { BarChart2, Settings2, Table } from 'lucide-react'
import {
Checkbox,
Popover,
PopoverContent,
PopoverSeparator,
PopoverTrigger,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
ToggleGroup,
ToggleGroupItem,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { type Snapshot } from 'valtio'
import { ExplorerToolbarAction } from '../ExplorerToolbar'
import { type DatabaseCell as DatabaseCellSchema } from '@/data/content/notebooks/notebook-schema'
import { useCurrentNotebook, useNotebooksStateSnapshot } from '@/state/notebooks/notebooks-state'
interface DisplaySettingsButtonProps {
cell: Snapshot<DatabaseCellSchema>
columns: string[]
disabled: boolean
}
// [Joshen] TODO support multiple y axis charts
export const DisplaySettingsButton = ({ cell, columns, disabled }: DisplaySettingsButtonProps) => {
const snap = useNotebooksStateSnapshot()
const currentNotebook = useCurrentNotebook()
const cells = currentNotebook?.notebook.content?.cells ?? []
const { view, chart } = cell
const {
type = 'bar',
x_column,
y_columns,
cumulative = false,
show_labels = false,
scale = 'linear',
} = chart ?? {}
const onChangeView = (view: 'table' | 'chart') => {
const notebookId = currentNotebook?.notebook.id
if (!notebookId) return
const nextCells = cells.map((c) =>
c.id === cell.id && c._tag === 'database_cell' ? { ...c, view } : c
)
snap.updateCells({ id: notebookId, cells: nextCells })
}
const onUpdateChartConfig = (
payload:
| { type: 'bar' | 'line' }
| { x_column: string }
| { y_columns: string[] }
| { cumulative: boolean }
| { show_labels: boolean }
| { scale: 'linear' | 'log' }
) => {
const notebookId = currentNotebook?.notebook.id
if (!notebookId) return
const nextCells = cells.map((c) => {
if (c.id !== cell.id || c._tag !== 'database_cell') return c
return {
...c,
chart: {
type: c.chart?.type ?? 'bar',
x_column: c.chart?.x_column ?? '',
y_columns: c.chart?.y_columns ?? [],
cumulative: c.chart?.cumulative ?? false,
scale: c.chart?.scale ?? 'linear',
show_labels: c.chart?.show_labels ?? false,
...payload,
},
}
})
snap.updateCells({ id: notebookId, cells: nextCells })
}
return (
<Popover>
<PopoverTrigger asChild>
<ExplorerToolbarAction disabled={disabled} icon={<Settings2 />} tooltip="Result settings" />
</PopoverTrigger>
<PopoverContent side="bottom" className="flex flex-col gap-y-3 p-0 py-3">
<div className="flex flex-col gap-y-3 px-3">
<p className="text-xs tracking-tighter uppercase font-mono text-foreground-lighter">
Result display settings
</p>
<FormItemLayout isReactForm={false} label="View data as">
<ToggleGroup
value={view}
onValueChange={(value) => {
if (value === 'table' || value === 'chart') onChangeView(value)
}}
variant="outline"
type="single"
>
<ToggleGroupItem value="table" className="w-full gap-x-2 data-[state=on]:bg-accent">
<Table size={14} />
<p>Table</p>
</ToggleGroupItem>
<ToggleGroupItem value="chart" className="w-full gap-x-2 data-[state=on]:bg-accent">
<BarChart2 size={14} />
<p>Chart</p>
</ToggleGroupItem>
</ToggleGroup>
</FormItemLayout>
</div>
{view === 'chart' && (
<>
<PopoverSeparator />
<FormItemLayout isReactForm={false} label="Render chart as" className="px-3 mb-1">
<ToggleGroup
value={type}
onValueChange={(value) => {
if (value === 'bar' || value === 'line') onUpdateChartConfig({ type: value })
}}
variant="outline"
type="single"
>
<ToggleGroupItem value="bar" className="w-full data-[state=on]:bg-accent">
Bar
</ToggleGroupItem>
<ToggleGroupItem value="line" className="w-full data-[state=on]:bg-accent">
Line
</ToggleGroupItem>
</ToggleGroup>
</FormItemLayout>
<div className="px-3 flex flex-col gap-y-2">
<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="X axis">
<Select
value={x_column}
onValueChange={(x_column) => onUpdateChartConfig({ x_column })}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
{columns.map((x) => (
<SelectItem key={x} value={x}>
{x}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItemLayout>
<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="Y Axis">
<Select
value={y_columns?.[0]}
onValueChange={(y_column) => onUpdateChartConfig({ y_columns: [y_column] })}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
{columns.map((x) => (
<SelectItem key={x} value={x}>
{x}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItemLayout>
<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="Scale">
<Select
value={scale}
onValueChange={(scale) =>
onUpdateChartConfig({ scale: scale as 'log' | 'linear' })
}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
<SelectItem value="linear">Linear</SelectItem>
<SelectItem value="log">Logarithmic</SelectItem>
</SelectContent>
</Select>
</FormItemLayout>
<FormItemLayout id="cumulative" isReactForm={false} layout="flex" label="Cumulative">
<Checkbox
id="cumulative"
checked={cumulative}
onCheckedChange={(cumulative) =>
onUpdateChartConfig({ cumulative: Boolean(cumulative) })
}
/>
</FormItemLayout>
<FormItemLayout
id="show_labels"
isReactForm={false}
layout="flex"
label="Show labels"
>
<Checkbox
id="show_labels"
checked={show_labels}
onCheckedChange={(show_labels) =>
onUpdateChartConfig({ show_labels: Boolean(show_labels) })
}
/>
</FormItemLayout>
</div>
</>
)}
</PopoverContent>
</Popover>
)
}