mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Improve feedback in Discover (#16771)
* Add Painless scripted field error callout to Discover. Remove recursive beginSegmentedFetch call. * Add getDocLink service. EUIfy Discover 'no results' state. * Rename initSegmentedFetch to handleSegmentedFetch to differentiate it from beginSegmentedFetch.
This commit is contained in:
parent
aeaf57dd97
commit
a7147f2ca7
25 changed files with 1175 additions and 192 deletions
|
@ -0,0 +1,63 @@
|
|||
import 'ngreact';
|
||||
import React, { Fragment } from 'react';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiCallOut,
|
||||
EuiCodeBlock,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import './fetch_error.less';
|
||||
|
||||
const DiscoverFetchError = ({ fetchError }) => {
|
||||
if (!fetchError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let body;
|
||||
|
||||
if (fetchError.lang === 'painless') {
|
||||
const managementUrl = chrome.getNavLinkById('kibana:management').url;
|
||||
const url = `${managementUrl}/kibana/indices`;
|
||||
|
||||
body = (
|
||||
<p>
|
||||
You can address this error by editing the ‘{fetchError.script}’ field
|
||||
in <a href={url}>Management > Index Patterns</a>,
|
||||
under the “Scripted fields” tab.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiFlexGroup justifyContent="center" data-test-subj="discoverFetchError">
|
||||
<EuiFlexItem grow={false} className="discoverFetchError">
|
||||
<EuiCallOut
|
||||
title={fetchError.message}
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
>
|
||||
{body}
|
||||
|
||||
<EuiCodeBlock>
|
||||
{fetchError.error}
|
||||
</EuiCodeBlock>
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const app = uiModules.get('apps/discover', ['react']);
|
||||
|
||||
app.directive('discoverFetchError', reactDirective => reactDirective(DiscoverFetchError));
|
|
@ -0,0 +1,3 @@
|
|||
.discoverFetchError {
|
||||
max-width: 1000px;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
import './fetch_error';
|
|
@ -15,7 +15,7 @@ import 'ui/state_management/app_state';
|
|||
import 'ui/timefilter';
|
||||
import 'ui/share';
|
||||
import 'ui/query_bar';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { toastNotifications, getPainlessError } from 'ui/notify';
|
||||
import { VisProvider } from 'ui/vis';
|
||||
import { BasicResponseHandlerProvider } from 'ui/vis/response_handlers/basic';
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
|
@ -31,6 +31,8 @@ import { migrateLegacyQuery } from 'ui/utils/migrateLegacyQuery';
|
|||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { recentlyAccessed } from 'ui/persisted_log';
|
||||
import { getDocLink } from 'ui/documentation_links';
|
||||
import '../components/fetch_error';
|
||||
|
||||
const app = uiModules.get('apps/discover', [
|
||||
'kibana/notify',
|
||||
|
@ -57,14 +59,14 @@ uiRoutes
|
|||
})
|
||||
.then(({ savedObjects }) => {
|
||||
/**
|
||||
* In making the indexPattern modifiable it was placed in appState. Unfortunately,
|
||||
* the load order of AppState conflicts with the load order of many other things
|
||||
* so in order to get the name of the index we should use, and to switch to the
|
||||
* default if necessary, we parse the appState with a temporary State object and
|
||||
* then destroy it immediatly after we're done
|
||||
*
|
||||
* @type {State}
|
||||
*/
|
||||
* In making the indexPattern modifiable it was placed in appState. Unfortunately,
|
||||
* the load order of AppState conflicts with the load order of many other things
|
||||
* so in order to get the name of the index we should use, and to switch to the
|
||||
* default if necessary, we parse the appState with a temporary State object and
|
||||
* then destroy it immediatly after we're done
|
||||
*
|
||||
* @type {State}
|
||||
*/
|
||||
const state = new State('_a', {});
|
||||
|
||||
const specified = !!state.index;
|
||||
|
@ -135,6 +137,7 @@ function discoverController(
|
|||
location: 'Discover'
|
||||
});
|
||||
|
||||
$scope.getDocLink = getDocLink;
|
||||
$scope.intervalOptions = Private(AggTypesBucketsIntervalOptionsProvider);
|
||||
$scope.showInterval = false;
|
||||
$scope.minimumVisibleRows = 50;
|
||||
|
@ -292,15 +295,6 @@ function discoverController(
|
|||
};
|
||||
|
||||
const init = _.once(function () {
|
||||
const showTotal = 5;
|
||||
$scope.failuresShown = showTotal;
|
||||
$scope.showAllFailures = function () {
|
||||
$scope.failuresShown = $scope.failures.length;
|
||||
};
|
||||
$scope.showLessFailures = function () {
|
||||
$scope.failuresShown = showTotal;
|
||||
};
|
||||
|
||||
stateMonitor = stateMonitorFactory.create($state, getStateDefaults());
|
||||
stateMonitor.onChange((status) => {
|
||||
$appStatus.dirty = status.dirty || !savedSearch.id;
|
||||
|
@ -453,6 +447,8 @@ function discoverController(
|
|||
// ignore requests to fetch before the app inits
|
||||
if (!init.complete) return;
|
||||
|
||||
$scope.fetchError = undefined;
|
||||
|
||||
$scope.updateTime();
|
||||
|
||||
$scope.updateDataSource()
|
||||
|
@ -470,8 +466,9 @@ function discoverController(
|
|||
};
|
||||
|
||||
|
||||
function initSegmentedFetch(segmented) {
|
||||
function handleSegmentedFetch(segmented) {
|
||||
function flushResponseData() {
|
||||
$scope.fetchError = undefined;
|
||||
$scope.hits = 0;
|
||||
$scope.faliures = [];
|
||||
$scope.rows = [];
|
||||
|
@ -584,10 +581,17 @@ function discoverController(
|
|||
|
||||
|
||||
function beginSegmentedFetch() {
|
||||
$scope.searchSource.onBeginSegmentedFetch(initSegmentedFetch)
|
||||
$scope.searchSource.onBeginSegmentedFetch(handleSegmentedFetch)
|
||||
.catch((error) => {
|
||||
notify.error(error);
|
||||
// Restart.
|
||||
const fetchError = getPainlessError(error);
|
||||
|
||||
if (fetchError) {
|
||||
$scope.fetchError = fetchError;
|
||||
} else {
|
||||
notify.error(error);
|
||||
}
|
||||
|
||||
// Restart. This enables auto-refresh functionality.
|
||||
beginSegmentedFetch();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DiscoverNoResults props queryLanguage supports lucene and renders doc link 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>,
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentCenter euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero discoverNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOut euiCallOut--warning"
|
||||
data-test-subj="discoverNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOutHeader"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiCallOutHeader__icon euiIcon--medium"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M13.6 12.186l-1.357-1.358c-.025-.025-.058-.034-.084-.056.53-.794.84-1.746.84-2.773a4.977 4.977 0 0 0-.84-2.772c.026-.02.059-.03.084-.056L13.6 3.813a6.96 6.96 0 0 1 0 8.373zM8 15A6.956 6.956 0 0 1 3.814 13.6l1.358-1.358c.025-.025.034-.057.055-.084C6.02 12.688 6.974 13 8 13a4.978 4.978 0 0 0 2.773-.84c.02.026.03.058.056.083l1.357 1.358A6.956 6.956 0 0 1 8 15zm-5.601-2.813a6.963 6.963 0 0 1 0-8.373l1.359 1.358c.024.025.057.035.084.056A4.97 4.97 0 0 0 3 8c0 1.027.31 1.98.842 2.773-.027.022-.06.031-.084.056l-1.36 1.358zm5.6-.187A4 4 0 1 1 8 4a4 4 0 0 1 0 8zM8 1c1.573 0 3.019.525 4.187 1.4l-1.357 1.358c-.025.025-.035.057-.056.084A4.979 4.979 0 0 0 8 3a4.979 4.979 0 0 0-2.773.842c-.021-.027-.03-.059-.055-.084L3.814 2.4A6.957 6.957 0 0 1 8 1zm0-1a8.001 8.001 0 1 0 .003 16.002A8.001 8.001 0 0 0 8 0z"
|
||||
id="help-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
fill-rule="evenodd"
|
||||
href="#help-a"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="euiCallOutHeader__title"
|
||||
>
|
||||
No results match your search criteria
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>
|
||||
<div
|
||||
class="euiText"
|
||||
>
|
||||
<h3>
|
||||
Refine your query
|
||||
</h3>
|
||||
<p>
|
||||
The search bar at the top uses Elasticsearch’s support for Lucene
|
||||
<a
|
||||
class="euiLink euiLink--primary"
|
||||
href="documentation-link"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Query String syntax
|
||||
</a>
|
||||
. Here are some examples of how you can search for web server logs that have been parsed into a few fields.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
<dl
|
||||
class="euiDescriptionList euiDescriptionList--column"
|
||||
>
|
||||
<dt
|
||||
class="euiDescriptionList__title"
|
||||
>
|
||||
<div
|
||||
class="euiText"
|
||||
>
|
||||
<strong>
|
||||
Find requests that contain the number 200, in any field
|
||||
</strong>
|
||||
</div>
|
||||
</dt>
|
||||
<dd
|
||||
class="euiDescriptionList__description"
|
||||
>
|
||||
<span
|
||||
class="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
|
||||
>
|
||||
<code
|
||||
class="euiCodeBlock__code"
|
||||
>
|
||||
200
|
||||
</code>
|
||||
</span>
|
||||
</dd>
|
||||
<dt
|
||||
class="euiDescriptionList__title"
|
||||
>
|
||||
<div
|
||||
class="euiText"
|
||||
>
|
||||
<strong>
|
||||
Find 200 in the status field
|
||||
</strong>
|
||||
</div>
|
||||
</dt>
|
||||
<dd
|
||||
class="euiDescriptionList__description"
|
||||
>
|
||||
<span
|
||||
class="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
|
||||
>
|
||||
<code
|
||||
class="euiCodeBlock__code"
|
||||
>
|
||||
status:200
|
||||
</code>
|
||||
</span>
|
||||
</dd>
|
||||
<dt
|
||||
class="euiDescriptionList__title"
|
||||
>
|
||||
<div
|
||||
class="euiText"
|
||||
>
|
||||
<strong>
|
||||
Find all status codes between 400-499
|
||||
</strong>
|
||||
</div>
|
||||
</dt>
|
||||
<dd
|
||||
class="euiDescriptionList__description"
|
||||
>
|
||||
<span
|
||||
class="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
|
||||
>
|
||||
<code
|
||||
class="euiCodeBlock__code"
|
||||
>
|
||||
status:[400 TO 499]
|
||||
</code>
|
||||
</span>
|
||||
</dd>
|
||||
<dt
|
||||
class="euiDescriptionList__title"
|
||||
>
|
||||
<div
|
||||
class="euiText"
|
||||
>
|
||||
<strong>
|
||||
Find status codes 400-499 with the extension php
|
||||
</strong>
|
||||
</div>
|
||||
</dt>
|
||||
<dd
|
||||
class="euiDescriptionList__description"
|
||||
>
|
||||
<span
|
||||
class="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
|
||||
>
|
||||
<code
|
||||
class="euiCodeBlock__code"
|
||||
>
|
||||
status:[400 TO 499] AND extension:PHP
|
||||
</code>
|
||||
</span>
|
||||
</dd>
|
||||
<dt
|
||||
class="euiDescriptionList__title"
|
||||
>
|
||||
<div
|
||||
class="euiText"
|
||||
>
|
||||
<strong>
|
||||
Find status codes 400-499 with the extension php or html
|
||||
</strong>
|
||||
</div>
|
||||
</dt>
|
||||
<dd
|
||||
class="euiDescriptionList__description"
|
||||
>
|
||||
<span
|
||||
class="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingLarge euiCodeBlock--inline"
|
||||
>
|
||||
<code
|
||||
class="euiCodeBlock__code"
|
||||
>
|
||||
status:[400 TO 499] AND (extension:php OR extension:html)
|
||||
</code>
|
||||
</span>
|
||||
</dd>
|
||||
</dl>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`DiscoverNoResults props shardFailures renders failures list 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>,
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentCenter euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero discoverNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOut euiCallOut--warning"
|
||||
data-test-subj="discoverNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOutHeader"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiCallOutHeader__icon euiIcon--medium"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M13.6 12.186l-1.357-1.358c-.025-.025-.058-.034-.084-.056.53-.794.84-1.746.84-2.773a4.977 4.977 0 0 0-.84-2.772c.026-.02.059-.03.084-.056L13.6 3.813a6.96 6.96 0 0 1 0 8.373zM8 15A6.956 6.956 0 0 1 3.814 13.6l1.358-1.358c.025-.025.034-.057.055-.084C6.02 12.688 6.974 13 8 13a4.978 4.978 0 0 0 2.773-.84c.02.026.03.058.056.083l1.357 1.358A6.956 6.956 0 0 1 8 15zm-5.601-2.813a6.963 6.963 0 0 1 0-8.373l1.359 1.358c.024.025.057.035.084.056A4.97 4.97 0 0 0 3 8c0 1.027.31 1.98.842 2.773-.027.022-.06.031-.084.056l-1.36 1.358zm5.6-.187A4 4 0 1 1 8 4a4 4 0 0 1 0 8zM8 1c1.573 0 3.019.525 4.187 1.4l-1.357 1.358c-.025.025-.035.057-.056.084A4.979 4.979 0 0 0 8 3a4.979 4.979 0 0 0-2.773.842c-.021-.027-.03-.059-.055-.084L3.814 2.4A6.957 6.957 0 0 1 8 1zm0-1a8.001 8.001 0 1 0 .003 16.002A8.001 8.001 0 0 0 8 0z"
|
||||
id="help-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
fill-rule="evenodd"
|
||||
href="#help-a"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="euiCallOutHeader__title"
|
||||
>
|
||||
No results match your search criteria
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>
|
||||
<div
|
||||
class="euiText"
|
||||
>
|
||||
<h3>
|
||||
Address shard failures
|
||||
</h3>
|
||||
<p>
|
||||
The following shard failures occurred:
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
class="euiText euiText--extraSmall"
|
||||
>
|
||||
<strong>
|
||||
Index ‘A’
|
||||
</strong>
|
||||
, shard ‘1’
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div
|
||||
class="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingSmall"
|
||||
>
|
||||
<pre
|
||||
class="euiCodeBlock__pre"
|
||||
>
|
||||
<code
|
||||
class="euiCodeBlock__code"
|
||||
>
|
||||
Awful error
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--l"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="euiText euiText--extraSmall"
|
||||
>
|
||||
<strong>
|
||||
Index ‘B’
|
||||
</strong>
|
||||
, shard ‘2’
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div
|
||||
class="euiCodeBlock euiCodeBlock--fontSmall euiCodeBlock--paddingSmall"
|
||||
>
|
||||
<pre
|
||||
class="euiCodeBlock__pre"
|
||||
>
|
||||
<code
|
||||
class="euiCodeBlock__code"
|
||||
>
|
||||
Bad error
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`DiscoverNoResults props timeFieldName renders time range feedback 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>,
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentCenter euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero discoverNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOut euiCallOut--warning"
|
||||
data-test-subj="discoverNoResults"
|
||||
>
|
||||
<div
|
||||
class="euiCallOutHeader"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiCallOutHeader__icon euiIcon--medium"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M13.6 12.186l-1.357-1.358c-.025-.025-.058-.034-.084-.056.53-.794.84-1.746.84-2.773a4.977 4.977 0 0 0-.84-2.772c.026-.02.059-.03.084-.056L13.6 3.813a6.96 6.96 0 0 1 0 8.373zM8 15A6.956 6.956 0 0 1 3.814 13.6l1.358-1.358c.025-.025.034-.057.055-.084C6.02 12.688 6.974 13 8 13a4.978 4.978 0 0 0 2.773-.84c.02.026.03.058.056.083l1.357 1.358A6.956 6.956 0 0 1 8 15zm-5.601-2.813a6.963 6.963 0 0 1 0-8.373l1.359 1.358c.024.025.057.035.084.056A4.97 4.97 0 0 0 3 8c0 1.027.31 1.98.842 2.773-.027.022-.06.031-.084.056l-1.36 1.358zm5.6-.187A4 4 0 1 1 8 4a4 4 0 0 1 0 8zM8 1c1.573 0 3.019.525 4.187 1.4l-1.357 1.358c-.025.025-.035.057-.056.084A4.979 4.979 0 0 0 8 3a4.979 4.979 0 0 0-2.773.842c-.021-.027-.03-.059-.055-.084L3.814 2.4A6.957 6.957 0 0 1 8 1zm0-1a8.001 8.001 0 1 0 .003 16.002A8.001 8.001 0 0 0 8 0z"
|
||||
id="help-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
fill-rule="evenodd"
|
||||
href="#help-a"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="euiCallOutHeader__title"
|
||||
>
|
||||
No results match your search criteria
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiSpacer euiSpacer--xl"
|
||||
/>
|
||||
<div
|
||||
class="euiText"
|
||||
>
|
||||
<h3>
|
||||
Expand your time range
|
||||
</h3>
|
||||
<p>
|
||||
One or more of the indices you’re looking at contains a date field. Your query may not match anything in the current time range, or there may not be any data at all in the currently selected time range. You can try
|
||||
<button
|
||||
aria-expanded="false"
|
||||
class="euiLink euiLink--primary"
|
||||
data-test-subj="discoverNoResultsTimefilter"
|
||||
type="button"
|
||||
>
|
||||
opening the time picker
|
||||
</button>
|
||||
and changing the time range to one which contains data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
13
src/core_plugins/kibana/public/discover/directives/index.js
Normal file
13
src/core_plugins/kibana/public/discover/directives/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import 'ngreact';
|
||||
import './no_results.less';
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
||||
import {
|
||||
DiscoverNoResults,
|
||||
} from './no_results';
|
||||
|
||||
import './timechart';
|
||||
|
||||
const app = uiModules.get('apps/discover', ['react']);
|
||||
|
||||
app.directive('discoverNoResults', reactDirective => reactDirective(DiscoverNoResults));
|
|
@ -1,12 +1,183 @@
|
|||
import { uiModules } from 'ui/modules';
|
||||
import noResultsTemplate from '../partials/no_results.html';
|
||||
import 'ui/directives/documentation_href';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
uiModules
|
||||
.get('apps/discover')
|
||||
.directive('discoverNoResults', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: noResultsTemplate
|
||||
};
|
||||
});
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiCode,
|
||||
EuiCodeBlock,
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export class DiscoverNoResults extends Component {
|
||||
static propTypes = {
|
||||
shardFailures: PropTypes.array,
|
||||
timeFieldName: PropTypes.string,
|
||||
queryLanguage: PropTypes.string,
|
||||
isTimePickerOpen: PropTypes.bool.isRequired,
|
||||
getDocLink: PropTypes.func.isRequired,
|
||||
topNavToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
onClickTimePickerButton = () => {
|
||||
this.props.topNavToggle('filter');
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
shardFailures,
|
||||
timeFieldName,
|
||||
queryLanguage,
|
||||
getDocLink,
|
||||
isTimePickerOpen,
|
||||
} = this.props;
|
||||
|
||||
let shardFailuresMessage;
|
||||
|
||||
if (shardFailures) {
|
||||
const failures = shardFailures.map((failure, index) => (
|
||||
<div key={`${failure.index}${failure.shard}${failure.reason}`}>
|
||||
<EuiText size="xs">
|
||||
<strong>Index ‘{failure.index}’</strong>, shard ‘{failure.shard}’
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiCodeBlock paddingSize="s">
|
||||
{failure.reason}
|
||||
</EuiCodeBlock>
|
||||
|
||||
{index < shardFailures.length - 1 ? <EuiSpacer size="l" /> : undefined}
|
||||
</div>
|
||||
));
|
||||
|
||||
shardFailuresMessage = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiText>
|
||||
<h3>
|
||||
Address shard failures
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
The following shard failures occurred:
|
||||
</p>
|
||||
|
||||
{failures}
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
let timeFieldMessage;
|
||||
|
||||
if (timeFieldName) {
|
||||
timeFieldMessage = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiText>
|
||||
<h3>
|
||||
Expand your time range
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
One or more of the indices you’re looking at contains a date field. Your query may
|
||||
not match anything in the current time range, or there may not be any data at all in
|
||||
the currently selected time range. You can try {(
|
||||
<EuiLink
|
||||
data-test-subj="discoverNoResultsTimefilter"
|
||||
onClick={this.onClickTimePickerButton}
|
||||
aria-expanded={isTimePickerOpen}
|
||||
>
|
||||
opening the time picker
|
||||
</EuiLink>
|
||||
)} and changing the time range to one which contains data.
|
||||
</p>
|
||||
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
let luceneQueryMessage;
|
||||
|
||||
if (queryLanguage === 'lucene') {
|
||||
const searchExamples = [{
|
||||
description: <EuiCode>200</EuiCode>,
|
||||
title: <EuiText><strong>Find requests that contain the number 200, in any field</strong></EuiText>,
|
||||
}, {
|
||||
description: <EuiCode>status:200</EuiCode>,
|
||||
title: <EuiText><strong>Find 200 in the status field</strong></EuiText>,
|
||||
}, {
|
||||
description: <EuiCode>status:[400 TO 499]</EuiCode>,
|
||||
title: <EuiText><strong>Find all status codes between 400-499</strong></EuiText>,
|
||||
}, {
|
||||
description: <EuiCode>status:[400 TO 499] AND extension:PHP</EuiCode>,
|
||||
title: <EuiText><strong>Find status codes 400-499 with the extension php</strong></EuiText>,
|
||||
}, {
|
||||
description: <EuiCode>status:[400 TO 499] AND (extension:php OR extension:html)</EuiCode>,
|
||||
title: <EuiText><strong>Find status codes 400-499 with the extension php or html</strong></EuiText>,
|
||||
}];
|
||||
|
||||
luceneQueryMessage = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiText>
|
||||
<h3>
|
||||
Refine your query
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
The search bar at the top uses Elasticsearch’s support for Lucene {(
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
href={getDocLink('query.luceneQuerySyntax')}
|
||||
>
|
||||
Query String syntax
|
||||
</EuiLink>
|
||||
)}. Here are some examples of how you can search for web server logs that have been
|
||||
parsed into a few fields.
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
listItems={searchExamples}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false} className="discoverNoResults">
|
||||
<EuiCallOut
|
||||
title="No results match your search criteria"
|
||||
color="warning"
|
||||
iconType="help"
|
||||
data-test-subj="discoverNoResults"
|
||||
/>
|
||||
|
||||
{shardFailuresMessage}
|
||||
{timeFieldMessage}
|
||||
{luceneQueryMessage}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
.discoverNoResults {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.discoverNoResultsShardFailure {
|
||||
display: block;
|
||||
|
||||
.euiFlexItem:last-child {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import React from 'react';
|
||||
import { render, mount } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
|
||||
import {
|
||||
DiscoverNoResults,
|
||||
} from './no_results';
|
||||
|
||||
describe('DiscoverNoResults', () => {
|
||||
describe('props', () => {
|
||||
describe('shardFailures', () => {
|
||||
test('renders failures list', () => {
|
||||
const shardFailures = [{
|
||||
index: 'A',
|
||||
shard: '1',
|
||||
reason: 'Awful error',
|
||||
}, {
|
||||
index: 'B',
|
||||
shard: '2',
|
||||
reason: 'Bad error',
|
||||
}];
|
||||
|
||||
const component = render(
|
||||
<DiscoverNoResults
|
||||
shardFailures={shardFailures}
|
||||
isTimePickerOpen={false}
|
||||
topNavToggle={() => {}}
|
||||
getDocLink={() => ''}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isTimePickerOpen', () => {
|
||||
test('false is reflected in the aria-expanded state of the button', () => {
|
||||
const component = render(
|
||||
<DiscoverNoResults
|
||||
timeFieldName="awesome_time_field"
|
||||
isTimePickerOpen={false}
|
||||
topNavToggle={() => {}}
|
||||
getDocLink={() => ''}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
component.find('[data-test-subj="discoverNoResultsTimefilter"]')[0].attribs['aria-expanded']
|
||||
).toBe('false');
|
||||
});
|
||||
|
||||
test('true is reflected in the aria-expanded state of the button', () => {
|
||||
const component = render(
|
||||
<DiscoverNoResults
|
||||
timeFieldName="awesome_time_field"
|
||||
isTimePickerOpen={true}
|
||||
topNavToggle={() => {}}
|
||||
getDocLink={() => ''}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
component.find('[data-test-subj="discoverNoResultsTimefilter"]')[0].attribs['aria-expanded']
|
||||
).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('timeFieldName', () => {
|
||||
test('renders time range feedback', () => {
|
||||
const component = render(
|
||||
<DiscoverNoResults
|
||||
timeFieldName="awesome_time_field"
|
||||
isTimePickerOpen={false}
|
||||
topNavToggle={() => {}}
|
||||
getDocLink={() => ''}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryLanguage', () => {
|
||||
test('supports lucene and renders doc link', () => {
|
||||
const component = render(
|
||||
<DiscoverNoResults
|
||||
queryLanguage="lucene"
|
||||
isTimePickerOpen={false}
|
||||
topNavToggle={() => {}}
|
||||
getDocLink={() => 'documentation-link'}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('topNavToggle', () => {
|
||||
test('is called whe time picker button is clicked', () => {
|
||||
const topNavToggleSpy = sinon.stub();
|
||||
const component = mount(
|
||||
<DiscoverNoResults
|
||||
timeFieldName="awesome_time_field"
|
||||
isTimePickerOpen={false}
|
||||
topNavToggle={topNavToggleSpy}
|
||||
getDocLink={() => ''}
|
||||
/>
|
||||
);
|
||||
|
||||
findTestSubject(component, 'discoverNoResultsTimefilter').simulate('click');
|
||||
sinon.assert.calledOnce(topNavToggleSpy);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -60,12 +60,28 @@
|
|||
|
||||
<div class="discover-wrapper col-md-10">
|
||||
<div class="discover-content">
|
||||
<discover-no-results ng-show="resultState === 'none'"></discover-no-results>
|
||||
<discover-no-results
|
||||
ng-show="resultState === 'none'"
|
||||
top-nav-toggle="kbnTopNav.toggle"
|
||||
is-time-picker-open="kbnTopNav.isCurrent('filter')"
|
||||
shard-failures="failures"
|
||||
time-field-name="opts.timefield"
|
||||
query-language="state.query.language"
|
||||
get-doc-link="getDocLink"
|
||||
></discover-no-results>
|
||||
|
||||
<!-- loading -->
|
||||
<div ng-show="resultState === 'loading'">
|
||||
<div class="discover-overlay">
|
||||
<div class="euiTitle">
|
||||
<discover-fetch-error
|
||||
ng-show="fetchError"
|
||||
fetch-error="fetchError"
|
||||
></discover-fetch-error>
|
||||
|
||||
<div
|
||||
ng-hide="fetchError"
|
||||
class="discover-overlay"
|
||||
>
|
||||
<div class="euiTitle" >
|
||||
<h2>Searching</h2>
|
||||
</div>
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'plugins/kibana/discover/saved_searches/saved_searches';
|
||||
import 'plugins/kibana/discover/directives/no_results';
|
||||
import 'plugins/kibana/discover/directives/timechart';
|
||||
import 'plugins/kibana/discover/directives';
|
||||
import 'ui/collapsible_sidebar';
|
||||
import 'plugins/kibana/discover/components/field_chooser/field_chooser';
|
||||
import 'plugins/kibana/discover/controllers/discover';
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
<div class="euiPage">
|
||||
|
||||
<div class="euiText" data-test-subj="discoverNoResults">
|
||||
|
||||
<h1>No results found <i aria-hidden="true" class="fa fa-meh-o"></i></h1>
|
||||
|
||||
<p>
|
||||
Unfortunately I could not find any results matching your search. I tried really hard. I looked all over the place and frankly, I just couldn't find anything good. Help me, help you. Here are some ideas:
|
||||
</p>
|
||||
|
||||
<div class="shard-failures" ng-show="failures">
|
||||
<h3>
|
||||
Shard Failures
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
The following shard failures ocurred:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li ng-repeat="failure in failures | limitTo: failuresShown"><strong>Index:</strong> {{failure.index}} <strong>Shard:</strong> {{failure.shard}} <strong>Reason:</strong> {{failure.reason}} </li>
|
||||
</ul>
|
||||
|
||||
<a
|
||||
ng-click="showAllFailures()"
|
||||
ng-if="failures.length > failuresShown"
|
||||
>
|
||||
Show More
|
||||
</a>
|
||||
|
||||
<a
|
||||
ng-click="showLessFailures()"
|
||||
ng-if="failures.length === failuresShown && failures.length > 5"
|
||||
>
|
||||
Show Less
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div ng-show="opts.timefield">
|
||||
<h3>
|
||||
Expand your time range
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
I see you are looking at an index with a date field. It is possible your query does not match anything in the current time range, or that there is no data at all in the currently selected time range. Click the button below to open the time picker. For future reference you can open the time picker by clicking on the
|
||||
<button
|
||||
class="kuiButton kuiButton--primary kuiButton--small"
|
||||
ng-click="kbnTopNav.toggle('filter')"
|
||||
aria-expanded="{{kbnTopNav.isCurrent('filter')}}"
|
||||
data-test-subj="discoverNoResultsTimefilter"
|
||||
>
|
||||
<span aria-hidden="true" class="kuiIcon fa-clock-o"></span> time picker
|
||||
</button>
|
||||
button in the top right corner of your screen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div ng-if="state.query.language === 'lucene'">
|
||||
<h3>
|
||||
Refine your query
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
The search bar at the top uses Elasticsearch's support for Lucene <a class="kuiLink" documentation-href="query.luceneQuerySyntax" target="_blank" rel="noopener noreferrer">Query String syntax</a>. Let's say we're searching web server logs that have been parsed into a few fields.
|
||||
</p>
|
||||
|
||||
<h4>Examples</h4>
|
||||
|
||||
<p>Find requests that contain the number 200, in any field:</p>
|
||||
<div class="euiCodeBlock euiCodeBlock--light euiCodeBlock--paddingSmall">
|
||||
<code class="euiCodeBlock__code">
|
||||
<pre class="euiCodeBlock__pre">200</pre>
|
||||
</code>
|
||||
</div>
|
||||
<div class="euiSpacer euiSpacer--l"></div>
|
||||
|
||||
<p>Or we can search in a specific field. Find 200 in the status field:</p>
|
||||
<div class="euiCodeBlock euiCodeBlock--light euiCodeBlock--paddingSmall">
|
||||
<code class="euiCodeBlock__code">
|
||||
<pre class="euiCodeBlock__pre">status:200</pre>
|
||||
</code>
|
||||
</div>
|
||||
<div class="euiSpacer euiSpacer--l"></div>
|
||||
|
||||
<p>Find all status codes between 400-499:</p>
|
||||
<div class="euiCodeBlock euiCodeBlock--light euiCodeBlock--paddingSmall">
|
||||
<code class="euiCodeBlock__code">
|
||||
<pre class="euiCodeBlock__pre">status:[400 TO 499]</pre>
|
||||
</code>
|
||||
</div>
|
||||
<div class="euiSpacer euiSpacer--l"></div>
|
||||
|
||||
<p>Find status codes 400-499 with the extension php:</p>
|
||||
<div class="euiCodeBlock euiCodeBlock--light euiCodeBlock--paddingSmall">
|
||||
<code class="euiCodeBlock__code">
|
||||
<pre class="euiCodeBlock__pre">status:[400 TO 499] AND extension:PHP</pre>
|
||||
</code>
|
||||
</div>
|
||||
<div class="euiSpacer euiSpacer--l"></div>
|
||||
|
||||
<p>Or HTML</p>
|
||||
<div class="euiCodeBlock euiCodeBlock--light euiCodeBlock--paddingSmall">
|
||||
<code class="euiCodeBlock__code">
|
||||
<pre class="euiCodeBlock__pre">status:[400 TO 499] AND (extension:php OR extension:html)</pre>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -2,7 +2,7 @@
|
|||
@import "~ui/styles/local_search.less";
|
||||
|
||||
.tab-discover {
|
||||
overflow-x: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.discover-sidebar-list-header {
|
||||
|
@ -149,18 +149,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shard-failures {
|
||||
color: @discover-shard-failures-color;
|
||||
background-color: @discover-shard-failures-bg !important;
|
||||
border: 1px solid;
|
||||
border-color: @discover-shard-failures-border;
|
||||
border-radius: @border-radius-base;
|
||||
padding: 0 20px 20px;
|
||||
li {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Override sidebar-item-title styles.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { get } from 'lodash';
|
||||
import { documentationLinks } from '../documentation_links';
|
||||
import { getDocLink } from '../documentation_links';
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
||||
const module = uiModules.get('kibana');
|
||||
|
@ -8,7 +7,7 @@ module.directive('documentationHref', function () {
|
|||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attributes) {
|
||||
element.attr('href', get(documentationLinks, attributes.documentationHref));
|
||||
element.attr('href', getDocLink(attributes.documentationHref));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
4
src/ui/public/documentation_links/get_doc_link.js
Normal file
4
src/ui/public/documentation_links/get_doc_link.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { get } from 'lodash';
|
||||
import { documentationLinks } from './documentation_links';
|
||||
|
||||
export const getDocLink = id => get(documentationLinks, id);
|
|
@ -1 +1,9 @@
|
|||
export { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION, documentationLinks } from './documentation_links';
|
||||
export {
|
||||
ELASTIC_WEBSITE_URL,
|
||||
DOC_LINK_VERSION,
|
||||
documentationLinks,
|
||||
} from './documentation_links';
|
||||
|
||||
export {
|
||||
getDocLink,
|
||||
} from './get_doc_link';
|
||||
|
|
|
@ -43,29 +43,29 @@ export function KbnTopNavControllerProvider($compile) {
|
|||
}
|
||||
|
||||
// change the current key and rerender
|
||||
setCurrent(key) {
|
||||
setCurrent = (key) => {
|
||||
if (key && !this.templates.hasOwnProperty(key)) {
|
||||
throw new TypeError(`KbnTopNav: unknown template key "${key}"`);
|
||||
}
|
||||
|
||||
this.currentKey = key || null;
|
||||
this._render();
|
||||
}
|
||||
};
|
||||
|
||||
// little usability helpers
|
||||
getCurrent() { return this.currentKey; }
|
||||
isCurrent(key) { return this.getCurrent() === key; }
|
||||
open(key) { this.setCurrent(key); }
|
||||
close(key) { (!key || this.isCurrent(key)) && this.setCurrent(null); }
|
||||
toggle(key) { this.setCurrent(this.isCurrent(key) ? null : key); }
|
||||
click(key) { this.handleClick(this.getItem(key)); }
|
||||
getItem(key) { return this.menuItems.find(i => i.key === key); }
|
||||
handleClick(menuItem) {
|
||||
getCurrent = () => { return this.currentKey; };
|
||||
isCurrent = (key) => { return this.getCurrent() === key; };
|
||||
open = (key) => { this.setCurrent(key); };
|
||||
close = (key) => { (!key || this.isCurrent(key)) && this.setCurrent(null); };
|
||||
toggle = (key) => { this.setCurrent(this.isCurrent(key) ? null : key); };
|
||||
click = (key) => { this.handleClick(this.getItem(key)); };
|
||||
getItem = (key) => { return this.menuItems.find(i => i.key === key); };
|
||||
handleClick = (menuItem) => {
|
||||
if (menuItem.disableButton()) {
|
||||
return false;
|
||||
}
|
||||
menuItem.run(menuItem, this);
|
||||
}
|
||||
};
|
||||
// apply the defaults to individual options
|
||||
_applyOptDefault(opt = {}) {
|
||||
const defaultedOpt = {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export { notify } from './notify';
|
||||
export { Notifier } from './notifier';
|
||||
export { getPainlessError } from './lib';
|
||||
export { fatalError, fatalErrorInternals, addFatalErrorCallback } from './fatal_error';
|
||||
export { GlobalToastList, toastNotifications } from './toasts';
|
||||
export { GlobalBannerList, banners } from './banners';
|
||||
|
|
|
@ -1,16 +1,40 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
const getRootCause = err => _.get(err, 'resp.error.root_cause');
|
||||
|
||||
/**
|
||||
* Utilize the extended error information returned from elasticsearch
|
||||
* @param {Error|String} err
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatESMsg(err) {
|
||||
const rootCause = _.get(err, 'resp.error.root_cause');
|
||||
export const formatESMsg = (err) => {
|
||||
const rootCause = getRootCause(err);
|
||||
|
||||
if (!rootCause) {
|
||||
return; //undefined
|
||||
return;
|
||||
}
|
||||
|
||||
const result = _.pluck(rootCause, 'reason').join('\n');
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
export const getPainlessError = (err) => {
|
||||
const rootCause = getRootCause(err);
|
||||
|
||||
if (!rootCause) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lang, script } = rootCause[0];
|
||||
|
||||
if (lang !== 'painless') {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
lang,
|
||||
script,
|
||||
message: `Error with Painless scripted field '${script}'`,
|
||||
error: err.message,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,36 +6,36 @@ const has = _.has;
|
|||
* Formats the error message from an error object, extended elasticsearch
|
||||
* object or simple string; prepends optional second parameter to the message
|
||||
* @param {Error|String} err
|
||||
* @param {String} from - Prefix for message indicating source (optional)
|
||||
* @param {String} source - Prefix for message indicating source (optional)
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatMsg(err, from) {
|
||||
let rtn = '';
|
||||
if (from) {
|
||||
rtn += from + ': ';
|
||||
export function formatMsg(err, source) {
|
||||
let message = '';
|
||||
if (source) {
|
||||
message += source + ': ';
|
||||
}
|
||||
|
||||
const esMsg = formatESMsg(err);
|
||||
|
||||
if (typeof err === 'string') {
|
||||
rtn += err;
|
||||
message += err;
|
||||
} else if (esMsg) {
|
||||
rtn += esMsg;
|
||||
message += esMsg;
|
||||
} else if (err instanceof Error) {
|
||||
rtn += formatMsg.describeError(err);
|
||||
message += formatMsg.describeError(err);
|
||||
} else if (has(err, 'status') && has(err, 'data')) {
|
||||
// is an Angular $http "error object"
|
||||
if (err.status === -1) {
|
||||
// status = -1 indicates that the request was failed to reach the server
|
||||
rtn += 'An HTTP request has failed to connect. ' +
|
||||
message += 'An HTTP request has failed to connect. ' +
|
||||
'Please check if the Kibana server is running and that your browser has a working connection, ' +
|
||||
'or contact your system administrator.';
|
||||
} else {
|
||||
rtn += 'Error ' + err.status + ' ' + err.statusText + ': ' + err.data.message;
|
||||
message += 'Error ' + err.status + ' ' + err.statusText + ': ' + err.data.message;
|
||||
}
|
||||
}
|
||||
|
||||
return rtn;
|
||||
return message;
|
||||
}
|
||||
|
||||
formatMsg.describeError = function (err) {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export { formatESMsg } from './format_es_msg';
|
||||
export { formatESMsg, getPainlessError } from './format_es_msg';
|
||||
export { formatMsg } from './format_msg';
|
||||
export { formatStack } from './format_stack';
|
||||
|
|
25
test/functional/apps/discover/_errors.js
Normal file
25
test/functional/apps/discover/_errors.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
|
||||
describe('errors', function describeIndexTests() {
|
||||
before(async function () {
|
||||
await esArchiver.load('invalid_scripted_field');
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await esArchiver.unload('invalid_scripted_field');
|
||||
});
|
||||
|
||||
describe('invalid scripted field error', () => {
|
||||
it('is rendered', async () => {
|
||||
const isFetchErrorVisible = await testSubjects.exists('discoverFetchError');
|
||||
expect(isFetchErrorVisible).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -12,6 +12,7 @@ export default function ({ getService, loadTestFile }) {
|
|||
});
|
||||
|
||||
loadTestFile(require.resolve('./_discover'));
|
||||
loadTestFile(require.resolve('./_errors'));
|
||||
loadTestFile(require.resolve('./_field_data'));
|
||||
loadTestFile(require.resolve('./_shared_links'));
|
||||
loadTestFile(require.resolve('./_collapse_expand'));
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,252 @@
|
|||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"index": ".kibana",
|
||||
"settings": {
|
||||
"index": {
|
||||
"number_of_shards": "1",
|
||||
"number_of_replicas": "0"
|
||||
}
|
||||
},
|
||||
"mappings": {
|
||||
"doc": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"config": {
|
||||
"dynamic": "true",
|
||||
"properties": {
|
||||
"buildNum": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"defaultIndex": {
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"hits": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kibanaSavedObjectMeta": {
|
||||
"properties": {
|
||||
"searchSourceJSON": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"optionsJSON": {
|
||||
"type": "text"
|
||||
},
|
||||
"panelsJSON": {
|
||||
"type": "text"
|
||||
},
|
||||
"refreshInterval": {
|
||||
"properties": {
|
||||
"display": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"pause": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"section": {
|
||||
"type": "integer"
|
||||
},
|
||||
"value": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeFrom": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"timeRestore": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"timeTo": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"uiStateJSON": {
|
||||
"type": "text"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"index-pattern": {
|
||||
"properties": {
|
||||
"fieldFormatMap": {
|
||||
"type": "text"
|
||||
},
|
||||
"fields": {
|
||||
"type": "text"
|
||||
},
|
||||
"intervalName": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"notExpandable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sourceFilters": {
|
||||
"type": "text"
|
||||
},
|
||||
"timeFieldName": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"properties": {
|
||||
"columns": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"hits": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kibanaSavedObjectMeta": {
|
||||
"properties": {
|
||||
"searchSourceJSON": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sort": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timelion-sheet": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"hits": {
|
||||
"type": "integer"
|
||||
},
|
||||
"kibanaSavedObjectMeta": {
|
||||
"properties": {
|
||||
"searchSourceJSON": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timelion_chart_height": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timelion_columns": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timelion_interval": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"timelion_other_interval": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"timelion_rows": {
|
||||
"type": "integer"
|
||||
},
|
||||
"timelion_sheet": {
|
||||
"type": "text"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "date"
|
||||
},
|
||||
"url": {
|
||||
"properties": {
|
||||
"accessCount": {
|
||||
"type": "long"
|
||||
},
|
||||
"accessDate": {
|
||||
"type": "date"
|
||||
},
|
||||
"createDate": {
|
||||
"type": "date"
|
||||
},
|
||||
"url": {
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 2048
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualization": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"kibanaSavedObjectMeta": {
|
||||
"properties": {
|
||||
"searchSourceJSON": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"savedSearchId": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"title": {
|
||||
"type": "text"
|
||||
},
|
||||
"uiStateJSON": {
|
||||
"type": "text"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"visState": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue