Files
supabase/apps/studio/state/explorer-query.test.ts
Joshen Lim fc3f6aaea8 Add source selector for explorer query tab (#49063)
## Context

This is just pulling out the relevant changes from
https://github.com/supabase/supabase/pull/49028 as I might have messed
up the stack while making changes down the PRs 🙏

Builds on the Query Tab in the Explorer UI, adds the source selector
component to run either a database query or a logs query - will
subsequently be looking into have the source selector component in the
QueryCell as well (within notebooks)

<img width="1095" height="594" alt="image"
src="https://github.com/user-attachments/assets/ab6e8d91-aed3-4845-9777-acbd7a3a7cb1"
/>
<img width="1086" height="667" alt="image"
src="https://github.com/user-attachments/assets/75d99063-755d-4ba6-a128-7ddcbbc9f161"
/>


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

## Summary by CodeRabbit

- **New Features**
  - Added query-source selection for Explorer and notebook queries.
- Supports log queries, database selection, and read-replica
connections.
- Added validation for custom log time ranges and retention limits, with
upgrade guidance when applicable.
  - Query source choices are saved and restored across sessions.
  - Changing sources clears previous results to prevent stale data.

- **Bug Fixes**
- Improved handling of unavailable log querying and missing database
connections.
- Legacy saved queries now fall back safely to the default database
source.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-14 01:07:20 +07:00

93 lines
3.3 KiB
TypeScript

import { LOCAL_STORAGE_KEYS } from 'common'
import { describe, expect, it } from 'vitest'
import { createExplorerQueryState } from './explorer-query'
const createMemoryStorage = () => {
const values = new Map<string, string>()
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => values.set(key, value),
removeItem: (key: string) => values.delete(key),
}
}
describe('explorer query drafts', () => {
it('persists and restores drafts within their project', () => {
const storage = createMemoryStorage()
const firstState = createExplorerQueryState(storage)
firstState.createDraft({ id: 'query-1', projectRef: 'project-a' })
firstState.updateDraft({ id: 'query-1', name: 'Active users', sql: 'select * from users' })
const secondState = createExplorerQueryState(storage)
expect(secondState.restoreDraft({ id: 'query-1', projectRef: 'project-a' })).toBe(true)
expect(secondState.drafts['query-1']).toMatchObject({
name: 'Active users',
source: { id: 'database', type: 'database', parameters: {} },
uncheckedSql: 'select * from users',
projectRef: 'project-a',
})
expect(secondState.restoreDraft({ id: 'query-1', projectRef: 'project-b' })).toBe(false)
})
it('persists source parameters and clears stale results when they change', () => {
const storage = createMemoryStorage()
const state = createExplorerQueryState(storage)
state.createDraft({ id: 'query-1', projectRef: 'project-a', sql: 'select 1' })
state.setResult({ id: 'query-1', result: { rows: [{ value: 1 }], executedAt: 1 } })
state.updateDraft({
id: 'query-1',
source: {
id: 'logs',
type: 'logs',
parameters: { time_range: { type: 'relative', amount: 3, unit: 'hour' } },
},
})
expect(state.results['query-1']).toBeUndefined()
const restored = createExplorerQueryState(storage)
expect(restored.restoreDraft({ id: 'query-1', projectRef: 'project-a' })).toBe(true)
expect(restored.drafts['query-1'].source).toEqual({
id: 'logs',
type: 'logs',
parameters: { time_range: { type: 'relative', amount: 3, unit: 'hour' } },
})
})
it('restores pre-source drafts as database queries', () => {
const storage = createMemoryStorage()
storage.setItem(
LOCAL_STORAGE_KEYS.EXPLORER_QUERY_DRAFTS('project-a'),
JSON.stringify({
'query-1': { name: 'Legacy query', sql: 'select 1', updatedAt: 1 },
})
)
const state = createExplorerQueryState(storage)
expect(state.restoreDraft({ id: 'query-1', projectRef: 'project-a' })).toBe(true)
expect(state.drafts['query-1'].source).toEqual({
id: 'database',
type: 'database',
parameters: {},
})
})
it('removes the persisted draft and its session result when its tab closes', () => {
const storage = createMemoryStorage()
const state = createExplorerQueryState(storage)
state.createDraft({ id: 'query-1', projectRef: 'project-a', sql: 'select 1' })
state.setResult({ id: 'query-1', result: { rows: [{ value: 1 }], executedAt: 1 } })
state.removeDraft({ id: 'query-1', projectRef: 'project-a' })
expect(state.drafts['query-1']).toBeUndefined()
expect(state.results['query-1']).toBeUndefined()
expect(storage.getItem(LOCAL_STORAGE_KEYS.EXPLORER_QUERY_DRAFTS('project-a'))).toBeNull()
})
})