Skip to content

Commit

Permalink
Add rollupSearchStrategy and SearchError (#20505)
Browse files Browse the repository at this point in the history
* Add /api/rollup/search endpoint and rollupSearchStrategy.
* Add SearchError for surfacing courier search errors.
- Use toastNotifications to surface errors in visualization editor.
- Add call-out react directive and use it to surface rollup errors in the visualization editor sidebar.
- Temporarily assign timezone and interval values from rollup job to the search to avoid errors.
  • Loading branch information
cjcenizal committed Oct 2, 2018
1 parent 02bed9c commit 016a2e1
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 3 deletions.
6 changes: 5 additions & 1 deletion src/ui/public/courier/fetch/fetch_now.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ export function FetchNowProvider(Private, Promise) {
return startRequests(searchRequests)
.then(function () {
replaceAbortedRequests();
return callClient(searchRequests);
return callClient(searchRequests)
.catch(() => {
// Silently swallow errors that result from search requests so the consumer can surface
// them as notifications instead of courier forcing fatal errors.
});
})
.then(function (responses) {
replaceAbortedRequests();
Expand Down
9 changes: 9 additions & 0 deletions src/ui/public/vis/editors/default/sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
</div>

<nav class="navbar navbar-default subnav visEditorSidebar__nav">
<div class="visEditorSidebarError" ng-if="sidebar.persistentError">
<call-out
color="'danger'"
iconType="'alert'"
title="sidebar.persistentError"
size="'s'"
/>
</div>

<div class="container-fluid">

<!-- tabs -->
Expand Down
19 changes: 19 additions & 0 deletions src/ui/public/vis/editors/default/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import './vis_options';
import { uiModules } from '../../../modules';
import sidebarTemplate from './sidebar.html';

import './sidebar.less';

uiModules
.get('app/visualize')
.directive('visEditorSidebar', function () {
Expand All @@ -32,6 +34,8 @@ uiModules
scope: true,
controllerAs: 'sidebar',
controller: function ($scope) {
this.persistentError = undefined;

$scope.$watch('vis.type', (visType) => {
if (visType) {
this.showData = visType.schemas.buckets || visType.schemas.metrics;
Expand All @@ -46,6 +50,21 @@ uiModules
this.section = this.section || (this.showData ? 'data' : _.get(visType, 'editorConfig.optionTabs[0].name'));
}
});

// TODO: This doesn't update when requestError is set.
$scope.$watch('vis.requestError', (requestError) => {
if (requestError && requestError.messsage) {
const { message } = requestError;
const isRollupError = message.includes('rollup');

if (isRollupError) {
this.persistentError = message;
return;
}
}

this.persistentError = undefined;
});
}
};
});
3 changes: 3 additions & 0 deletions src/ui/public/vis/editors/default/sidebar.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.visEditorSidebarError {
padding: 8px;
}
8 changes: 6 additions & 2 deletions x-pack/plugins/rollup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import { resolve } from 'path';
import { PLUGIN } from './common/constants';
import { registerLicenseChecker } from './server/lib/register_license_checker';
import { registerIndicesRoute, registerFieldsForWildcardRoute } from './server/routes/api';
import { registerIndicesRoute, registerFieldsForWildcardRoute, registerSearchRoute } from './server/routes/api';

export function rollup(kibana) {
export function rollup(kibana) {
return new kibana.Plugin({
id: PLUGIN.ID,
publicDir: resolve(__dirname, 'public'),
Expand All @@ -22,11 +22,15 @@ export function rollup(kibana) {
visualize: [
'plugins/rollup/visualize',
],
search: [
'plugins/rollup/search',
],
},
init: function (server) {
registerLicenseChecker(server);
registerIndicesRoute(server);
registerFieldsForWildcardRoute(server);
registerSearchRoute(server);
}
});
}
7 changes: 7 additions & 0 deletions x-pack/plugins/rollup/public/search/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import './register';
10 changes: 10 additions & 0 deletions x-pack/plugins/rollup/public/search/register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { addSearchStrategy } from 'ui/courier';
import { rollupSearchStrategy } from './rollup_search_strategy';

addSearchStrategy(rollupSearchStrategy);
114 changes: 114 additions & 0 deletions x-pack/plugins/rollup/public/search/rollup_search_strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { kfetchAbortable } from 'ui/kfetch';
import { SearchError } from 'ui/courier';

export const rollupSearchStrategy = {
id: 'rollup',

search: async ({ searchRequests, Promise }) => {
// TODO: Batch together requests to hit a bulk rollup search endpoint.
const searchRequest = searchRequests[0];
const {
index: { title: indexPattern },
body: {
size,
aggs,
},
} = await searchRequest.getFetchParams();

function findDateHistogram(aggs) {
if (Array.isArray(aggs)) {
for (let i = 0; i < aggs.length; i++) {
const dateHistogram = findDateHistogram(aggs[i]);

if (dateHistogram) {
return dateHistogram;
}
}
} else if (typeof aggs === 'object') {
const aggNames = Object.keys(aggs);
const aggsList = aggNames.map(aggName => aggs[aggName]);

if (aggsList.includes('date_histogram')) {
return aggs;
}

return findDateHistogram(aggsList);
}
}

// TODO: Temporarily automatically assign same timezone and interval as what's defined by
// the rollup job. This should be done by the visualization itself.
const searchableAggs = JSON.parse(searchRequest.source.getField('index').originalBody.typeMeta);
const { time_zone: timeZone, interval } = findDateHistogram(searchableAggs);

Object.keys(aggs).forEach(aggName => {
const subAggs = aggs[aggName];

Object.keys(subAggs).forEach(subAggName => {
if (subAggName === 'date_histogram') {
const subAgg = subAggs[subAggName];
subAgg.time_zone = timeZone;
subAgg.interval = interval;
}
});
});

const index = indexPattern;
const query = {
'size': size,
'aggregations': aggs,
};

const {
fetching,
abort,
} = kfetchAbortable({
pathname: '../api/rollup/search',
method: 'POST',
body: JSON.stringify({ index, query }),
});

// TODO: Implement this. Search requests which can't be sent.
const failedSearchRequests = [];

return {
// Munge data into shape expected by consumer.
searching: new Promise((resolve, reject) => {
fetching.then(result => {
resolve([ result ]);
}).catch(error => {
const {
body: { statusText, error: title, message },
res: { url },
} = error;

// Format kfetch error as a SearchError.
const searchError = new SearchError({
status: statusText,
title,
message,
path: url,
});

reject(searchError);
});
}),
abort,
failedSearchRequests,
};
},

isViable: (indexPattern) => {
if (!indexPattern) {
return false;
}

return indexPattern.type === 'rollup';
},
};
15 changes: 15 additions & 0 deletions x-pack/plugins/rollup/server/client/elasticsearch_rollup.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,20 @@ export const elasticsearchJsPlugin = (Client, config, components) => {
],
method: 'GET'
});

rollup.search = ca({
urls: [
{
fmt: '/<%=index%>/_rollup_search',
req: {
index: {
type: 'string'
}
}
}
],
needBody: true,
method: 'POST'
});
};

1 change: 1 addition & 0 deletions x-pack/plugins/rollup/server/routes/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

export { registerIndicesRoute } from './indices';
export { registerFieldsForWildcardRoute } from './index_patterns';
export { registerSearchRoute } from './search';
36 changes: 36 additions & 0 deletions x-pack/plugins/rollup/server/routes/api/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { callWithRequestFactory } from '../../lib/call_with_request_factory';
import { isEsErrorFactory } from '../../lib/is_es_error_factory';
import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers';

export function registerSearchRoute(server) {
const isEsError = isEsErrorFactory(server);

server.route({
path: '/api/rollup/search',
method: 'POST',
handler: async (request, reply) => {
const { index, query } = request.payload;
const callWithRequest = callWithRequestFactory(server, request);

try {
const results = await callWithRequest('rollup.search', {
index,
body: query,
});

reply(results);
} catch(err) {
if (isEsError(err)) {
return reply(wrapEsError(err));
}

reply(wrapUnknownError(err));
}
},
});
}

0 comments on commit 016a2e1

Please sign in to comment.