diff --git a/src/ui/public/courier/fetch/call_client.js b/src/ui/public/courier/fetch/call_client.js
index 6d59a631c53bb..f830d52a2dcb3 100644
--- a/src/ui/public/courier/fetch/call_client.js
+++ b/src/ui/public/courier/fetch/call_client.js
@@ -161,7 +161,7 @@ export function CallClientProvider(Private, Promise, es, config) {
return;
}
- const segregatedResponses = await Promise.all(abortableSearches.map(({ searching }) => searching));
+ const segregatedResponses = await Promise.all(abortableSearches.map(({ searching }) => searching.catch((e) => [{ error: e }])));
// Assigning searchRequests to strategies means that the responses come back in a different
// order than the original searchRequests. So we'll put them back in order so that we can
diff --git a/src/ui/public/courier/fetch/call_response_handlers.js b/src/ui/public/courier/fetch/call_response_handlers.js
index 4554608680c4a..766ea8af3a0e8 100644
--- a/src/ui/public/courier/fetch/call_response_handlers.js
+++ b/src/ui/public/courier/fetch/call_response_handlers.js
@@ -20,6 +20,7 @@
import { toastNotifications } from '../../notify';
import { RequestFailure } from '../../errors';
import { RequestStatus } from './req_status';
+import { SearchError } from '../search_strategy/search_error';
export function CallResponseHandlersProvider(Private, Promise) {
const ABORTED = RequestStatus.ABORTED;
@@ -58,7 +59,7 @@ export function CallResponseHandlersProvider(Private, Promise) {
if (searchRequest.filterError(response)) {
return progress();
} else {
- return searchRequest.handleFailure(new RequestFailure(null, response));
+ return searchRequest.handleFailure(response.error instanceof SearchError ? response.error : new RequestFailure(null, response));
}
}
diff --git a/src/ui/public/courier/fetch/request/search_request/search_request.js b/src/ui/public/courier/fetch/request/search_request/search_request.js
index b1c075f4f1b97..1471df8d35223 100644
--- a/src/ui/public/courier/fetch/request/search_request/search_request.js
+++ b/src/ui/public/courier/fetch/request/search_request/search_request.js
@@ -123,7 +123,8 @@ export function SearchRequestProvider(Promise) {
handleFailure(error) {
this.success = false;
- this.resp = error && error.resp;
+ this.resp = error;
+ this.resp = (error && error.resp) || error;
return this.errorHandler(this, error);
}
diff --git a/src/ui/public/courier/index.d.ts b/src/ui/public/courier/index.d.ts
index 72170adc2b129..c06518697422b 100644
--- a/src/ui/public/courier/index.d.ts
+++ b/src/ui/public/courier/index.d.ts
@@ -18,3 +18,4 @@
*/
export * from './search_source';
+export * from './search_strategy';
diff --git a/src/ui/public/courier/index.js b/src/ui/public/courier/index.js
index 583172f1731cd..244042317ebf2 100644
--- a/src/ui/public/courier/index.js
+++ b/src/ui/public/courier/index.js
@@ -34,4 +34,5 @@ export {
hasSearchStategyForIndexPattern,
isDefaultTypeIndexPattern,
SearchError,
+ getSearchErrorType,
} from './search_strategy';
diff --git a/src/ui/public/courier/search_strategy/index.d.ts b/src/ui/public/courier/search_strategy/index.d.ts
new file mode 100644
index 0000000000000..dc98484655d00
--- /dev/null
+++ b/src/ui/public/courier/search_strategy/index.d.ts
@@ -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 { SearchError, getSearchErrorType } from './search_error';
diff --git a/src/ui/public/courier/search_strategy/index.js b/src/ui/public/courier/search_strategy/index.js
index 102610538df98..3f6d172426d0d 100644
--- a/src/ui/public/courier/search_strategy/index.js
+++ b/src/ui/public/courier/search_strategy/index.js
@@ -25,4 +25,4 @@ export {
export { isDefaultTypeIndexPattern } from './is_default_type_index_pattern';
-export { SearchError } from './search_error';
+export { SearchError, getSearchErrorType } from './search_error';
diff --git a/src/ui/public/courier/search_strategy/search_error.d.ts b/src/ui/public/courier/search_strategy/search_error.d.ts
new file mode 100644
index 0000000000000..bf49853957c75
--- /dev/null
+++ b/src/ui/public/courier/search_strategy/search_error.d.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 type SearchError = any;
+export type getSearchErrorType = any;
diff --git a/src/ui/public/courier/search_strategy/search_error.js b/src/ui/public/courier/search_strategy/search_error.js
index 9406ceb4beaeb..9c35d11a6abf4 100644
--- a/src/ui/public/courier/search_strategy/search_error.js
+++ b/src/ui/public/courier/search_strategy/search_error.js
@@ -18,13 +18,14 @@
*/
export class SearchError extends Error {
- constructor({ status, title, message, path }) {
+ constructor({ status, title, message, path, type }) {
super(message);
this.name = 'SearchError';
this.status = status;
this.title = title;
this.message = message;
this.path = path;
+ this.type = type;
// captureStackTrace is only available in the V8 engine, so any browser using
// a different JS engine won't have access to this method.
@@ -37,3 +38,10 @@ export class SearchError extends Error {
Object.setPrototypeOf(this, SearchError.prototype);
}
}
+
+export function getSearchErrorType({ message }) {
+ const msg = message.toLowerCase();
+ if(msg.indexOf('unsupported query') > -1) {
+ return 'UNSUPPORTED_QUERY';
+ }
+}
diff --git a/src/ui/public/visualize/components/__snapshots__/visualization_requesterror.test.js.snap b/src/ui/public/visualize/components/__snapshots__/visualization_requesterror.test.js.snap
new file mode 100644
index 0000000000000..7eabc9118f74b
--- /dev/null
+++ b/src/ui/public/visualize/components/__snapshots__/visualization_requesterror.test.js.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`VisualizationRequestError should render according to snapshot 1`] = `
+
+
+
+`;
diff --git a/src/ui/public/visualize/components/visualization.less b/src/ui/public/visualize/components/visualization.less
index fbd1ba39cd00c..5ff9117a3313e 100644
--- a/src/ui/public/visualize/components/visualization.less
+++ b/src/ui/public/visualize/components/visualization.less
@@ -65,7 +65,15 @@
display: flex;
align-items: center;
justify-content: center;
- .top { align-self: flext-start; }
+ .top { align-self: flex-start; }
.item { }
- .bottom { align-self: flext-end; }
+ .bottom { align-self: flex-end; }
+}
+
+/**
+ * 1. Prevent large request errors from overflowing the container
+ */
+.visualize-request-error {
+ max-width: 100%;
+ max-height: 100%;
}
diff --git a/src/ui/public/visualize/components/visualization.test.js b/src/ui/public/visualize/components/visualization.test.js
index 1d768787ce193..cdeec28712850 100644
--- a/src/ui/public/visualize/components/visualization.test.js
+++ b/src/ui/public/visualize/components/visualization.test.js
@@ -79,6 +79,13 @@ describe('', () => {
expect(wrapper.text()).toBe('No results found');
});
+ it('should display error message when there is a request error that should be shown and no data', () => {
+ const errorVis = { ...vis, requestError: { message: 'Request error' }, showRequestError: true };
+ const data = null;
+ const wrapper = render();
+ expect(wrapper.text()).toBe('Request error');
+ });
+
it('should render chart when data is present', () => {
const wrapper = render();
expect(wrapper.text()).not.toBe('No results found');
diff --git a/src/ui/public/visualize/components/visualization.tsx b/src/ui/public/visualize/components/visualization.tsx
index 04dcd7851a560..ccf9f2d9a5cd2 100644
--- a/src/ui/public/visualize/components/visualization.tsx
+++ b/src/ui/public/visualize/components/visualization.tsx
@@ -25,6 +25,7 @@ import { memoizeLast } from '../../utils/memoize';
import { Vis } from '../../vis';
import { VisualizationChart } from './visualization_chart';
import { VisualizationNoResults } from './visualization_noresults';
+import { VisualizationRequestError } from './visualization_requesterror';
import './visualization.less';
@@ -37,6 +38,12 @@ function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean {
return Boolean(requiresSearch && isZeroHits && shouldShowMessage);
}
+function shouldShowRequestErrorMessage(vis: Vis, visData: any): boolean {
+ const requestError = get(vis, 'requestError');
+ const showRequestError = get(vis, 'showRequestError');
+ return Boolean(!visData && requestError && showRequestError);
+}
+
interface VisualizationProps {
listenOnChange: boolean;
onInit?: () => void;
@@ -63,10 +70,13 @@ export class Visualization extends React.Component {
const { vis, visData, onInit, uiState } = this.props;
const noResults = this.showNoResultsMessage(vis, visData);
+ const requestError = shouldShowRequestErrorMessage(vis, visData);
return (
- {noResults ? (
+ {requestError ? (
+
+ ) : noResults ? (
) : (
diff --git a/src/ui/public/visualize/components/visualization_requesterror.test.js b/src/ui/public/visualize/components/visualization_requesterror.test.js
new file mode 100644
index 0000000000000..6a07c9ec4e145
--- /dev/null
+++ b/src/ui/public/visualize/components/visualization_requesterror.test.js
@@ -0,0 +1,39 @@
+/*
+ * 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 { render } from 'enzyme';
+import { VisualizationRequestError } from './visualization_requesterror';
+
+describe('VisualizationRequestError', () => {
+ it('should render according to snapshot', () => {
+ const wrapper = render(
);
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('should set html when error is an object', () => {
+ const wrapper = render(
);
+ expect(wrapper.text()).toBe('Request error');
+ });
+
+ it('should set html when error is a string', () => {
+ const wrapper = render(
);
+ expect(wrapper.text()).toBe('Request error');
+ });
+});
diff --git a/src/ui/public/visualize/components/visualization_requesterror.tsx b/src/ui/public/visualize/components/visualization_requesterror.tsx
new file mode 100644
index 0000000000000..8dbec10973d50
--- /dev/null
+++ b/src/ui/public/visualize/components/visualization_requesterror.tsx
@@ -0,0 +1,62 @@
+/*
+ * 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 { EuiText } from '@elastic/eui';
+import React from 'react';
+import { SearchError } from 'ui/courier';
+import { dispatchRenderComplete } from '../../render_complete';
+
+interface VisualizationRequestErrorProps {
+ onInit?: () => void;
+ error: SearchError | string;
+}
+
+export class VisualizationRequestError extends React.Component
{
+ private containerDiv = React.createRef();
+
+ public render() {
+ const { error } = this.props;
+ const errorMessage = (error && error.message) || error;
+
+ return (
+
+
+ {errorMessage}
+
+
+ );
+ }
+
+ public componentDidMount() {
+ this.afterRender();
+ }
+
+ public componentDidUpdate() {
+ this.afterRender();
+ }
+
+ private afterRender() {
+ if (this.props.onInit) {
+ this.props.onInit();
+ }
+ if (this.containerDiv.current) {
+ dispatchRenderComplete(this.containerDiv.current);
+ }
+ }
+}
diff --git a/src/ui/public/visualize/loader/visualize_data_loader.ts b/src/ui/public/visualize/loader/visualize_data_loader.ts
index 780a4d21efc7d..6b6fbf5ba46bd 100644
--- a/src/ui/public/visualize/loader/visualize_data_loader.ts
+++ b/src/ui/public/visualize/loader/visualize_data_loader.ts
@@ -67,8 +67,10 @@ export class VisualizeDataLoader {
this.responseHandler = getHandler(responseHandlers, responseHandler);
}
- public async fetch(params: RequestHandlerParams): Promise {
+ public fetch = async (params: RequestHandlerParams): Promise => {
this.vis.filters = { timeRange: params.timeRange };
+ this.vis.requestError = undefined;
+ this.vis.showRequestError = false;
try {
// searchSource is only there for courier request handler
@@ -95,6 +97,7 @@ export class VisualizeDataLoader {
} catch (e) {
params.searchSource.cancelQueued();
this.vis.requestError = e;
+ this.vis.showRequestError = e.type && e.type === 'UNSUPPORTED_QUERY';
if (isTermSizeZeroError(e)) {
return toastNotifications.addDanger(
`Your visualization ('${this.vis.title}') has an error: it has a term ` +
@@ -107,5 +110,5 @@ export class VisualizeDataLoader {
text: e.message,
});
}
- }
+ };
}
diff --git a/x-pack/plugins/rollup/public/search/rollup_search_strategy.js b/x-pack/plugins/rollup/public/search/rollup_search_strategy.js
index 0b4f446981508..ea5d7f9bd5b4e 100644
--- a/x-pack/plugins/rollup/public/search/rollup_search_strategy.js
+++ b/x-pack/plugins/rollup/public/search/rollup_search_strategy.js
@@ -5,7 +5,7 @@
*/
import { kfetchAbortable } from 'ui/kfetch';
-import { SearchError } from 'ui/courier';
+import { SearchError, getSearchErrorType } from 'ui/courier';
function getAllFetchParams(searchRequests, Promise) {
return Promise.map(searchRequests, (searchRequest) => {
@@ -118,8 +118,9 @@ export const rollupSearchStrategy = {
const searchError = new SearchError({
status: statusText,
title,
- message,
+ message: `Rollup search error: ${message}`,
path: url,
+ type: getSearchErrorType({ message }),
});
reject(searchError);