Skip to content

Commit

Permalink
[Console Monaco migration] Implement history (#183181)
Browse files Browse the repository at this point in the history
## Summary

Fixes #182948 

This PR migrates history component from ace to monaco and re-implements
the logic to insert the saved requests into the monaco editor.

To test: 
1. click the "History" tab and check that the component is using Monaco
to display requests
2. Check that "Clear history" button works
3. Check that a request from history can be inserted into the editor
- when the cursor is on the 1st line of the request, the history request
is inserted before the request in the editor
- if the cursor is not on the 1st line of the request, the history
request is inserted after the request in the editor



### Screen recording 


https://github.com/elastic/kibana/assets/6585477/6031ee6a-8211-4ca7-96d7-3aafbaee0509

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
yuliacech and kibanamachine committed May 22, 2024
1 parent 7e21a1e commit 9194c88
Show file tree
Hide file tree
Showing 14 changed files with 419 additions and 39 deletions.
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 @@ -33,17 +34,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 @@ -55,6 +50,7 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
const actionsProvider = useRef<MonacoEditorActionsProvider | null>(null);
const [editorActionsCss, setEditorActionsCss] = useState<CSSProperties>({});

const setInputEditor = useSetInputEditor();
const getCurlCallback = useCallback(async (): Promise<string> => {
const curl = await actionsProvider.current?.getCurl(esHostService.getHost());
return curl ?? '';
Expand All @@ -69,12 +65,14 @@ 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 editorDidMountCallback = useCallback(
(editor: monaco.editor.IStandaloneCodeEditor) => {
actionsProvider.current = new MonacoEditorActionsProvider(editor, setEditorActionsCss);
const provider = new MonacoEditorActionsProvider(editor, setEditorActionsCss);
setInputEditor(provider);
actionsProvider.current = provider;
setupResizeChecker(divRef.current!, editor);
registerKeyboardCommands({
editor,
Expand All @@ -86,7 +84,13 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
moveToNextRequestEdge: async () => await actionsProvider.current?.moveToNextRequestEdge(),
});
},
[getDocumenationLink, registerKeyboardCommands, sendRequestsCallback, setupResizeChecker]
[
getDocumenationLink,
registerKeyboardCommands,
sendRequestsCallback,
setupResizeChecker,
setInputEditor,
]
);

const editorWillUnmountCallback = useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ describe('Editor actions provider', () => {
getPosition: jest.fn(),
getTopForLineNumber: jest.fn(),
getScrollTop: jest.fn(),
executeEdits: jest.fn(),
setPosition: jest.fn(),
} as unknown as jest.Mocked<monaco.editor.IStandaloneCodeEditor>;

Expand Down Expand Up @@ -401,4 +402,160 @@ describe('Editor actions provider', () => {
});
});
});

describe('restoreRequestFromHistory', () => {
const testHistoryRequest = 'GET _alias';
beforeEach(() => {
/*
* The editor has the text
* "POST _search" on line 1
* { "test": "test" } on lines 2-4
* and "GET _analyze" on line 5
*/
mockGetParsedRequests.mockReturnValue([
{
startOffset: 0,
method: 'POST',
url: '_search',
endOffset: 35,
data: [
{
test: 'test',
},
],
},
{
startOffset: 36,
method: 'GET',
url: '_analyze',
endOffset: 48,
},
]);

editor.getModel.mockReturnValue({
getLineMaxColumn: (lineNumber: number) => {
// mock this function for line 4
return 2;
},
getPositionAt: (offset: number) => {
// mock this function for start offsets of the mocked requests
if (offset === 0) {
return { lineNumber: 1, column: 1 };
}
if (offset === 36) {
return { lineNumber: 5, column: 1 };
}
// mock this function for end offsets of the mocked requests
if (offset === 35) {
return { lineNumber: 4, column: 2 };
}
if (offset === 48) {
return { lineNumber: 5, column: 13 };
}
},
getLineContent: (lineNumber: number) => {
// mock this functions for line 1 and line 2
if (lineNumber === 1) {
return 'POST _search';
}
if (lineNumber === 2) {
return '{';
}
if (lineNumber === 3) {
return ' "test": "test"';
}
if (lineNumber === 4) {
return '}';
}
if (lineNumber === 5) {
return 'GET _analyze';
}
},
} as unknown as monaco.editor.ITextModel);
});

it('insert the request at the beginning of the selected request', async () => {
// the position of the cursor is in the middle of line 5
editor.getPosition.mockReturnValue({
lineNumber: 5,
column: 4,
} as monaco.Position);
editor.getSelection.mockReturnValue({
startLineNumber: 5,
endLineNumber: 5,
} as monaco.Selection);

await editorActionsProvider.restoreRequestFromHistory(testHistoryRequest);
const expectedRange = {
startLineNumber: 5,
startColumn: 1,
endLineNumber: 5,
endColumn: 1,
};
const expectedText = testHistoryRequest + '\n';
const expectedEdit = {
range: expectedRange,
text: expectedText,
forceMoveMarkers: true,
};
expect(editor.executeEdits).toHaveBeenCalledTimes(1);
expect(editor.executeEdits).toHaveBeenCalledWith('restoreFromHistory', [expectedEdit]);
});

it('insert the request at the end of the selected request', async () => {
// the position of the cursor is at the end of line 4
editor.getPosition.mockReturnValue({
lineNumber: 4,
column: 2,
} as monaco.Position);
editor.getSelection.mockReturnValue({
startLineNumber: 4,
endLineNumber: 4,
} as monaco.Selection);
await editorActionsProvider.restoreRequestFromHistory(testHistoryRequest);
const expectedRange = {
startLineNumber: 4,
startColumn: 2,
endLineNumber: 4,
endColumn: 2,
};
const expectedText = '\n' + testHistoryRequest;
const expectedEdit = {
range: expectedRange,
text: expectedText,
forceMoveMarkers: true,
};
expect(editor.executeEdits).toHaveBeenCalledTimes(1);
expect(editor.executeEdits).toHaveBeenCalledWith('restoreFromHistory', [expectedEdit]);
});

it('insert at the beginning of the line, if no selected request', async () => {
// mock no parsed requests
mockGetParsedRequests.mockReturnValue([]);
// the position of the cursor is at the end of line 4
editor.getPosition.mockReturnValue({
lineNumber: 4,
column: 2,
} as monaco.Position);
editor.getSelection.mockReturnValue({
startLineNumber: 4,
endLineNumber: 4,
} as monaco.Selection);
await editorActionsProvider.restoreRequestFromHistory(testHistoryRequest);
const expectedRange = {
startLineNumber: 4,
startColumn: 1,
endLineNumber: 4,
endColumn: 1,
};
const expectedText = testHistoryRequest + '\n';
const expectedEdit = {
range: expectedRange,
text: expectedText,
forceMoveMarkers: true,
};
expect(editor.executeEdits).toHaveBeenCalledTimes(1);
expect(editor.executeEdits).toHaveBeenCalledWith('restoreFromHistory', [expectedEdit]);
});
});
});

0 comments on commit 9194c88

Please sign in to comment.