diff --git a/x-pack/legacy/plugins/painless_playground/index.ts b/x-pack/legacy/plugins/painless_playground/index.ts index 6ceb5e97ad6b0..caa2cff4eb826 100644 --- a/x-pack/legacy/plugins/painless_playground/index.ts +++ b/x-pack/legacy/plugins/painless_playground/index.ts @@ -23,6 +23,7 @@ export const painlessPlayground = (kibana: any) => }).default(); }, uiExports: { + styleSheetPaths: resolve(__dirname, 'public/index.scss'), devTools: [resolve(__dirname, 'public/register')], }, init: (server: Legacy.Server) => { diff --git a/x-pack/legacy/plugins/painless_playground/public/common/constants.ts b/x-pack/legacy/plugins/painless_playground/public/common/constants.ts deleted file mode 100644 index 293f26b835470..0000000000000 --- a/x-pack/legacy/plugins/painless_playground/public/common/constants.ts +++ /dev/null @@ -1,28 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { i18n } from '@kbn/i18n'; - -export const painlessContextOptions = [ - { - value: 'painless_test', - text: i18n.translate('xpack.painless_playground.selectDefaultLabel', { - defaultMessage: 'Default - Execute as it is', - }), - }, - { - value: 'filter', - text: i18n.translate('xpack.painless_playground.selectFilterLabel', { - defaultMessage: 'Filter - Execute like inside a script query of a filter', - }), - }, - { - value: 'score', - text: i18n.translate('xpack.painless_playground.selectScoreLabel', { - defaultMessage: - 'Score - Execution like inside a script_score function in function_score query', - }), - }, -]; diff --git a/x-pack/legacy/plugins/painless_playground/public/common/constants.tsx b/x-pack/legacy/plugins/painless_playground/public/common/constants.tsx new file mode 100644 index 0000000000000..ef3feb1738118 --- /dev/null +++ b/x-pack/legacy/plugins/painless_playground/public/common/constants.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +const defaultLabel = i18n.translate('xpack.painless_playground.contextDefaultLabel', { + defaultMessage: 'Basic', +}); + +const filterLabel = i18n.translate('xpack.painless_playground.contextFilterLabel', { + defaultMessage: 'Filter', +}); + +const scoreLabel = i18n.translate('xpack.painless_playground.contextScoreLabel', { + defaultMessage: 'Score', +}); + +export const painlessContextOptions = [ + { + value: 'painless_test', + inputDisplay: defaultLabel, + dropdownDisplay: ( + <> + {defaultLabel} + +

The script result will be converted to a string

+
+ + ), + }, + { + value: 'filter', + inputDisplay: filterLabel, + dropdownDisplay: ( + <> + {filterLabel} + +

Use the context of a filter’s script query

+
+ + ), + }, + { + value: 'score', + inputDisplay: scoreLabel, + dropdownDisplay: ( + <> + {scoreLabel} + +

+ Use the context of a cript_score function in function_score query +

+
+ + ), + }, +]; diff --git a/x-pack/legacy/plugins/painless_playground/public/components/editor.tsx b/x-pack/legacy/plugins/painless_playground/public/components/editor.tsx index 1a9ba18fa7dc0..f77f4ab888be6 100644 --- a/x-pack/legacy/plugins/painless_playground/public/components/editor.tsx +++ b/x-pack/legacy/plugins/painless_playground/public/components/editor.tsx @@ -10,34 +10,27 @@ import { CodeEditor } from '../../../../../../src/plugins/kibana_react/public'; interface Props { code: string; setCode: (code: string) => void; - renderMainControls: () => React.ReactElement; } -export function Editor({ code, setCode, renderMainControls }: Props) { +export function Editor({ code, setCode }: Props) { return ( - <> - - - - - - - {renderMainControls()} - - + ); } diff --git a/x-pack/legacy/plugins/painless_playground/public/components/main_controls.tsx b/x-pack/legacy/plugins/painless_playground/public/components/main_controls.tsx index d087663c9b58e..98e8f25f171b1 100644 --- a/x-pack/legacy/plugins/painless_playground/public/components/main_controls.tsx +++ b/x-pack/legacy/plugins/painless_playground/public/components/main_controls.tsx @@ -3,40 +3,139 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useState } from 'react'; +import { + EuiPopover, + EuiBottomBar, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; interface Props { - submit: () => void; - disabled: boolean; - toggleFlyout: () => void; + toggleRequestFlyout: () => void; + isRequestFlyoutOpen: boolean; + isLoading: boolean; + reset: () => void; } -export function MainControls({ submit, disabled, toggleFlyout }: Props) { +export function MainControls({ + toggleRequestFlyout, + isRequestFlyoutOpen, + isLoading, + reset, +}: Props) { + const [isHelpOpen, setIsHelpOpen] = useState(false); + + const items = [ + setIsHelpOpen(false)} + > + {i18n.translate('xpack.painless_playground.walkthroughButtonLabel', { + defaultMessage: 'Walkthrough', + })} + , + + setIsHelpOpen(false)} + > + {i18n.translate('xpack.painless_playground.apiReferenceButtonLabel', { + defaultMessage: 'API reference', + })} + , + + setIsHelpOpen(false)} + > + {i18n.translate('xpack.painless_playground.languageSpecButtonLabel', { + defaultMessage: 'Language spec', + })} + , + + { + reset(); + setIsHelpOpen(false); + }} + > + {i18n.translate('xpack.painless_playground.resetButtonLabel', { + defaultMessage: 'Reset script', + })} + , + ]; + return ( - - - - - - - - - {i18n.translate('xpack.painless_playground.previewRequestButtonLabel', { - defaultMessage: 'Preview Request', - })} - - - + <> +
+ + + + + + + setIsHelpOpen(!isHelpOpen)} + > + {i18n.translate('xpack.painless_playground.helpButtonLabel', { + defaultMessage: 'Help', + })} + + } + isOpen={isHelpOpen} + closePopover={() => setIsHelpOpen(false)} + panelPaddingSize="none" + withTitle + anchorPosition="upRight" + > + + + + + + + + {isRequestFlyoutOpen + ? i18n.translate('xpack.painless_playground.hideRequestButtonLabel', { + defaultMessage: 'Hide API request', + }) + : i18n.translate('xpack.painless_playground.showRequestButtonLabel', { + defaultMessage: 'Show API request', + })} + + + + + ); } diff --git a/x-pack/legacy/plugins/painless_playground/public/components/output.tsx b/x-pack/legacy/plugins/painless_playground/public/components/output.tsx deleted file mode 100644 index ed693cd33c82c..0000000000000 --- a/x-pack/legacy/plugins/painless_playground/public/components/output.tsx +++ /dev/null @@ -1,48 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { EuiCodeBlock, EuiPanel, EuiTabbedContent, EuiSpacer } from '@elastic/eui'; - -import { formatJson, formatResponse } from '../lib/helpers'; -import { Response } from '../common/types'; - -export function Output({ response }: { response: Response }) { - return ( - - - - - {formatResponse(response)} - - - - ), - }, - { - id: 'request', - name: 'Response', - content: ( - <> - - - - {formatJson(response)} - - - - ), - }, - ]} - /> - ); -} diff --git a/x-pack/legacy/plugins/painless_playground/public/components/output_pane/context_tab.tsx b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/context_tab.tsx new file mode 100644 index 0000000000000..ec0f27c005b96 --- /dev/null +++ b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/context_tab.tsx @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + EuiFieldText, + EuiFormRow, + EuiPanel, + EuiSpacer, + EuiIcon, + EuiToolTip, + EuiLink, + EuiText, + EuiSuperSelect, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { CodeEditor } from '../../../../../../../src/plugins/kibana_react/public'; +import { painlessContextOptions } from '../../common/constants'; + +interface Props { + context: string; + contextSetup: Record; + setContext: (context: string) => void; + setContextSetup: (contextSetup: Record) => void; + renderMainControls: () => React.ReactElement; +} + +export function ContextTab({ context, contextSetup, setContext, setContextSetup }: Props) { + return ( + <> + + + + {' '} + + + + } + labelAppend={ + + + {i18n.translate('xpack.painless_playground.contextFieldDocLinkText', { + defaultMessage: 'Context docs', + })} + + + } + fullWidth + > + setContext(value)} + itemLayoutAlign="top" + hasDividers + fullWidth + /> + + + {['filter', 'score'].indexOf(context) !== -1 && ( + + + {' '} + + + + } + fullWidth + > + + setContextSetup(Object.assign({}, contextSetup, { index: e.target.value })) + } + /> + + )} + {['filter', 'score'].indexOf(context) !== -1 && ( + + + {' '} + + + + } + fullWidth + > + + + setContextSetup(Object.assign({}, contextSetup, { document: value })) + } + options={{ + fontSize: 12, + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + automaticLayout: true, + }} + /> + + + )} + + ); +} diff --git a/x-pack/legacy/plugins/painless_playground/public/components/output_pane/index.ts b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/index.ts new file mode 100644 index 0000000000000..85b7a7816b5aa --- /dev/null +++ b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { OutputPane } from './output_pane'; diff --git a/x-pack/legacy/plugins/painless_playground/public/components/output_pane/output_pane.tsx b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/output_pane.tsx new file mode 100644 index 0000000000000..69393ca47a66c --- /dev/null +++ b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/output_pane.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPanel, + EuiTabbedContent, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { formatJson, formatResponse } from '../../lib/helpers'; +import { Response } from '../../common/types'; +import { OutputTab } from './output_tab'; +import { ParametersTab } from './parameters_tab'; +import { ContextTab } from './context_tab'; + +export function OutputPane({ + response, + context, + contextSetup, + setContext, + setContextSetup, + isLoading, +}: { + response?: Response; +}) { + const outputTabLabel = ( + + + {isLoading ? ( + + ) : response.error ? ( + + ) : ( + + )} + + + + {i18n.translate('xpack.painless_playground.outputTabLabel', { + defaultMessage: 'Output', + })} + + + ); + + return ( + + , + }, + { + id: 'parameters', + name: i18n.translate('xpack.painless_playground.parametersTabLabel', { + defaultMessage: 'Parameters', + }), + content: ( + + ), + }, + { + id: 'context', + name: i18n.translate('xpack.painless_playground.contextTabLabel', { + defaultMessage: 'Context', + }), + content: ( + + ), + }, + ]} + /> + + ); +} diff --git a/x-pack/legacy/plugins/painless_playground/public/components/output_pane/output_tab.tsx b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/output_tab.tsx new file mode 100644 index 0000000000000..4b69e7969c924 --- /dev/null +++ b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/output_tab.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiCodeBlock, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { formatResponse } from '../../lib/helpers'; +import { Response } from '../../common/types'; + +interface Props { + response?: Response; +} + +export function OutputTab({ response = {} }: Props) { + return ( + <> + + + {formatResponse(response.success || response.error)} + + + ); +} diff --git a/x-pack/legacy/plugins/painless_playground/public/components/output_pane/parameters_tab.tsx b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/parameters_tab.tsx new file mode 100644 index 0000000000000..02dd5b0971e2c --- /dev/null +++ b/x-pack/legacy/plugins/painless_playground/public/components/output_pane/parameters_tab.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { + EuiFormRow, + EuiPanel, + EuiSpacer, + EuiIcon, + EuiToolTip, + EuiLink, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { CodeEditor } from '../../../../../../../src/plugins/kibana_react/public'; +import { painlessContextOptions } from '../../common/constants'; + +interface Props { + context: string; + contextSetup: Record; + setContext: (context: string) => void; + setContextSetup: (contextSetup: Record) => void; + renderMainControls: () => React.ReactElement; +} + +export function ParametersTab({ context, contextSetup, setContext, setContextSetup }: Props) { + return ( + <> + + + + {' '} + + + + } + fullWidth + labelAppend={ + + + {i18n.translate('xpack.painless_playground.parametersFieldDocLinkText', { + defaultMessage: 'Parameters docs', + })} + + + } + helpText={i18n.translate('xpack.painless_playground.helpIconAriaLabel', { + defaultMessage: 'Use JSON format', + })} + > + + setContextSetup({ params: value })} + options={{ + fontSize: 12, + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + automaticLayout: true, + }} + /> + + + + ); +} diff --git a/x-pack/legacy/plugins/painless_playground/public/components/painless_playground.tsx b/x-pack/legacy/plugins/painless_playground/public/components/painless_playground.tsx index 292aedc54bfa1..1a94d42606120 100644 --- a/x-pack/legacy/plugins/painless_playground/public/components/painless_playground.tsx +++ b/x-pack/legacy/plugins/painless_playground/public/components/painless_playground.tsx @@ -3,7 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; + +import React, { useState, useEffect } from 'react'; +import { debounce } from 'lodash'; import { EuiCodeBlock, EuiFlexGroup, @@ -17,68 +19,120 @@ import { import { i18n } from '@kbn/i18n'; import { buildRequestPayload, formatJson, getFromLocalStorage } from '../lib/helpers'; import { Request, Response } from '../common/types'; -import { Output } from './output'; +import { OutputPane } from './output_pane'; import { MainControls } from './main_controls'; -import { Settings } from './settings'; import { Editor } from './editor'; +import { RequestFlyout } from './request_flyout'; + +let _mostRecentRequestId = 0; + +const submit = async (code, context, contextSetup, executeCode, setResponse, setIsLoading) => { + // Prevent an older request that resolves after a more recent request from clobbering it. + // We store the resulting ID in this closure for comparison when the request resolves. + const requestId = ++_mostRecentRequestId; + setIsLoading(true); + + try { + localStorage.setItem('painlessPlaygroundCode', code); + localStorage.setItem('painlessPlaygroundContext', context); + localStorage.setItem('painlessPlaygroundContextSetup', JSON.stringify(contextSetup)); + const response = await executeCode(buildRequestPayload(code, context, contextSetup)); + + if (_mostRecentRequestId === requestId) { + if (response.error) { + setResponse({ + success: undefined, + error: response.error, + }); + } else { + setResponse({ + success: response, + error: undefined, + }); + } + setIsLoading(false); + } + } catch (error) { + if (_mostRecentRequestId === requestId) { + setResponse({ + success: undefined, + error: { error }, + }); + setIsLoading(false); + } + } +}; + +const debouncedSubmit = debounce(submit, 800); + +// Render a heart as an example. +const exampleScript = ` +def result = ''; +int charCount = 0; + +int n = 10; +int threshold = n*n/2; +int dimension = 3*n/2; + +for (int i = -dimension; i <= n; i++) { + int a = -n/2-i; + + for (int j = -dimension; j <= dimension; j++) { + int b = n/2-j; + int c = -n/2-j; + + def isHeartVentricles = (Math.abs(i) + Math.abs(j) < n); + def isRightAtrium = ((a * a) + (b * b) <= threshold); + def isLeftAtrium = ((a * a) + (c * c) <= threshold); + + if (isHeartVentricles || isRightAtrium || isLeftAtrium) { + result += "* "; + } else { + result += ". "; + } + + // Make sure the heart doesn't deform as the container changes width. + charCount++; + if (charCount % 31 === 0) { + result += "\\\\n"; + } + } +} + +return result; +`; export function PainlessPlayground({ executeCode, }: { executeCode: (payload: Request) => Promise; }) { - const [code, setCode] = useState( - getFromLocalStorage('painlessPlaygroundCode', 'return "Hello painless world!"') - ); - const [response, setResponse] = useState({}); + const [code, setCode] = useState(getFromLocalStorage('painlessPlaygroundCode', exampleScript)); + const [response, setResponse] = useState({ error: undefined, success: undefined }); + const [isRequestFlyoutOpen, setRequestFlyoutOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [context, setContext] = useState( getFromLocalStorage('painlessPlaygroundContext', 'painless_test_without_params') ); + const [contextSetup, setContextSetup] = useState( getFromLocalStorage('painlessPlaygroundContextSetup', {}, true) ); - const [showRequestFlyout, setShowRequestFlyout] = useState(false); - - const buildRequestPayloadPreview = () => buildRequestPayload(code, context, contextSetup); - - const submit = async () => { - try { - localStorage.setItem('painlessPlaygroundCode', code); - localStorage.setItem('painlessPlaygroundContext', context); - localStorage.setItem('painlessPlaygroundContextSetup', JSON.stringify(contextSetup)); - const res = await executeCode(buildRequestPayloadPreview()); - setResponse(res); - } catch (e) { - setResponse({ - error: { - message: e.message, - }, - }); - } - }; + // Live-update the output as the user changes the input code. + useEffect(() => { + debouncedSubmit(code, context, contextSetup, executeCode, setResponse, setIsLoading); + }, [code, context, contextSetup, executeCode]); - const toggleViewRequestFlyout = () => { - setShowRequestFlyout(!showRequestFlyout); + const toggleRequestFlyout = () => { + setRequestFlyoutOpen(!isRequestFlyoutOpen); }; - const renderMainControls = () => ( - - ); - return ( <> - - + +

{i18n.translate('xpack.painless_playground.title', { @@ -87,55 +141,35 @@ export function PainlessPlayground({

- - ), - }, - { - id: 'settings', - name: 'Settings', - content: ( - - ), - }, - ]} + +
+ + + - {response.error || typeof response.result !== 'undefined' ? ( - - - - ) : null}
- {showRequestFlyout && ( - setShowRequestFlyout(false)}> - - -

- {i18n.translate('xpack.painless_playground.flyoutTitle', { - defaultMessage: 'Console Request', - })} -

-
- - - {'POST _scripts/painless/_execute\n'} - {formatJson(buildRequestPayloadPreview())} - -
-
+ + submit(code, context, contextSetup, executeCode, setResponse)} + isLoading={isLoading} + toggleRequestFlyout={toggleRequestFlyout} + isRequestFlyoutOpen={isRequestFlyoutOpen} + reset={() => setCode(exampleScript)} + /> + + {isRequestFlyoutOpen && ( + setRequestFlyoutOpen(false)} + requestBody={formatJson(buildRequestPayload(code, context, contextSetup))} + response={formatJson(response.success || response.error)} + /> )} ); diff --git a/x-pack/legacy/plugins/painless_playground/public/components/request_flyout.tsx b/x-pack/legacy/plugins/painless_playground/public/components/request_flyout.tsx new file mode 100644 index 0000000000000..60ecdd5d4799b --- /dev/null +++ b/x-pack/legacy/plugins/painless_playground/public/components/request_flyout.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useEffect } from 'react'; +import { + EuiCodeBlock, + EuiTabbedContent, + EuiTitle, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export function RequestFlyout({ + onClose, + requestBody, + response, +}: { + onClose: any; + requestBody: string; + response?: string; +}) { + return ( + + + + + {/* We need an extra div to get out of flex grow */} +
+ +

+ {i18n.translate('xpack.painless_playground.flyoutTitle', { + defaultMessage: 'API request', + })} +

+
+
+
+ + + + {i18n.translate('xpack.painless_playground.flyoutDocLink', { + defaultMessage: 'API documentation', + })} + + +
+
+ + + + {'POST _scripts/painless/_execute\n'} + {requestBody} + + ), + }, + { + id: 'response', + name: 'Response', + content: ( + + {response} + + ), + }, + ]} + /> + +
+ + + ); +} diff --git a/x-pack/legacy/plugins/painless_playground/public/components/settings.tsx b/x-pack/legacy/plugins/painless_playground/public/components/settings.tsx deleted file mode 100644 index 8ea2c9929da29..0000000000000 --- a/x-pack/legacy/plugins/painless_playground/public/components/settings.tsx +++ /dev/null @@ -1,180 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { - EuiFieldText, - EuiForm, - EuiFormRow, - EuiPanel, - EuiSelect, - EuiIconTip, - EuiSpacer, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { CodeEditor } from '../../../../../../src/plugins/kibana_react/public'; -import { painlessContextOptions } from '../common/constants'; - -interface Props { - context: string; - contextSetup: Record; - setContext: (context: string) => void; - setContextSetup: (contextSetup: Record) => void; - renderMainControls: () => React.ReactElement; -} - -export function Settings({ - context, - contextSetup, - setContext, - setContextSetup, - renderMainControls, -}: Props) { - return ( - <> - - - - - } - fullWidth - > - ) => setContext(e.target.value)} - /> - - - - } - fullWidth - labelAppend={ - - } - /> - } - > -
- setContextSetup({ params: value })} - options={{ - fontSize: 12, - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'on', - wrappingIndent: 'indent', - }} - /> -
-
- - {['filter', 'score'].indexOf(context) !== -1 && ( - - } - fullWidth - labelAppend={ - - } - /> - } - > - - setContextSetup(Object.assign({}, contextSetup, { index: e.target.value })) - } - /> - - )} - {['filter', 'score'].indexOf(context) !== -1 && ( - - } - fullWidth - labelAppend={ - - } - /> - } - > -
- - setContextSetup(Object.assign({}, contextSetup, { document: value })) - } - options={{ - fontSize: 12, - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - wordWrap: 'on', - wrappingIndent: 'indent', - }} - /> -
-
- )} - - {renderMainControls()} -
-
- - ); -} diff --git a/x-pack/legacy/plugins/painless_playground/public/index.scss b/x-pack/legacy/plugins/painless_playground/public/index.scss new file mode 100644 index 0000000000000..46b834c83786c --- /dev/null +++ b/x-pack/legacy/plugins/painless_playground/public/index.scss @@ -0,0 +1,33 @@ +// Import the EUI global scope so we can use EUI constants +@import 'src/legacy/ui/public/styles/_styling_constants'; + +/** + * 1. This is a very brittle way of preventing the editor and other content from disappearing + * behind the bottom bar. + */ +.painlessPlaygroundBottomBarPlaceholder { + height: $euiSize * 2; /* [1] */ +} + +.painlessPlaygroundRightPane { + border-right: none; + border-top: none; + border-bottom: none; + border-radius: 0; + padding-top: 0; + height: 100%; +} + +.painlessPlaygroundRightPane__tabs { + display: flex; + flex-direction: column; + height: 100%; + + [role="tabpanel"] { + height: 100%; + } +} + +.painlessPlayground__betaLabelContainer { + line-height: 0; +} diff --git a/x-pack/legacy/plugins/painless_playground/public/lib/helpers.ts b/x-pack/legacy/plugins/painless_playground/public/lib/helpers.ts index 43edd08fd043e..ddc633b2e99d7 100644 --- a/x-pack/legacy/plugins/painless_playground/public/lib/helpers.ts +++ b/x-pack/legacy/plugins/painless_playground/public/lib/helpers.ts @@ -71,7 +71,11 @@ export function formatJson(json: unknown): string { } } -export function formatResponse(response: Response): string { +export function formatResponse(response?: Response): string { + if (!response) { + return ''; + } + if (typeof response.result === 'string') { return response.result.replace(/\\n/g, '\n'); } else if (response.error) { diff --git a/x-pack/legacy/plugins/painless_playground/public/register.ts b/x-pack/legacy/plugins/painless_playground/public/register.tsx similarity index 68% rename from x-pack/legacy/plugins/painless_playground/public/register.ts rename to x-pack/legacy/plugins/painless_playground/public/register.tsx index 74d59ab2af228..4908eeb758114 100644 --- a/x-pack/legacy/plugins/painless_playground/public/register.ts +++ b/x-pack/legacy/plugins/painless_playground/public/register.tsx @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import { i18n } from '@kbn/i18n'; +import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; // @ts-ignore import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; import { npSetup, npStart } from 'ui/new_platform'; @@ -15,7 +17,7 @@ import { ADVANCED_SETTINGS_FLAG_NAME } from '../common/constants'; npSetup.plugins.home.featureCatalogue.register({ id: 'painless_playground', title: i18n.translate('xpack.painless_playground.registryProviderTitle', { - defaultMessage: 'Painless Playground', + defaultMessage: 'Painless Playground (beta)', }), description: i18n.translate('xpack.painless_playground.registryProviderDescription', { defaultMessage: 'Simulate and debug painless code', @@ -33,10 +35,26 @@ npSetup.core.uiSettings.get$(ADVANCED_SETTINGS_FLAG_NAME, false).subscribe(value npSetup.plugins.devTools.register({ order: 7, - title: i18n.translate('xpack.painless_playground.displayName', { - defaultMessage: 'Painless Playground', - }), - id: 'painless_playground', + title: ( + + + {i18n.translate('xpack.painless_playground.displayName', { + defaultMessage: 'Painless Playground', + })} + + + + + + + ), enableRouting: false, disabled: false, tooltipContent: xpackInfo.get('features.painlessPlayground.message'),