Skip to content

Commit 68c07c7

Browse files
authored
handle EuiSearchBar query parse failures (#25235)
* handle EuiSearchBar query parse failures * I18n parse failure messages * review updates * more cleanup on settings search.test
1 parent 5366de4 commit 68c07c7

File tree

6 files changed

+221
-86
lines changed

6 files changed

+221
-86
lines changed

src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/__snapshots__/table.test.js.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
exports[`Table should render normally 1`] = `
44
<React.Fragment>
55
<EuiSearchBar
6+
box={
7+
Object {
8+
"data-test-subj": "savedObjectSearchBar",
9+
}
10+
}
611
filters={
712
Array [
813
Object {

src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
*/
1919

2020
import React from 'react';
21-
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
21+
import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
22+
import { findTestSubject } from '@elastic/eui/lib/test';
23+
import { keyCodes } from '@elastic/eui/lib/services';
2224

2325
jest.mock('ui/errors', () => ({
2426
SavedObjectNotFound: class SavedObjectNotFound extends Error {
@@ -39,37 +41,62 @@ jest.mock('ui/chrome', () => ({
3941

4042
import { Table } from '../table';
4143

44+
const defaultProps = {
45+
selectedSavedObjects: [1],
46+
selectionConfig: {
47+
onSelectionChange: () => {},
48+
},
49+
filterOptions: [{ value: 2 }],
50+
onDelete: () => {},
51+
onExport: () => {},
52+
getEditUrl: () => {},
53+
goInApp: () => {},
54+
pageIndex: 1,
55+
pageSize: 2,
56+
items: [3],
57+
itemId: 'id',
58+
totalItemCount: 3,
59+
onQueryChange: () => {},
60+
onTableChange: () => {},
61+
isSearching: false,
62+
onShowRelationships: () => {},
63+
};
64+
4265
describe('Table', () => {
4366
it('should render normally', () => {
44-
const props = {
45-
selectedSavedObjects: [1],
46-
selectionConfig: {
47-
onSelectionChange: () => {},
48-
},
49-
filterOptions: [{ value: 2 }],
50-
onDelete: () => {},
51-
onExport: () => {},
52-
getEditUrl: () => {},
53-
goInApp: () => {},
67+
const component = shallowWithIntl(
68+
<Table.WrappedComponent
69+
{...defaultProps}
70+
/>
71+
);
5472

55-
pageIndex: 1,
56-
pageSize: 2,
57-
items: [3],
58-
itemId: 'id',
59-
totalItemCount: 3,
60-
onQueryChange: () => {},
61-
onTableChange: () => {},
62-
isSearching: false,
73+
expect(component).toMatchSnapshot();
74+
});
6375

64-
onShowRelationships: () => {},
76+
it('should handle query parse error', () => {
77+
const onQueryChangeMock = jest.fn();
78+
const customizedProps = {
79+
...defaultProps,
80+
onQueryChange: onQueryChangeMock
6581
};
6682

67-
const component = shallowWithIntl(
83+
const component = mountWithIntl(
6884
<Table.WrappedComponent
69-
{...props}
85+
{...customizedProps}
7086
/>
7187
);
88+
const searchBar = findTestSubject(component, 'savedObjectSearchBar');
7289

73-
expect(component).toMatchSnapshot();
90+
// Send invalid query
91+
searchBar.simulate('keyup', { keyCode: keyCodes.ENTER, target: { value: '?' } });
92+
expect(onQueryChangeMock).toHaveBeenCalledTimes(0);
93+
expect(component.state().isSearchTextValid).toBe(false);
94+
95+
onQueryChangeMock.mockReset();
96+
97+
// Send valid query to ensure component can recover from invalid query
98+
searchBar.simulate('keyup', { keyCode: keyCodes.ENTER, target: { value: 'I am valid' } });
99+
expect(onQueryChangeMock).toHaveBeenCalledTimes(1);
100+
expect(component.state().isSearchTextValid).toBe(true);
74101
});
75102
});

src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import {
2727
EuiIcon,
2828
EuiLink,
2929
EuiSpacer,
30-
EuiToolTip
30+
EuiToolTip,
31+
EuiFormErrorText
3132
} from '@elastic/eui';
3233
import { getSavedObjectLabel, getSavedObjectIcon } from '../../../../lib';
3334
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
@@ -61,6 +62,27 @@ class TableUI extends PureComponent {
6162
onShowRelationships: PropTypes.func.isRequired,
6263
};
6364

65+
state = {
66+
isSearchTextValid: true,
67+
parseErrorMessage: null,
68+
}
69+
70+
onChange = ({ query, error }) => {
71+
if (error) {
72+
this.setState({
73+
isSearchTextValid: false,
74+
parseErrorMessage: error.message,
75+
});
76+
return;
77+
}
78+
79+
this.setState({
80+
isSearchTextValid: true,
81+
parseErrorMessage: null,
82+
});
83+
this.props.onQueryChange({ query });
84+
}
85+
6486
render() {
6587
const {
6688
pageIndex,
@@ -74,7 +96,6 @@ class TableUI extends PureComponent {
7496
onDelete,
7597
onExport,
7698
selectedSavedObjects,
77-
onQueryChange,
7899
onTableChange,
79100
goInApp,
80101
getEditUrl,
@@ -182,11 +203,25 @@ class TableUI extends PureComponent {
182203
},
183204
];
184205

206+
let queryParseError;
207+
if (!this.state.isSearchTextValid) {
208+
const parseErrorMsg = intl.formatMessage({
209+
id: 'kbn.management.objects.objectsTable.searchBar.unableToParseQueryErrorMessage',
210+
defaultMessage: 'Unable to parse query',
211+
});
212+
queryParseError = (
213+
<EuiFormErrorText>
214+
{`${parseErrorMsg}. ${this.state.parseErrorMessage}`}
215+
</EuiFormErrorText>
216+
);
217+
}
218+
185219
return (
186220
<Fragment>
187221
<EuiSearchBar
222+
box={{ 'data-test-subj': 'savedObjectSearchBar' }}
188223
filters={filters}
189-
onChange={onQueryChange}
224+
onChange={this.onChange}
190225
toolsRight={[
191226
<EuiButton
192227
key="deleteSO"
@@ -213,6 +248,7 @@ class TableUI extends PureComponent {
213248
</EuiButton>,
214249
]}
215250
/>
251+
{queryParseError}
216252
<EuiSpacer size="s" />
217253
<div data-test-subj="savedObjectsTable">
218254
<EuiBasicTable
Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,61 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`Search should render normally 1`] = `
4-
<EuiSearchBar
5-
box={
6-
Object {
7-
"aria-label": "Search advanced settings",
8-
"incremental": true,
9-
}
10-
}
11-
filters={
12-
Array [
4+
<React.Fragment>
5+
<EuiSearchBar
6+
box={
137
Object {
14-
"field": "category",
15-
"multiSelect": "or",
16-
"name": "Category",
17-
"options": Array [
18-
Object {
19-
"name": "General",
20-
"value": "general",
21-
},
22-
Object {
23-
"name": "Dashboard",
24-
"value": "dashboard",
25-
},
26-
Object {
27-
"name": "HiddenCategory",
28-
"value": "hiddenCategory",
29-
},
30-
Object {
31-
"name": "X-pack",
32-
"value": "x-pack",
8+
"aria-label": "Search advanced settings",
9+
"data-test-subj": "settingsSearchBar",
10+
"incremental": true,
11+
}
12+
}
13+
filters={
14+
Array [
15+
Object {
16+
"field": "category",
17+
"multiSelect": "or",
18+
"name": "Category",
19+
"options": Array [
20+
Object {
21+
"name": "General",
22+
"value": "general",
23+
},
24+
Object {
25+
"name": "Dashboard",
26+
"value": "dashboard",
27+
},
28+
Object {
29+
"name": "HiddenCategory",
30+
"value": "hiddenCategory",
31+
},
32+
Object {
33+
"name": "X-pack",
34+
"value": "x-pack",
35+
},
36+
],
37+
"type": "field_value_selection",
38+
},
39+
]
40+
}
41+
onChange={[Function]}
42+
query={
43+
Query {
44+
"ast": _AST {
45+
"_clauses": Array [],
46+
"_indexedClauses": Object {
47+
"field": Object {},
48+
"is": Object {},
49+
"term": Array [],
3350
},
34-
],
35-
"type": "field_value_selection",
36-
},
37-
]
38-
}
39-
onChange={[Function]}
40-
query={
41-
Query {
42-
"ast": _AST {
43-
"_clauses": Array [],
44-
"_indexedClauses": Object {
45-
"field": Object {},
46-
"is": Object {},
47-
"term": Array [],
4851
},
49-
},
50-
"syntax": Object {
51-
"parse": [Function],
52-
"print": [Function],
53-
},
54-
"text": "",
52+
"syntax": Object {
53+
"parse": [Function],
54+
"print": [Function],
55+
},
56+
"text": "",
57+
}
5558
}
56-
}
57-
/>
59+
/>
60+
</React.Fragment>
5861
`;

src/core_plugins/kibana/public/management/sections/settings/components/search/search.js

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
* under the License.
1818
*/
1919

20-
import React, { PureComponent } from 'react';
20+
import React, { Fragment, PureComponent } from 'react';
2121
import PropTypes from 'prop-types';
2222
import { injectI18n } from '@kbn/i18n/react';
2323

2424
import {
2525
EuiSearchBar,
26+
EuiFormErrorText,
2627
} from '@elastic/eui';
2728

2829
import { getCategoryName } from '../../lib';
@@ -46,11 +47,33 @@ class SearchUI extends PureComponent {
4647
});
4748
}
4849

50+
state = {
51+
isSearchTextValid: true,
52+
parseErrorMessage: null,
53+
}
54+
55+
onChange = ({ query, error }) => {
56+
if (error) {
57+
this.setState({
58+
isSearchTextValid: false,
59+
parseErrorMessage: error.message,
60+
});
61+
return;
62+
}
63+
64+
this.setState({
65+
isSearchTextValid: true,
66+
parseErrorMessage: null,
67+
});
68+
this.props.onQueryChange({ query });
69+
}
70+
4971
render() {
50-
const { query, onQueryChange, intl } = this.props;
72+
const { query, intl } = this.props;
5173

5274
const box = {
5375
incremental: true,
76+
'data-test-subj': 'settingsSearchBar',
5477
'aria-label': intl.formatMessage({
5578
id: 'kbn.management.settings.searchBarAriaLabel',
5679
defaultMessage: 'Search advanced settings',
@@ -71,14 +94,29 @@ class SearchUI extends PureComponent {
7194
}
7295
];
7396

74-
return (
75-
<EuiSearchBar
76-
box={box}
77-
filters={filters}
78-
onChange={onQueryChange}
79-
query={query}
80-
/>
97+
let queryParseError;
98+
if (!this.state.isSearchTextValid) {
99+
const parseErrorMsg = intl.formatMessage({
100+
id: 'kbn.management.settings.searchBar.unableToParseQueryErrorMessage',
101+
defaultMessage: 'Unable to parse query',
102+
});
103+
queryParseError = (
104+
<EuiFormErrorText>
105+
{`${parseErrorMsg}. ${this.state.parseErrorMessage}`}
106+
</EuiFormErrorText>
107+
);
108+
}
81109

110+
return (
111+
<Fragment>
112+
<EuiSearchBar
113+
box={box}
114+
filters={filters}
115+
onChange={this.onChange}
116+
query={query}
117+
/>
118+
{queryParseError}
119+
</Fragment>
82120
);
83121
}
84122
}

0 commit comments

Comments
 (0)