diff --git a/x-pack/plugins/searchprofiler/public/application/components/_index.scss b/x-pack/plugins/searchprofiler/public/application/components/_index.scss index 9d6688a2d4d98..ee36c5e8e6567 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/_index.scss +++ b/x-pack/plugins/searchprofiler/public/application/components/_index.scss @@ -3,5 +3,4 @@ $badgeSize: $euiSize * 5.5; @import 'highlight_details_flyout/highlight_details_flyout'; @import 'license_warning_notice/license_warning_notice'; @import 'percentage_badge/percentage_badge'; -@import 'profile_query_editor/profile_query_editor'; @import 'profile_tree/index'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/_profile_query_editor.scss b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/_profile_query_editor.scss deleted file mode 100644 index 035ff16c990bb..0000000000000 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/_profile_query_editor.scss +++ /dev/null @@ -1,25 +0,0 @@ - -.prfDevTool__sense { - order: 1; - // To anchor ace editor - position: relative; - - // Ace Editor overrides - .ace_editor { - min-height: $euiSize * 10; - flex-grow: 1; - margin-bottom: $euiSize; - margin-top: $euiSize; - outline: solid 1px $euiColorLightShade; - } - - .errorMarker { - position: absolute; - background: rgba($euiColorDanger, .5); - z-index: 20; - } -} - -.prfDevTool__profileButtonContainer { - flex-shrink: 1; -} diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx index 34e0867df8ec6..483f0ef7f6106 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx @@ -5,20 +5,14 @@ * 2.0. */ -import 'brace'; -import 'brace/mode/json'; - -import { coreMock } from '@kbn/core/public/mocks'; import { registerTestBed } from '@kbn/test-jest-helpers'; import { Editor, Props } from './editor'; -const coreStart = coreMock.createStart(); - describe('Editor Component', () => { it('renders', async () => { const props: Props = { - ...coreStart, - initialValue: '', + editorValue: '', + setEditorValue: () => {}, licenseEnabled: true, onEditorReady: (e: any) => {}, }; diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.tsx index 068673d4ce4c1..3701323d414c2 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.tsx @@ -5,67 +5,37 @@ * 2.0. */ -import React, { memo, useRef, useEffect, useState } from 'react'; +import React, { memo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiScreenReaderOnly } from '@elastic/eui'; -import { Editor as AceEditor } from 'brace'; +import { EuiScreenReaderOnly, EuiSpacer } from '@elastic/eui'; +import { CodeEditor } from '@kbn/code-editor'; +import { monaco, XJsonLang } from '@kbn/monaco'; -import { SearchProfilerStartServices } from '../../../../types'; -import { ace } from '../../../../shared_imports'; -import { initializeEditor } from './init_editor'; - -const { useUIAceKeyboardMode } = ace; - -type EditorShim = ReturnType; - -export type EditorInstance = EditorShim; - -export interface Props extends SearchProfilerStartServices { +export interface Props { licenseEnabled: boolean; - initialValue: string; - onEditorReady: (editor: EditorShim) => void; + editorValue: string; + setEditorValue: (value: string) => void; + onEditorReady: (props: EditorProps) => void; } -const createEditorShim = (aceEditor: AceEditor) => { - return { - getValue() { - return aceEditor.getValue(); - }, - focus() { - aceEditor.focus(); - }, - }; -}; - const EDITOR_INPUT_ID = 'SearchProfilerTextArea'; -export const Editor = memo( - ({ licenseEnabled, initialValue, onEditorReady, ...startServices }: Props) => { - const containerRef = useRef(null as any); - const editorInstanceRef = useRef(null as any); - - const [textArea, setTextArea] = useState(null); - - useUIAceKeyboardMode(textArea, startServices); - - useEffect(() => { - const divEl = containerRef.current; - editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled }); - editorInstanceRef.current.setValue(initialValue, 1); - const textarea = divEl.querySelector('textarea'); - if (textarea) { - textarea.setAttribute('id', EDITOR_INPUT_ID); - } - setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null); - - onEditorReady(createEditorShim(editorInstanceRef.current)); +export interface EditorProps { + focus: () => void; +} - return () => { - if (editorInstanceRef.current) { - editorInstanceRef.current.destroy(); - } - }; - }, [initialValue, onEditorReady, licenseEnabled]); +export const Editor = memo( + ({ licenseEnabled, editorValue, setEditorValue, onEditorReady }: Props) => { + const editorDidMountCallback = useCallback( + (editor: monaco.editor.IStandaloneCodeEditor) => { + onEditorReady({ + focus: () => { + editor.focus(); + }, + } as EditorProps); + }, + [onEditorReady] + ); return ( <> @@ -76,7 +46,26 @@ export const Editor = memo( })} -
+ + + + ); } diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/index.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/index.ts index 5d8be48041176..1ac3ec704bc5d 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/index.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export type { EditorInstance } from './editor'; -export { Editor } from './editor'; +export { Editor, type EditorProps } from './editor'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/init_editor.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/init_editor.ts deleted file mode 100644 index 24d119254db78..0000000000000 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/init_editor.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import ace from 'brace'; -import { installXJsonMode } from '@kbn/ace'; - -export function initializeEditor({ - el, - licenseEnabled, -}: { - el: HTMLDivElement; - licenseEnabled: boolean; -}) { - const editor: ace.Editor = ace.acequire('ace/ace').edit(el); - - installXJsonMode(editor); - editor.$blockScrolling = Infinity; - - if (!licenseEnabled) { - editor.setReadOnly(true); - editor.container.style.pointerEvents = 'none'; - editor.container.style.opacity = '0.5'; - const textArea = editor.container.querySelector('textarea'); - if (textArea) { - textArea.setAttribute('tabindex', '-1'); - } - editor.renderer.setStyle('disabled'); - editor.blur(); - } - - return editor; -} diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx index 577c3e530e8cc..a88f1040caa3a 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useRef, memo, useCallback } from 'react'; +import React, { useRef, memo, useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiForm, @@ -23,7 +23,7 @@ import { decompressFromEncodedURIComponent } from 'lz-string'; import { useRequestProfile } from '../../hooks'; import { useAppContext } from '../../contexts/app_context'; import { useProfilerActionContext } from '../../contexts/profiler_context'; -import { Editor, EditorInstance } from './editor'; +import { Editor, type EditorProps } from './editor'; const DEFAULT_INDEX_VALUE = '_all'; @@ -39,33 +39,36 @@ const INITIAL_EDITOR_VALUE = `{ * Drives state changes for mine via profiler action context. */ export const ProfileQueryEditor = memo(() => { - const editorRef = useRef(null as any); + const editorPropsRef = useRef(null as any); const indexInputRef = useRef(null as any); const dispatch = useProfilerActionContext(); - const { getLicenseStatus, notifications, location, ...startServices } = useAppContext(); + const { getLicenseStatus, notifications, location } = useAppContext(); const queryParams = new URLSearchParams(location.search); const indexName = queryParams.get('index'); const searchProfilerQueryURI = queryParams.get('load_from'); + const searchProfilerQuery = searchProfilerQueryURI && decompressFromEncodedURIComponent(searchProfilerQueryURI.replace(/^data:text\/plain,/, '')); + const [editorValue, setEditorValue] = useState( + searchProfilerQuery ? searchProfilerQuery : INITIAL_EDITOR_VALUE + ); const requestProfile = useRequestProfile(); const handleProfileClick = async () => { dispatch({ type: 'setProfiling', value: true }); try { - const { current: editor } = editorRef; const { data: result, error } = await requestProfile({ - query: editorRef.current.getValue(), + query: editorValue, index: indexInputRef.current.value, }); if (error) { notifications.addDanger(error); - editor.focus(); + editorPropsRef.current.focus(); return; } if (result === null) { @@ -78,18 +81,13 @@ export const ProfileQueryEditor = memo(() => { }; const onEditorReady = useCallback( - (editorInstance: any) => (editorRef.current = editorInstance), + (editorPropsInstance: EditorProps) => (editorPropsRef.current = editorPropsInstance), [] ); const licenseEnabled = getLicenseStatus().valid; return ( - + {/* Form */} @@ -120,9 +118,9 @@ export const ProfileQueryEditor = memo(() => { diff --git a/x-pack/plugins/searchprofiler/public/shared_imports.ts b/x-pack/plugins/searchprofiler/public/shared_imports.ts index b1af4bab9e62d..3daab65e28db8 100644 --- a/x-pack/plugins/searchprofiler/public/shared_imports.ts +++ b/x-pack/plugins/searchprofiler/public/shared_imports.ts @@ -5,6 +5,4 @@ * 2.0. */ -export { ace } from '@kbn/es-ui-shared-plugin/public'; - export { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; diff --git a/x-pack/plugins/searchprofiler/tsconfig.json b/x-pack/plugins/searchprofiler/tsconfig.json index b99b0962e39fc..063b7dfa63ce6 100644 --- a/x-pack/plugins/searchprofiler/tsconfig.json +++ b/x-pack/plugins/searchprofiler/tsconfig.json @@ -20,9 +20,10 @@ "@kbn/expect", "@kbn/test-jest-helpers", "@kbn/i18n-react", - "@kbn/ace", "@kbn/config-schema", "@kbn/react-kibana-context-render", + "@kbn/code-editor", + "@kbn/monaco", ], "exclude": [ "target/**/*", diff --git a/x-pack/test/accessibility/apps/group1/search_profiler.ts b/x-pack/test/accessibility/apps/group1/search_profiler.ts index 522c5e4cf730e..fbd3649120ea1 100644 --- a/x-pack/test/accessibility/apps/group1/search_profiler.ts +++ b/x-pack/test/accessibility/apps/group1/search_profiler.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'security']); const testSubjects = getService('testSubjects'); - const aceEditor = getService('aceEditor'); + const monacoEditor = getService('monacoEditor'); const a11y = getService('a11y'); const esArchiver = getService('esArchiver'); @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); }); - it('input the JSON in the aceeditor', async () => { + it('input the JSON in the editor', async () => { const input = { query: { bool: { @@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, }; - await aceEditor.setValue('searchProfilerEditor', JSON.stringify(input)); + await monacoEditor.setCodeEditorValue(JSON.stringify(input), 0); await a11y.testAppSnapshot(); }); diff --git a/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts b/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts index 174f3d4527178..87c36de62bba6 100644 --- a/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts +++ b/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts @@ -67,7 +67,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { `parser errors to match expectation: HAS ${expectation ? 'ERRORS' : 'NO ERRORS'}`, async () => { const actual = await PageObjects.searchProfiler.editorHasParseErrors(); - return expectation === actual; + return expectation === actual?.length > 0; } ); } diff --git a/x-pack/test/functional/page_objects/search_profiler_page.ts b/x-pack/test/functional/page_objects/search_profiler_page.ts index a110bd16eeafe..151b9a613c356 100644 --- a/x-pack/test/functional/page_objects/search_profiler_page.ts +++ b/x-pack/test/functional/page_objects/search_profiler_page.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function SearchProfilerPageProvider({ getService }: FtrProviderContext) { const find = getService('find'); const testSubjects = getService('testSubjects'); - const aceEditor = getService('aceEditor'); + const monacoEditor = getService('monacoEditor'); const editorTestSubjectSelector = 'searchProfilerEditor'; return { @@ -19,10 +19,10 @@ export function SearchProfilerPageProvider({ getService }: FtrProviderContext) { return await testSubjects.exists(editorTestSubjectSelector); }, async setQuery(query: any) { - await aceEditor.setValue(editorTestSubjectSelector, JSON.stringify(query)); + await monacoEditor.setCodeEditorValue(JSON.stringify(query), 0); }, async getQuery() { - return JSON.parse(await aceEditor.getValue(editorTestSubjectSelector)); + return JSON.parse(await monacoEditor.getCodeEditorValue(0)); }, async setIndexName(indexName: string) { await testSubjects.setValue('indexName', indexName); @@ -36,6 +36,7 @@ export function SearchProfilerPageProvider({ getService }: FtrProviderContext) { }, async getProfileContent() { const profileTree = await find.byClassName('prfDevTool__main__profiletree'); + // const profileTree = await find.byClassName('prfDevTool__page'); return profileTree.getVisibleText(); }, getUrlWithIndexAndQuery({ indexName, query }: { indexName: string; query: any }) { @@ -43,7 +44,7 @@ export function SearchProfilerPageProvider({ getService }: FtrProviderContext) { return `/searchprofiler?index=${indexName}&load_from=${searchQueryURI}`; }, async editorHasParseErrors() { - return await aceEditor.hasParseErrors(editorTestSubjectSelector); + return await monacoEditor.getCurrentMarkers(editorTestSubjectSelector); }, async editorHasErrorNotification() { const notification = await testSubjects.find('noShardsNotification'); diff --git a/x-pack/test_serverless/functional/test_suites/common/dev_tools/search_profiler.ts b/x-pack/test_serverless/functional/test_suites/common/dev_tools/search_profiler.ts index 6a908ce4e0fe8..979943ffa602c 100644 --- a/x-pack/test_serverless/functional/test_suites/common/dev_tools/search_profiler.ts +++ b/x-pack/test_serverless/functional/test_suites/common/dev_tools/search_profiler.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -const testIndex = 'test-index'; +const indexName = 'my_index'; const testQuery = { query: { match_all: {}, @@ -53,10 +53,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, }; - // Since we're not actually running the query in the test, - // this index name is just an input placeholder and does not exist - const indexName = 'my_index'; - await PageObjects.common.navigateToUrl( 'searchProfiler', PageObjects.searchProfiler.getUrlWithIndexAndQuery({ indexName, query }), @@ -77,21 +73,21 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('With a test index', () => { before(async () => { - await es.indices.create({ index: testIndex }); + await es.indices.create({ index: indexName }); }); after(async () => { - await es.indices.delete({ index: testIndex }); + await es.indices.delete({ index: indexName }); }); it('profiles a simple query', async () => { - await PageObjects.searchProfiler.setIndexName(testIndex); + await PageObjects.searchProfiler.setIndexName(indexName); await PageObjects.searchProfiler.setQuery(testQuery); await PageObjects.searchProfiler.clickProfileButton(); const content = await PageObjects.searchProfiler.getProfileContent(); - expect(content).to.contain(testIndex); + expect(content).to.contain(indexName); }); }); });