mirror of
https://github.com/supabase/supabase.git
synced 2026-09-08 19:08:44 +08:00
## What PR 6 of the SQL editor state re-layering stack. Moves ephemeral, never-persisted SQL editor state out of the snippet/folder "god store". **Session store** — `state/sql-editor/sql-editor-session-state.ts` holds per-snippet, read-by-many session state: - query `results` - `explainResults` - the row `limit` …with their mutators (`addResult`/`addResultError`/`resetResult`, `addExplainResult`/`addExplainResultError`/`resetExplainResult`, `resetResults`, `setLimit`). `removeSnippet` drops a snippet's session entries via `clearForSnippet(id)`. **Diff-request slice** — `state/sql-editor/sql-editor-diff-request.ts`. The Assistant's "Insert code" / "Replace code" diff is *not* per-snippet session state: it's a transient, fire-and-forget command produced outside the editor (e.g. query blocks / assistant) and consumed exactly once by whichever editor is active. It's modeled as a consume-once request (`requestDiff` / `consumeDiffRequest`) rather than durable state — the editor drains it on apply, so a stale diff can't leak into a later editor or session. (Previously this was `diffContent` in the god store: never cleared and triggered by object-reference identity.) Consumers read session state from `useSqlEditorSessionSnapshot` and the diff channel from `useSqlEditorDiffRequestSnapshot`, keeping `useSqlEditorV2StateSnapshot` only for snippets/folders. ### Why not the TanStack Query cache for results/explain? Editor execution is a **mutation**, not a keyed query — `mutation.data` is per-hook-instance and not keyed by snippet id, and there's no caching value to capture (re-running SQL must return *fresh* data, never a cached result). `EXPLAIN ANALYZE` actually executes the statement, so a declarative/auto-refetching `useQuery` is semantically wrong. Results/explain are imperative mutation outputs, scoped to the session, read by several decoupled consumers keyed by snippet id — exactly what a small in-memory keyed store models honestly. ## Consumers migrated - `SQLEditor.tsx` — results/explain/limit reads + `addResult`/`addResultError`/`addExplainResult`/`addExplainResultError`/`setLimit`; diff-apply effect now drains a consume-once request - `UtilityPanel.tsx`, `UtilityTabResults.tsx`, `UtilityTabExplain.tsx`, `UtilityActions.tsx` - `QueryBlock/EditQueryButton.tsx` — produces via `requestDiff` ## Notes - Result/explain types are kept verbatim from the god store (pre-existing `any` row/error types come along unchanged; tightening them is out of scope for this move). - `ref()` on result rows is preserved to avoid Valtio proxying large row sets. ## Tests - `sql-editor-session-state.test.ts` — result/explain mutators, `resetResults`, `clearForSnippet`, `limit` - `sql-editor-diff-request.test.ts` — `requestDiff`, `consumeDiffRequest` (drain + queue-of-one) Validation: - `pnpm --filter studio typecheck` ✅ - `pnpm exec vitest --run state/sql-editor/` ✅ (110 passed) - lint ✅ (no new errors) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * SQL editor query results, EXPLAIN output, and the “Limit results to” setting now persist more reliably across a session. * AI-assisted SQL insert/replace actions now use a pending diff workflow to apply updates more consistently. * **Bug Fixes** * Results/EXPLAIN rendering and downloads stay in sync with the latest executed data. * Switching databases/snippets now clears the correct temporary results. * Diff application is more resilient when an editor is still loading, including empty-vs-non-empty editor cases. * **Tests** * Added coverage for the session and diff-request state logic. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import { Edit } from 'lucide-react'
|
|
import { useRouter } from 'next/router'
|
|
import { ComponentProps } from 'react'
|
|
import {
|
|
cn,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
TooltipContent,
|
|
} from 'ui'
|
|
|
|
import { ButtonTooltip } from '../ButtonTooltip'
|
|
import { useIsInlineEditorEnabled } from '@/components/interfaces/Account/Preferences/useDashboardSettings'
|
|
import { useNewQuery } from '@/components/interfaces/SQLEditor/hooks'
|
|
import { DiffType } from '@/components/interfaces/SQLEditor/SQLEditor.types'
|
|
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
import { editorPanelState } from '@/state/editor-panel-state'
|
|
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
|
|
import { useSqlEditorDiffRequestSnapshot } from '@/state/sql-editor/sql-editor-diff-request'
|
|
|
|
interface EditQueryButtonProps {
|
|
id?: string
|
|
title: string
|
|
sql?: string
|
|
className?: string
|
|
variant?: 'default' | 'text'
|
|
}
|
|
|
|
export const EditQueryButton = ({
|
|
id,
|
|
sql,
|
|
title,
|
|
className,
|
|
variant = 'text',
|
|
}: EditQueryButtonProps) => {
|
|
const router = useRouter()
|
|
const { newQuery } = useNewQuery()
|
|
|
|
const diffRequest = useSqlEditorDiffRequestSnapshot()
|
|
const { closeSidebar, openSidebar } = useSidebarManagerSnapshot()
|
|
|
|
const isInSQLEditor = router.pathname.includes('/sql')
|
|
const isInNewSnippet = router.pathname.endsWith('/sql')
|
|
const isInlineEditorEnabled = useIsInlineEditorEnabled()
|
|
const tooltip: { content: ComponentProps<typeof TooltipContent> & { text: string } } = {
|
|
content: { side: 'bottom', text: 'Edit in SQL Editor' },
|
|
}
|
|
|
|
const track = useTrack()
|
|
|
|
if (id !== undefined) {
|
|
return (
|
|
<ButtonTooltip
|
|
variant={variant}
|
|
size="tiny"
|
|
className={cn('w-7 h-7', className)}
|
|
icon={<Edit size={14} strokeWidth={1.5} />}
|
|
tooltip={tooltip}
|
|
onClick={() => {
|
|
editorPanelState.setActiveSnippetId(id)
|
|
openSidebar(SIDEBAR_KEYS.EDITOR_PANEL)
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return !isInSQLEditor || isInNewSnippet ? (
|
|
<ButtonTooltip
|
|
variant={variant}
|
|
size="tiny"
|
|
className={cn('w-7 h-7', className)}
|
|
icon={<Edit size={14} strokeWidth={1.5} />}
|
|
onClick={() => {
|
|
if (isInlineEditorEnabled) {
|
|
// This component needs to be updated to work with local EditorPanel state
|
|
// For now, fall back to creating a new query
|
|
if (sql) newQuery(sql, title)
|
|
closeSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
|
|
} else {
|
|
if (sql) newQuery(sql, title)
|
|
}
|
|
track('assistant_edit_in_sql_editor_clicked', {
|
|
isInSQLEditor,
|
|
isInNewSnippet,
|
|
})
|
|
}}
|
|
tooltip={tooltip}
|
|
/>
|
|
) : (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<ButtonTooltip
|
|
variant={variant}
|
|
size="tiny"
|
|
disabled={!sql}
|
|
className={cn('w-7 h-7', className)}
|
|
icon={<Edit size={14} strokeWidth={1.5} />}
|
|
tooltip={!!sql ? tooltip : { content: { side: 'bottom', text: undefined } }}
|
|
/>
|
|
</DropdownMenuTrigger>
|
|
{!!sql && (
|
|
<DropdownMenuContent className="w-36">
|
|
<DropdownMenuItem onClick={() => diffRequest.requestDiff(sql, DiffType.Addition)}>
|
|
Insert code
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => diffRequest.requestDiff(sql, DiffType.Modification)}>
|
|
Replace code
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => newQuery(sql, title)}>
|
|
Create new snippet
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
)}
|
|
</DropdownMenu>
|
|
)
|
|
}
|