Skip to content

fix(QueryEditor): dont render Results every time editor input changes #1879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 15 additions & 190 deletions src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {
Expand All @@ -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 = {
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -150,7 +123,7 @@ export default function QueryEditor(props: QueryEditorProps) {

sendQuery({
actionType: 'execute',
query,
query: text,
database: tenantName,
querySettings,
enableTracingLevel,
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -184,7 +157,7 @@ export default function QueryEditor(props: QueryEditorProps) {

sendQuery({
actionType: 'explain',
query: input,
query: text,
database: tenantName,
querySettings,
enableTracingLevel,
Expand All @@ -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<any>('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<boolean>('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);
};
Expand All @@ -321,7 +187,6 @@ export default function QueryEditor(props: QueryEditorProps) {
onSettingsButtonClick={handleSettingsClick}
isLoading={Boolean(result?.isLoading)}
handleGetExplainQueryClick={handleGetExplainQueryClick}
disabled={!input}
highlightedAction={lastUsedQueryAction}
/>
);
Expand All @@ -345,14 +210,11 @@ export default function QueryEditor(props: QueryEditorProps) {
>
<div className={b('monaco-wrapper')}>
<div className={b('monaco')}>
<MonacoEditor
language={YQL_LANGUAGE_ID}
value={input}
options={editorOptions}
onChange={onChange}
editorDidMount={editorDidMount}
theme={`vs-${theme}`}
editorWillUnmount={editorWillUnmount}
<YqlEditor
changeUserInput={changeUserInput}
theme={theme}
handleSendExecuteClick={handleSendExecuteClick}
handleGetExplainQueryClick={handleGetExplainQueryClick}
/>
</div>
</div>
Expand Down Expand Up @@ -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;
}
}
Loading
Loading