[Uptime] [Synthetics Integration] transition to monaco code editor (#102642)

* update synthetics integration code editor

* add basic support for xml and javascript

* fix types

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Dominique Clarke 2021-06-22 10:14:56 -04:00 committed by GitHub
parent 564807c0b0
commit 34490a355e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 77 deletions

View file

@ -7,7 +7,6 @@
*/
/* eslint-disable @kbn/eslint/module_migration */
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import 'monaco-editor/esm/vs/base/common/worker/simpleWorker';
@ -23,4 +22,7 @@ import 'monaco-editor/esm/vs/editor/contrib/hover/hover.js'; // Needed for hover
import 'monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js'; // Needed for signature
import 'monaco-editor/esm/vs/editor/contrib/bracketMatching/bracketMatching.js'; // Needed for brackets matching highlight
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js'; // Needed for basic javascript support
import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution.js'; // Needed for basic xml support
export { monaco };

View file

@ -0,0 +1,45 @@
/*
* 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 React from 'react';
import styled from 'styled-components';
import { EuiPanel } from '@elastic/eui';
import { CodeEditor as MonacoCodeEditor } from '../../../../../../src/plugins/kibana_react/public';
import { MonacoEditorLangId } from './types';
const CodeEditorContainer = styled(EuiPanel)`
padding: 0;
`;
interface Props {
ariaLabel: string;
id: string;
languageId: MonacoEditorLangId;
onChange: (value: string) => void;
value: string;
}
export const CodeEditor = ({ ariaLabel, id, languageId, onChange, value }: Props) => {
return (
<CodeEditorContainer borderRadius="none" hasShadow={false} hasBorder={true}>
<div id={`${id}-editor`} aria-label={ariaLabel} data-test-subj="codeEditorContainer">
<MonacoCodeEditor
languageId={languageId}
width="100%"
height="250px"
value={value}
onChange={onChange}
options={{
renderValidationDecorations: value ? 'on' : 'off',
}}
/>
</div>
</CodeEditorContainer>
);
};

View file

@ -36,7 +36,7 @@ export const initialValues = {
[ConfigKeys.RESPONSE_STATUS_CHECK]: [],
[ConfigKeys.REQUEST_BODY_CHECK]: {
value: '',
type: Mode.TEXT,
type: Mode.PLAINTEXT,
},
[ConfigKeys.REQUEST_HEADERS_CHECK]: {},
[ConfigKeys.REQUEST_METHOD_CHECK]: HTTPMethod.GET,

View file

@ -76,14 +76,14 @@ describe('<HeaderField />', () => {
});
it('handles content mode', async () => {
const contentMode: Mode = Mode.TEXT;
const contentMode: Mode = Mode.PLAINTEXT;
render(
<HeaderField defaultValue={defaultValue} onChange={onChange} contentMode={contentMode} />
);
await waitFor(() => {
expect(onChange).toBeCalledWith({
'Content-Type': contentTypes[Mode.TEXT],
'Content-Type': contentTypes[Mode.PLAINTEXT],
});
});
});

View file

@ -61,7 +61,7 @@ export const HeaderField = ({ contentMode, defaultValue, onChange }: Props) => {
export const contentTypes: Record<Mode, ContentType> = {
[Mode.JSON]: ContentType.JSON,
[Mode.TEXT]: ContentType.TEXT,
[Mode.PLAINTEXT]: ContentType.TEXT,
[Mode.XML]: ContentType.XML,
[Mode.FORM]: ContentType.FORM,
};

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import 'jest-canvas-mock';
import React, { useState, useCallback } from 'react';
import { fireEvent, waitFor } from '@testing-library/react';
import { render } from '../../lib/helper/rtl_helpers';
@ -16,7 +18,7 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
}));
describe('<RequestBodyField />', () => {
const defaultMode = Mode.TEXT;
const defaultMode = Mode.PLAINTEXT;
const defaultValue = 'sample value';
const WrappedComponent = () => {
const [config, setConfig] = useState({

View file

@ -5,67 +5,13 @@
* 2.0.
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { stringify, parse } from 'query-string';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { stringify, parse } from 'query-string';
import styled from 'styled-components';
import { EuiCodeEditor, EuiPanel, EuiTabbedContent } from '@elastic/eui';
import { Mode } from './types';
import { EuiTabbedContent } from '@elastic/eui';
import { Mode, MonacoEditorLangId } from './types';
import { KeyValuePairsField, Pair } from './key_value_field';
import 'brace/theme/github';
import 'brace/mode/xml';
import 'brace/mode/json';
import 'brace/ext/language_tools';
const CodeEditorContainer = styled(EuiPanel)`
padding: 0;
`;
enum ResponseBodyType {
CODE = 'code',
FORM = 'form',
}
const CodeEditor = ({
ariaLabel,
id,
mode,
onChange,
value,
}: {
ariaLabel: string;
id: string;
mode: Mode;
onChange: (value: string) => void;
value: string;
}) => {
return (
<CodeEditorContainer borderRadius="none" hasShadow={false} hasBorder={true}>
<div id={`${id}-editor`}>
<EuiCodeEditor
mode={mode}
theme="github"
width="100%"
height="250px"
value={value}
onChange={onChange}
setOptions={{
fontSize: '14px',
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
}}
aria-label={ariaLabel}
/>
</div>
</CodeEditorContainer>
);
};
import { CodeEditor } from './code_editor';
interface Props {
onChange: (requestBody: { type: Mode; value: string }) => void;
@ -73,6 +19,11 @@ interface Props {
value: string;
}
enum ResponseBodyType {
CODE = 'code',
FORM = 'form',
}
// TO DO: Look into whether or not code editor reports errors, in order to prevent form submission on an error
export const RequestBodyField = ({ onChange, type, value }: Props) => {
const [values, setValues] = useState<Record<ResponseBodyType, string>>({
@ -129,9 +80,9 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
const tabs = [
{
id: Mode.TEXT,
name: modeLabels[Mode.TEXT],
'data-test-subj': `syntheticsRequestBodyTab__${Mode.TEXT}`,
id: Mode.PLAINTEXT,
name: modeLabels[Mode.PLAINTEXT],
'data-test-subj': `syntheticsRequestBodyTab__${Mode.PLAINTEXT}`,
content: (
<CodeEditor
ariaLabel={i18n.translate(
@ -140,8 +91,8 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
defaultMessage: 'Text code editor',
}
)}
id={Mode.TEXT}
mode={Mode.TEXT}
id={Mode.PLAINTEXT}
languageId={MonacoEditorLangId.PLAINTEXT}
onChange={(code) =>
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }))
}
@ -162,7 +113,7 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
}
)}
id={Mode.JSON}
mode={Mode.JSON}
languageId={MonacoEditorLangId.JSON}
onChange={(code) =>
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }))
}
@ -183,7 +134,7 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
}
)}
id={Mode.XML}
mode={Mode.XML}
languageId={MonacoEditorLangId.XML}
onChange={(code) =>
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }))
}
@ -229,7 +180,7 @@ const modeLabels = {
defaultMessage: 'Form',
}
),
[Mode.TEXT]: i18n.translate(
[Mode.PLAINTEXT]: i18n.translate(
'xpack.uptime.createPackagePolicy.stepConfigure.requestBodyType.text',
{
defaultMessage: 'Text',

View file

@ -25,10 +25,17 @@ export enum ResponseBodyIndexPolicy {
ON_ERROR = 'on_error',
}
export enum MonacoEditorLangId {
JSON = 'xjson',
PLAINTEXT = 'plaintext',
XML = 'xml',
JAVASCRIPT = 'javascript',
}
export enum Mode {
FORM = 'form',
JSON = 'json',
TEXT = 'text',
PLAINTEXT = 'text',
XML = 'xml',
}
@ -192,11 +199,11 @@ export interface PolicyConfig {
[DataStream.ICMP]: ICMPFields;
}
export type Validation = Partial<Record<ConfigKeys, (value: unknown, ...args: any[]) => void>>;
export type Validation = Partial<Record<ConfigKeys, (value: unknown, ...args: any[]) => boolean>>;
export const contentTypesToMode = {
[ContentType.FORM]: Mode.FORM,
[ContentType.JSON]: Mode.JSON,
[ContentType.TEXT]: Mode.TEXT,
[ContentType.TEXT]: Mode.PLAINTEXT,
[ContentType.XML]: Mode.XML,
};

View file

@ -277,7 +277,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
},
requestBody: {
type: 'xml',
value: '<samplexml>samplexml',
value: '<samplexml>samplexml<samplexml>',
},
indexResponseBody: false,
indexResponseHeaders: false,
@ -308,7 +308,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
},
'check.response.headers': advancedConfig.responseHeaders,
'check.response.status': [advancedConfig.responseStatusCheck],
'check.request.body': `${advancedConfig.requestBody.value}</samplexml>`, // code editor adds closing tag
'check.request.body': advancedConfig.requestBody.value,
'check.response.body.positive': [advancedConfig.responseBodyCheckPositive],
'check.response.body.negative': [advancedConfig.responseBodyCheckNegative],
'response.include_body': advancedConfig.indexResponseBody ? 'on_error' : 'never',