mirror of
https://github.com/supabase/supabase.git
synced 2026-10-14 05:39:11 +03:00
## 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 -->
226 lines
7.5 KiB
TypeScript
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>
|
|
)
|
|
}
|