mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 03:15:06 +03:00
Removes the SQL Editor Pretty Explain feature — the Explain tab, the Run EXPLAIN ANALYZE action + shortcut, and its dead plumbing. It's been gated off behind the `DisablePrettyExplainOnSqlEditor` kill switch for weeks with no usage or complaints. `ExplainVisualizer` / `isExplainQuery` are kept — they're used independently by Query Insights, Query Performance, and the EditorPanel quick-runner. Manually-run `EXPLAIN` queries still render as raw rows in the Results tab. Typecheck, lint, and all affected unit tests pass. Closes FE-3930 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Changes** * Removed the SQL editor’s EXPLAIN execution workflow, including its toolbar action, keyboard shortcut, utility tab, and visual query-plan display. * Simplified query execution to focus on standard results and charts. * Improved result clearing when switching databases and refined execution error handling. * Updated SQL editor state and tests to reflect the streamlined experience. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
254 lines
8.5 KiB
TypeScript
254 lines
8.5 KiB
TypeScript
import { Monaco, OnMount } from '@monaco-editor/react'
|
|
import { LOCAL_STORAGE_KEYS } from 'common'
|
|
import { noop } from 'lodash'
|
|
import { RefObject, useRef } from 'react'
|
|
import { Admonition } from 'ui-patterns/admonition'
|
|
|
|
import type { IStandaloneCodeEditor } from './SQLEditor.types'
|
|
import { useSnippetEditor } from './useSnippetEditor'
|
|
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
|
|
import { getEditorSelectionParts } from '@/components/ui/AIEditor/utils'
|
|
import { CodeEditor } from '@/components/ui/CodeEditor/CodeEditor'
|
|
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
|
|
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
|
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
|
import { useIsShortcutEnabled } from '@/state/shortcuts/useIsShortcutEnabled'
|
|
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
|
|
import { useSqlEditorSaveCoordinator } from '@/state/sql-editor/sql-editor-save-coordinator'
|
|
|
|
export type MonacoEditorProps = {
|
|
id: string
|
|
snippetName: string
|
|
className?: string
|
|
editorRef: RefObject<IStandaloneCodeEditor | null>
|
|
monacoRef: RefObject<Monaco | null>
|
|
autoFocus?: boolean
|
|
executeQuery: () => void
|
|
prettifyQuery: () => void
|
|
onHasSelection: (value: boolean) => void
|
|
onMount?: (editor: IStandaloneCodeEditor) => void
|
|
onPrompt?: (value: {
|
|
selection: string
|
|
beforeSelection: string
|
|
afterSelection: string
|
|
startLineNumber: number
|
|
endLineNumber: number
|
|
}) => void
|
|
placeholder?: string
|
|
}
|
|
|
|
export const MonacoEditor = ({
|
|
id,
|
|
snippetName,
|
|
editorRef,
|
|
monacoRef,
|
|
autoFocus = true,
|
|
placeholder = '',
|
|
className,
|
|
executeQuery,
|
|
prettifyQuery,
|
|
onHasSelection,
|
|
onPrompt,
|
|
onMount,
|
|
}: MonacoEditorProps) => {
|
|
const aiSnap = useAiAssistantStateSnapshot()
|
|
const { openSidebar, toggleSidebar } = useSidebarManagerSnapshot()
|
|
|
|
const [intellisenseEnabled] = useLocalStorageQuery(
|
|
LOCAL_STORAGE_KEYS.SQL_EDITOR_INTELLISENSE,
|
|
true
|
|
)
|
|
|
|
const { snippet, disableEdit, handleEditorChange } = useSnippetEditor({ id, snippetName })
|
|
|
|
// The Monaco save action is registered once on mount, but `snippet` starts
|
|
// undefined for a new/deep-linked snippet and is only created on first edit.
|
|
// Read it through a ref so Cmd/Ctrl+S sees the latest value, not the stale
|
|
// mount-time closure.
|
|
const snippetRef = useRef(snippet)
|
|
snippetRef.current = snippet
|
|
|
|
const prettifyQueryRef = useRef(prettifyQuery)
|
|
prettifyQueryRef.current = prettifyQuery
|
|
|
|
const isAIAssistantHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.AI_ASSISTANT_TOGGLE)
|
|
const aiHotkeyEnabledRef = useRef(isAIAssistantHotkeyEnabled)
|
|
aiHotkeyEnabledRef.current = isAIAssistantHotkeyEnabled
|
|
|
|
const { requestSave } = useSqlEditorSaveCoordinator()
|
|
const requestSaveRef = useRef(requestSave)
|
|
requestSaveRef.current = requestSave
|
|
|
|
const handleEditorOnMount: OnMount = (editor, monaco) => {
|
|
const model = editor.getModel()
|
|
if (model !== null) {
|
|
monaco.editor.setModelMarkers(model, 'owner', [])
|
|
}
|
|
|
|
// Blur the editor on Escape so users can hop out to the rest of the UI.
|
|
// The precondition defers to Monaco's own Escape consumers (suggest widget,
|
|
// find widget, parameter hints, snippet/rename mode, inline suggestions) and
|
|
// to selection/multi-cursor cancellation, so inline features keep working.
|
|
editor.addCommand(
|
|
monaco.KeyCode.Escape,
|
|
() => {
|
|
;(document.activeElement as HTMLElement | null)?.blur()
|
|
},
|
|
[
|
|
'editorTextFocus',
|
|
'!editorHasSelection',
|
|
'!editorHasMultipleSelections',
|
|
'!suggestWidgetVisible',
|
|
'!findWidgetVisible',
|
|
'!parameterHintsVisible',
|
|
'!renameInputVisible',
|
|
'!inSnippetMode',
|
|
'!accessibilityHelpWidgetVisible',
|
|
'!inlineSuggestionVisible',
|
|
].join(' && ')
|
|
)
|
|
|
|
editor.addAction({
|
|
id: 'save-query',
|
|
label: 'Save Query',
|
|
keybindings: [monaco.KeyMod.CtrlCmd + monaco.KeyCode.KeyS],
|
|
contextMenuGroupId: 'operation',
|
|
contextMenuOrder: 0,
|
|
run: () => {
|
|
const currentSnippet = snippetRef.current
|
|
if (currentSnippet) requestSaveRef.current(currentSnippet.snippet.id)
|
|
},
|
|
})
|
|
|
|
editor.addAction({
|
|
id: 'prettify-query',
|
|
label: 'Prettify SQL',
|
|
keybindings: [monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.KeyF],
|
|
contextMenuGroupId: 'operation',
|
|
contextMenuOrder: 2,
|
|
run: () => {
|
|
prettifyQueryRef.current()
|
|
},
|
|
})
|
|
|
|
editor.addAction({
|
|
id: 'explain-code',
|
|
label: 'Explain Code',
|
|
contextMenuGroupId: 'operation',
|
|
contextMenuOrder: 1,
|
|
run: () => {
|
|
const selection = editorRef?.current?.getSelection()
|
|
if (!selection) return
|
|
|
|
const selectedValue = editorRef?.current?.getModel()?.getValueInRange(selection)
|
|
|
|
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
|
|
aiSnap.newChat({
|
|
name: 'Explain code section',
|
|
sqlSnippets: [selectedValue ?? ''],
|
|
initialInput: 'Can you explain this section to me in more detail?',
|
|
})
|
|
},
|
|
})
|
|
|
|
editor.addAction({
|
|
id: 'toggle-ai-assistant',
|
|
label: 'Toggle AI Assistant',
|
|
keybindings: [monaco.KeyMod.CtrlCmd + monaco.KeyCode.KeyI],
|
|
run: () => {
|
|
if (aiHotkeyEnabledRef.current) {
|
|
toggleSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
|
|
}
|
|
},
|
|
})
|
|
|
|
if (onPrompt) {
|
|
editor.addAction({
|
|
id: 'generate-sql',
|
|
label: 'Generate SQL',
|
|
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyK],
|
|
run: () => {
|
|
const selectionParts = getEditorSelectionParts(editor)
|
|
if (selectionParts) onPrompt(selectionParts)
|
|
},
|
|
})
|
|
}
|
|
|
|
editor.onDidChangeCursorSelection(({ selection }) => {
|
|
const noSelection =
|
|
selection.startLineNumber === selection.endLineNumber &&
|
|
selection.startColumn === selection.endColumn
|
|
onHasSelection(!noSelection)
|
|
})
|
|
|
|
onMount?.(editor)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{disableEdit && (
|
|
<Admonition
|
|
type="default"
|
|
className="rounded-none border-0 border-b"
|
|
title="Read-only snippet"
|
|
description="This snippet has been shared to the project and is only editable by the owner who created this snippet. You may duplicate this snippet into a personal copy by right clicking on the snippet and selecting “Duplicate query”."
|
|
/>
|
|
)}
|
|
<CodeEditor
|
|
id={id}
|
|
language="pgsql"
|
|
className={className}
|
|
autofocus={autoFocus}
|
|
isReadOnly={disableEdit}
|
|
defaultValue={snippet?.snippet.content?.unchecked_sql}
|
|
editorRef={editorRef}
|
|
monacoRef={monacoRef}
|
|
actions={{
|
|
runQuery: { enabled: true, callback: executeQuery },
|
|
formatDocument: { enabled: false, callback: noop },
|
|
placeholderFill: { enabled: false },
|
|
}}
|
|
options={{
|
|
placeholder,
|
|
lineDecorationsWidth: 0,
|
|
fixedOverflowWidgets: false,
|
|
lineNumbersMinChars: 5,
|
|
scrollBeyondLastLine: true,
|
|
suggest: {
|
|
showMethods: intellisenseEnabled,
|
|
showFunctions: intellisenseEnabled,
|
|
showConstructors: intellisenseEnabled,
|
|
showDeprecated: intellisenseEnabled,
|
|
showFields: intellisenseEnabled,
|
|
showVariables: intellisenseEnabled,
|
|
showClasses: intellisenseEnabled,
|
|
showStructs: intellisenseEnabled,
|
|
showInterfaces: intellisenseEnabled,
|
|
showModules: intellisenseEnabled,
|
|
showProperties: intellisenseEnabled,
|
|
showEvents: intellisenseEnabled,
|
|
showOperators: intellisenseEnabled,
|
|
showUnits: intellisenseEnabled,
|
|
showValues: intellisenseEnabled,
|
|
showConstants: intellisenseEnabled,
|
|
showEnums: intellisenseEnabled,
|
|
showEnumMembers: intellisenseEnabled,
|
|
showKeywords: intellisenseEnabled,
|
|
showWords: intellisenseEnabled,
|
|
showColors: intellisenseEnabled,
|
|
showFiles: intellisenseEnabled,
|
|
showReferences: intellisenseEnabled,
|
|
showFolders: intellisenseEnabled,
|
|
showTypeParameters: intellisenseEnabled,
|
|
showIssues: intellisenseEnabled,
|
|
showUsers: intellisenseEnabled,
|
|
showSnippets: intellisenseEnabled,
|
|
},
|
|
}}
|
|
onInputChange={handleEditorChange}
|
|
onMount={handleEditorOnMount}
|
|
/>
|
|
</>
|
|
)
|
|
}
|