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',
- })}
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {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'),