Add SQL syntax highlighting to Query Performance page (#38386)

* Add CodeBlock for SQL query display to add syntax highlight

* use monaco editor for better highlighting

* create `SqlMonacoBlock` component for SQL in detail panel

* add mean_time to most_time_comsuming queries

* make outline transparent

* Add keyboard navigation for query performance grid

* Stop default RDG behavior on arrow key navigation

* Update column minWidth values to remove horizontal scrollbar

* Load SqlMonacoBlock and Editor client-side only

- following a pattern in `SQLEditor.tsx`
This commit is contained in:
Taishi
2025-09-04 20:20:05 -04:00
committed by GitHub
parent f65a375d6f
commit 97b631fff7
5 changed files with 183 additions and 24 deletions

View File

@@ -1,7 +1,8 @@
import { ArrowDown, ArrowUp, TextSearch, X } from 'lucide-react'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
import dynamic from 'next/dynamic'
import { useParams } from 'common'
import { DbQueryHook } from 'hooks/analytics/useDbQuery'
@@ -31,6 +32,11 @@ interface QueryPerformanceGridProps {
queryPerformanceQuery: DbQueryHook<any>
}
// Load the monaco editor client-side only (does not behave well server-side)
const Editor = dynamic(() => import('@monaco-editor/react').then(({ Editor }) => Editor), {
ssr: false,
})
export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformanceGridProps) => {
const router = useRouter()
const gridRef = useRef<DataGridHandle>(null)
@@ -74,7 +80,7 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
const value = props.row?.[col.id]
if (col.id === 'query') {
return (
<div className="w-full flex items-center gap-x-2">
<div className="w-full flex items-center gap-x-2 pointer-events-none">
{hasIndexRecommendations(props.row.index_advisor_result, true) && (
<IndexSuggestionIcon
indexAdvisorResult={props.row.index_advisor_result}
@@ -85,7 +91,38 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
}}
/>
)}
<div className="font-mono text-xs">{value}</div>
<Editor
height={20}
theme="supabase"
language="pgsql"
value={value.replace(/\s+/g, ' ').trim()}
wrapperProps={{
className:
'[&_.monaco-editor]:!bg-transparent [&_.monaco-editor-background]:!bg-transparent [&_.monaco-editor]:!outline-transparent',
}}
options={{
readOnly: true,
domReadOnly: true,
cursorBlinking: 'solid',
tabIndex: -1,
fontSize: 12,
minimap: { enabled: false },
lineNumbers: 'off',
renderLineHighlight: 'none',
scrollbar: { vertical: 'hidden', horizontal: 'hidden' },
overviewRulerLanes: 0,
overviewRulerBorder: false,
glyphMargin: false,
folding: false,
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
wordWrap: 'off',
scrollBeyondLastLine: false,
contextmenu: false,
selectionHighlight: false,
occurrencesHighlight: 'off',
}}
/>
</div>
)
}
@@ -170,6 +207,43 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
setSelectedRow(undefined)
}, [preset, search, roles, urlSort, order])
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
if (!reportData.length || selectedRow === undefined) return
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return
// stop default RDG behavior (which moves focus to header when selectedRow is 0)
event.stopPropagation()
let nextIndex = selectedRow
if (event.key === 'ArrowUp' && selectedRow > 0) {
nextIndex = selectedRow - 1
} else if (event.key === 'ArrowDown' && selectedRow < reportData.length - 1) {
nextIndex = selectedRow + 1
}
if (nextIndex !== selectedRow) {
setSelectedRow(nextIndex)
gridRef.current?.scrollToCell({ idx: 0, rowIdx: nextIndex })
const rowQuery = reportData[nextIndex]?.query ?? ''
if (!rowQuery.trim().toLowerCase().startsWith('select')) {
setView('details')
}
}
},
[reportData, selectedRow]
)
useEffect(() => {
// run before RDG to prevent header focus (the third param: true)
window.addEventListener('keydown', handleKeyDown, true)
return () => {
window.removeEventListener('keydown', handleKeyDown, true)
}
}, [handleKeyDown])
return (
<ResizablePanelGroup
direction="horizontal"