Move new Code Editor component to kibana_react so it can be used through Kibana (#45914) (#52225)

This commit is contained in:
Poff Poffenberger 2019-12-04 15:59:01 -06:00 committed by GitHub
parent 31c8f0c821
commit 860f7e4b30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 222 additions and 132 deletions

View file

@ -202,6 +202,7 @@
"minimatch": "^3.0.4",
"moment": "^2.24.0",
"moment-timezone": "^0.5.27",
"monaco-editor": "~0.17.0",
"mustache": "2.3.2",
"ngreact": "0.5.1",
"node-fetch": "1.7.3",
@ -220,7 +221,9 @@
"react-grid-layout": "^0.16.2",
"react-input-range": "^1.3.0",
"react-markdown": "^3.4.1",
"react-monaco-editor": "~0.27.0",
"react-redux": "^5.1.2",
"react-resize-detector": "^4.2.0",
"react-router-dom": "^4.3.1",
"react-sizeme": "^2.3.6",
"reactcss": "1.2.3",
@ -331,6 +334,7 @@
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.4",
"@types/react-redux": "^6.0.6",
"@types/react-resize-detector": "^4.0.1",
"@types/react-router-dom": "^4.3.1",
"@types/react-virtualized": "^9.18.7",
"@types/redux": "^3.6.31",

View file

@ -98,8 +98,9 @@ export default {
'^.+\\.html?$': 'jest-raw-loader',
},
transformIgnorePatterns: [
// ignore all node_modules except @elastic/eui which requires babel transforms to handle dynamic import()
'[/\\\\]node_modules(?![\\/\\\\]@elastic[\\/\\\\]eui)[/\\\\].+\\.js$',
// ignore all node_modules except @elastic/eui and monaco-editor which both require babel transforms to handle dynamic import()
// since ESM modules are not natively supported in Jest yet (https://github.com/facebook/jest/issues/4842)
'[/\\\\]node_modules(?![\\/\\\\]@elastic[\\/\\\\]eui)(?![\\/\\\\]monaco-editor)[/\\\\].+\\.js$',
'packages/kbn-pm/dist/index.js'
],
snapshotSerializers: [

View file

@ -1,4 +1,4 @@
# Editor Component
# Code Editor Component
This re-usable code editor component was built as a layer of abstraction on top of the [Monaco Code Editor](https://microsoft.github.io/monaco-editor/) (and the [React Monaco Editor component](https://github.com/react-monaco-editor/react-monaco-editor)). The goal of this component is to expose a set of the most-used, most-helpful features from Monaco in a way that's easy to use out of the box. If a use case requires additional features, this component still allows access to all other Monaco features.

View file

@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`is rendered 1`] = `
<Fragment>
<MonacoEditor
defaultValue=""
editorDidMount={[Function]}
editorWillMount={[Function]}
height={250}
language="loglang"
onChange={[Function]}
options={Object {}}
overrideServices={Object {}}
theme="euiColors"
value="
[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice!
[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
"
width="100%"
/>
<ResizeDetector
handleHeight={true}
handleWidth={true}
nodeType="div"
onResize={[Function]}
querySelector={null}
refreshRate={1000}
skipOnMount={false}
targetDomEl={null}
/>
</Fragment>
`;

View file

@ -0,0 +1,118 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { CodeEditor } from './code_editor';
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
import { shallow } from 'enzyme';
import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js';
// A sample language definition with a few example tokens
const simpleLogLang: monacoEditor.languages.IMonarchLanguage = {
tokenizer: {
root: [
[/\[error.*/, 'constant'],
[/\[notice.*/, 'variable'],
[/\[info.*/, 'string'],
[/\[[a-zA-Z 0-9:]+\]/, 'tag'],
],
},
};
monacoEditor.languages.register({ id: 'loglang' });
monacoEditor.languages.setMonarchTokensProvider('loglang', simpleLogLang);
const logs = `
[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice!
[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
`;
test('is rendered', () => {
const component = shallow(
<CodeEditor languageId="loglang" height={250} value={logs} onChange={() => {}} />
);
expect(component).toMatchSnapshot();
});
test('editor mount setup', () => {
const suggestionProvider = {
provideCompletionItems: (
model: monacoEditor.editor.ITextModel,
position: monacoEditor.Position
) => ({ suggestions: [] }),
};
const signatureProvider = {
provideSignatureHelp: () => ({ signatures: [], activeParameter: 0, activeSignature: 0 }),
};
const hoverProvider = {
provideHover: (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => ({
contents: [],
}),
};
const editorWillMount = jest.fn();
monacoEditor.languages.onLanguage = jest.fn((languageId, func) => {
expect(languageId).toBe('loglang');
// Call the function immediately so we can see our providers
// get setup without a monaco editor setting up completely
func();
}) as any;
monacoEditor.languages.registerCompletionItemProvider = jest.fn();
monacoEditor.languages.registerSignatureHelpProvider = jest.fn();
monacoEditor.languages.registerHoverProvider = jest.fn();
monacoEditor.editor.defineTheme = jest.fn();
const wrapper = shallow(
<CodeEditor
languageId="loglang"
value={logs}
onChange={() => {}}
editorWillMount={editorWillMount}
suggestionProvider={suggestionProvider}
signatureProvider={signatureProvider}
hoverProvider={hoverProvider}
/>
);
const instance = wrapper.instance() as CodeEditor;
instance._editorWillMount(monacoEditor);
// Verify our mount callback will be called
expect(editorWillMount.mock.calls.length).toBe(1);
// Verify our theme will be setup
expect((monacoEditor.editor.defineTheme as jest.Mock).mock.calls.length).toBe(1);
// Verify our language features have been registered
expect((monacoEditor.languages.onLanguage as jest.Mock).mock.calls.length).toBe(1);
expect(
(monacoEditor.languages.registerCompletionItemProvider as jest.Mock).mock.calls.length
).toBe(1);
expect(
(monacoEditor.languages.registerSignatureHelpProvider as jest.Mock).mock.calls.length
).toBe(1);
expect((monacoEditor.languages.registerHoverProvider as jest.Mock).mock.calls.length).toBe(1);
});

View file

@ -1,7 +1,20 @@
/*
* 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.
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
@ -86,7 +99,7 @@ export interface Props {
useDarkTheme?: boolean;
}
export class Editor extends React.Component<Props, {}> {
export class CodeEditor extends React.Component<Props, {}> {
_editor: monacoEditor.editor.IStandaloneCodeEditor | null = null;
_editorWillMount = (monaco: typeof monacoEditor) => {

View file

@ -1,7 +1,20 @@
/*
* 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.
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';

View file

@ -0,0 +1,27 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { useUiSetting } from '../ui_settings';
import { CodeEditor as BaseEditor, Props } from './code_editor';
export const CodeEditor: React.FunctionComponent<Props> = props => {
const darkMode = useUiSetting<boolean>('theme:darkMode');
return <BaseEditor {...props} useDarkTheme={darkMode} />;
};

View file

@ -17,6 +17,7 @@
* under the License.
*/
export * from './code_editor';
export * from './saved_objects';
export * from './exit_full_screen_button';
export * from './context';

View file

@ -1,46 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots components/Editor custom log language 1`] = `
<div>
<div
className="react-monaco-editor-container"
style={
Object {
"height": "250px",
"width": "100%",
}
}
/>
<div />
</div>
`;
exports[`Storyshots components/Editor default 1`] = `
<div>
<div
className="react-monaco-editor-container"
style={
Object {
"height": "250px",
"width": "100%",
}
}
/>
<div />
</div>
`;
exports[`Storyshots components/Editor html 1`] = `
<div>
<div
className="react-monaco-editor-container"
style={
Object {
"height": "250px",
"width": "100%",
}
}
/>
<div />
</div>
`;

View file

@ -1,55 +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 { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
import { Editor } from '../editor';
import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js';
// A sample language definition with a few example tokens
const simpleLogLang: monacoEditor.languages.IMonarchLanguage = {
tokenizer: {
root: [
[/\[error.*/, 'constant'],
[/\[notice.*/, 'variable'],
[/\[info.*/, 'string'],
[/\[[a-zA-Z 0-9:]+\]/, 'tag'],
],
},
};
monacoEditor.languages.register({ id: 'loglang' });
monacoEditor.languages.setMonarchTokensProvider('loglang', simpleLogLang);
const logs = `
[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice!
[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
`;
const html = `<section>
<span>Hello World!</span>
</section>`;
storiesOf('components/Editor', module)
.add('default', () => (
<div>
<Editor languageId="plaintext" height={250} value="Hello!" onChange={action('onChange')} />
</div>
))
.add('html', () => (
<div>
<Editor languageId="html" height={250} value={html} onChange={action('onChange')} />
</div>
))
.add('custom log language', () => (
<div>
<Editor languageId="loglang" height={250} value={logs} onChange={action('onChange')} />
</div>
));

View file

@ -1,14 +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 { useUiSetting } from '../../../../../../../src/plugins/kibana_react/public';
import { Editor as BaseEditor, Props } from './editor';
export const Editor: React.FunctionComponent<Props> = props => {
const darkMode = useUiSetting<boolean>('theme:darkMode');
return <BaseEditor {...props} useDarkTheme={darkMode} />;
};

View file

@ -10,7 +10,7 @@ import { EuiFormRow } from '@elastic/eui';
import { debounce } from 'lodash';
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
import { Editor } from '../editor';
import { CodeEditor } from '../../../../../../../src/plugins/kibana_react/public';
import { CanvasFunction } from '../../../types';
import {
@ -268,7 +268,7 @@ export class ExpressionInput extends React.Component<Props> {
error={error}
>
<div className="canvasExpressionInput__editor">
<Editor
<CodeEditor
languageId={LANGUAGE_ID}
value={value}
onChange={this.onChange}

View file

@ -30,7 +30,6 @@
@import '../components/debug/debug';
@import '../components/dom_preview/dom_preview';
@import '../components/dragbox_annotation/dragbox_annotation';
@import '../components/editor/editor';
@import '../components/element_card/element_card';
@import '../components/element_content/element_content';
@import '../components/expression/expression';

View file

@ -91,7 +91,6 @@
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.4",
"@types/react-redux": "^6.0.6",
"@types/react-resize-detector": "^4.0.1",
"@types/react-router-dom": "^4.3.1",
"@types/react-sticky": "^6.0.3",
"@types/react-test-renderer": "^16.9.1",
@ -272,7 +271,6 @@
"moment": "^2.24.0",
"moment-duration-format": "^2.3.2",
"moment-timezone": "^0.5.27",
"monaco-editor": "~0.17.0",
"ngreact": "^0.5.1",
"nock": "10.0.6",
"node-fetch": "^2.6.0",
@ -301,10 +299,8 @@
"react-fast-compare": "^2.0.4",
"react-markdown": "^3.4.1",
"react-moment-proptypes": "^1.7.0",
"react-monaco-editor": "~0.27.0",
"react-portal": "^3.2.0",
"react-redux": "^5.1.2",
"react-resize-detector": "^4.2.0",
"react-reverse-portal": "^1.0.4",
"react-router-dom": "^4.3.1",
"react-shortcuts": "^2.0.0",