Files
supabase/apps/studio/components/ui/QueryBlock/QueryBlock.utils.ts
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

72 lines
2.2 KiB
TypeScript

export const checkHasNonPositiveValues = (
data: readonly Record<string, unknown>[],
key: string
): boolean => data.some((row) => (row[key] as number) <= 0)
export const formatYAxisTick = (value: number): string => {
if (Math.abs(value) >= 1_000_000) {
const n = value / 1_000_000
return `${Number.isInteger(n) ? n : n.toFixed(1)}M`
}
if (Math.abs(value) >= 1_000) {
const n = value / 1_000
return `${Number.isInteger(n) ? n : n.toFixed(1)}K`
}
if (value !== 0 && Math.abs(value) < 1) {
return parseFloat(value.toFixed(2)).toString()
}
if (!Number.isInteger(value)) {
return parseFloat(value.toFixed(1)).toString()
}
return String(value)
}
export const computeYAxisWidth = (
data: Record<string, unknown>[],
key: string,
{
isLogScale = false,
isPercentage = false,
}: { isLogScale?: boolean; isPercentage?: boolean } = {}
): number => {
if (isLogScale) return 52
if (isPercentage) return Math.max(36, (3 + 1) * 8) // max tick is "100"
const maxMagnitude = data.reduce((max, d) => {
const magnitude = Math.abs(Number(d[key]) || 0)
return magnitude > max ? magnitude : max
}, 0)
return Math.max(36, (formatYAxisTick(maxMagnitude).length + 1) * 8)
}
export const formatLogTick = (value: number): string => {
if (value >= 1_000_000)
return `${(value / 1_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}M`
if (value >= 1_000)
return `${(value / 1_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}k`
return value.toLocaleString()
}
export const getCumulativeResults = (
results: { rows: readonly any[] },
config: { yKey: string }
) => {
if (!results?.rows?.length) {
return []
}
const cumulativeResults = results.rows.reduce((acc, row) => {
const prev = acc[acc.length - 1] || {}
// Coerce to Number before adding: Postgres returns `bigint`, `numeric`,
// `money` and `count(*)` columns as strings, so a bare `+` would
// concatenate (e.g. "10" + "20" -> "1020") instead of summing.
const next = {
...row,
[config.yKey]: (Number(prev[config.yKey]) || 0) + (Number(row[config.yKey]) || 0),
}
return [...acc, next]
}, [])
return cumulativeResults
}