mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
## Context Previous PR [here](https://github.com/supabase/supabase/pull/49098) introduced row limits to the Explorer -> Query Tab, but the setting was global (e.g selected row limit value would be the same despite switching query tabs) Changes here shifts the scope of row limit into the query draft so that the value is tied to each individual query tab instead Also added a logic as CodeRabbit suggested [here](https://github.com/supabase/supabase/pull/49138#discussion_r3795525802) - to default invalid row limit values to 100 if the persisted data is mutated incorrectly <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit * **New Features** * Query tabs now retain row-limit settings independently for each database draft. * Row-limit preferences are restored when reopening Studio, with older drafts defaulting to 100 rows. * **Bug Fixes** * Changing the row limit now persists immediately and no longer affects other query drafts. * Invalid saved row limits are safely normalized to a supported value. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
109 lines
3.8 KiB
TypeScript
109 lines
3.8 KiB
TypeScript
import { useParams } from 'common'
|
|
import { Loader2, SquareCode } from 'lucide-react'
|
|
import { useRouter } from 'next/router'
|
|
import { useContext, useEffect, useState } from 'react'
|
|
import { Button } from 'ui'
|
|
|
|
import { QueryEditor, type ExplorerQueryModel } from './QueryEditor'
|
|
import { type QueryResult } from './types'
|
|
import { toQuerySourceBinding } from '@/data/query-sources/query-source-registry'
|
|
import { explorerQueryState, useExplorerQueryStateSnapshot } from '@/state/explorer-query'
|
|
import { useLocalRoleImpersonationState } from '@/state/role-impersonation-state'
|
|
import { createTabId, TabsStateContext } from '@/state/tabs'
|
|
|
|
/** Query-tab lifecycle adapter around the shared QueryEditor. */
|
|
export const QueryTab = () => {
|
|
const { id, ref } = useParams()
|
|
const router = useRouter()
|
|
const tabs = useContext(TabsStateContext)
|
|
const querySnap = useExplorerQueryStateSnapshot()
|
|
const roleImpersonationState = useLocalRoleImpersonationState()
|
|
|
|
const [restoredQueryKey, setRestoredQueryKey] = useState<string>()
|
|
|
|
const stateDraft = id ? querySnap.drafts[id] : undefined
|
|
const draft = stateDraft?.projectRef === ref ? stateDraft : undefined
|
|
const result = draft && id ? querySnap.results[id] : undefined
|
|
const queryKey = id && ref ? `${ref}:${id}` : undefined
|
|
|
|
useEffect(() => {
|
|
if (!id || !ref) return
|
|
|
|
const restored = explorerQueryState.restoreDraft({ id, projectRef: ref })
|
|
const restoredDraft = explorerQueryState.drafts[id]
|
|
if (restored && restoredDraft) {
|
|
tabs.addTab({
|
|
id: createTabId('query', { id }),
|
|
type: 'query',
|
|
label: restoredDraft.name,
|
|
metadata: { queryId: id },
|
|
isPreview: false,
|
|
})
|
|
}
|
|
setRestoredQueryKey(`${ref}:${id}`)
|
|
}, [id, ref, tabs])
|
|
|
|
if (!queryKey || restoredQueryKey !== queryKey) {
|
|
return (
|
|
<div
|
|
role="status"
|
|
aria-label="Loading query"
|
|
className="flex h-full items-center justify-center bg-surface-100"
|
|
>
|
|
<Loader2 className="animate-spin text-foreground-muted" size={18} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!id || !draft) {
|
|
return (
|
|
<div className="flex h-full flex-col items-center justify-center gap-3 bg-surface-100">
|
|
<SquareCode className="text-foreground-muted" size={24} />
|
|
<div className="text-center">
|
|
<p className="text-sm font-medium">Query draft not found</p>
|
|
<p className="text-sm text-foreground-lighter">
|
|
This local draft may have been closed or cleared from this browser.
|
|
</p>
|
|
</div>
|
|
<Button onClick={() => router.push(`/project/${ref}/explorer`)}>Back to Explorer</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const handleResultChange = (nextResult: QueryResult) => {
|
|
explorerQueryState.setResult({
|
|
id,
|
|
result: { ...nextResult, executedAt: Date.now() },
|
|
})
|
|
}
|
|
|
|
const query: ExplorerQueryModel =
|
|
draft._tag === 'logs'
|
|
? { ...toQuerySourceBinding(draft), uncheckedSql: draft.uncheckedSql }
|
|
: {
|
|
...toQuerySourceBinding(draft),
|
|
uncheckedSql: draft.uncheckedSql,
|
|
rowLimit: draft.rowLimit,
|
|
}
|
|
|
|
return (
|
|
<QueryEditor
|
|
id={id}
|
|
variant="viewport"
|
|
title={draft.name}
|
|
query={query}
|
|
result={result}
|
|
roleImpersonationState={roleImpersonationState}
|
|
onTitleChange={(value) => {
|
|
const name = value.trim() || 'Untitled query'
|
|
explorerQueryState.updateDraft({ id, name })
|
|
tabs.updateTab(createTabId('query', { id }), { label: name })
|
|
}}
|
|
onSqlChange={(sql) => explorerQueryState.updateDraft({ id, sql })}
|
|
onSourceChange={(source) => explorerQueryState.updateDraft({ id, source })}
|
|
onResultChange={handleResultChange}
|
|
onRowLimitChange={(rowLimit) => explorerQueryState.updateDraft({ id, rowLimit })}
|
|
/>
|
|
)
|
|
}
|