Files
supabase/apps/studio/components/interfaces/Explorer/QueryCell/QueryCell.utils.test.ts
Joshen Lim d61477085e Joshenlim/fe 4175 add row limit in explorerquerysourcemenu (#49098)
## Context

Related to Explorer/Notebook - builds on the ExplorerQuerySourceMenu by
adding an option for row limit in both Notebook Query cell + Query Tab

## Side note RE persistence of the selected row limit value
Note that for QueryTab - its intentional that for now that the row limit
isn't scoped to the query draft atm as I wanna avoid making changes to
`explorer-query` atm as there's a couple of PRs in flux that touches
that file. So will handle that separately

^ This means that switching between query tabs will not change nor
persist the row limit

<img width="931" height="335" alt="image"
src="https://github.com/user-attachments/assets/d1d52ee7-7c1d-42aa-a6ae-1d7d99ab95c9"
/>

<img width="1381" height="486" alt="image"
src="https://github.com/user-attachments/assets/689368c9-c0fc-4000-a09e-f59bfa7afa97"
/>

## To test
- [ ] Verify that row limit behaviour works in notebooks
- [ ] Verify that row limit behaviour works in explorer query tab
2026-08-17 13:36:31 +08:00

179 lines
5.2 KiB
TypeScript

import { untrustedSql } from '@supabase/pg-meta'
import { describe, expect, it } from 'vitest'
import {
changeCellSource,
cloneQueryCell,
DEFAULT_CELL_ROW_LIMIT,
getCellDisplay,
setCellRowLimit,
setCellSql,
toQueryModel,
} from './QueryCell.utils'
import { type ChartConfig, type QueryCell } from '@/data/content/notebooks/notebook-schema'
import { untrustedLogSql } from '@/data/logs/safe-analytics-sql'
const CHART: ChartConfig = {
type: 'bar',
x_column: 'day',
y_columns: ['signups'],
cumulative: false,
scale: 'linear',
show_labels: true,
}
const DATABASE_CELL: QueryCell = {
_tag: 'database_cell',
id: 'cell-1',
title: 'Signups',
view: 'chart',
chart: CHART,
unchecked_sql: untrustedSql('select * from auth.users'),
row_limit: 50,
database_identifier: 'replica-1',
}
const LOG_CELL: QueryCell = {
_tag: 'log_cell',
id: 'cell-2',
title: 'Edge errors',
view: 'table',
chart: CHART,
unchecked_sql: untrustedLogSql('select timestamp from logs'),
time_range: { _tag: 'relative_time_range', unit: 'hour', amount: 1 },
}
describe('changeCellSource', () => {
it('keeps the query when only the database changes', () => {
const next = changeCellSource(DATABASE_CELL, {
_tag: 'database',
database_identifier: 'replica-2',
})
expect(next).toEqual({ ...DATABASE_CELL, database_identifier: 'replica-2' })
})
it('keeps the query when only the log time range changes', () => {
const time_range = { _tag: 'relative_time_range', unit: 'day', amount: 7 } as const
const next = changeCellSource(LOG_CELL, { _tag: 'logs', time_range })
expect(next).toEqual({ ...LOG_CELL, time_range })
})
it('carries the query text over when moving from the database to logs', () => {
const time_range = { _tag: 'relative_time_range', unit: 'hour', amount: 1 } as const
const next = changeCellSource(DATABASE_CELL, { _tag: 'logs', time_range })
expect(next).toEqual({
_tag: 'log_cell',
id: 'cell-1',
title: 'Signups',
view: 'chart',
chart: CHART,
unchecked_sql: 'select * from auth.users',
time_range,
})
})
it('carries the query text over and restores a default row limit when moving from logs to the database', () => {
const next = changeCellSource(LOG_CELL, { _tag: 'database', database_identifier: undefined })
expect(next).toEqual({
_tag: 'database_cell',
id: 'cell-2',
title: 'Edge errors',
view: 'table',
chart: CHART,
unchecked_sql: 'select timestamp from logs',
row_limit: DEFAULT_CELL_ROW_LIMIT,
database_identifier: undefined,
})
})
it('applies the selected database when moving from logs to the database', () => {
const next = changeCellSource(LOG_CELL, {
_tag: 'database',
database_identifier: 'replica-2',
})
expect(next).toMatchObject({ _tag: 'database_cell', database_identifier: 'replica-2' })
})
it('preserves the chart across a backend change so display settings survive', () => {
const next = changeCellSource(DATABASE_CELL, {
_tag: 'logs',
time_range: { _tag: 'relative_time_range', unit: 'hour', amount: 1 },
})
expect(next.chart).toEqual(CHART)
expect(next.chart).not.toBe(DATABASE_CELL.chart)
})
})
describe('setCellSql', () => {
it('writes the text back onto a database cell without touching its source', () => {
expect(setCellSql(DATABASE_CELL, 'select 1')).toEqual({
...DATABASE_CELL,
unchecked_sql: 'select 1',
})
})
it('writes the text back onto a log cell without touching its time range', () => {
expect(setCellSql(LOG_CELL, 'select 2')).toEqual({ ...LOG_CELL, unchecked_sql: 'select 2' })
})
})
describe('setCellRowLimit', () => {
it('writes the row limit onto a database cell without touching its query', () => {
expect(setCellRowLimit(DATABASE_CELL, 500)).toEqual({ ...DATABASE_CELL, row_limit: 500 })
})
it('leaves a log cell unchanged, since it has no row limit concept', () => {
expect(setCellRowLimit(LOG_CELL, 500)).toEqual(LOG_CELL)
})
})
describe('cloneQueryCell', () => {
it('copies the chart series array rather than aliasing it', () => {
const clone = cloneQueryCell(DATABASE_CELL)
expect(clone).toEqual(DATABASE_CELL)
expect(clone.chart?.y_columns).not.toBe(DATABASE_CELL.chart?.y_columns)
})
})
describe('getCellDisplay', () => {
it('keeps a configured chart while the table view is selected', () => {
expect(getCellDisplay({ ...DATABASE_CELL, view: 'table' })).toEqual({
view: 'table',
chart: CHART,
})
})
it('reports no chart when a cell has never configured one', () => {
expect(getCellDisplay({ ...DATABASE_CELL, view: 'table', chart: undefined })).toEqual({
view: 'table',
chart: undefined,
})
})
})
describe('toQueryModel', () => {
it('tags a database cell with its row limit and database', () => {
expect(toQueryModel(DATABASE_CELL, 'select 3')).toEqual({
_tag: 'database',
uncheckedSql: 'select 3',
database_identifier: 'replica-1',
rowLimit: 50,
})
})
it('tags a log cell with its time range and no row limit', () => {
expect(toQueryModel(LOG_CELL, 'select 4')).toEqual({
_tag: 'logs',
uncheckedSql: 'select 4',
time_range: LOG_CELL.time_range,
})
})
})