From d662d6f8aa9db79e77dd62128e0e1772a54fd08c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 12 Jul 2018 14:53:06 -0600 Subject: [PATCH 01/14] add preview for creating scripted fields --- src/ui/public/field_editor/field_editor.js | 25 +- src/ui/public/field_editor/script_editor.js | 252 ++++++++++++++++++ src/ui/public/field_editor/script_editor.less | 7 + 3 files changed, 269 insertions(+), 15 deletions(-) create mode 100644 src/ui/public/field_editor/script_editor.js create mode 100644 src/ui/public/field_editor/script_editor.less diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index 0f748c35bc1b4..5795791590acb 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -39,6 +39,8 @@ import { toastNotifications } from 'ui/notify'; +import { ScriptEditor } from './script_editor'; + import { EuiButton, EuiButtonEmpty, @@ -56,7 +58,6 @@ import { EuiSelect, EuiSpacer, EuiText, - EuiTextArea, EUI_MODAL_CONFIRM_BUTTON, } from '@elastic/eui'; @@ -341,22 +342,16 @@ export class FieldEditor extends PureComponent { renderScript() { const { field } = this.state; - const isInvalid = !field.script || !field.script.trim(); return field.scripted ? ( - Scripting help)} - isInvalid={isInvalid} - error={isInvalid ? 'Script is required' : null} - > - { this.onFieldChange('script', e.target.value); }} - isInvalid={isInvalid} - /> - + { this.onFieldChange('script', e.target.value); }} + showScriptingHelp={this.showScriptingHelp} + /> ) : null; } diff --git a/src/ui/public/field_editor/script_editor.js b/src/ui/public/field_editor/script_editor.js new file mode 100644 index 0000000000000..cb2faf900ff36 --- /dev/null +++ b/src/ui/public/field_editor/script_editor.js @@ -0,0 +1,252 @@ +/* + * 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 './script_editor.less'; +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { kfetch } from 'ui/kfetch'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiTextArea, + EuiLink, + EuiCodeBlock, + EuiCallOut, + EuiComboBox, + EuiFieldNumber, +} from '@elastic/eui'; + +export class ScriptEditor extends Component { + + state = { + isLoading: false, + sourceFields: [], + size: 10, + } + + executeScript = async () => { + const { + indexPattern, + lang, + name, + script + } = this.props; + + this.setState({ + isLoading: true, + }); + + // Using _msearch because _search with index name in path dorks everything up + const header = { + index: indexPattern.title, + ignore_unavailable: true, + timeout: 30000 + }; + const search = { + query: { + match_all: {} + }, + script_fields: { + [name]: { + script: { + lang: lang, + source: script + } + } + } + }; + if (!isNaN(this.state.size)) { + search.size = this.state.size; + } + if (this.state.sourceFields.length > 0) { + search._source = this.state.sourceFields.map(option => { + return option.value; + }); + } + const body = `${JSON.stringify(header)}\n${JSON.stringify(search)}\n`; + const esResp = await kfetch({ method: 'POST', pathname: '/elasticsearch/_msearch', body }); + // unwrap _msearch response + const scriptResponse = esResp.responses[0]; + + if (scriptResponse.status !== 200) { + this.setState({ + isLoading: false, + previewData: scriptResponse.error + }); + return; + } + + this.setState({ + isLoading: false, + previewData: scriptResponse.hits.hits.map(hit => { + return Object.assign({ _id: hit._id }, hit._source, hit.fields); + }) + }); + } + + onSourceFieldsChange = (selectedOptions) => { + this.setState({ + sourceFields: selectedOptions + }); + } + + onSizeChange = (e) => { + this.setState({ + size: parseInt(e.target.value, 10) + }); + }; + + renderPreview() { + if (!this.state.previewData) { + return ( + +

+ Click the play button to preview the results of your script. +

+
+ ); + } + + return ( + + {JSON.stringify(this.state.previewData, null, ' ')} + + ); + } + renderToolbar() { + const fieldsByTypeMap = new Map(); + const fields = []; + this.props.indexPattern.fields + .filter(field => { + return !field.name.startsWith('_'); + }) + .forEach(field => { + if (fieldsByTypeMap.has(field.type)) { + const fieldsList = fieldsByTypeMap.get(field.type); + fieldsList.push(field.name); + fieldsByTypeMap.set(field.type, fieldsList); + } else { + fieldsByTypeMap.set(field.type, [field.name]); + } + }); + + fieldsByTypeMap.forEach((fieldsList, fieldType) => { + fields.push({ + label: fieldType, + options: fieldsList.sort().map(fieldName => { + return { value: fieldName, label: fieldName }; + }) + }); + }); + + fields.sort((a, b) => { + if (a.label < b.label) return -1; + if (a.label > b.label) return 1; + return 0; + }); + + return ( + + + + + + + + + + + + ); + } + + render() { + const { + script, + onScriptChange, + showScriptingHelp + } = this.props; + + const isInvalid = !script || !script.trim(); + + return ( +
+ + + + {this.renderToolbar()} + + + + +
+ Scripting help +
+
+ + {this.renderPreview()} + +
+
+ ); + } +} + +ScriptEditor.propTypes = { + indexPattern: PropTypes.object.isRequired, + lang: PropTypes.string.isRequired, + name: PropTypes.string, + script: PropTypes.string, + onScriptChange: PropTypes.func.isRequired, + showScriptingHelp: PropTypes.func.isRequired, +}; + +ScriptEditor.defaultProps = { + name: 'myScriptedField', +}; diff --git a/src/ui/public/field_editor/script_editor.less b/src/ui/public/field_editor/script_editor.less new file mode 100644 index 0000000000000..40186c04c9ce2 --- /dev/null +++ b/src/ui/public/field_editor/script_editor.less @@ -0,0 +1,7 @@ +.scriptEditorFormRow { + max-width: 850px; +} + +.scriptPreviewCodeBlock { + max-width: 400px; +} From 7597d1b82dcaea89e8544805e235dedbc397892a Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 13 Jul 2018 13:41:05 -0700 Subject: [PATCH 02/14] Move previewed results into flyout. (#25) * Move previewed results into flyout. Execute script as soon as the tab is selected. * Format error in a danger callout. --- .../scripting_call_outs/help_flyout.js | 113 ------------ .../components/scripting_call_outs/index.js | 1 - .../components/scripting_help/help_flyout.js | 76 ++++++++ .../help_flyout.test.js | 0 .../components/scripting_help/index.js | 20 ++ .../scripting_help/scripting_syntax.js | 104 +++++++++++ .../scripting_help/test_script.js} | 174 ++++++++---------- src/ui/public/field_editor/field_editor.js | 46 +++-- src/ui/public/field_editor/script_editor.less | 7 - 9 files changed, 313 insertions(+), 228 deletions(-) delete mode 100644 src/ui/public/field_editor/components/scripting_call_outs/help_flyout.js create mode 100644 src/ui/public/field_editor/components/scripting_help/help_flyout.js rename src/ui/public/field_editor/components/{scripting_call_outs => scripting_help}/help_flyout.test.js (100%) create mode 100644 src/ui/public/field_editor/components/scripting_help/index.js create mode 100644 src/ui/public/field_editor/components/scripting_help/scripting_syntax.js rename src/ui/public/field_editor/{script_editor.js => components/scripting_help/test_script.js} (60%) delete mode 100644 src/ui/public/field_editor/script_editor.less diff --git a/src/ui/public/field_editor/components/scripting_call_outs/help_flyout.js b/src/ui/public/field_editor/components/scripting_call_outs/help_flyout.js deleted file mode 100644 index 3d7c9b2025867..0000000000000 --- a/src/ui/public/field_editor/components/scripting_call_outs/help_flyout.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 { getDocLink } from 'ui/documentation_links'; - -import { - EuiCode, - EuiFlyout, - EuiFlyoutBody, - EuiIcon, - EuiLink, - EuiText, -} from '@elastic/eui'; - -export const ScriptingHelpFlyout = ({ - isVisible = false, - onClose = () => {}, -}) => { - return isVisible ? ( - - - -

Scripting help

-

- By default, Kibana scripted fields use {( - - Painless - - )}, a simple and secure scripting language designed specifically for use with Elasticsearch, - to access values in the document use the following format: -

-

- doc['some_field'].value -

-

- Painless is powerful but easy to use. It provides access to many {( - - native Java APIs - - )}. Read up on its {( - - syntax - - )} and you'll be up to speed in no time! -

-

- Kibana currently imposes one special limitation on the painless scripts you write. They cannot contain named functions. -

-

- Coming from an older version of Kibana? The {( - - Lucene Expressions - - )} you know and love are still available. Lucene expressions are a lot like JavaScript, - but limited to basic arithmetic, bitwise and comparison operations. -

-

- There are a few limitations when using Lucene Expressions: -

-
    -
  • Only numeric, boolean, date, and geo_point fields may be accessed
  • -
  • Stored fields are not available
  • -
  • If a field is sparse (only some documents contain a value), documents missing the field will have a value of 0
  • -
-

- Here are all the operations available to lucene expressions: -

-
    -
  • Arithmetic operators: + - * / %
  • -
  • Bitwise operators: | & ^ ~ << >> >>>
  • -
  • Boolean operators (including the ternary operator): && || ! ?:
  • -
  • Comparison operators: < <= == >= >
  • -
  • Common mathematic functions: abs ceil exp floor ln log10 logn max min sqrt pow
  • -
  • Trigonometric library functions: acosh acos asinh asin atanh atan atan2 cosh cos sinh sin tanh tan
  • -
  • Distance functions: haversin
  • -
  • Miscellaneous functions: min, max
  • -
-
-
-
- ) : null; -}; - -ScriptingHelpFlyout.displayName = 'ScriptingHelpFlyout'; diff --git a/src/ui/public/field_editor/components/scripting_call_outs/index.js b/src/ui/public/field_editor/components/scripting_call_outs/index.js index 44da62f3e22f2..1ecf919ce6b16 100644 --- a/src/ui/public/field_editor/components/scripting_call_outs/index.js +++ b/src/ui/public/field_editor/components/scripting_call_outs/index.js @@ -19,4 +19,3 @@ export { ScriptingDisabledCallOut } from './disabled_call_out'; export { ScriptingWarningCallOut } from './warning_call_out'; -export { ScriptingHelpFlyout } from './help_flyout'; diff --git a/src/ui/public/field_editor/components/scripting_help/help_flyout.js b/src/ui/public/field_editor/components/scripting_help/help_flyout.js new file mode 100644 index 0000000000000..fa92145311f21 --- /dev/null +++ b/src/ui/public/field_editor/components/scripting_help/help_flyout.js @@ -0,0 +1,76 @@ +/* + * 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 PropTypes from 'prop-types'; + +import { + EuiFlyout, + EuiFlyoutBody, + EuiTabbedContent, +} from '@elastic/eui'; + +import { ScriptingSyntax } from './scripting_syntax'; +import { TestScript } from './test_script'; + +export const ScriptingHelpFlyout = ({ + isVisible = false, + onClose = () => {}, + indexPattern, + lang, + name, + script, +}) => { + const tabs = [{ + id: 'syntax', + name: 'Syntax', + content: , + }, { + id: 'test', + name: 'Preview results', + content: ( + + ), + }]; + + return isVisible ? ( + + + + + + ) : null; +}; + +ScriptingHelpFlyout.displayName = 'ScriptingHelpFlyout'; + +ScriptingHelpFlyout.propTypes = { + indexPattern: PropTypes.object.isRequired, + lang: PropTypes.string.isRequired, + name: PropTypes.string, + script: PropTypes.string, +}; diff --git a/src/ui/public/field_editor/components/scripting_call_outs/help_flyout.test.js b/src/ui/public/field_editor/components/scripting_help/help_flyout.test.js similarity index 100% rename from src/ui/public/field_editor/components/scripting_call_outs/help_flyout.test.js rename to src/ui/public/field_editor/components/scripting_help/help_flyout.test.js diff --git a/src/ui/public/field_editor/components/scripting_help/index.js b/src/ui/public/field_editor/components/scripting_help/index.js new file mode 100644 index 0000000000000..13b3b10fb5859 --- /dev/null +++ b/src/ui/public/field_editor/components/scripting_help/index.js @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export { ScriptingHelpFlyout } from './help_flyout'; diff --git a/src/ui/public/field_editor/components/scripting_help/scripting_syntax.js b/src/ui/public/field_editor/components/scripting_help/scripting_syntax.js new file mode 100644 index 0000000000000..892bf5376ea1b --- /dev/null +++ b/src/ui/public/field_editor/components/scripting_help/scripting_syntax.js @@ -0,0 +1,104 @@ +/* + * 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, { Fragment } from 'react'; +import { getDocLink } from 'ui/documentation_links'; + +import { + EuiCode, + EuiIcon, + EuiLink, + EuiText, + EuiSpacer, +} from '@elastic/eui'; + +export const ScriptingSyntax = () => ( + + + +

Syntax

+

+ By default, Kibana scripted fields use {( + + Painless + + )}, a simple and secure scripting language designed specifically for use with Elasticsearch, + to access values in the document use the following format: +

+

+ doc['some_field'].value +

+

+ Painless is powerful but easy to use. It provides access to many {( + + native Java APIs + + )}. Read up on its {( + + syntax + + )} and you'll be up to speed in no time! +

+

+ Kibana currently imposes one special limitation on the painless scripts you write. They cannot contain named functions. +

+

+ Coming from an older version of Kibana? The {( + + Lucene Expressions + + )} you know and love are still available. Lucene expressions are a lot like JavaScript, + but limited to basic arithmetic, bitwise and comparison operations. +

+

+ There are a few limitations when using Lucene Expressions: +

+
    +
  • Only numeric, boolean, date, and geo_point fields may be accessed
  • +
  • Stored fields are not available
  • +
  • If a field is sparse (only some documents contain a value), documents missing the field will have a value of 0
  • +
+

+ Here are all the operations available to lucene expressions: +

+
    +
  • Arithmetic operators: + - * / %
  • +
  • Bitwise operators: | & ^ ~ << >> >>>
  • +
  • Boolean operators (including the ternary operator): && || ! ?:
  • +
  • Comparison operators: < <= == >= >
  • +
  • Common mathematic functions: abs ceil exp floor ln log10 logn max min sqrt pow
  • +
  • Trigonometric library functions: acosh acos asinh asin atanh atan atan2 cosh cos sinh sin tanh tan
  • +
  • Distance functions: haversin
  • +
  • Miscellaneous functions: min, max
  • +
+
+
+); diff --git a/src/ui/public/field_editor/script_editor.js b/src/ui/public/field_editor/components/scripting_help/test_script.js similarity index 60% rename from src/ui/public/field_editor/script_editor.js rename to src/ui/public/field_editor/components/scripting_help/test_script.js index cb2faf900ff36..e05da7609fcdd 100644 --- a/src/ui/public/field_editor/script_editor.js +++ b/src/ui/public/field_editor/components/scripting_help/test_script.js @@ -16,29 +16,32 @@ * specific language governing permissions and limitations * under the License. */ -import './script_editor.less'; + +import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import React, { Component } from 'react'; import { kfetch } from 'ui/kfetch'; import { - EuiFlexGroup, - EuiFlexItem, EuiButton, - EuiTextArea, - EuiLink, EuiCodeBlock, - EuiCallOut, EuiComboBox, - EuiFieldNumber, + EuiFormRow, + EuiText, + EuiSpacer, + EuiTitle, + EuiCallOut, } from '@elastic/eui'; -export class ScriptEditor extends Component { - +export class TestScript extends Component { state = { isLoading: false, sourceFields: [], - size: 10, + } + + componentDidMount() { + if (this.props.script) { + this.executeScript(); + } } executeScript = async () => { @@ -46,7 +49,7 @@ export class ScriptEditor extends Component { indexPattern, lang, name, - script + script, } = this.props; this.setState({ @@ -59,6 +62,7 @@ export class ScriptEditor extends Component { ignore_unavailable: true, timeout: 30000 }; + const search = { query: { match_all: {} @@ -66,20 +70,20 @@ export class ScriptEditor extends Component { script_fields: { [name]: { script: { - lang: lang, + lang, source: script } } - } + }, + size: 10, }; - if (!isNaN(this.state.size)) { - search.size = this.state.size; - } + if (this.state.sourceFields.length > 0) { search._source = this.state.sourceFields.map(option => { return option.value; }); } + const body = `${JSON.stringify(header)}\n${JSON.stringify(search)}\n`; const esResp = await kfetch({ method: 'POST', pathname: '/elasticsearch/_msearch', body }); // unwrap _msearch response @@ -88,16 +92,18 @@ export class ScriptEditor extends Component { if (scriptResponse.status !== 200) { this.setState({ isLoading: false, - previewData: scriptResponse.error + previewData: scriptResponse }); return; } this.setState({ isLoading: false, - previewData: scriptResponse.hits.hits.map(hit => { - return Object.assign({ _id: hit._id }, hit._source, hit.fields); - }) + previewData: scriptResponse.hits.hits.map(hit => ({ + _id: hit._id, + ...hit._source, + ...hit.fields, + })), }); } @@ -107,34 +113,42 @@ export class ScriptEditor extends Component { }); } - onSizeChange = (e) => { - this.setState({ - size: parseInt(e.target.value, 10) - }); - }; - renderPreview() { - if (!this.state.previewData) { + const { previewData } = this.state; + + if (!previewData) { + return null; + } + + if (previewData.error) { return ( -

- Click the play button to preview the results of your script. -

+ + {JSON.stringify(previewData.error, null, ' ')} +
); } return ( - - {JSON.stringify(this.state.previewData, null, ' ')} - + +

First 10 results

+ + + {JSON.stringify(previewData, null, ' ')} + +
); } + renderToolbar() { const fieldsByTypeMap = new Map(); const fields = []; + this.props.indexPattern.fields .filter(field => { return !field.name.startsWith('_'); @@ -165,88 +179,56 @@ export class ScriptEditor extends Component { }); return ( - - - - - - - - + + - - + + + + Run script + + ); } render() { - const { - script, - onScriptChange, - showScriptingHelp - } = this.props; - - const isInvalid = !script || !script.trim(); - return ( -
- - - + + + +

Preview results

+

+ Run your script to preview the first 10 results. You can also select some + additional fields to include in your results to gain more context. +

+
+ {this.renderToolbar()} - - - - -
- Scripting help -
-
- - {this.renderPreview()} - -
-
+ + {this.renderPreview()} + ); } } -ScriptEditor.propTypes = { +TestScript.propTypes = { indexPattern: PropTypes.object.isRequired, lang: PropTypes.string.isRequired, name: PropTypes.string, script: PropTypes.string, - onScriptChange: PropTypes.func.isRequired, - showScriptingHelp: PropTypes.func.isRequired, }; -ScriptEditor.defaultProps = { +TestScript.defaultProps = { name: 'myScriptedField', }; diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index 5795791590acb..1fa2853dbaa3b 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -39,8 +39,6 @@ import { toastNotifications } from 'ui/notify'; -import { ScriptEditor } from './script_editor'; - import { EuiButton, EuiButtonEmpty, @@ -58,15 +56,19 @@ import { EuiSelect, EuiSpacer, EuiText, + EuiTextArea, EUI_MODAL_CONFIRM_BUTTON, } from '@elastic/eui'; import { ScriptingDisabledCallOut, ScriptingWarningCallOut, - ScriptingHelpFlyout, } from './components/scripting_call_outs'; +import { + ScriptingHelpFlyout, +} from './components/scripting_help'; + import { FieldFormatEditor } from './components/field_format_editor'; @@ -342,16 +344,34 @@ export class FieldEditor extends PureComponent { renderScript() { const { field } = this.state; + const isInvalid = !field.script || !field.script.trim(); return field.scripted ? ( - { this.onFieldChange('script', e.target.value); }} - showScriptingHelp={this.showScriptingHelp} - /> + + + + + + + + Get help with the syntax and preview the results of your script + + + + )} + isInvalid={isInvalid} + error={isInvalid ? 'Script is required' : null} + > + { this.onFieldChange('script', e.target.value); }} + isInvalid={isInvalid} + /> + ) : null; } @@ -516,6 +536,10 @@ export class FieldEditor extends PureComponent { {this.renderName()} {this.renderLanguage()} diff --git a/src/ui/public/field_editor/script_editor.less b/src/ui/public/field_editor/script_editor.less deleted file mode 100644 index 40186c04c9ce2..0000000000000 --- a/src/ui/public/field_editor/script_editor.less +++ /dev/null @@ -1,7 +0,0 @@ -.scriptEditorFormRow { - max-width: 850px; -} - -.scriptPreviewCodeBlock { - max-width: 400px; -} From 063aa5810bbf7a1a63514dec9013a0073324c78d Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 13 Jul 2018 17:33:52 -0600 Subject: [PATCH 03/14] add functional test for preview --- .../components/scripting_help/help_flyout.js | 4 +- .../components/scripting_help/test_script.js | 14 +++- src/ui/public/field_editor/field_editor.js | 2 +- .../management/_scripted_fields_preview.js | 67 +++++++++++++++++++ test/functional/apps/management/index.js | 1 + test/functional/page_objects/settings_page.js | 47 ++++++++++++- 6 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 test/functional/apps/management/_scripted_fields_preview.js diff --git a/src/ui/public/field_editor/components/scripting_help/help_flyout.js b/src/ui/public/field_editor/components/scripting_help/help_flyout.js index fa92145311f21..b8739663addea 100644 --- a/src/ui/public/field_editor/components/scripting_help/help_flyout.js +++ b/src/ui/public/field_editor/components/scripting_help/help_flyout.js @@ -40,10 +40,12 @@ export const ScriptingHelpFlyout = ({ const tabs = [{ id: 'syntax', name: 'Syntax', + ['data-test-subj']: 'syntaxTab', content: , }, { id: 'test', name: 'Preview results', + ['data-test-subj']: 'testTab', content: ( + - + {JSON.stringify(previewData.error, null, ' ')} @@ -138,7 +142,11 @@ export class TestScript extends Component {

First 10 results

- + {JSON.stringify(previewData, null, ' ')}
@@ -188,6 +196,7 @@ export class TestScript extends Component { options={fields} selectedOptions={this.state.sourceFields} onChange={this.onSourceFieldsChange} + data-test-subj="additionalFieldsSelect" /> @@ -195,6 +204,7 @@ export class TestScript extends Component { onClick={this.executeScript} disabled={this.props.script ? false : true} isLoading={this.state.isLoading} + data-test-subj="runScriptButton" > Run script diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index 1fa2853dbaa3b..ead2b26dd66a1 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -356,7 +356,7 @@ export class FieldEditor extends PureComponent { - + Get help with the syntax and preview the results of your script diff --git a/test/functional/apps/management/_scripted_fields_preview.js b/test/functional/apps/management/_scripted_fields_preview.js new file mode 100644 index 0000000000000..d2281600ae9ba --- /dev/null +++ b/test/functional/apps/management/_scripted_fields_preview.js @@ -0,0 +1,67 @@ +/* + * 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 expect from 'expect.js'; + +export default function ({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const remote = getService('remote'); + const PageObjects = getPageObjects(['settings']); + const SCRIPTED_FIELD_NAME = 'myScriptedField'; + + describe('scripted fields preview', () => { + before(async function () { + await remote.setWindowSize(1200, 800); + // delete .kibana index and then wait for Kibana to re-create it + await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'UTC' }); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndices(); + await PageObjects.settings.createIndexPattern(); + await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'UTC' }); + + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndices(); + await PageObjects.settings.clickScriptedFieldsTab(); + await PageObjects.settings.clickAddScriptedField(); + await PageObjects.settings.setScriptedFieldName(SCRIPTED_FIELD_NAME); + + }); + + after(async function afterAll() { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndices(); + await PageObjects.settings.removeIndexPattern(); + }); + + it('should display script error when script is invalid', async function () { + const scriptResults = await PageObjects.settings.executeScriptedField(`doc['iHaveNoClosingTick].value`); + expect(scriptResults.includes('search_phase_execution_exception')).to.be(true); + }); + + it('should display script results when script is valid', async function () { + const scriptResults = await PageObjects.settings.executeScriptedField(`doc['bytes'].value * 2`); + expect(scriptResults.replace(/\s/g, '').includes('"myScriptedField":[6196')).to.be(true); + }); + + it('should display additional fields', async function () { + const scriptResults = await PageObjects.settings.executeScriptedField(`doc['bytes'].value * 2`, ['bytes']); + expect(scriptResults.replace(/\s/g, '').includes('"bytes":3098')).to.be(true); + }); + }); +} diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.js index 346125b4cea89..adc9a9b882f3c 100644 --- a/test/functional/apps/management/index.js +++ b/test/functional/apps/management/index.js @@ -40,6 +40,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./_index_pattern_popularity')); loadTestFile(require.resolve('./_kibana_settings')); loadTestFile(require.resolve('./_scripted_fields')); + loadTestFile(require.resolve('./_scripted_fields_preview')); loadTestFile(require.resolve('./_index_pattern_filter')); loadTestFile(require.resolve('./_scripted_fields_filter')); loadTestFile(require.resolve('./_import_objects')); diff --git a/test/functional/page_objects/settings_page.js b/test/functional/page_objects/settings_page.js index 51c3f40c2dbea..4276b37388e7e 100644 --- a/test/functional/page_objects/settings_page.js +++ b/test/functional/page_objects/settings_page.js @@ -25,8 +25,9 @@ export function SettingsPageProvider({ getService, getPageObjects }) { const config = getService('config'); const remote = getService('remote'); const find = getService('find'); + const flyout = getService('flyout'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['header', 'common']); + const PageObjects = getPageObjects(['header', 'common', 'visualize']); const defaultFindTimeout = config.get('timeouts.find'); @@ -503,6 +504,50 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await field.type(script); } + async openScriptedFieldHelp(activeTab) { + log.debug('open Scripted Fields help'); + let isOpen = await testSubjects.exists('scriptedFieldsHelpFlyout'); + if (!isOpen) { + await retry.try(async () => { + await testSubjects.click('scriptedFieldsHelpLink'); + isOpen = await testSubjects.exists('scriptedFieldsHelpFlyout'); + if (!isOpen) { + throw new Error('Failed to open scripted fields help'); + } + }); + } + + if (activeTab) { + await testSubjects.click(activeTab); + } + } + + async closeScriptedFieldHelp() { + log.debug('close Scripted Fields help'); + let isOpen = await testSubjects.exists('scriptedFieldsHelpFlyout'); + if (isOpen) { + await retry.try(async () => { + await flyout.close('scriptedFieldsHelpFlyout'); + isOpen = await testSubjects.exists('scriptedFieldsHelpFlyout'); + if (isOpen) { + throw new Error('Failed to close scripted fields help'); + } + }); + } + } + + async executeScriptedField(script, additionalField) { + log.debug('execute Scripted Fields help'); + await this.closeScriptedFieldHelp(); // ensure script help is closed so script input is not blocked + await this.setScriptedFieldScript(script); + await this.openScriptedFieldHelp('testTab'); + if (additionalField) { + await PageObjects.visualize.setComboBox('additionalFieldsSelect', additionalField); + await testSubjects.click('runScriptButton'); + } + return await testSubjects.getVisibleText('scriptedFieldPreview'); + } + async importFile(path, overwriteAll = true) { log.debug(`importFile(${path})`); From 8b643032b18387223dc838693c44170628c0bef5 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Sat, 14 Jul 2018 07:25:04 -0600 Subject: [PATCH 04/14] move executeScript functionallity into lib so it can be used to validate on save --- .../components/scripting_help/help_flyout.js | 3 + .../components/scripting_help/test_script.js | 58 ++++++------------- src/ui/public/field_editor/field_editor.js | 3 +- src/ui/public/field_editor/lib/index.js | 1 + .../field_editor/lib/validate_script.js | 57 ++++++++++++++++++ 5 files changed, 81 insertions(+), 41 deletions(-) create mode 100644 src/ui/public/field_editor/lib/validate_script.js diff --git a/src/ui/public/field_editor/components/scripting_help/help_flyout.js b/src/ui/public/field_editor/components/scripting_help/help_flyout.js index b8739663addea..45975fe07194b 100644 --- a/src/ui/public/field_editor/components/scripting_help/help_flyout.js +++ b/src/ui/public/field_editor/components/scripting_help/help_flyout.js @@ -36,6 +36,7 @@ export const ScriptingHelpFlyout = ({ lang, name, script, + executeScript, }) => { const tabs = [{ id: 'syntax', @@ -52,6 +53,7 @@ export const ScriptingHelpFlyout = ({ lang={lang} name={name} script={script} + executeScript={executeScript} /> ), }]; @@ -75,4 +77,5 @@ ScriptingHelpFlyout.propTypes = { lang: PropTypes.string.isRequired, name: PropTypes.string, script: PropTypes.string, + executeScript: PropTypes.func.isRequired, }; diff --git a/src/ui/public/field_editor/components/scripting_help/test_script.js b/src/ui/public/field_editor/components/scripting_help/test_script.js index b3541b6f0fd4f..3869e38626894 100644 --- a/src/ui/public/field_editor/components/scripting_help/test_script.js +++ b/src/ui/public/field_editor/components/scripting_help/test_script.js @@ -19,7 +19,6 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { kfetch } from 'ui/kfetch'; import { EuiButton, @@ -35,59 +34,37 @@ import { export class TestScript extends Component { state = { isLoading: false, - sourceFields: [], + additionalFields: [], } componentDidMount() { if (this.props.script) { - this.executeScript(); + this.previewScript(); } } - executeScript = async () => { + previewScript = async () => { const { indexPattern, lang, name, script, + executeScript, } = this.props; this.setState({ isLoading: true, }); - // Using _msearch because _search with index name in path dorks everything up - const header = { - index: indexPattern.title, - ignore_unavailable: true, - timeout: 30000 - }; - - const search = { - query: { - match_all: {} - }, - script_fields: { - [name]: { - script: { - lang, - source: script - } - } - }, - size: 10, - }; - - if (this.state.sourceFields.length > 0) { - search._source = this.state.sourceFields.map(option => { + const scriptResponse = await executeScript({ + name, + lang, + script, + indexPatternTitle: indexPattern.title, + additionalFields: this.state.additionalFields.map(option => { return option.value; - }); - } - - const body = `${JSON.stringify(header)}\n${JSON.stringify(search)}\n`; - const esResp = await kfetch({ method: 'POST', pathname: '/elasticsearch/_msearch', body }); - // unwrap _msearch response - const scriptResponse = esResp.responses[0]; + }) + }); if (scriptResponse.status !== 200) { this.setState({ @@ -107,9 +84,9 @@ export class TestScript extends Component { }); } - onSourceFieldsChange = (selectedOptions) => { + onAdditionalFieldsChange = (selectedOptions) => { this.setState({ - sourceFields: selectedOptions + additionalFields: selectedOptions }); } @@ -194,14 +171,14 @@ export class TestScript extends Component { {this.renderName()} {this.renderLanguage()} diff --git a/src/ui/public/field_editor/lib/index.js b/src/ui/public/field_editor/lib/index.js index c97f6db91c5fd..219da88407e84 100644 --- a/src/ui/public/field_editor/lib/index.js +++ b/src/ui/public/field_editor/lib/index.js @@ -20,3 +20,4 @@ export { copyField } from './copy_field'; export { getDefaultFormat } from './get_default_format'; export { convertSampleInput } from './convert_sample_input'; +export { executeScript, validateScript } from './validate_script'; diff --git a/src/ui/public/field_editor/lib/validate_script.js b/src/ui/public/field_editor/lib/validate_script.js new file mode 100644 index 0000000000000..738c877a53f1d --- /dev/null +++ b/src/ui/public/field_editor/lib/validate_script.js @@ -0,0 +1,57 @@ +/* + * 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 { kfetch } from 'ui/kfetch'; + +export const executeScript = async ({ name, lang, script, indexPatternTitle, additionalFields = [] }) => { + // Using _msearch because _search with index name in path dorks everything up + const header = { + index: indexPatternTitle, + ignore_unavailable: true, + timeout: 30000 + }; + + const search = { + query: { + match_all: {} + }, + script_fields: { + [name]: { + script: { + lang, + source: script + } + } + }, + size: 10, + }; + + if (additionalFields.length > 0) { + search._source = additionalFields; + } + + const body = `${JSON.stringify(header)}\n${JSON.stringify(search)}\n`; + const esResp = await kfetch({ method: 'POST', pathname: '/elasticsearch/_msearch', body }); + // unwrap _msearch response + return esResp.responses[0]; +} + +export const validateScript = async ({ name, lang, script, indexPatternTitle, additionalFields = [] }) => { + +} From 4e4191a9e83703b598a4c876449202289ff24e61 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Sat, 14 Jul 2018 11:13:02 -0600 Subject: [PATCH 05/14] validate script on save --- .../components/scripting_help/test_script.js | 4 ++ src/ui/public/field_editor/field_editor.js | 53 +++++++++++++++---- src/ui/public/field_editor/lib/index.js | 2 +- .../field_editor/lib/validate_script.js | 12 +++-- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/ui/public/field_editor/components/scripting_help/test_script.js b/src/ui/public/field_editor/components/scripting_help/test_script.js index 3869e38626894..04a457c118e55 100644 --- a/src/ui/public/field_editor/components/scripting_help/test_script.js +++ b/src/ui/public/field_editor/components/scripting_help/test_script.js @@ -52,6 +52,10 @@ export class TestScript extends Component { executeScript, } = this.props; + if (!script || script.length === 0) { + return; + } + this.setState({ isLoading: true, }); diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index 52447c7b21e5b..2abfaf386a351 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -74,7 +74,7 @@ import { } from './components/field_format_editor'; import { FIELD_TYPES_BY_LANG, DEFAULT_FIELD_TYPES } from './constants'; -import { copyField, getDefaultFormat, executeScript } from './lib'; +import { copyField, getDefaultFormat, executeScript, isScriptValid } from './lib'; export class FieldEditor extends PureComponent { static propTypes = { @@ -112,6 +112,8 @@ export class FieldEditor extends PureComponent { showScriptingHelp: false, showDeleteModal: false, hasFormatError: false, + hasScriptError: false, + isSaving: false, }; this.supportedLangs = getSupportedScriptingLanguages(); this.deprecatedLangs = getDeprecatedScriptingLanguages(); @@ -342,9 +344,19 @@ export class FieldEditor extends PureComponent { ); } + onScriptChange = (e) => { + this.setState({ + hasScriptError: false + }); + this.onFieldChange('script', e.target.value); + } + renderScript() { - const { field } = this.state; - const isInvalid = !field.script || !field.script.trim(); + const { field, hasScriptError } = this.state; + const isInvalid = !field.script || !field.script.trim() || hasScriptError; + const errorMsg = hasScriptError + ? 'Script is invalid. View script preview for details' + : 'Script is required'; return field.scripted ? ( )} isInvalid={isInvalid} - error={isInvalid ? 'Script is required' : null} + error={isInvalid ? errorMsg : null} > { this.onFieldChange('script', e.target.value); }} + onChange={this.onScriptChange} isInvalid={isInvalid} /> @@ -424,7 +436,7 @@ export class FieldEditor extends PureComponent { } renderActions() { - const { isCreating, field } = this.state; + const { isCreating, field, isSaving } = this.state; const { redirectAway } = this.props.helpers; return ( @@ -434,6 +446,7 @@ export class FieldEditor extends PureComponent { fill onClick={this.saveField} isDisabled={this.isSavingDisabled()} + isLoading={isSaving} data-test-subj="fieldSaveButton" > {isCreating ? 'Create field' : 'Save field'} @@ -479,12 +492,31 @@ export class FieldEditor extends PureComponent { } } - saveField = () => { - const { redirectAway } = this.props.helpers; + saveField = async () => { + const field = this.state.field.toActualField(); const { indexPattern } = this.props; const { fieldFormatId } = this.state; - const field = this.state.field.toActualField(); + this.setState({ + isSaving: true + }); + + const isValid = await isScriptValid({ + name: field.name, + lang: field.lang, + script: field.script, + indexPatternTitle: indexPattern.title + }); + + if (!isValid) { + this.setState({ + hasScriptError: true, + isSaving: false + }); + return; + } + + const { redirectAway } = this.props.helpers; const index = indexPattern.fields.findIndex(f => f.name === field.name); if (index > -1) { @@ -507,10 +539,11 @@ export class FieldEditor extends PureComponent { } isSavingDisabled() { - const { field, hasFormatError } = this.state; + const { field, hasFormatError, hasScriptError } = this.state; if( hasFormatError + || hasScriptError || !field.name || !field.name.trim() || (field.scripted && (!field.script || !field.script.trim())) diff --git a/src/ui/public/field_editor/lib/index.js b/src/ui/public/field_editor/lib/index.js index 219da88407e84..5838ecfe93da6 100644 --- a/src/ui/public/field_editor/lib/index.js +++ b/src/ui/public/field_editor/lib/index.js @@ -20,4 +20,4 @@ export { copyField } from './copy_field'; export { getDefaultFormat } from './get_default_format'; export { convertSampleInput } from './convert_sample_input'; -export { executeScript, validateScript } from './validate_script'; +export { executeScript, isScriptValid } from './validate_script'; diff --git a/src/ui/public/field_editor/lib/validate_script.js b/src/ui/public/field_editor/lib/validate_script.js index 738c877a53f1d..c62e0dbdecde8 100644 --- a/src/ui/public/field_editor/lib/validate_script.js +++ b/src/ui/public/field_editor/lib/validate_script.js @@ -50,8 +50,14 @@ export const executeScript = async ({ name, lang, script, indexPatternTitle, add const esResp = await kfetch({ method: 'POST', pathname: '/elasticsearch/_msearch', body }); // unwrap _msearch response return esResp.responses[0]; -} +}; -export const validateScript = async ({ name, lang, script, indexPatternTitle, additionalFields = [] }) => { +export const isScriptValid = async ({ name, lang, script, indexPatternTitle }) => { + const scriptResponse = await executeScript({ name, lang, script, indexPatternTitle }); -} + if (scriptResponse.status !== 200) { + return false; + } + + return true; +}; From 78a724bde4ccc1810d6b142f07b9d4b89f5e25e4 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Sat, 14 Jul 2018 12:42:11 -0600 Subject: [PATCH 06/14] add functional test for script validation during save --- .../apps/management/_scripted_fields.js | 15 +++++++++++++++ .../apps/management/_scripted_fields_preview.js | 1 - test/functional/page_objects/settings_page.js | 6 +++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index b5c7d272077f5..c3847619543de 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -36,6 +36,7 @@ export default function ({ getService, getPageObjects }) { const log = getService('log'); const remote = getService('remote'); const retry = getService('retry'); + const find = getService('find'); const PageObjects = getPageObjects(['common', 'header', 'settings', 'visualize', 'discover']); describe('scripted fields', () => { @@ -56,6 +57,20 @@ export default function ({ getService, getPageObjects }) { await PageObjects.settings.removeIndexPattern(); }); + it('should not allow saving of invalid scripts', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndices(); + await PageObjects.settings.clickScriptedFieldsTab(); + await PageObjects.settings.clickAddScriptedField(); + await PageObjects.settings.setScriptedFieldName('doomedScriptedField'); + await PageObjects.settings.setScriptedFieldScript(`doc['iHaveNoClosingTick].value`); + await PageObjects.settings.clickSaveScriptedField(); + await retry.try(async () => { + const formErrorExists = await find.existsByDisplayedByCssSelector('.euiFormErrorText'); + expect(formErrorExists).to.be(true); + }); + }); + describe('creating and using Painless numeric scripted fields', function describeIndexTests() { const scriptedPainlessFieldName = 'ram_Pain1'; diff --git a/test/functional/apps/management/_scripted_fields_preview.js b/test/functional/apps/management/_scripted_fields_preview.js index d2281600ae9ba..21eef4bebdbc3 100644 --- a/test/functional/apps/management/_scripted_fields_preview.js +++ b/test/functional/apps/management/_scripted_fields_preview.js @@ -40,7 +40,6 @@ export default function ({ getService, getPageObjects }) { await PageObjects.settings.clickScriptedFieldsTab(); await PageObjects.settings.clickAddScriptedField(); await PageObjects.settings.setScriptedFieldName(SCRIPTED_FIELD_NAME); - }); after(async function afterAll() { diff --git a/test/functional/page_objects/settings_page.js b/test/functional/page_objects/settings_page.js index 4276b37388e7e..163d3e089aa35 100644 --- a/test/functional/page_objects/settings_page.js +++ b/test/functional/page_objects/settings_page.js @@ -545,7 +545,11 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await PageObjects.visualize.setComboBox('additionalFieldsSelect', additionalField); await testSubjects.click('runScriptButton'); } - return await testSubjects.getVisibleText('scriptedFieldPreview'); + let scriptResults; + await retry.try(async () => { + scriptResults = await testSubjects.getVisibleText('scriptedFieldPreview'); + }); + return scriptResults; } async importFile(path, overwriteAll = true) { From 222bcd569121dc9fcee85fe8a3fa4d06e9c2d23f Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 16 Jul 2018 10:21:50 -0600 Subject: [PATCH 07/14] fix jest tests --- .../field_editor/__mocks__/ui/kfetch.js | 22 ++ .../field_editor/__mocks__/ui/metadata.js | 23 +++ .../__snapshots__/field_editor.test.js.snap | 193 ++++++++++++++++-- .../__snapshots__/help_flyout.test.js.snap | 134 ------------ .../__snapshots__/help_flyout.test.js.snap | 48 +++++ .../scripting_help/help_flyout.test.js | 11 +- 6 files changed, 276 insertions(+), 155 deletions(-) create mode 100644 src/ui/public/field_editor/__mocks__/ui/kfetch.js create mode 100644 src/ui/public/field_editor/__mocks__/ui/metadata.js delete mode 100644 src/ui/public/field_editor/components/scripting_call_outs/__snapshots__/help_flyout.test.js.snap create mode 100644 src/ui/public/field_editor/components/scripting_help/__snapshots__/help_flyout.test.js.snap diff --git a/src/ui/public/field_editor/__mocks__/ui/kfetch.js b/src/ui/public/field_editor/__mocks__/ui/kfetch.js new file mode 100644 index 0000000000000..3a884a4c6d676 --- /dev/null +++ b/src/ui/public/field_editor/__mocks__/ui/kfetch.js @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export const kfetch = () => { + return Promise.resolve('mock value'); +}; diff --git a/src/ui/public/field_editor/__mocks__/ui/metadata.js b/src/ui/public/field_editor/__mocks__/ui/metadata.js new file mode 100644 index 0000000000000..280e8a5a8f6b6 --- /dev/null +++ b/src/ui/public/field_editor/__mocks__/ui/metadata.js @@ -0,0 +1,23 @@ +/* + * 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. + */ + +export const metadata = { + branch: 'my-metadata-branch', + version: 'my-metadata-version' +}; diff --git a/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap b/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap index 84bc861c30dc5..8cfa5fe1cf9bf 100644 --- a/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap +++ b/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap @@ -17,8 +17,19 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` - - Scripting help - + + + + + + Get help with the syntax and preview the results of your script + + + } isInvalid={true} label="Script" @@ -159,6 +185,7 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` data-test-subj="fieldSaveButton" fill={true} isDisabled={true} + isLoading={false} onClick={[Function]} > Create field @@ -199,9 +226,30 @@ exports[`FieldEditor should render edit scripted field correctly 1`] = ` - - Scripting help - + + + + + + Get help with the syntax and preview the results of your script + + + } isInvalid={false} label="Script" @@ -328,6 +391,7 @@ exports[`FieldEditor should render edit scripted field correctly 1`] = ` data-test-subj="fieldSaveButton" fill={true} isDisabled={false} + isLoading={false} onClick={[Function]} > Save field @@ -378,8 +442,36 @@ exports[`FieldEditor should show conflict field warning 1`] = ` - - Scripting help - + + + + + + Get help with the syntax and preview the results of your script + + + } isInvalid={true} label="Script" @@ -537,6 +644,7 @@ exports[`FieldEditor should show conflict field warning 1`] = ` data-test-subj="fieldSaveButton" fill={true} isDisabled={true} + isLoading={false} onClick={[Function]} > Create field @@ -577,9 +685,38 @@ exports[`FieldEditor should show deprecated lang warning 1`] = ` - - Scripting help - + + + + + + Get help with the syntax and preview the results of your script + + + } isInvalid={false} label="Script" @@ -762,6 +914,7 @@ exports[`FieldEditor should show deprecated lang warning 1`] = ` data-test-subj="fieldSaveButton" fill={true} isDisabled={false} + isLoading={false} onClick={[Function]} > Save field diff --git a/src/ui/public/field_editor/components/scripting_call_outs/__snapshots__/help_flyout.test.js.snap b/src/ui/public/field_editor/components/scripting_call_outs/__snapshots__/help_flyout.test.js.snap deleted file mode 100644 index 7685113d9715a..0000000000000 --- a/src/ui/public/field_editor/components/scripting_call_outs/__snapshots__/help_flyout.test.js.snap +++ /dev/null @@ -1,134 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ScriptingHelpFlyout should render normally 1`] = ` - - - -

- Scripting help -

-

- By default, Kibana scripted fields use - - Painless - - - , a simple and secure scripting language designed specifically for use with Elasticsearch, to access values in the document use the following format: -

-

- - doc['some_field'].value - -

-

- Painless is powerful but easy to use. It provides access to many - - native Java APIs - - - . Read up on its - - syntax - - - and you'll be up to speed in no time! -

-

- Kibana currently imposes one special limitation on the painless scripts you write. They cannot contain named functions. -

-

- Coming from an older version of Kibana? The - - Lucene Expressions - - - you know and love are still available. Lucene expressions are a lot like JavaScript, but limited to basic arithmetic, bitwise and comparison operations. -

-

- There are a few limitations when using Lucene Expressions: -

-
    -
  • - Only numeric, boolean, date, and geo_point fields may be accessed -
  • -
  • - Stored fields are not available -
  • -
  • - If a field is sparse (only some documents contain a value), documents missing the field will have a value of 0 -
  • -
-

- Here are all the operations available to lucene expressions: -

-
    -
  • - Arithmetic operators: + - * / % -
  • -
  • - Bitwise operators: | & ^ ~ << >> >>> -
  • -
  • - Boolean operators (including the ternary operator): && || ! ?: -
  • -
  • - Comparison operators: < <= == >= > -
  • -
  • - Common mathematic functions: abs ceil exp floor ln log10 logn max min sqrt pow -
  • -
  • - Trigonometric library functions: acosh acos asinh asin atanh atan atan2 cosh cos sinh sin tanh tan -
  • -
  • - Distance functions: haversin -
  • -
  • - Miscellaneous functions: min, max -
  • -
-
-
-
-`; - -exports[`ScriptingHelpFlyout should render nothing if not visible 1`] = `""`; diff --git a/src/ui/public/field_editor/components/scripting_help/__snapshots__/help_flyout.test.js.snap b/src/ui/public/field_editor/components/scripting_help/__snapshots__/help_flyout.test.js.snap new file mode 100644 index 0000000000000..5e222240624e4 --- /dev/null +++ b/src/ui/public/field_editor/components/scripting_help/__snapshots__/help_flyout.test.js.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ScriptingHelpFlyout should render normally 1`] = ` + + + , + "data-test-subj": "syntaxTab", + "id": "syntax", + "name": "Syntax", + } + } + tabs={ + Array [ + Object { + "content": , + "data-test-subj": "syntaxTab", + "id": "syntax", + "name": "Syntax", + }, + Object { + "content": , + "data-test-subj": "testTab", + "id": "test", + "name": "Preview results", + }, + ] + } + /> + + +`; + +exports[`ScriptingHelpFlyout should render nothing if not visible 1`] = `""`; diff --git a/src/ui/public/field_editor/components/scripting_help/help_flyout.test.js b/src/ui/public/field_editor/components/scripting_help/help_flyout.test.js index 0ffa92dde9ef2..bfcd2ea7e3c5c 100644 --- a/src/ui/public/field_editor/components/scripting_help/help_flyout.test.js +++ b/src/ui/public/field_editor/components/scripting_help/help_flyout.test.js @@ -26,11 +26,16 @@ jest.mock('ui/documentation_links', () => ({ getDocLink: (doc) => `(docLink for ${doc})`, })); +const indexPatternMock = {}; + describe('ScriptingHelpFlyout', () => { it('should render normally', async () => { const component = shallow( {}} /> ); @@ -39,7 +44,11 @@ describe('ScriptingHelpFlyout', () => { it('should render nothing if not visible', async () => { const component = shallow( - + {}} + /> ); expect(component).toMatchSnapshot(); From cd383e2dca2dec11357f8dbdf68ad12087d68308 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 16 Jul 2018 17:26:27 -0600 Subject: [PATCH 08/14] fix jest tests --- .../field_editor/__mocks__/ui/kfetch.js | 22 -------- .../field_editor/__mocks__/ui/metadata.js | 23 -------- .../field_editor/__tests__/field_editor.js | 0 .../editors/default/default.js | 23 ++++++-- .../editors/default/default.test.js | 35 ++++++++++++- .../public/field_editor/field_editor.test.js | 2 + .../__tests__/convert_sample_input.test.js | 52 ------------------- .../field_editor/lib/convert_sample_input.js | 39 -------------- src/ui/public/field_editor/lib/index.js | 1 - 9 files changed, 56 insertions(+), 141 deletions(-) delete mode 100644 src/ui/public/field_editor/__mocks__/ui/kfetch.js delete mode 100644 src/ui/public/field_editor/__mocks__/ui/metadata.js delete mode 100644 src/ui/public/field_editor/__tests__/field_editor.js delete mode 100644 src/ui/public/field_editor/lib/__tests__/convert_sample_input.test.js delete mode 100644 src/ui/public/field_editor/lib/convert_sample_input.js diff --git a/src/ui/public/field_editor/__mocks__/ui/kfetch.js b/src/ui/public/field_editor/__mocks__/ui/kfetch.js deleted file mode 100644 index 3a884a4c6d676..0000000000000 --- a/src/ui/public/field_editor/__mocks__/ui/kfetch.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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. - */ - -export const kfetch = () => { - return Promise.resolve('mock value'); -}; diff --git a/src/ui/public/field_editor/__mocks__/ui/metadata.js b/src/ui/public/field_editor/__mocks__/ui/metadata.js deleted file mode 100644 index 280e8a5a8f6b6..0000000000000 --- a/src/ui/public/field_editor/__mocks__/ui/metadata.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -export const metadata = { - branch: 'my-metadata-branch', - version: 'my-metadata-version' -}; diff --git a/src/ui/public/field_editor/__tests__/field_editor.js b/src/ui/public/field_editor/__tests__/field_editor.js deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/ui/public/field_editor/components/field_format_editor/editors/default/default.js b/src/ui/public/field_editor/components/field_format_editor/editors/default/default.js index 495bac06a4eac..0642c6d660f64 100644 --- a/src/ui/public/field_editor/components/field_format_editor/editors/default/default.js +++ b/src/ui/public/field_editor/components/field_format_editor/editors/default/default.js @@ -20,9 +20,26 @@ import { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { - convertSampleInput -} from '../../../../lib'; +export const convertSampleInput = (converter, inputs) => { + let error = null; + let samples = []; + + try { + samples = inputs.map(input => { + return { + input, + output: converter(input), + }; + }); + } catch(e) { + error = `An error occurred while trying to use this format configuration: ${e.message}`; + } + + return { + error, + samples, + }; +}; export class DefaultFormatEditor extends PureComponent { static propTypes = { diff --git a/src/ui/public/field_editor/components/field_format_editor/editors/default/default.test.js b/src/ui/public/field_editor/components/field_format_editor/editors/default/default.test.js index 2f1b1b2107eb0..069846ccfadc4 100644 --- a/src/ui/public/field_editor/components/field_format_editor/editors/default/default.test.js +++ b/src/ui/public/field_editor/components/field_format_editor/editors/default/default.test.js @@ -20,7 +20,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { DefaultFormatEditor } from './default'; +import { DefaultFormatEditor, convertSampleInput } from './default'; const fieldType = 'number'; const format = { @@ -31,6 +31,39 @@ const onChange = jest.fn(); const onError = jest.fn(); describe('DefaultFormatEditor', () => { + + describe('convertSampleInput', () => { + const converter = (input) => { + if(isNaN(input)) { + throw { + message: 'Input is not a number' + }; + } else { + return input * 2; + } + }; + + it('should convert a set of inputs', () => { + const inputs = [1, 10, 15]; + const output = convertSampleInput(converter, inputs); + + expect(output.error).toEqual(null); + expect(JSON.stringify(output.samples)).toEqual(JSON.stringify([ + { input: 1, output: 2 }, + { input: 10, output: 20 }, + { input: 15, output: 30 }, + ])); + }); + + it('should return error if converter throws one', () => { + const inputs = [1, 10, 15, 'invalid']; + const output = convertSampleInput(converter, inputs); + + expect(output.error).toEqual('An error occurred while trying to use this format configuration: Input is not a number'); + expect(JSON.stringify(output.samples)).toEqual(JSON.stringify([])); + }); + }); + it('should render nothing', async () => { const component = shallow( ({})); + import React from 'react'; import { shallow } from 'enzyme'; diff --git a/src/ui/public/field_editor/lib/__tests__/convert_sample_input.test.js b/src/ui/public/field_editor/lib/__tests__/convert_sample_input.test.js deleted file mode 100644 index 7a3fcea15e615..0000000000000 --- a/src/ui/public/field_editor/lib/__tests__/convert_sample_input.test.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 { convertSampleInput } from '../convert_sample_input'; - -const converter = (input) => { - if(isNaN(input)) { - throw { - message: 'Input is not a number' - }; - } else { - return input * 2; - } -}; - -describe('convertSampleInput', () => { - it('should convert a set of inputs', () => { - const inputs = [1, 10, 15]; - const output = convertSampleInput(converter, inputs); - - expect(output.error).toEqual(null); - expect(JSON.stringify(output.samples)).toEqual(JSON.stringify([ - { input: 1, output: 2 }, - { input: 10, output: 20 }, - { input: 15, output: 30 }, - ])); - }); - - it('should return error if converter throws one', () => { - const inputs = [1, 10, 15, 'invalid']; - const output = convertSampleInput(converter, inputs); - - expect(output.error).toEqual('An error occurred while trying to use this format configuration: Input is not a number'); - expect(JSON.stringify(output.samples)).toEqual(JSON.stringify([])); - }); -}); diff --git a/src/ui/public/field_editor/lib/convert_sample_input.js b/src/ui/public/field_editor/lib/convert_sample_input.js deleted file mode 100644 index 58d1917afc574..0000000000000 --- a/src/ui/public/field_editor/lib/convert_sample_input.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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. - */ - -export const convertSampleInput = (converter, inputs) => { - let error = null; - let samples = []; - - try { - samples = inputs.map(input => { - return { - input, - output: converter(input), - }; - }); - } catch(e) { - error = `An error occurred while trying to use this format configuration: ${e.message}`; - } - - return { - error, - samples, - }; -}; diff --git a/src/ui/public/field_editor/lib/index.js b/src/ui/public/field_editor/lib/index.js index 5838ecfe93da6..5e12d51763a18 100644 --- a/src/ui/public/field_editor/lib/index.js +++ b/src/ui/public/field_editor/lib/index.js @@ -19,5 +19,4 @@ export { copyField } from './copy_field'; export { getDefaultFormat } from './get_default_format'; -export { convertSampleInput } from './convert_sample_input'; export { executeScript, isScriptValid } from './validate_script'; From 0e4d2c74d1c0a73ca2028d03ec8b39f216412af6 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 16 Jul 2018 18:57:49 -0600 Subject: [PATCH 09/14] only validate script when field is scripted --- src/ui/public/field_editor/field_editor.js | 32 ++++++++++++---------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index 2abfaf386a351..98164be32a197 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -497,23 +497,25 @@ export class FieldEditor extends PureComponent { const { indexPattern } = this.props; const { fieldFormatId } = this.state; - this.setState({ - isSaving: true - }); - - const isValid = await isScriptValid({ - name: field.name, - lang: field.lang, - script: field.script, - indexPatternTitle: indexPattern.title - }); - - if (!isValid) { + if (field.scripted) { this.setState({ - hasScriptError: true, - isSaving: false + isSaving: true + }); + + const isValid = await isScriptValid({ + name: field.name, + lang: field.lang, + script: field.script, + indexPatternTitle: indexPattern.title }); - return; + + if (!isValid) { + this.setState({ + hasScriptError: true, + isSaving: false + }); + return; + } } const { redirectAway } = this.props.helpers; From 24219cfb86eb10bc2b8c197b48f9a01fb3ff3c33 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Jul 2018 09:30:23 -0600 Subject: [PATCH 10/14] only add script callouts and help flyout to DOM for scripted fields --- src/ui/public/field_editor/field_editor.js | 37 +++++++++++++++------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index 98164be32a197..1b53ca401801c 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -559,6 +559,29 @@ export class FieldEditor extends PureComponent { render() { const { isReady, isCreating, scriptingLangs, field, showScriptingHelp } = this.state; + let scriptDisabledCallout; + let scriptWarningCallout; + let scriptHelpFlyout; + if (field.scripted) { + scriptDisabledCallout = ( + + ); + scriptWarningCallout = ( + + ); + scriptHelpFlyout = ( + + ); + } + return isReady ? (
@@ -566,17 +589,9 @@ export class FieldEditor extends PureComponent { - - - + {scriptDisabledCallout} + {scriptWarningCallout} + {scriptHelpFlyout} {this.renderName()} {this.renderLanguage()} {this.renderType()} From c71bfce0f5f96f4ae925a88d2211a1ef94b4091c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Jul 2018 11:54:14 -0600 Subject: [PATCH 11/14] move scripting stuff to its own render method --- .../__snapshots__/field_editor.test.js.snap | 258 +++++++++--------- src/ui/public/field_editor/field_editor.js | 53 ++-- 2 files changed, 159 insertions(+), 152 deletions(-) diff --git a/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap b/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap index 8cfa5fe1cf9bf..ff1fe2105f22a 100644 --- a/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap +++ b/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap @@ -11,27 +11,29 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` size="m" /> - - - + + + + isVisible={false} + lang="painless" + onClose={[Function]} + /> + - - - + + + + isVisible={false} + lang="painless" + name="test" + onClose={[Function]} + script="doc.test.value" + /> + - - - + + + + isVisible={false} + lang="painless" + name="foobar" + onClose={[Function]} + /> + - - - + + + + isVisible={false} + lang="testlang" + name="test" + onClose={[Function]} + script="doc.test.value" + /> + diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index 1b53ca401801c..5dc5cbbf547a4 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -476,6 +476,30 @@ export class FieldEditor extends PureComponent { ); } + renderScriptingPanels = () => { + const { scriptingLangs, field, showScriptingHelp } = this.state; + + if (!field.scripted) { + return; + } + + return ( + + + + + + ); + } + deleteField = () => { const { redirectAway } = this.props.helpers; const { indexPattern } = this.props; @@ -557,30 +581,7 @@ export class FieldEditor extends PureComponent { } render() { - const { isReady, isCreating, scriptingLangs, field, showScriptingHelp } = this.state; - - let scriptDisabledCallout; - let scriptWarningCallout; - let scriptHelpFlyout; - if (field.scripted) { - scriptDisabledCallout = ( - - ); - scriptWarningCallout = ( - - ); - scriptHelpFlyout = ( - - ); - } + const { isReady, isCreating, field } = this.state; return isReady ? (
@@ -589,9 +590,7 @@ export class FieldEditor extends PureComponent { - {scriptDisabledCallout} - {scriptWarningCallout} - {scriptHelpFlyout} + {this.renderScriptingPanels()} {this.renderName()} {this.renderLanguage()} {this.renderType()} From c3af10cd48f1ae98f6f5b7a8a21948067c5533b0 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Jul 2018 12:18:45 -0600 Subject: [PATCH 12/14] right justify delete button to have same style as delete button for user management --- .../__snapshots__/field_editor.test.js.snap | 244 ++++++++++-------- src/ui/public/field_editor/field_editor.js | 72 +++--- 2 files changed, 171 insertions(+), 145 deletions(-) diff --git a/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap b/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap index ff1fe2105f22a..1259f9463b6ba 100644 --- a/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap +++ b/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap @@ -179,31 +179,33 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` onChange={[Function]} /> - - - + + - Create field - - - - + Create field + + + - Cancel - - - + + Cancel + + + + - - - - Save field - - - - + + - Cancel - - - - + Save field + + + - Delete - - - + + Cancel + + + + + + + Delete + + + + + + - - - + + - Create field - - - - + Create field + + + - Cancel - - - + + Cancel + + + + - - - - Save field - - - - + + - Cancel - - - - + Save field + + + - Delete - - - + + Cancel + + + + + + + Delete + + + + + + - - - {isCreating ? 'Create field' : 'Save field'} - - - - - Cancel - - - { - !isCreating && field.scripted ? ( - - - Delete - - - ) : null - } - + + + + + {isCreating ? 'Create field' : 'Save field'} + + + + + Cancel + + + { + !isCreating && field.scripted ? ( + + + + + Delete + + + + + ) : null + } + + ); } From 56392150bde9b6976538fa515b4967dda546f792 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Jul 2018 12:34:56 -0600 Subject: [PATCH 13/14] use data-test-subj key to find invalid script error msg in functional tests --- src/ui/public/field_editor/field_editor.js | 2 +- test/functional/apps/management/_scripted_fields.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index 2f909d9f98754..06498ae237cd9 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -355,7 +355,7 @@ export class FieldEditor extends PureComponent { const { field, hasScriptError } = this.state; const isInvalid = !field.script || !field.script.trim() || hasScriptError; const errorMsg = hasScriptError - ? 'Script is invalid. View script preview for details' + ? (Script is invalid. View script preview for details) : 'Script is required'; return field.scripted ? ( diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index c3847619543de..0849d2286dbf3 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }) { const log = getService('log'); const remote = getService('remote'); const retry = getService('retry'); - const find = getService('find'); + const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'header', 'settings', 'visualize', 'discover']); describe('scripted fields', () => { @@ -66,8 +66,8 @@ export default function ({ getService, getPageObjects }) { await PageObjects.settings.setScriptedFieldScript(`doc['iHaveNoClosingTick].value`); await PageObjects.settings.clickSaveScriptedField(); await retry.try(async () => { - const formErrorExists = await find.existsByDisplayedByCssSelector('.euiFormErrorText'); - expect(formErrorExists).to.be(true); + const invalidScriptErrorExists = await testSubjects.exists('invalidScriptError'); + expect(invalidScriptErrorExists).to.be(true); }); }); From 568f2a65cc405fb939d7a16a9bcbb143af482b9c Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Jul 2018 12:51:21 -0600 Subject: [PATCH 14/14] move help flyout link out of formRow help text and into its own row --- .../__snapshots__/field_editor.test.js.snap | 252 +++++++++--------- src/ui/public/field_editor/field_editor.js | 48 ++-- 2 files changed, 146 insertions(+), 154 deletions(-) diff --git a/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap b/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap index 1259f9463b6ba..292db61bbea44 100644 --- a/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap +++ b/src/ui/public/field_editor/__snapshots__/field_editor.test.js.snap @@ -146,39 +146,37 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` onChange={[Function]} /> - - - - - - - Get help with the syntax and preview the results of your script - - - - } - isInvalid={true} - label="Script" - > - + - + label="Script" + > + + + + + + Access fields with + + doc['some_field'].value + + . + +
+ + Get help with the syntax and preview the results of your script. + +
+
+ - - - - - - - Get help with the syntax and preview the results of your script - - - - } - isInvalid={false} - label="Script" - > - + - + label="Script" + > + + + + + + Access fields with + + doc['some_field'].value + + . + +
+ + Get help with the syntax and preview the results of your script. + +
+
+ - - - - - - - Get help with the syntax and preview the results of your script - - - - } - isInvalid={true} - label="Script" - > - + - + label="Script" + > + + + + + + Access fields with + + doc['some_field'].value + + . + +
+ + Get help with the syntax and preview the results of your script. + +
+
+ - - - - - - - Get help with the syntax and preview the results of your script - - - - } - isInvalid={false} - label="Script" - > - + - + label="Script" + > + + + + + + Access fields with + + doc['some_field'].value + + . + +
+ + Get help with the syntax and preview the results of your script. + +
+
+ - - - - - - - Get help with the syntax and preview the results of your script - - - - )} - isInvalid={isInvalid} - error={isInvalid ? errorMsg : null} - > - + - + error={isInvalid ? errorMsg : null} + > + + + + + + Access fields with {`doc['some_field'].value`}. +
+ + Get help with the syntax and preview the results of your script. + +
+
+ + ) : null; }