mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 05:09:12 +03:00
## 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 -->
93 lines
3.3 KiB
TypeScript
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()
|
|
})
|
|
})
|