From cc2c75ac05797b7ed46c5cd6bead449a2aee6321 Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Mon, 27 Jan 2025 15:18:41 +0300 Subject: [PATCH 1/2] fix(QueryEditor): dont render Results every time editor input changes --- .../Tenant/Query/QueryEditor/QueryEditor.tsx | 205 ++--------------- .../Tenant/Query/QueryEditor/YqlEditor.tsx | 213 ++++++++++++++++++ .../QueryEditorControls.tsx | 15 +- 3 files changed, 237 insertions(+), 196 deletions(-) create mode 100644 src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx index b6f69982d..9de44fce5 100644 --- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx @@ -1,24 +1,17 @@ import React from 'react'; -import NiceModal from '@ebay/nice-modal-react'; import {isEqual} from 'lodash'; -import throttle from 'lodash/throttle'; -import type Monaco from 'monaco-editor'; import {v4 as uuidv4} from 'uuid'; -import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor'; import SplitPane from '../../../../components/SplitPane'; import {useTracingLevelOptionAvailable} from '../../../../store/reducers/capabilities/hooks'; import { - goToNextQuery, - goToPreviousQuery, queryApi, saveQueryToHistory, selectQueriesHistory, selectQueriesHistoryCurrentIndex, selectResult, selectTenantPath, - selectUserInput, setTenantPath, } from '../../../../store/reducers/query/query'; import type {QueryResult} from '../../../../store/reducers/query/types'; @@ -41,8 +34,6 @@ import { } from '../../../../utils/hooks'; import {useChangedQuerySettings} from '../../../../utils/hooks/useChangedQuerySettings'; import {useLastQueryExecutionSettings} from '../../../../utils/hooks/useLastQueryExecutionSettings'; -import {YQL_LANGUAGE_ID} from '../../../../utils/monaco/constats'; -import {useUpdateErrorsHighlighting} from '../../../../utils/monaco/highlightErrors'; import {QUERY_ACTIONS} from '../../../../utils/query'; import type {InitialPaneState} from '../../utils/paneVisibilityToggleHelpers'; import { @@ -53,16 +44,11 @@ import {Preview} from '../Preview/Preview'; import {QueryEditorControls} from '../QueryEditorControls/QueryEditorControls'; import {QueryResultViewer} from '../QueryResult/QueryResultViewer'; import {QuerySettingsDialog} from '../QuerySettingsDialog/QuerySettingsDialog'; -import {SAVE_QUERY_DIALOG} from '../SaveQuery/SaveQuery'; -import i18n from '../i18n'; -import {useEditorOptions} from './helpers'; -import {getKeyBindings} from './keybindings'; +import {YqlEditor} from './YqlEditor'; import './QueryEditor.scss'; -const CONTEXT_MENU_GROUP_ID = 'navigation'; - const b = cn('query-editor'); const initialTenantCommonInfoState = { @@ -80,18 +66,14 @@ interface QueryEditorProps { } export default function QueryEditor(props: QueryEditorProps) { - const editorOptions = useEditorOptions(); const dispatch = useTypedDispatch(); const {tenantName, path, type, theme, changeUserInput} = props; const savedPath = useTypedSelector(selectTenantPath); const result = useTypedSelector(selectResult); const historyQueries = useTypedSelector(selectQueriesHistory); const historyCurrentIndex = useTypedSelector(selectQueriesHistoryCurrentIndex); - const input = useTypedSelector(selectUserInput); const showPreview = useTypedSelector(selectShowPreview); - const updateErrorsHighlighting = useUpdateErrorsHighlighting(); - const isResultLoaded = Boolean(result); const [querySettings] = useQueryExecutionSettings(); @@ -130,18 +112,9 @@ export default function QueryEditor(props: QueryEditorProps) { } }, [showPreview, isResultLoaded]); - const getLastQueryText = useEventHandler(() => { - if (!historyQueries || historyQueries.length === 0) { - return ''; - } - return historyQueries[historyQueries.length - 1].queryText; - }); - - const handleSendExecuteClick = useEventHandler((text?: string) => { - const query = text ?? input; - + const handleSendExecuteClick = useEventHandler((text: string, partial?: boolean) => { setLastUsedQueryAction(QUERY_ACTIONS.execute); - setLastExecutedQueryText(query); + setLastExecutedQueryText(text); if (!isEqual(lastQueryExecutionSettings, querySettings)) { resetBanner(); setLastQueryExecutionSettings(querySettings); @@ -150,7 +123,7 @@ export default function QueryEditor(props: QueryEditorProps) { sendQuery({ actionType: 'execute', - query, + query: text, database: tenantName, querySettings, enableTracingLevel, @@ -160,9 +133,9 @@ export default function QueryEditor(props: QueryEditorProps) { dispatch(setShowPreview(false)); // Don't save partial queries in history - if (!text) { - if (query !== historyQueries[historyCurrentIndex]?.queryText) { - dispatch(saveQueryToHistory({queryText: input, queryId})); + if (!partial) { + if (text !== historyQueries[historyCurrentIndex]?.queryText) { + dispatch(saveQueryToHistory({queryText: text, queryId})); } } dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand); @@ -172,9 +145,9 @@ export default function QueryEditor(props: QueryEditorProps) { dispatch(setQueryAction('settings')); }; - const handleGetExplainQueryClick = useEventHandler(() => { + const handleGetExplainQueryClick = useEventHandler((text: string) => { setLastUsedQueryAction(QUERY_ACTIONS.explain); - setLastExecutedQueryText(input); + setLastExecutedQueryText(text); if (!isEqual(lastQueryExecutionSettings, querySettings)) { resetBanner(); setLastQueryExecutionSettings(querySettings); @@ -184,7 +157,7 @@ export default function QueryEditor(props: QueryEditorProps) { sendQuery({ actionType: 'explain', - query: input, + query: text, database: tenantName, querySettings, enableTracingLevel, @@ -196,113 +169,6 @@ export default function QueryEditor(props: QueryEditorProps) { dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand); }); - const handleSendQuery = useEventHandler(() => { - if (lastUsedQueryAction === QUERY_ACTIONS.explain) { - handleGetExplainQueryClick(); - } else { - handleSendExecuteClick(); - } - }); - - const editorWillUnmount = () => { - window.ydbEditor = undefined; - }; - - const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { - window.ydbEditor = editor; - const keybindings = getKeyBindings(monaco); - monaco.editor.registerCommand('insertSnippetToEditor', (_asessor, input: string) => { - //suggestController is not properly typed yet in monaco-editor package - const contribution = editor.getContribution('snippetController2'); - if (contribution) { - editor.focus(); - editor.setValue(''); - contribution.insert(input); - } - }); - initResizeHandler(editor); - initUserPrompt(editor, getLastQueryText); - editor.focus(); - editor.addAction({ - id: 'sendQuery', - label: i18n('action.send-query'), - keybindings: [keybindings.sendQuery], - // A precondition for this action. - precondition: undefined, - // A rule to evaluate on top of the precondition in order to dispatch the keybindings. - keybindingContext: undefined, - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 1, - // Method that will be executed when the action is triggered. - // @param editor The editor instance is passed in as a convenience - run: () => handleSendQuery(), - }); - - const canSendSelectedText = editor.createContextKey('canSendSelectedText', false); - editor.onDidChangeCursorSelection(({selection, secondarySelections}) => { - const notEmpty = - selection.selectionStartLineNumber !== selection.positionLineNumber || - selection.selectionStartColumn !== selection.positionColumn; - const hasMultipleSelections = secondarySelections.length > 0; - canSendSelectedText.set(notEmpty && !hasMultipleSelections); - }); - editor.addAction({ - id: 'sendSelectedQuery', - label: i18n('action.send-selected-query'), - keybindings: [keybindings.sendSelectedQuery], - precondition: 'canSendSelectedText', - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 1, - run: (e) => { - const selection = e.getSelection(); - const model = e.getModel(); - if (selection && model) { - const text = model.getValueInRange({ - startLineNumber: selection.getSelectionStart().lineNumber, - startColumn: selection.getSelectionStart().column, - endLineNumber: selection.getPosition().lineNumber, - endColumn: selection.getPosition().column, - }); - handleSendExecuteClick(text); - } - }, - }); - - editor.addAction({ - id: 'previous-query', - label: i18n('action.previous-query'), - keybindings: [keybindings.selectPreviousQuery], - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 2, - run: () => { - dispatch(goToPreviousQuery()); - }, - }); - editor.addAction({ - id: 'next-query', - label: i18n('action.next-query'), - keybindings: [keybindings.selectNextQuery], - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 3, - run: () => { - dispatch(goToNextQuery()); - }, - }); - editor.addAction({ - id: 'save-query', - label: i18n('action.save-query'), - keybindings: [keybindings.saveQuery], - run: () => { - NiceModal.show(SAVE_QUERY_DIALOG); - }, - }); - }; - - const onChange = (newValue: string) => { - updateErrorsHighlighting(); - changeUserInput({input: newValue}); - }; - const onCollapseResultHandler = () => { dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerCollapse); }; @@ -321,7 +187,6 @@ export default function QueryEditor(props: QueryEditorProps) { onSettingsButtonClick={handleSettingsClick} isLoading={Boolean(result?.isLoading)} handleGetExplainQueryClick={handleGetExplainQueryClick} - disabled={!input} highlightedAction={lastUsedQueryAction} /> ); @@ -345,14 +210,11 @@ export default function QueryEditor(props: QueryEditorProps) { >
-
@@ -424,40 +286,3 @@ function Result({ return null; } - -function initResizeHandler(editor: Monaco.editor.IStandaloneCodeEditor) { - const layoutEditor = throttle(() => { - editor.layout(); - }, 100); - - editor.layout(); - - window.addEventListener('resize', layoutEditor); - editor.onDidDispose(() => { - window.removeEventListener('resize', layoutEditor); - }); -} - -function initUserPrompt(editor: Monaco.editor.IStandaloneCodeEditor, getInitialText: () => string) { - setUserPrompt(editor.getValue(), getInitialText()); - editor.onDidChangeModelContent(() => { - setUserPrompt(editor.getValue(), getInitialText()); - }); - editor.onDidDispose(() => { - window.onbeforeunload = null; - }); -} - -function setUserPrompt(text: string, initialText: string) { - const hasUnsavedInput = text ? text !== initialText : false; - - if (hasUnsavedInput) { - window.onbeforeunload = (e) => { - e.preventDefault(); - // Chrome requires returnValue to be set - e.returnValue = ''; - }; - } else { - window.onbeforeunload = null; - } -} diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx new file mode 100644 index 000000000..281fd3111 --- /dev/null +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -0,0 +1,213 @@ +import NiceModal from '@ebay/nice-modal-react'; +import throttle from 'lodash/throttle'; +import type Monaco from 'monaco-editor'; + +import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor'; +import { + goToNextQuery, + goToPreviousQuery, + selectQueriesHistory, + selectUserInput, +} from '../../../../store/reducers/query/query'; +import type {QueryAction} from '../../../../types/store/query'; +import {LAST_USED_QUERY_ACTION_KEY} from '../../../../utils/constants'; +import { + useEventHandler, + useSetting, + useTypedDispatch, + useTypedSelector, +} from '../../../../utils/hooks'; +import {YQL_LANGUAGE_ID} from '../../../../utils/monaco/constats'; +import {useUpdateErrorsHighlighting} from '../../../../utils/monaco/highlightErrors'; +import {QUERY_ACTIONS} from '../../../../utils/query'; +import {SAVE_QUERY_DIALOG} from '../SaveQuery/SaveQuery'; +import i18n from '../i18n'; + +import {useEditorOptions} from './helpers'; +import {getKeyBindings} from './keybindings'; + +const CONTEXT_MENU_GROUP_ID = 'navigation'; + +interface YqlEditorProps { + changeUserInput: (arg: {input: string}) => void; + theme: string; + handleGetExplainQueryClick: (text: string) => void; + handleSendExecuteClick: (text: string, partial?: boolean) => void; +} + +export function YqlEditor({ + changeUserInput, + theme, + handleSendExecuteClick, + handleGetExplainQueryClick, +}: YqlEditorProps) { + const input = useTypedSelector(selectUserInput); + const dispatch = useTypedDispatch(); + const historyQueries = useTypedSelector(selectQueriesHistory); + const editorOptions = useEditorOptions(); + const updateErrorsHighlighting = useUpdateErrorsHighlighting(); + + const [lastUsedQueryAction] = useSetting(LAST_USED_QUERY_ACTION_KEY); + + const getLastQueryText = useEventHandler(() => { + if (!historyQueries || historyQueries.length === 0) { + return ''; + } + return historyQueries[historyQueries.length - 1].queryText; + }); + + const handleSendQuery = useEventHandler(() => { + if (lastUsedQueryAction === QUERY_ACTIONS.explain) { + handleGetExplainQueryClick(input); + } else { + handleSendExecuteClick(input); + } + }); + + const editorWillUnmount = () => { + window.ydbEditor = undefined; + }; + + const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { + window.ydbEditor = editor; + const keybindings = getKeyBindings(monaco); + monaco.editor.registerCommand('insertSnippetToEditor', (_asessor, input: string) => { + //suggestController is not properly typed yet in monaco-editor package + const contribution = editor.getContribution('snippetController2'); + if (contribution) { + editor.focus(); + editor.setValue(''); + contribution.insert(input); + } + }); + initResizeHandler(editor); + initUserPrompt(editor, getLastQueryText); + editor.focus(); + editor.addAction({ + id: 'sendQuery', + label: i18n('action.send-query'), + keybindings: [keybindings.sendQuery], + // A precondition for this action. + precondition: undefined, + // A rule to evaluate on top of the precondition in order to dispatch the keybindings. + keybindingContext: undefined, + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 1, + // Method that will be executed when the action is triggered. + // @param editor The editor instance is passed in as a convenience + run: () => handleSendQuery(), + }); + + const canSendSelectedText = editor.createContextKey('canSendSelectedText', false); + editor.onDidChangeCursorSelection(({selection, secondarySelections}) => { + const notEmpty = + selection.selectionStartLineNumber !== selection.positionLineNumber || + selection.selectionStartColumn !== selection.positionColumn; + const hasMultipleSelections = secondarySelections.length > 0; + canSendSelectedText.set(notEmpty && !hasMultipleSelections); + }); + editor.addAction({ + id: 'sendSelectedQuery', + label: i18n('action.send-selected-query'), + keybindings: [keybindings.sendSelectedQuery], + precondition: 'canSendSelectedText', + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 1, + run: (e) => { + const selection = e.getSelection(); + const model = e.getModel(); + if (selection && model) { + const text = model.getValueInRange({ + startLineNumber: selection.getSelectionStart().lineNumber, + startColumn: selection.getSelectionStart().column, + endLineNumber: selection.getPosition().lineNumber, + endColumn: selection.getPosition().column, + }); + handleSendExecuteClick(text, true); + } + }, + }); + + editor.addAction({ + id: 'previous-query', + label: i18n('action.previous-query'), + keybindings: [keybindings.selectPreviousQuery], + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 2, + run: () => { + dispatch(goToPreviousQuery()); + }, + }); + editor.addAction({ + id: 'next-query', + label: i18n('action.next-query'), + keybindings: [keybindings.selectNextQuery], + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 3, + run: () => { + dispatch(goToNextQuery()); + }, + }); + editor.addAction({ + id: 'save-query', + label: i18n('action.save-query'), + keybindings: [keybindings.saveQuery], + run: () => { + NiceModal.show(SAVE_QUERY_DIALOG); + }, + }); + }; + + const onChange = (newValue: string) => { + updateErrorsHighlighting(); + changeUserInput({input: newValue}); + }; + return ( + + ); +} + +function initResizeHandler(editor: Monaco.editor.IStandaloneCodeEditor) { + const layoutEditor = throttle(() => { + editor.layout(); + }, 100); + + editor.layout(); + + window.addEventListener('resize', layoutEditor); + editor.onDidDispose(() => { + window.removeEventListener('resize', layoutEditor); + }); +} + +function initUserPrompt(editor: Monaco.editor.IStandaloneCodeEditor, getInitialText: () => string) { + setUserPrompt(editor.getValue(), getInitialText()); + editor.onDidChangeModelContent(() => { + setUserPrompt(editor.getValue(), getInitialText()); + }); + editor.onDidDispose(() => { + window.onbeforeunload = null; + }); +} + +function setUserPrompt(text: string, initialText: string) { + const hasUnsavedInput = text ? text !== initialText : false; + + if (hasUnsavedInput) { + window.onbeforeunload = (e) => { + e.preventDefault(); + // Chrome requires returnValue to be set + e.returnValue = ''; + }; + } else { + window.onbeforeunload = null; + } +} diff --git a/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx b/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx index a662edf41..807de93b4 100644 --- a/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx +++ b/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx @@ -3,8 +3,10 @@ import type {ButtonView} from '@gravity-ui/uikit'; import {Button, Icon, Tooltip} from '@gravity-ui/uikit'; import QuerySettingsDescription from '../../../../components/QuerySettingsDescription/QuerySettingsDescription'; +import {selectUserInput} from '../../../../store/reducers/query/query'; import type {QueryAction} from '../../../../types/store/query'; import {cn} from '../../../../utils/cn'; +import {useTypedSelector} from '../../../../utils/hooks'; import {useChangedQuerySettings} from '../../../../utils/hooks/useChangedQuerySettings'; import {NewSQL} from '../NewSQL/NewSQL'; import {SaveQuery} from '../SaveQuery/SaveQuery'; @@ -56,11 +58,11 @@ const SettingsButton = ({onClick, runIsLoading}: SettingsButtonProps) => { interface QueryEditorControlsProps { isLoading: boolean; - disabled: boolean; + disabled?: boolean; highlightedAction: QueryAction; - handleGetExplainQueryClick: () => void; - handleSendExecuteClick: () => void; + handleGetExplainQueryClick: (text: string) => void; + handleSendExecuteClick: (text: string) => void; onSettingsButtonClick: () => void; } @@ -73,16 +75,17 @@ export const QueryEditorControls = ({ onSettingsButtonClick, handleGetExplainQueryClick, }: QueryEditorControlsProps) => { + const input = useTypedSelector(selectUserInput); const runView: ButtonView | undefined = highlightedAction === 'execute' ? 'action' : undefined; const explainView: ButtonView | undefined = highlightedAction === 'explain' ? 'action' : undefined; const onRunButtonClick = () => { - handleSendExecuteClick(); + handleSendExecuteClick(input); }; const onExplainButtonClick = () => { - handleGetExplainQueryClick(); + handleGetExplainQueryClick(input); }; return ( @@ -90,7 +93,7 @@ export const QueryEditorControls = ({