merge conflicts (#20959)

This commit is contained in:
Nathan Reese 2018-07-18 21:11:48 -06:00 committed by GitHub
parent d19079f6e3
commit 8ea7af9511
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1252 additions and 609 deletions

View file

@ -11,16 +11,29 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = `
size="m"
/>
<eui-form>
<scripting-disabled-callOut
isVisible={false}
/>
<scripting-warning-callOut
isVisible={true}
/>
<scripting-help-flyout
isVisible={false}
onClose={[Function]}
/>
<React.Fragment>
<scripting-disabled-callOut
isVisible={false}
/>
<scripting-warning-callOut
isVisible={true}
/>
<ScriptingHelpFlyout
executeScript={[Function]}
indexPattern={
Object {
"fields": Array [
Object {
"name": "foobar",
},
],
}
}
isVisible={false}
lang="painless"
onClose={[Function]}
/>
</React.Fragment>
<eui-form-row
error="Name is required"
helpText={null}
@ -133,48 +146,64 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = `
onChange={[Function]}
/>
</eui-form-row>
<eui-form-row
error="Script is required"
helpText={
<eui-link
onClick={[Function]}
>
Scripting help
</eui-link>
}
isInvalid={true}
label="Script"
>
<eui-textArea
data-test-subj="editorFieldScript"
<React.Fragment>
<eui-form-row
error="Script is required"
isInvalid={true}
onChange={[Function]}
/>
label="Script"
>
<eui-textArea
data-test-subj="editorFieldScript"
isInvalid={true}
onChange={[Function]}
/>
</eui-form-row>
<eui-form-row>
<React.Fragment>
<eui-text>
Access fields with
<code>
doc['some_field'].value
</code>
.
</eui-text>
<br />
<eui-link
data-test-subj="scriptedFieldsHelpLink"
onClick={[Function]}
>
Get help with the syntax and preview the results of your script.
</eui-link>
</React.Fragment>
</eui-form-row>
</React.Fragment>
<eui-form-row>
<eui-flex-group>
<eui-flex-item
grow={false}
>
<eui-button
data-test-subj="fieldSaveButton"
fill={true}
isDisabled={true}
isLoading={false}
onClick={[Function]}
>
Create field
</eui-button>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
data-test-subj="fieldCancelButton"
onClick={[Function]}
>
Cancel
</eui-button-empty>
</eui-flex-item>
</eui-flex-group>
</eui-form-row>
<eui-flex-group>
<eui-flex-item
grow={false}
>
<eui-button
data-test-subj="fieldSaveButton"
fill={true}
isDisabled={true}
onClick={[Function]}
>
Create field
</eui-button>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
data-test-subj="fieldCancelButton"
onClick={[Function]}
>
Cancel
</eui-button-empty>
</eui-flex-item>
</eui-flex-group>
</eui-form>
<eui-spacer
size="l"
@ -193,16 +222,39 @@ exports[`FieldEditor should render edit scripted field correctly 1`] = `
size="m"
/>
<eui-form>
<scripting-disabled-callOut
isVisible={false}
/>
<scripting-warning-callOut
isVisible={true}
/>
<scripting-help-flyout
isVisible={false}
onClose={[Function]}
/>
<React.Fragment>
<scripting-disabled-callOut
isVisible={false}
/>
<scripting-warning-callOut
isVisible={true}
/>
<ScriptingHelpFlyout
executeScript={[Function]}
indexPattern={
Object {
"fields": Array [
Object {
"name": "foobar",
},
Object {
"format": Format {},
"lang": "painless",
"name": "test",
"script": "doc.test.value",
"scripted": true,
"type": "number",
},
],
}
}
isVisible={false}
lang="painless"
name="test"
onClose={[Function]}
script="doc.test.value"
/>
</React.Fragment>
<eui-form-row
helpText={null}
label="Language"
@ -301,59 +353,81 @@ exports[`FieldEditor should render edit scripted field correctly 1`] = `
onChange={[Function]}
/>
</eui-form-row>
<eui-form-row
error={null}
helpText={
<eui-link
onClick={[Function]}
>
Scripting help
</eui-link>
}
isInvalid={false}
label="Script"
>
<eui-textArea
data-test-subj="editorFieldScript"
<React.Fragment>
<eui-form-row
error={null}
isInvalid={false}
onChange={[Function]}
value="doc.test.value"
/>
label="Script"
>
<eui-textArea
data-test-subj="editorFieldScript"
isInvalid={false}
onChange={[Function]}
value="doc.test.value"
/>
</eui-form-row>
<eui-form-row>
<React.Fragment>
<eui-text>
Access fields with
<code>
doc['some_field'].value
</code>
.
</eui-text>
<br />
<eui-link
data-test-subj="scriptedFieldsHelpLink"
onClick={[Function]}
>
Get help with the syntax and preview the results of your script.
</eui-link>
</React.Fragment>
</eui-form-row>
</React.Fragment>
<eui-form-row>
<eui-flex-group>
<eui-flex-item
grow={false}
>
<eui-button
data-test-subj="fieldSaveButton"
fill={true}
isDisabled={false}
isLoading={false}
onClick={[Function]}
>
Save field
</eui-button>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
data-test-subj="fieldCancelButton"
onClick={[Function]}
>
Cancel
</eui-button-empty>
</eui-flex-item>
<eui-flex-item>
<eui-flex-group
justifyContent="flexEnd"
>
<eui-flex-item
grow={false}
>
<eui-button-empty
color="danger"
onClick={[Function]}
>
Delete
</eui-button-empty>
</eui-flex-item>
</eui-flex-group>
</eui-flex-item>
</eui-flex-group>
</eui-form-row>
<eui-flex-group>
<eui-flex-item
grow={false}
>
<eui-button
data-test-subj="fieldSaveButton"
fill={true}
isDisabled={false}
onClick={[Function]}
>
Save field
</eui-button>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
data-test-subj="fieldCancelButton"
onClick={[Function]}
>
Cancel
</eui-button-empty>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
color="danger"
onClick={[Function]}
>
Delete
</eui-button-empty>
</eui-flex-item>
</eui-flex-group>
</eui-form>
<eui-spacer
size="l"
@ -372,16 +446,46 @@ exports[`FieldEditor should show conflict field warning 1`] = `
size="m"
/>
<eui-form>
<scripting-disabled-callOut
isVisible={false}
/>
<scripting-warning-callOut
isVisible={true}
/>
<scripting-help-flyout
isVisible={false}
onClose={[Function]}
/>
<React.Fragment>
<scripting-disabled-callOut
isVisible={false}
/>
<scripting-warning-callOut
isVisible={true}
/>
<ScriptingHelpFlyout
executeScript={[Function]}
indexPattern={
Object {
"fields": Array [
Object {
"name": "foobar",
},
Object {
"format": Format {},
"lang": "painless",
"name": "test",
"script": "doc.test.value",
"scripted": true,
"type": "number",
},
Object {
"format": Format {},
"lang": "testlang",
"name": "test",
"script": "doc.test.value",
"scripted": true,
"type": "number",
},
],
}
}
isVisible={false}
lang="painless"
name="foobar"
onClose={[Function]}
/>
</React.Fragment>
<eui-form-row
error={null}
helpText={
@ -511,48 +615,64 @@ exports[`FieldEditor should show conflict field warning 1`] = `
onChange={[Function]}
/>
</eui-form-row>
<eui-form-row
error="Script is required"
helpText={
<eui-link
onClick={[Function]}
>
Scripting help
</eui-link>
}
isInvalid={true}
label="Script"
>
<eui-textArea
data-test-subj="editorFieldScript"
<React.Fragment>
<eui-form-row
error="Script is required"
isInvalid={true}
onChange={[Function]}
/>
label="Script"
>
<eui-textArea
data-test-subj="editorFieldScript"
isInvalid={true}
onChange={[Function]}
/>
</eui-form-row>
<eui-form-row>
<React.Fragment>
<eui-text>
Access fields with
<code>
doc['some_field'].value
</code>
.
</eui-text>
<br />
<eui-link
data-test-subj="scriptedFieldsHelpLink"
onClick={[Function]}
>
Get help with the syntax and preview the results of your script.
</eui-link>
</React.Fragment>
</eui-form-row>
</React.Fragment>
<eui-form-row>
<eui-flex-group>
<eui-flex-item
grow={false}
>
<eui-button
data-test-subj="fieldSaveButton"
fill={true}
isDisabled={true}
isLoading={false}
onClick={[Function]}
>
Create field
</eui-button>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
data-test-subj="fieldCancelButton"
onClick={[Function]}
>
Cancel
</eui-button-empty>
</eui-flex-item>
</eui-flex-group>
</eui-form-row>
<eui-flex-group>
<eui-flex-item
grow={false}
>
<eui-button
data-test-subj="fieldSaveButton"
fill={true}
isDisabled={true}
onClick={[Function]}
>
Create field
</eui-button>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
data-test-subj="fieldCancelButton"
onClick={[Function]}
>
Cancel
</eui-button-empty>
</eui-flex-item>
</eui-flex-group>
</eui-form>
<eui-spacer
size="l"
@ -571,16 +691,47 @@ exports[`FieldEditor should show deprecated lang warning 1`] = `
size="m"
/>
<eui-form>
<scripting-disabled-callOut
isVisible={false}
/>
<scripting-warning-callOut
isVisible={true}
/>
<scripting-help-flyout
isVisible={false}
onClose={[Function]}
/>
<React.Fragment>
<scripting-disabled-callOut
isVisible={false}
/>
<scripting-warning-callOut
isVisible={true}
/>
<ScriptingHelpFlyout
executeScript={[Function]}
indexPattern={
Object {
"fields": Array [
Object {
"name": "foobar",
},
Object {
"format": Format {},
"lang": "painless",
"name": "test",
"script": "doc.test.value",
"scripted": true,
"type": "number",
},
Object {
"format": Format {},
"lang": "testlang",
"name": "test",
"script": "doc.test.value",
"scripted": true,
"type": "number",
},
],
}
}
isVisible={false}
lang="testlang"
name="test"
onClose={[Function]}
script="doc.test.value"
/>
</React.Fragment>
<eui-form-row
helpText={
<span>
@ -735,59 +886,81 @@ exports[`FieldEditor should show deprecated lang warning 1`] = `
onChange={[Function]}
/>
</eui-form-row>
<eui-form-row
error={null}
helpText={
<eui-link
onClick={[Function]}
>
Scripting help
</eui-link>
}
isInvalid={false}
label="Script"
>
<eui-textArea
data-test-subj="editorFieldScript"
<React.Fragment>
<eui-form-row
error={null}
isInvalid={false}
onChange={[Function]}
value="doc.test.value"
/>
label="Script"
>
<eui-textArea
data-test-subj="editorFieldScript"
isInvalid={false}
onChange={[Function]}
value="doc.test.value"
/>
</eui-form-row>
<eui-form-row>
<React.Fragment>
<eui-text>
Access fields with
<code>
doc['some_field'].value
</code>
.
</eui-text>
<br />
<eui-link
data-test-subj="scriptedFieldsHelpLink"
onClick={[Function]}
>
Get help with the syntax and preview the results of your script.
</eui-link>
</React.Fragment>
</eui-form-row>
</React.Fragment>
<eui-form-row>
<eui-flex-group>
<eui-flex-item
grow={false}
>
<eui-button
data-test-subj="fieldSaveButton"
fill={true}
isDisabled={false}
isLoading={false}
onClick={[Function]}
>
Save field
</eui-button>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
data-test-subj="fieldCancelButton"
onClick={[Function]}
>
Cancel
</eui-button-empty>
</eui-flex-item>
<eui-flex-item>
<eui-flex-group
justifyContent="flexEnd"
>
<eui-flex-item
grow={false}
>
<eui-button-empty
color="danger"
onClick={[Function]}
>
Delete
</eui-button-empty>
</eui-flex-item>
</eui-flex-group>
</eui-flex-item>
</eui-flex-group>
</eui-form-row>
<eui-flex-group>
<eui-flex-item
grow={false}
>
<eui-button
data-test-subj="fieldSaveButton"
fill={true}
isDisabled={false}
onClick={[Function]}
>
Save field
</eui-button>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
data-test-subj="fieldCancelButton"
onClick={[Function]}
>
Cancel
</eui-button-empty>
</eui-flex-item>
<eui-flex-item
grow={false}
>
<eui-button-empty
color="danger"
onClick={[Function]}
>
Delete
</eui-button-empty>
</eui-flex-item>
</eui-flex-group>
</eui-form>
<eui-spacer
size="l"

View file

@ -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 = {

View file

@ -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(
<DefaultFormatEditor

View file

@ -1,134 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ScriptingHelpFlyout should render normally 1`] = `
<EuiFlyout
hideCloseButton={false}
onClose={[Function]}
ownFocus={false}
size="s"
>
<EuiFlyoutBody>
<EuiText
grow={true}
>
<h3>
Scripting help
</h3>
<p>
By default, Kibana scripted fields use
<EuiLink
color="primary"
href="(docLink for scriptedFields.painless)"
target="_window"
type="button"
>
Painless
<EuiIcon
size="m"
type="link"
/>
</EuiLink>
, a simple and secure scripting language designed specifically for use with Elasticsearch, to access values in the document use the following format:
</p>
<p>
<EuiCode>
doc['some_field'].value
</EuiCode>
</p>
<p>
Painless is powerful but easy to use. It provides access to many
<EuiLink
color="primary"
href="(docLink for scriptedFields.painlessApi)"
target="_window"
type="button"
>
native Java APIs
<EuiIcon
size="m"
type="link"
/>
</EuiLink>
. Read up on its
<EuiLink
color="primary"
href="(docLink for scriptedFields.painlessSyntax)"
target="_window"
type="button"
>
syntax
<EuiIcon
size="m"
type="link"
/>
</EuiLink>
and you'll be up to speed in no time!
</p>
<p>
Kibana currently imposes one special limitation on the painless scripts you write. They cannot contain named functions.
</p>
<p>
Coming from an older version of Kibana? The
<EuiLink
color="primary"
href="(docLink for scriptedFields.luceneExpressions)"
target="_window"
type="button"
>
Lucene Expressions
<EuiIcon
size="m"
type="link"
/>
</EuiLink>
you know and love are still available. Lucene expressions are a lot like JavaScript, but limited to basic arithmetic, bitwise and comparison operations.
</p>
<p>
There are a few limitations when using Lucene Expressions:
</p>
<ul>
<li>
Only numeric, boolean, date, and geo_point fields may be accessed
</li>
<li>
Stored fields are not available
</li>
<li>
If a field is sparse (only some documents contain a value), documents missing the field will have a value of 0
</li>
</ul>
<p>
Here are all the operations available to lucene expressions:
</p>
<ul>
<li>
Arithmetic operators: + - * / %
</li>
<li>
Bitwise operators: | & ^ ~ &lt;&lt; &gt;&gt; &gt;&gt;&gt;
</li>
<li>
Boolean operators (including the ternary operator): && || ! ?:
</li>
<li>
Comparison operators: &lt; &lt;= == &gt;= &gt;
</li>
<li>
Common mathematic functions: abs ceil exp floor ln log10 logn max min sqrt pow
</li>
<li>
Trigonometric library functions: acosh acos asinh asin atanh atan atan2 cosh cos sinh sin tanh tan
</li>
<li>
Distance functions: haversin
</li>
<li>
Miscellaneous functions: min, max
</li>
</ul>
</EuiText>
</EuiFlyoutBody>
</EuiFlyout>
`;
exports[`ScriptingHelpFlyout should render nothing if not visible 1`] = `""`;

View file

@ -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 ? (
<EuiFlyout onClose={onClose} size="s">
<EuiFlyoutBody>
<EuiText>
<h3>Scripting help</h3>
<p>
By default, Kibana scripted fields use {(
<EuiLink
target="_window"
href={getDocLink('scriptedFields.painless')}
>
Painless <EuiIcon type="link"/>
</EuiLink>
)}, a simple and secure scripting language designed specifically for use with Elasticsearch,
to access values in the document use the following format:
</p>
<p>
<EuiCode>doc[&apos;some_field&apos;].value</EuiCode>
</p>
<p>
Painless is powerful but easy to use. It provides access to many {(
<EuiLink
target="_window"
href={getDocLink('scriptedFields.painlessApi')}
>
native Java APIs <EuiIcon type="link"/>
</EuiLink>
)}. Read up on its {(
<EuiLink
target="_window"
href={getDocLink('scriptedFields.painlessSyntax')}
>
syntax <EuiIcon type="link"/>
</EuiLink>
)} and you&apos;ll be up to speed in no time!
</p>
<p>
Kibana currently imposes one special limitation on the painless scripts you write. They cannot contain named functions.
</p>
<p>
Coming from an older version of Kibana? The {(
<EuiLink
target="_window"
href={getDocLink('scriptedFields.luceneExpressions')}
>
Lucene Expressions <EuiIcon type="link"/>
</EuiLink>
)} you know and love are still available. Lucene expressions are a lot like JavaScript,
but limited to basic arithmetic, bitwise and comparison operations.
</p>
<p>
There are a few limitations when using Lucene Expressions:
</p>
<ul>
<li> Only numeric, boolean, date, and geo_point fields may be accessed</li>
<li> Stored fields are not available</li>
<li> If a field is sparse (only some documents contain a value), documents missing the field will have a value of 0</li>
</ul>
<p>
Here are all the operations available to lucene expressions:
</p>
<ul>
<li> Arithmetic operators: + - * / %</li>
<li> Bitwise operators: | & ^ ~ &#x3C;&#x3C; &#x3E;&#x3E; &#x3E;&#x3E;&#x3E;</li>
<li> Boolean operators (including the ternary operator): && || ! ?:</li>
<li> Comparison operators: &#x3C; &#x3C;= == &#x3E;= &#x3E;</li>
<li> Common mathematic functions: abs ceil exp floor ln log10 logn max min sqrt pow</li>
<li> Trigonometric library functions: acosh acos asinh asin atanh atan atan2 cosh cos sinh sin tanh tan</li>
<li> Distance functions: haversin</li>
<li> Miscellaneous functions: min, max</li>
</ul>
</EuiText>
</EuiFlyoutBody>
</EuiFlyout>
) : null;
};
ScriptingHelpFlyout.displayName = 'ScriptingHelpFlyout';

View file

@ -19,4 +19,3 @@
export { ScriptingDisabledCallOut } from './disabled_call_out';
export { ScriptingWarningCallOut } from './warning_call_out';
export { ScriptingHelpFlyout } from './help_flyout';

View file

@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ScriptingHelpFlyout should render normally 1`] = `
<EuiFlyout
data-test-subj="scriptedFieldsHelpFlyout"
hideCloseButton={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutBody>
<EuiTabbedContent
initialSelectedTab={
Object {
"content": <Unknown />,
"data-test-subj": "syntaxTab",
"id": "syntax",
"name": "Syntax",
}
}
tabs={
Array [
Object {
"content": <Unknown />,
"data-test-subj": "syntaxTab",
"id": "syntax",
"name": "Syntax",
},
Object {
"content": <TestScript
executeScript={[Function]}
indexPattern={Object {}}
lang="painless"
name="myScriptedField"
script={undefined}
/>,
"data-test-subj": "testTab",
"id": "test",
"name": "Preview results",
},
]
}
/>
</EuiFlyoutBody>
</EuiFlyout>
`;
exports[`ScriptingHelpFlyout should render nothing if not visible 1`] = `""`;

View file

@ -0,0 +1,81 @@
/*
* 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,
executeScript,
}) => {
const tabs = [{
id: 'syntax',
name: 'Syntax',
['data-test-subj']: 'syntaxTab',
content: <ScriptingSyntax />,
}, {
id: 'test',
name: 'Preview results',
['data-test-subj']: 'testTab',
content: (
<TestScript
indexPattern={indexPattern}
lang={lang}
name={name}
script={script}
executeScript={executeScript}
/>
),
}];
return isVisible ? (
<EuiFlyout onClose={onClose} data-test-subj="scriptedFieldsHelpFlyout">
<EuiFlyoutBody>
<EuiTabbedContent
tabs={tabs}
initialSelectedTab={tabs[0]}
/>
</EuiFlyoutBody>
</EuiFlyout>
) : null;
};
ScriptingHelpFlyout.displayName = 'ScriptingHelpFlyout';
ScriptingHelpFlyout.propTypes = {
indexPattern: PropTypes.object.isRequired,
lang: PropTypes.string.isRequired,
name: PropTypes.string,
script: PropTypes.string,
executeScript: PropTypes.func.isRequired,
};

View file

@ -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(
<ScriptingHelpFlyout
isVisible={true}
indexPattern={indexPatternMock}
lang="painless"
executeScript={() => {}}
/>
);
@ -39,7 +44,11 @@ describe('ScriptingHelpFlyout', () => {
it('should render nothing if not visible', async () => {
const component = shallow(
<ScriptingHelpFlyout />
<ScriptingHelpFlyout
indexPattern={indexPatternMock}
lang="painless"
executeScript={() => {}}
/>
);
expect(component).toMatchSnapshot();

View file

@ -17,23 +17,4 @@
* 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,
};
};
export { ScriptingHelpFlyout } from './help_flyout';

View file

@ -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 = () => (
<Fragment>
<EuiSpacer />
<EuiText>
<h3>Syntax</h3>
<p>
By default, Kibana scripted fields use {(
<EuiLink
target="_window"
href={getDocLink('scriptedFields.painless')}
>
Painless <EuiIcon type="link"/>
</EuiLink>
)}, a simple and secure scripting language designed specifically for use with Elasticsearch,
to access values in the document use the following format:
</p>
<p>
<EuiCode>doc[&apos;some_field&apos;].value</EuiCode>
</p>
<p>
Painless is powerful but easy to use. It provides access to many {(
<EuiLink
target="_window"
href={getDocLink('scriptedFields.painlessApi')}
>
native Java APIs <EuiIcon type="link"/>
</EuiLink>
)}. Read up on its {(
<EuiLink
target="_window"
href={getDocLink('scriptedFields.painlessSyntax')}
>
syntax <EuiIcon type="link"/>
</EuiLink>
)} and you&apos;ll be up to speed in no time!
</p>
<p>
Kibana currently imposes one special limitation on the painless scripts you write. They cannot contain named functions.
</p>
<p>
Coming from an older version of Kibana? The {(
<EuiLink
target="_window"
href={getDocLink('scriptedFields.luceneExpressions')}
>
Lucene Expressions <EuiIcon type="link"/>
</EuiLink>
)} you know and love are still available. Lucene expressions are a lot like JavaScript,
but limited to basic arithmetic, bitwise and comparison operations.
</p>
<p>
There are a few limitations when using Lucene Expressions:
</p>
<ul>
<li> Only numeric, boolean, date, and geo_point fields may be accessed</li>
<li> Stored fields are not available</li>
<li> If a field is sparse (only some documents contain a value), documents missing the field will have a value of 0</li>
</ul>
<p>
Here are all the operations available to lucene expressions:
</p>
<ul>
<li> Arithmetic operators: <code>+ - * / %</code></li>
<li> Bitwise operators: <code>| & ^ ~ &#x3C;&#x3C; &#x3E;&#x3E; &#x3E;&#x3E;&#x3E;</code></li>
<li> Boolean operators (including the ternary operator): <code>&& || ! ?:</code></li>
<li> Comparison operators: <code>&#x3C; &#x3C;= == &#x3E;= &#x3E;</code></li>
<li> Common mathematic functions: <code>abs ceil exp floor ln log10 logn max min sqrt pow</code></li>
<li> Trigonometric library functions: <code>acosh acos asinh asin atanh atan atan2 cosh cos sinh sin tanh tan</code></li>
<li> Distance functions: <code>haversin</code></li>
<li> Miscellaneous functions: <code>min, max</code></li>
</ul>
</EuiText>
</Fragment>
);

View file

@ -0,0 +1,226 @@
/*
* 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, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import {
EuiButton,
EuiCodeBlock,
EuiComboBox,
EuiFormRow,
EuiText,
EuiSpacer,
EuiTitle,
EuiCallOut,
} from '@elastic/eui';
export class TestScript extends Component {
state = {
isLoading: false,
additionalFields: [],
}
componentDidMount() {
if (this.props.script) {
this.previewScript();
}
}
previewScript = async () => {
const {
indexPattern,
lang,
name,
script,
executeScript,
} = this.props;
if (!script || script.length === 0) {
return;
}
this.setState({
isLoading: true,
});
const scriptResponse = await executeScript({
name,
lang,
script,
indexPatternTitle: indexPattern.title,
additionalFields: this.state.additionalFields.map(option => {
return option.value;
})
});
if (scriptResponse.status !== 200) {
this.setState({
isLoading: false,
previewData: scriptResponse
});
return;
}
this.setState({
isLoading: false,
previewData: scriptResponse.hits.hits.map(hit => ({
_id: hit._id,
...hit._source,
...hit.fields,
})),
});
}
onAdditionalFieldsChange = (selectedOptions) => {
this.setState({
additionalFields: selectedOptions
});
}
renderPreview() {
const { previewData } = this.state;
if (!previewData) {
return null;
}
if (previewData.error) {
return (
<EuiCallOut
title="There's an error in your script"
color="danger"
iconType="cross"
>
<EuiCodeBlock
language="json"
className="scriptPreviewCodeBlock"
data-test-subj="scriptedFieldPreview"
>
{JSON.stringify(previewData.error, null, ' ')}
</EuiCodeBlock>
</EuiCallOut>
);
}
return (
<Fragment>
<EuiTitle size="xs"><p>First 10 results</p></EuiTitle>
<EuiSpacer size="s" />
<EuiCodeBlock
language="json"
className="scriptPreviewCodeBlock"
data-test-subj="scriptedFieldPreview"
>
{JSON.stringify(previewData, null, ' ')}
</EuiCodeBlock>
</Fragment>
);
}
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 (
<Fragment>
<EuiFormRow
label="Additional fields"
>
<EuiComboBox
placeholder="Select..."
options={fields}
selectedOptions={this.state.additionalFields}
onChange={this.onAdditionalFieldsChange}
data-test-subj="additionalFieldsSelect"
/>
</EuiFormRow>
<EuiButton
onClick={this.previewScript}
disabled={this.props.script ? false : true}
isLoading={this.state.isLoading}
data-test-subj="runScriptButton"
>
Run script
</EuiButton>
</Fragment>
);
}
render() {
return (
<Fragment>
<EuiSpacer />
<EuiText>
<h3>Preview results</h3>
<p>
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.
</p>
</EuiText>
<EuiSpacer />
{this.renderToolbar()}
<EuiSpacer />
{this.renderPreview()}
</Fragment>
);
}
}
TestScript.propTypes = {
indexPattern: PropTypes.object.isRequired,
lang: PropTypes.string.isRequired,
name: PropTypes.string,
script: PropTypes.string,
executeScript: PropTypes.func.isRequired,
};
TestScript.defaultProps = {
name: 'myScriptedField',
};

View file

@ -63,15 +63,18 @@ import {
import {
ScriptingDisabledCallOut,
ScriptingWarningCallOut,
ScriptingHelpFlyout,
} from './components/scripting_call_outs';
import {
ScriptingHelpFlyout,
} from './components/scripting_help';
import {
FieldFormatEditor
} from './components/field_format_editor';
import { FIELD_TYPES_BY_LANG, DEFAULT_FIELD_TYPES } from './constants';
import { copyField, getDefaultFormat } from './lib';
import { copyField, getDefaultFormat, executeScript, isScriptValid } from './lib';
export class FieldEditor extends PureComponent {
static propTypes = {
@ -109,6 +112,8 @@ export class FieldEditor extends PureComponent {
showScriptingHelp: false,
showDeleteModal: false,
hasFormatError: false,
hasScriptError: false,
isSaving: false,
};
this.supportedLangs = getSupportedScriptingLanguages();
this.deprecatedLangs = getDeprecatedScriptingLanguages();
@ -339,24 +344,46 @@ 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
? (<span data-test-subj="invalidScriptError">Script is invalid. View script preview for details</span>)
: 'Script is required';
return field.scripted ? (
<EuiFormRow
label="Script"
helpText={(<EuiLink onClick={this.showScriptingHelp}>Scripting help</EuiLink>)}
isInvalid={isInvalid}
error={isInvalid ? 'Script is required' : null}
>
<EuiTextArea
value={field.script}
data-test-subj="editorFieldScript"
onChange={(e) => { this.onFieldChange('script', e.target.value); }}
<Fragment>
<EuiFormRow
label="Script"
isInvalid={isInvalid}
/>
</EuiFormRow>
error={isInvalid ? errorMsg : null}
>
<EuiTextArea
value={field.script}
data-test-subj="editorFieldScript"
onChange={this.onScriptChange}
isInvalid={isInvalid}
/>
</EuiFormRow>
<EuiFormRow>
<Fragment>
<EuiText>Access fields with <code>{`doc['some_field'].value`}</code>.</EuiText>
<br />
<EuiLink onClick={this.showScriptingHelp} data-test-subj="scriptedFieldsHelpLink">
Get help with the syntax and preview the results of your script.
</EuiLink>
</Fragment>
</EuiFormRow>
</Fragment>
) : null;
}
@ -409,42 +436,73 @@ export class FieldEditor extends PureComponent {
}
renderActions() {
const { isCreating, field } = this.state;
const { isCreating, field, isSaving } = this.state;
const { redirectAway } = this.props.helpers;
return (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton
fill
onClick={this.saveField}
isDisabled={this.isSavingDisabled()}
data-test-subj="fieldSaveButton"
>
{isCreating ? 'Create field' : 'Save field'}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={redirectAway}
data-test-subj="fieldCancelButton"
>
Cancel
</EuiButtonEmpty>
</EuiFlexItem>
{
!isCreating && field.scripted ? (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="danger"
onClick={this.showDeleteModal}
>
Delete
</EuiButtonEmpty>
</EuiFlexItem>
) : null
}
</EuiFlexGroup>
<EuiFormRow>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton
fill
onClick={this.saveField}
isDisabled={this.isSavingDisabled()}
isLoading={isSaving}
data-test-subj="fieldSaveButton"
>
{isCreating ? 'Create field' : 'Save field'}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={redirectAway}
data-test-subj="fieldCancelButton"
>
Cancel
</EuiButtonEmpty>
</EuiFlexItem>
{
!isCreating && field.scripted ? (
<EuiFlexItem>
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="danger"
onClick={this.showDeleteModal}
>
Delete
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
) : null
}
</EuiFlexGroup>
</EuiFormRow>
);
}
renderScriptingPanels = () => {
const { scriptingLangs, field, showScriptingHelp } = this.state;
if (!field.scripted) {
return;
}
return (
<Fragment>
<ScriptingDisabledCallOut isVisible={!scriptingLangs.length} />
<ScriptingWarningCallOut isVisible />
<ScriptingHelpFlyout
isVisible={showScriptingHelp}
onClose={this.hideScriptingHelp}
indexPattern={this.props.indexPattern}
lang={field.lang}
name={field.name}
script={field.script}
executeScript={executeScript}
/>
</Fragment>
);
}
@ -464,12 +522,33 @@ 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();
if (field.scripted) {
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) {
@ -492,10 +571,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()))
@ -507,7 +587,7 @@ export class FieldEditor extends PureComponent {
}
render() {
const { isReady, isCreating, scriptingLangs, field, showScriptingHelp } = this.state;
const { isReady, isCreating, field } = this.state;
return isReady ? (
<div>
@ -516,12 +596,7 @@ export class FieldEditor extends PureComponent {
</EuiText>
<EuiSpacer size="m" />
<EuiForm>
<ScriptingDisabledCallOut isVisible={field.scripted && !scriptingLangs.length} />
<ScriptingWarningCallOut isVisible={field.scripted} />
<ScriptingHelpFlyout
isVisible={field.scripted && showScriptingHelp}
onClose={this.hideScriptingHelp}
/>
{this.renderScriptingPanels()}
{this.renderName()}
{this.renderLanguage()}
{this.renderType()}

View file

@ -17,6 +17,8 @@
* under the License.
*/
jest.mock('ui/kfetch', () => ({}));
import React from 'react';
import { shallow } from 'enzyme';

View file

@ -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([]));
});
});

View file

@ -19,4 +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';

View file

@ -0,0 +1,63 @@
/*
* 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 isScriptValid = async ({ name, lang, script, indexPatternTitle }) => {
const scriptResponse = await executeScript({ name, lang, script, indexPatternTitle });
if (scriptResponse.status !== 200) {
return false;
}
return true;
};

View file

@ -37,6 +37,7 @@ export default function ({ getService, getPageObjects }) {
const log = getService('log');
const remote = getService('remote');
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'header', 'settings', 'visualize', 'discover']);
describe('scripted fields', () => {
@ -57,6 +58,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 invalidScriptErrorExists = await testSubjects.exists('invalidScriptError');
expect(invalidScriptErrorExists).to.be(true);
});
});
describe('creating and using Lucence expression scripted fields', function describeIndexTests() {
const scriptedExpressionFieldName = 'ram_expr1';

View file

@ -0,0 +1,66 @@
/*
* 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);
});
});
}

View file

@ -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'));

View file

@ -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,54 @@ 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');
}
let scriptResults;
await retry.try(async () => {
scriptResults = await testSubjects.getVisibleText('scriptedFieldPreview');
});
return scriptResults;
}
async importFile(path, overwriteAll = true) {
log.debug(`importFile(${path})`);