Skip to content
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

[Console Monaco migration] Implement history #183181

Merged
merged 35 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
00533b2
[Console] Implement method and url path autocomplete
yuliacech Apr 22, 2024
6f2911b
[Console] Tests
yuliacech Apr 23, 2024
cc4f6ae
[Console] Implement url params autocomplete
yuliacech Apr 24, 2024
084e274
[Console] Implement body params autocomplete suggestions
yuliacech Apr 30, 2024
2562e53
[Console] Fix merge conflict issues
yuliacech May 3, 2024
0485996
[Console] Fix merge conflict issues
yuliacech May 3, 2024
5e17ca1
[Console] Remove console log
yuliacech May 3, 2024
80f801d
[Console] Add template
yuliacech May 3, 2024
eb7f43c
Merge branch 'main' into console/implement_autocomplete_body
yuliacech May 6, 2024
fc47ebd
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine May 6, 2024
b00fa24
[Console] Clean up tests
yuliacech May 6, 2024
3e0d610
[Console] Fix imports
yuliacech May 7, 2024
9e192d1
[Console] Fix tokens parsing for nested objects
yuliacech May 7, 2024
a911984
Merge branch 'main' into console/implement_autocomplete_body
yuliacech May 8, 2024
c1b393d
[Console] Start migrating the console history to monaco
yuliacech May 8, 2024
1abcc66
[Console] Implement console history in monaco
yuliacech May 10, 2024
28bf9b7
Merge branch 'main' into console/migrate_history
yuliacech May 15, 2024
8c03ef3
[Console] Remove unused imports
yuliacech May 15, 2024
4c172b5
[Console] Fixed the new line insertion
yuliacech May 15, 2024
aa86dcf
Merge branch 'main' into console/migrate_history
yuliacech May 15, 2024
9c33bfe
Merge branch 'main' into console/migrate_history
yuliacech May 15, 2024
511722d
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine May 15, 2024
48c0fb6
[Console] Add comments
yuliacech May 15, 2024
cb83b15
Merge branch 'main' into console/migrate_history
yuliacech May 16, 2024
42ff316
[Console] Fix types
yuliacech May 16, 2024
a7cfd66
[Console] Fix types
yuliacech May 16, 2024
7f93171
[Console] Fix an issue when inserting a history request
yuliacech May 17, 2024
77495e0
Merge branch 'main' into console/migrate_history
yuliacech May 17, 2024
ca60bbc
[Console] Add unit tests
yuliacech May 17, 2024
5f6ed4e
Merge branch 'main' into console/migrate_history
yuliacech May 21, 2024
dc2e42e
Merge branch 'main' into console/migrate_history
yuliacech May 21, 2024
67968c6
[Console] Fix inserting the history request on a line outside of a pa…
yuliacech May 21, 2024
c4e0741
Merge branch 'main' into console/migrate_history
yuliacech May 22, 2024
fb2b7dc
[Console] Fix merge conflict issues
yuliacech May 22, 2024
f5ae554
[Console] Fix eslint issues
yuliacech May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {

import { useServicesContext } from '../../contexts';
import { HistoryViewer } from './history_viewer';
import { HistoryViewer as HistoryViewerMonaco } from './history_viewer_monaco';
import { useEditorReadContext } from '../../contexts/editor_context';
import { useRestoreRequestFromHistory } from '../../hooks';

Expand All @@ -35,6 +36,7 @@ const CHILD_ELEMENT_PREFIX = 'historyReq';
export function ConsoleHistory({ close }: Props) {
const {
services: { history },
config: { isMonacoEnabled },
} = useServicesContext();

const { settings: readOnlySettings } = useEditorReadContext();
Expand Down Expand Up @@ -91,7 +93,7 @@ export function ConsoleHistory({ close }: Props) {
initialize();
};

const restoreRequestFromHistory = useRestoreRequestFromHistory();
const restoreRequestFromHistory = useRestoreRequestFromHistory(isMonacoEnabled);

useEffect(() => {
initialize();
Expand Down Expand Up @@ -181,7 +183,11 @@ export function ConsoleHistory({ close }: Props) {

<div className="conHistory__body__spacer" />

<HistoryViewer settings={readOnlySettings} req={viewingReq} />
{isMonacoEnabled ? (
<HistoryViewerMonaco settings={readOnlySettings} req={viewingReq} />
) : (
<HistoryViewer settings={readOnlySettings} req={viewingReq} />
)}
</div>

<EuiSpacer size="s" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { useCallback, useRef } from 'react';
import { css } from '@emotion/react';
import { CONSOLE_LANG_ID, CONSOLE_THEME_ID, monaco } from '@kbn/monaco';
import { CodeEditor } from '@kbn/code-editor';
import { i18n } from '@kbn/i18n';
import { formatRequestBodyDoc } from '../../../lib/utils';
import { DevToolsSettings } from '../../../services';
import { useResizeCheckerUtils } from '../editor/monaco/hooks';

export const HistoryViewer = ({
settings,
req,
}: {
settings: DevToolsSettings;
req: { method: string; endpoint: string; data: string; time: string } | null;
}) => {
const divRef = useRef<HTMLDivElement | null>(null);
const { setupResizeChecker, destroyResizeChecker } = useResizeCheckerUtils();

const editorDidMountCallback = useCallback(
(editor: monaco.editor.IStandaloneCodeEditor) => {
setupResizeChecker(divRef.current!, editor);
},
[setupResizeChecker]
);

const editorWillUnmountCallback = useCallback(() => {
destroyResizeChecker();
}, [destroyResizeChecker]);
let renderedHistoryRequest: string;
if (req) {
const indent = true;
const formattedData = req.data ? formatRequestBodyDoc([req.data], indent).data : '';
renderedHistoryRequest = req.method + ' ' + req.endpoint + '\n' + formattedData;
} else {
renderedHistoryRequest = i18n.translate('console.historyPage.noHistoryTextMessage', {
defaultMessage: 'No history available',
});
}
return (
<div
css={css`
width: 100%;
`}
ref={divRef}
>
<CodeEditor
languageId={CONSOLE_LANG_ID}
value={renderedHistoryRequest}
fullWidth={true}
editorDidMount={editorDidMountCallback}
editorWillUnmount={editorWillUnmountCallback}
options={{
readOnly: true,
fontSize: settings.fontSize,
wordWrap: settings.wrapMode ? 'on' : 'off',
theme: CONSOLE_THEME_ID,
automaticLayout: true,
}}
/>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface SetInitialValueParams {
/**
* Util function for reading the load_from parameter from the current url.
*/

export const readLoadFromParam = () => {
const [, queryString] = (window.location.hash || window.location.search || '').split('?');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { css } from '@emotion/react';
import { CodeEditor } from '@kbn/code-editor';
import { CONSOLE_LANG_ID, CONSOLE_THEME_ID, monaco } from '@kbn/monaco';
import { i18n } from '@kbn/i18n';
import { useSetInputEditor } from '../../../hooks';
import { ConsoleMenu } from '../../../components';
import {
useServicesContext,
Expand All @@ -32,17 +33,11 @@ export interface EditorProps {
}

export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
const context = useServicesContext();
const {
services: {
notifications,
esHostService,
trackUiMetric,
http,
settings: settingsService,
autocompleteInfo,
},
services: { notifications, esHostService, settings: settingsService, autocompleteInfo },
docLinkVersion,
} = useServicesContext();
} = context;
const { toasts } = notifications;
const { settings } = useEditorReadContext();

Expand All @@ -53,12 +48,15 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
const actionsProvider = useRef<MonacoEditorActionsProvider | null>(null);
const [editorActionsCss, setEditorActionsCss] = useState<CSSProperties>({});

const setInputEditor = useSetInputEditor();
const editorDidMountCallback = useCallback(
(editor: monaco.editor.IStandaloneCodeEditor) => {
actionsProvider.current = new MonacoEditorActionsProvider(editor, setEditorActionsCss);
const provider = new MonacoEditorActionsProvider(editor, setEditorActionsCss);
actionsProvider.current = provider;
setupResizeChecker(divRef.current!, editor);
setInputEditor(provider);
},
[setupResizeChecker]
[setInputEditor, setupResizeChecker]
);

const editorWillUnmountCallback = useCallback(() => {
Expand All @@ -79,8 +77,8 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
}, []);

const sendRequestsCallback = useCallback(async () => {
await actionsProvider.current?.sendRequests(toasts, dispatch, trackUiMetric, http);
}, [dispatch, http, toasts, trackUiMetric]);
await actionsProvider.current?.sendRequests(dispatch, context);
}, [dispatch, context]);

const suggestionProvider = useMemo(() => {
return getSuggestionProvider(actionsProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@
import { CSSProperties, Dispatch } from 'react';
import { debounce } from 'lodash';
import { ConsoleParsedRequestsProvider, getParsedRequestsProvider, monaco } from '@kbn/monaco';
import { IToasts } from '@kbn/core-notifications-browser';
import { i18n } from '@kbn/i18n';
import type { HttpSetup } from '@kbn/core-http-browser';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { isQuotaExceededError } from '../../../../services/history';
import { DEFAULT_VARIABLES } from '../../../../../common/constants';
import { getStorage, StorageKeys } from '../../../../services';
import { sendRequest } from '../../../hooks';
import { MetricsTracker } from '../../../../types';
import { Actions } from '../../../stores/request';

import {
Expand All @@ -38,6 +37,8 @@ import {
} from './utils';

import type { AdjustedParsedRequest } from './types';
import { StorageQuotaError } from '../../../components/storage_quota_error';
import { ContextValue } from '../../../contexts';

const AUTO_INDENTATION_ACTION_LABEL = 'Apply indentations';

Expand Down Expand Up @@ -180,12 +181,12 @@ export class MonacoEditorActionsProvider {
return curlRequests.join('\n');
}

public async sendRequests(
toasts: IToasts,
dispatch: Dispatch<Actions>,
trackUiMetric: MetricsTracker,
http: HttpSetup
): Promise<void> {
public async sendRequests(dispatch: Dispatch<Actions>, context: ContextValue): Promise<void> {
const {
services: { notifications, trackUiMetric, http, settings, history, autocompleteInfo },
startServices,
} = context;
const { toasts } = notifications;
try {
const requests = await this.getRequests();
if (!requests.length) {
Expand All @@ -205,8 +206,63 @@ export class MonacoEditorActionsProvider {

const results = await sendRequest({ http, requests });

// TODO save to history
// TODO restart autocomplete polling
let saveToHistoryError: undefined | Error;
const isHistoryEnabled = settings.getIsHistoryEnabled();

if (isHistoryEnabled) {
results.forEach(({ request: { path, method, data } }) => {
try {
history.addToHistory(path, method, data);
} catch (e) {
// Grab only the first error
if (!saveToHistoryError) {
saveToHistoryError = e;
}
}
});

if (saveToHistoryError) {
const errorTitle = i18n.translate('console.notification.error.couldNotSaveRequestTitle', {
defaultMessage: 'Could not save request to Console history.',
});
if (isQuotaExceededError(saveToHistoryError)) {
const toast = notifications.toasts.addWarning({
title: i18n.translate('console.notification.error.historyQuotaReachedMessage', {
defaultMessage:
'Request history is full. Clear the console history or disable saving new requests.',
}),
text: toMountPoint(
StorageQuotaError({
onClearHistory: () => {
history.clearHistory();
notifications.toasts.remove(toast);
},
onDisableSavingToHistory: () => {
settings.setIsHistoryEnabled(false);
notifications.toasts.remove(toast);
},
}),
startServices
),
});
} else {
// Best effort, but still notify the user.
notifications.toasts.addError(saveToHistoryError, {
title: errorTitle,
});
}
}
}

const polling = settings.getPolling();
if (polling) {
// If the user has submitted a request against ES, something in the fields, indices, aliases,
// or templates may have changed, so we'll need to update this data. Assume that if
// the user disables polling they're trying to optimize performance or otherwise
// preserve resources, so they won't want this request sent either.
autocompleteInfo.retrieve(settings, settings.getAutocomplete());
}

dispatch({
type: 'requestSuccess',
payload: {
Expand Down Expand Up @@ -347,6 +403,61 @@ export class MonacoEditorActionsProvider {
return this.getSuggestions(model, position, context);
}

/*
* This function inserts a request from the history into the editor
*/
public async restoreRequestFromHistory(request: string) {
const model = this.editor.getModel();
if (!model) {
return;
}
let position = this.editor.getPosition() as monaco.IPosition;
const requests = await this.getSelectedParsedRequests();
// if there are requests at the cursor/selection, insert either before or after
if (requests.length > 0) {
// if on the 1st line of the 1st request, insert before
if (position && position.lineNumber === requests[0].startLineNumber) {
position = { column: 1, lineNumber: position.lineNumber < 2 ? 1 : position.lineNumber - 1 };
} else {
// otherwise insert after
position = { column: 1, lineNumber: requests[requests.length - 1].endLineNumber + 1 };
}
} else {
// if not inside a request, insert the request at the cursor position
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, the request from history will always end up at column 1 unless it is not inside a request - then it might end up in the middle of the line if the cursor is there. In Ace editor, it always ends up at column 1. Should we do the same here for consistency?

Suggested change
// if not inside a request, insert the request at the cursor position
// the cursor is not inside a request
if (!position) {
// if no cursor position, insert at the beginning of the first line
position = { lineNumber: 1, column: 1 };
} else {
// insert the request at the beginning of the line of the cursor position
position = { lineNumber: position.lineNumber, column: 1 };
}

In Ace editor:

Screen.Recording.2024-05-17.at.17.00.22.mov

In this PR:

Screen.Recording.2024-05-17.at.16.58.34.mov

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot, @ElenaStoeva! Another great catch, fixed in 67968c6

if (!position) {
// if no cursor position, insert at the beginning
position = { lineNumber: 1, column: 1 };
}
}
// add new lines around the request inserted from history
let prefix = '\n';
let suffix = '\n';
// check the text before the inserted request: content on the line above or empty
const textBefore = position.lineNumber > 1 ? model.getLineContent(position.lineNumber - 1) : '';
if (!textBefore) {
// if there is no text before, don't insert a new line before the request
prefix = '';
}
// check the text after the inserted request
const textAfter =
position.lineNumber <= model.getLineCount() ? model.getLineContent(position.lineNumber) : '';
if (!textAfter) {
// if there is no text after, don't insert a new line after the request
suffix = '';
}
const edit: monaco.editor.IIdentifiedSingleEditOperation = {
range: {
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: position.lineNumber,
endColumn: position.column,
},
text: prefix + request + suffix,
forceMoveMarkers: true,
};
this.editor.executeEdits('restoreFromHistory', [edit]);
}

/*
This function returns the text in the provided range.
If no range is provided, it returns all text in the editor.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,6 @@ const getInsertText = (
} else {
templateLines = JSON.stringify(template, null, 2).split(newLineRegex);
}
// TODO add correct indentation
insertText += ': ' + templateLines.join('\n');
} else if (value === '{') {
insertText += '{}';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
* Side Public License, v 1.
*/

import { MonacoEditorActionsProvider } from '../../containers/editor/monaco/monaco_editor_actions_provider';
import { SenseEditor } from '../../models/sense_editor';

export class EditorRegistry {
private inputEditor: SenseEditor | undefined;
private inputEditor: SenseEditor | MonacoEditorActionsProvider | undefined;

setInputEditor(inputEditor: SenseEditor) {
setInputEditor(inputEditor: SenseEditor | MonacoEditorActionsProvider) {
this.inputEditor = inputEditor;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface ContextValue {
services: ContextServices;
docLinkVersion: string;
docLinks: DocLinksStart['links'];
config?: {
config: {
isMonacoEnabled: boolean;
};
startServices: ConsoleStartServices;
Expand Down