[Uptime] [Bug] Add error callout for uptime filter bar (#38140)

* Add error callout for uptime filter bar.

* Update KQL query persistence logic.

* Update test snapshot and add new test.

* Rename test, update snapshot.

* Replace bare i18n translation with FormattedMessage component.

* Remove obsolete try...catch block.

* Remove empty whitespace query processing.

* Update out-of-date test snapshot.
This commit is contained in:
Justin Kambic 2019-06-25 17:03:14 -04:00 committed by GitHub
parent b029e82d53
commit 4afed1af4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 518 additions and 215 deletions

View file

@ -1,206 +1,462 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FilterBar component renders the component without errors 1`] = `
<div
data-test-subj="xpack.uptime.filterBar"
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiSearchBar
box={
Object {
"incremental": false,
}
}
className="euiFlexGroup--gutterSmall"
filters={
Array [
Object {
"field": "monitor.status",
"items": Array [
<EuiFlexItem>
<div
data-test-subj="xpack.uptime.filterBar"
>
<EuiSearchBar
box={
Object {
"incremental": false,
"placeholder": undefined,
}
}
className="euiFlexGroup--gutterSmall"
filters={
Array [
Object {
"name": "Up",
"value": "up",
"field": "monitor.status",
"items": Array [
Object {
"name": "Up",
"value": "up",
},
Object {
"name": "Down",
"value": "down",
},
],
"type": "field_value_toggle_group",
},
Object {
"name": "Down",
"value": "down",
},
],
"type": "field_value_toggle_group",
},
Object {
"field": "observer.geo.name",
"name": "Location",
"options": Array [],
"type": "field_value_selection",
},
Object {
"field": "monitor.id",
"multiSelect": false,
"name": "ID",
"options": Array [
Object {
"value": "auto-tcp-0X81440A68E839814C",
"view": "auto-tcp-0X81440A68E839814C",
"field": "observer.geo.name",
"name": "Location",
"options": Array [],
"type": "field_value_selection",
},
Object {
"value": "auto-http-0X3675F89EF0612091",
"view": "auto-http-0X3675F89EF0612091",
"field": "monitor.id",
"multiSelect": false,
"name": "ID",
"options": Array [
Object {
"value": "auto-tcp-0X81440A68E839814C",
"view": "auto-tcp-0X81440A68E839814C",
},
Object {
"value": "auto-http-0X3675F89EF0612091",
"view": "auto-http-0X3675F89EF0612091",
},
Object {
"value": "auto-http-0X970CBD2F2102BFA8",
"view": "auto-http-0X970CBD2F2102BFA8",
},
Object {
"value": "auto-http-0X131221E73F825974",
"view": "auto-http-0X131221E73F825974",
},
Object {
"value": "auto-http-0X9CB71300ABD5A2A8",
"view": "auto-http-0X9CB71300ABD5A2A8",
},
Object {
"value": "auto-http-0XD9AE729FC1C1E04A",
"view": "auto-http-0XD9AE729FC1C1E04A",
},
Object {
"value": "auto-http-0XDD2D4E60FD4A61C3",
"view": "auto-http-0XDD2D4E60FD4A61C3",
},
Object {
"value": "auto-http-0XA8096548ECEB85B7",
"view": "auto-http-0XA8096548ECEB85B7",
},
Object {
"value": "auto-http-0XC9CDA429418EDC2B",
"view": "auto-http-0XC9CDA429418EDC2B",
},
Object {
"value": "auto-http-0XE3B163481423197D",
"view": "auto-http-0XE3B163481423197D",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"value": "auto-http-0X970CBD2F2102BFA8",
"view": "auto-http-0X970CBD2F2102BFA8",
"field": "monitor.name",
"multiSelect": false,
"name": "Name",
"options": Array [],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"value": "auto-http-0X131221E73F825974",
"view": "auto-http-0X131221E73F825974",
"field": "url.full",
"multiSelect": false,
"name": "URL",
"options": Array [
Object {
"value": "tcp://localhost:9200",
"view": "tcp://localhost:9200",
},
Object {
"value": "http://localhost:12349/",
"view": "http://localhost:12349/",
},
Object {
"value": "http://www.google.com/",
"view": "http://www.google.com/",
},
Object {
"value": "https://www.google.com/",
"view": "https://www.google.com/",
},
Object {
"value": "https://www.github.com/",
"view": "https://www.github.com/",
},
Object {
"value": "http://www.reddit.com/",
"view": "http://www.reddit.com/",
},
Object {
"value": "https://www.elastic.co",
"view": "https://www.elastic.co",
},
Object {
"value": "http://www.example.com/",
"view": "http://www.example.com/",
},
Object {
"value": "https://www.wikipedia.org/",
"view": "https://www.wikipedia.org/",
},
Object {
"value": "https://news.google.com/",
"view": "https://news.google.com/",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"value": "auto-http-0X9CB71300ABD5A2A8",
"view": "auto-http-0X9CB71300ABD5A2A8",
"field": "url.port",
"multiSelect": false,
"name": "Port",
"options": Array [
Object {
"value": 9200,
"view": 9200,
},
Object {
"value": 12349,
"view": 12349,
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"value": "auto-http-0XD9AE729FC1C1E04A",
"view": "auto-http-0XD9AE729FC1C1E04A",
"field": "monitor.type",
"multiSelect": false,
"name": "Scheme",
"options": Array [
Object {
"value": "tcp",
"view": "tcp",
},
Object {
"value": "http",
"view": "http",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"value": "auto-http-0XDD2D4E60FD4A61C3",
"view": "auto-http-0XDD2D4E60FD4A61C3",
]
}
onChange={[MockFunction]}
schema={
Object {
"fields": Object {
"monitor.host": Object {
"type": "string",
},
"monitor.id": Object {
"type": "string",
},
"monitor.ip": Object {
"type": "string",
},
"monitor.scheme": Object {
"type": "string",
},
"monitor.status": Object {
"type": "string",
},
"url.port": Object {
"type": "number",
},
},
Object {
"value": "auto-http-0XA8096548ECEB85B7",
"view": "auto-http-0XA8096548ECEB85B7",
},
Object {
"value": "auto-http-0XC9CDA429418EDC2B",
"view": "auto-http-0XC9CDA429418EDC2B",
},
Object {
"value": "auto-http-0XE3B163481423197D",
"view": "auto-http-0XE3B163481423197D",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "monitor.name",
"multiSelect": false,
"name": "Name",
"options": Array [],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "url.full",
"multiSelect": false,
"name": "URL",
"options": Array [
Object {
"value": "tcp://localhost:9200",
"view": "tcp://localhost:9200",
},
Object {
"value": "http://localhost:12349/",
"view": "http://localhost:12349/",
},
Object {
"value": "http://www.google.com/",
"view": "http://www.google.com/",
},
Object {
"value": "https://www.google.com/",
"view": "https://www.google.com/",
},
Object {
"value": "https://www.github.com/",
"view": "https://www.github.com/",
},
Object {
"value": "http://www.reddit.com/",
"view": "http://www.reddit.com/",
},
Object {
"value": "https://www.elastic.co",
"view": "https://www.elastic.co",
},
Object {
"value": "http://www.example.com/",
"view": "http://www.example.com/",
},
Object {
"value": "https://www.wikipedia.org/",
"view": "https://www.wikipedia.org/",
},
Object {
"value": "https://news.google.com/",
"view": "https://news.google.com/",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "url.port",
"multiSelect": false,
"name": "Port",
"options": Array [
Object {
"value": 9200,
"view": 9200,
},
Object {
"value": 12349,
"view": 12349,
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "monitor.type",
"multiSelect": false,
"name": "Scheme",
"options": Array [
Object {
"value": "tcp",
"view": "tcp",
},
Object {
"value": "http",
"view": "http",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
]
}
onChange={[MockFunction]}
schema={
Object {
"fields": Object {
"monitor.host": Object {
"type": "string",
},
"monitor.id": Object {
"type": "string",
},
"monitor.ip": Object {
"type": "string",
},
"monitor.scheme": Object {
"type": "string",
},
"monitor.status": Object {
"type": "string",
},
"url.port": Object {
"type": "number",
},
},
"strict": true,
}
}
/>
</div>
"strict": true,
}
}
/>
</div>
</EuiFlexItem>
</EuiFlexGroup>
`;
exports[`FilterBar component renders the component's error state when an error prop is passed 1`] = `
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexItem>
<div
data-test-subj="xpack.uptime.filterBar"
>
<EuiSearchBar
box={
Object {
"incremental": false,
"placeholder": "foo:bar:isInvalid",
}
}
className="euiFlexGroup--gutterSmall"
filters={
Array [
Object {
"field": "monitor.status",
"items": Array [
Object {
"name": "Up",
"value": "up",
},
Object {
"name": "Down",
"value": "down",
},
],
"type": "field_value_toggle_group",
},
Object {
"field": "observer.geo.name",
"name": "Location",
"options": Array [],
"type": "field_value_selection",
},
Object {
"field": "monitor.id",
"multiSelect": false,
"name": "ID",
"options": Array [
Object {
"value": "auto-tcp-0X81440A68E839814C",
"view": "auto-tcp-0X81440A68E839814C",
},
Object {
"value": "auto-http-0X3675F89EF0612091",
"view": "auto-http-0X3675F89EF0612091",
},
Object {
"value": "auto-http-0X970CBD2F2102BFA8",
"view": "auto-http-0X970CBD2F2102BFA8",
},
Object {
"value": "auto-http-0X131221E73F825974",
"view": "auto-http-0X131221E73F825974",
},
Object {
"value": "auto-http-0X9CB71300ABD5A2A8",
"view": "auto-http-0X9CB71300ABD5A2A8",
},
Object {
"value": "auto-http-0XD9AE729FC1C1E04A",
"view": "auto-http-0XD9AE729FC1C1E04A",
},
Object {
"value": "auto-http-0XDD2D4E60FD4A61C3",
"view": "auto-http-0XDD2D4E60FD4A61C3",
},
Object {
"value": "auto-http-0XA8096548ECEB85B7",
"view": "auto-http-0XA8096548ECEB85B7",
},
Object {
"value": "auto-http-0XC9CDA429418EDC2B",
"view": "auto-http-0XC9CDA429418EDC2B",
},
Object {
"value": "auto-http-0XE3B163481423197D",
"view": "auto-http-0XE3B163481423197D",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "monitor.name",
"multiSelect": false,
"name": "Name",
"options": Array [],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "url.full",
"multiSelect": false,
"name": "URL",
"options": Array [
Object {
"value": "tcp://localhost:9200",
"view": "tcp://localhost:9200",
},
Object {
"value": "http://localhost:12349/",
"view": "http://localhost:12349/",
},
Object {
"value": "http://www.google.com/",
"view": "http://www.google.com/",
},
Object {
"value": "https://www.google.com/",
"view": "https://www.google.com/",
},
Object {
"value": "https://www.github.com/",
"view": "https://www.github.com/",
},
Object {
"value": "http://www.reddit.com/",
"view": "http://www.reddit.com/",
},
Object {
"value": "https://www.elastic.co",
"view": "https://www.elastic.co",
},
Object {
"value": "http://www.example.com/",
"view": "http://www.example.com/",
},
Object {
"value": "https://www.wikipedia.org/",
"view": "https://www.wikipedia.org/",
},
Object {
"value": "https://news.google.com/",
"view": "https://news.google.com/",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "url.port",
"multiSelect": false,
"name": "Port",
"options": Array [
Object {
"value": 9200,
"view": 9200,
},
Object {
"value": 12349,
"view": 12349,
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
Object {
"field": "monitor.type",
"multiSelect": false,
"name": "Scheme",
"options": Array [
Object {
"value": "tcp",
"view": "tcp",
},
Object {
"value": "http",
"view": "http",
},
],
"searchThreshold": 2,
"type": "field_value_selection",
},
]
}
onChange={[MockFunction]}
schema={
Object {
"fields": Object {
"monitor.host": Object {
"type": "string",
},
"monitor.id": Object {
"type": "string",
},
"monitor.ip": Object {
"type": "string",
},
"monitor.scheme": Object {
"type": "string",
},
"monitor.status": Object {
"type": "string",
},
"url.port": Object {
"type": "number",
},
},
"strict": true,
}
}
/>
</div>
</EuiFlexItem>
<EuiFlexItem>
<EuiCallOut
color="danger"
iconType="cross"
size="m"
title="foo"
>
<EuiFlexGroup
direction="column"
>
<EuiFlexItem
grow={false}
>
<EuiText
size="s"
>
<FormattedMessage
defaultMessage="{codeBlock} cannot be parsed"
id="xpack.uptime.filterBar.errorCalloutMessage"
values={
Object {
"codeBlock": <EuiCode>
foo:bar:isInvalid
</EuiCode>,
}
}
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
testing
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
`;

View file

@ -6,7 +6,7 @@
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { FilterBarComponent } from '../filter_bar';
import { FilterBarComponent as FilterBar } from '../filter_bar';
describe('FilterBar component', () => {
const data = {
@ -33,9 +33,17 @@ describe('FilterBar component', () => {
it('renders the component without errors', () => {
currentQuery = undefined;
const component = shallowWithIntl(
<FilterBarComponent
currentQuery={currentQuery}
<FilterBar currentQuery={currentQuery} data={data} loading={false} updateQuery={jest.fn()} />
);
expect(component).toMatchSnapshot();
});
it(`renders the component's error state when an error prop is passed`, () => {
const component = shallowWithIntl(
<FilterBar
currentQuery="foo:bar:isInvalid"
data={data}
error={{ message: 'testing', name: 'foo', found: 'bar' }}
loading={false}
updateQuery={jest.fn()}
/>

View file

@ -4,9 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore No typings for EuiSearchBar
import { EuiIcon, EuiSearchBar, EuiToolTip } from '@elastic/eui';
import {
EuiCallOut,
EuiCode,
EuiFlexItem,
EuiFlexGroup,
// @ts-ignore EuiSearchBar not typed yet
EuiSearchBar,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { FilterBar as FilterBarType, MonitorKey } from '../../../common/graphql/types';
import { UptimeSearchBarQueryChangeHandler } from '../../pages/overview';
@ -21,6 +29,7 @@ interface FilterBarQueryResult {
interface FilterBarProps {
currentQuery?: string;
error?: any;
updateQuery: UptimeSearchBarQueryChangeHandler;
}
@ -28,7 +37,7 @@ type Props = FilterBarProps & UptimeGraphQLQueryProps<FilterBarQueryResult>;
const SEARCH_THRESHOLD = 2;
export const FilterBarComponent = ({ currentQuery, data, updateQuery }: Props) => {
export const FilterBarComponent = ({ currentQuery, data, error, updateQuery }: Props) => {
if (!data || !data.filterBar) {
return <FilterBarLoading />;
}
@ -134,16 +143,41 @@ export const FilterBarComponent = ({ currentQuery, data, updateQuery }: Props) =
},
];
return (
<div data-test-subj="xpack.uptime.filterBar">
<EuiSearchBar
box={{ incremental: false }}
className="euiFlexGroup--gutterSmall"
onChange={updateQuery}
filters={filters}
query={currentQuery}
schema={filterBarSearchSchema}
/>
</div>
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
<div data-test-subj="xpack.uptime.filterBar">
<EuiSearchBar
box={{
incremental: false,
placeholder: currentQuery,
}}
className="euiFlexGroup--gutterSmall"
onChange={updateQuery}
filters={filters}
query={error || currentQuery === '' ? undefined : currentQuery}
schema={filterBarSearchSchema}
/>
</div>
</EuiFlexItem>
{!!error && (
<EuiFlexItem>
<EuiCallOut title={error.name || ''} color="danger" iconType="cross">
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<EuiText size="s">
<FormattedMessage
id="xpack.uptime.filterBar.errorCalloutMessage"
defaultMessage="{codeBlock} cannot be parsed"
values={{ codeBlock: <EuiCode>{currentQuery}</EuiCode> }}
/>
</EuiText>
</EuiFlexItem>
{!!error.message && <EuiFlexItem>{error.message}</EuiFlexItem>}
</EuiFlexGroup>
</EuiCallOut>
</EuiFlexItem>
)}
</EuiFlexGroup>
);
};

View file

@ -27,7 +27,9 @@ interface OverviewPageProps {
type Props = OverviewPageProps;
export type UptimeSearchBarQueryChangeHandler = ({ query }: { query?: { text: string } }) => void;
export type UptimeSearchBarQueryChangeHandler = (
queryChangedEvent: { query?: { text: string }; queryText?: string }
) => void;
export const OverviewPage = ({ basePath, setBreadcrumbs, history, location }: Props) => {
const { absoluteStartDate, absoluteEndDate, colors, refreshApp, setHeadingText } = useContext(
@ -49,23 +51,25 @@ export const OverviewPage = ({ basePath, setBreadcrumbs, history, location }: Pr
}, []);
const filterQueryString = search || '';
let error: any;
let filters: any | undefined;
try {
// toESQuery will throw errors
if (filterQueryString) {
filters = JSON.stringify(EuiSearchBar.Query.toESQuery(filterQueryString));
}
} catch (e) {
error = e;
}
const sharedProps = {
dateRangeStart,
dateRangeEnd,
filters: search ? JSON.stringify(EuiSearchBar.Query.toESQuery(filterQueryString)) : undefined,
filters,
};
const updateQuery: UptimeSearchBarQueryChangeHandler = ({ query }) => {
try {
if (query && typeof query.text !== 'undefined') {
updateUrl({ search: query.text });
}
if (refreshApp) {
refreshApp();
}
} catch (e) {
updateUrl({ search: '' });
}
const updateQuery: UptimeSearchBarQueryChangeHandler = ({ queryText }) => {
updateUrl({ search: queryText || '' });
refreshApp();
};
const linkParameters = stringifyUrlParams(params);
@ -75,6 +79,7 @@ export const OverviewPage = ({ basePath, setBreadcrumbs, history, location }: Pr
<EmptyState basePath={basePath} implementsCustomErrorState={true} variables={sharedProps}>
<FilterBar
currentQuery={filterQueryString}
error={error}
updateQuery={updateQuery}
variables={sharedProps}
/>