Adapt the app for Kibana 7.3.0

This commit is contained in:
JuanCarlos 2019-08-01 15:00:25 +02:00
parent de0e46a714
commit 61ee711bd0
No known key found for this signature in database
GPG Key ID: B1C4FB733616273A
10 changed files with 721 additions and 530 deletions

View File

@ -1,10 +1,10 @@
{ {
"name": "wazuh", "name": "wazuh",
"version": "3.9.4", "version": "3.9.4",
"revision": "0527", "revision": "0528",
"code": "0527-0", "code": "0528-0",
"kibana": { "kibana": {
"version": "7.2.0" "version": "7.3.0"
}, },
"description": "Wazuh app", "description": "Wazuh app",
"main": "index.js", "main": "index.js",

View File

@ -15,7 +15,7 @@ import { uiModules } from 'ui/modules';
import discoverTemplate from '../templates/kibana-template/kibana-discover-template.html'; import discoverTemplate from '../templates/kibana-template/kibana-discover-template.html';
uiModules.get('app/wazuh', ['kibana/courier']).directive('kbnDis', [ uiModules.get('app/wazuh', ['kibana/courier']).directive('kbnDis', [
function() { function () {
return { return {
restrict: 'E', restrict: 'E',
scope: {}, scope: {},
@ -25,7 +25,7 @@ uiModules.get('app/wazuh', ['kibana/courier']).directive('kbnDis', [
]); ]);
// Added dependencies (from Kibana module) // Added dependencies (from Kibana module)
import 'ui/pager'; import 'plugins/kibana/discover/doc_table/pager';
import 'ui/render_directive'; import 'ui/render_directive';
// Added from its index.js // Added from its index.js
@ -40,10 +40,7 @@ import 'plugins/kibana/discover/controllers/discover';
import 'ui/style_compile/style_compile'; import 'ui/style_compile/style_compile';
import 'ui/registry/doc_views'; import 'ui/registry/doc_views';
import 'plugins/kbn_doc_views/kbn_doc_views'; import 'plugins/kbn_doc_views/kbn_doc_views';
import 'ui/pager_control'; import 'plugins/kibana/discover/doc_table/pager_control';
import 'ui/pager';
import { data } from 'plugins/data';
import _ from 'lodash'; import _ from 'lodash';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import React from 'react'; import React from 'react';
@ -71,15 +68,17 @@ import {
} from 'ui/courier'; } from 'ui/courier';
import { toastNotifications } from 'ui/notify'; import { toastNotifications } from 'ui/notify';
import { VisProvider } from 'ui/vis'; import { VisProvider } from 'ui/vis';
import { VislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter';
import { DocTitleProvider } from 'ui/doc_title'; import { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib';
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; import { docTitle } from 'ui/doc_title';
import { intervalOptions } from 'ui/agg_types/buckets/_interval_options'; import { intervalOptions } from 'ui/agg_types/buckets/_interval_options';
import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory';
import uiRoutes from 'ui/routes'; import uiRoutes from 'ui/routes';
import { StateProvider } from 'ui/state_management/state'; import { StateProvider } from 'ui/state_management/state';
import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
import { FilterManagerProvider } from 'ui/filter_manager'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
import { getFilterGenerator } from 'ui/filter_manager';
import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { VisualizeLoaderProvider } from 'ui/visualize/loader/visualize_loader'; import { VisualizeLoaderProvider } from 'ui/visualize/loader/visualize_loader';
import { recentlyAccessed } from 'ui/persisted_log'; import { recentlyAccessed } from 'ui/persisted_log';
@ -104,15 +103,14 @@ import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_s
const { getRootBreadcrumbs, getSavedSearchBreadcrumbs } = data.search.ui; const { getRootBreadcrumbs, getSavedSearchBreadcrumbs } = data.search.ui;
import { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; import { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline';
import 'ui/capabilities/route_setup'; import 'ui/capabilities/route_setup';
import { defaultSearchStrategy } from 'ui/courier/search_strategy/default_search_strategy'; import { defaultSearchStrategy } from 'ui/courier/search_strategy/default_search_strategy';
import { data } from 'plugins/data/setup';
data.search.loadLegacyDirectives(); data.search.loadLegacyDirectives();
const fetchStatuses = { const fetchStatuses = {
UNINITIALIZED: 'uninitialized', UNINITIALIZED: 'uninitialized',
LOADING: 'loading', LOADING: 'loading',
COMPLETE: 'complete' COMPLETE: 'complete',
}; };
const app = uiModules.get('apps/discover', [ const app = uiModules.get('apps/discover', [
@ -123,7 +121,7 @@ const app = uiModules.get('apps/discover', [
'app/wazuh' 'app/wazuh'
]); ]);
app.directive('discoverAppW', function() { app.directive('discoverAppW', function () {
return { return {
restrict: 'E', restrict: 'E',
controllerAs: 'discoverApp', controllerAs: 'discoverApp',
@ -138,7 +136,6 @@ function discoverController(
$timeout, $timeout,
$window, $window,
AppState, AppState,
Notifier,
Private, Private,
Promise, Promise,
config, config,
@ -158,17 +155,13 @@ function discoverController(
const visualizeLoader = Private(VisualizeLoaderProvider); const visualizeLoader = Private(VisualizeLoaderProvider);
let visualizeHandler; let visualizeHandler;
const Vis = Private(VisProvider); const Vis = Private(VisProvider);
const docTitle = Private(DocTitleProvider); const responseHandler = vislibSeriesResponseHandlerProvider().handler;
const queryFilter = Private(FilterBarQueryFilterProvider);
const responseHandler = Private(VislibSeriesResponseHandlerProvider).handler;
const filterManager = Private(FilterManagerProvider);
const notify = new Notifier({
location: 'Discover'
});
const getUnhashableStates = Private(getUnhashableStatesProvider); const getUnhashableStates = Private(getUnhashableStatesProvider);
const shareContextMenuExtensions = Private( const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider);
ShareContextMenuExtensionsRegistryProvider
); const queryFilter = Private(FilterBarQueryFilterProvider);
const filterGen = getFilterGenerator(queryFilter);
const inspectorAdapters = { const inspectorAdapters = {
requests: new RequestAdapter() requests: new RequestAdapter()
}; };
@ -186,7 +179,7 @@ function discoverController(
$scope.fetchStatus = fetchStatuses.UNINITIALIZED; $scope.fetchStatus = fetchStatuses.UNINITIALIZED;
$scope.refreshInterval = timefilter.getRefreshInterval(); $scope.refreshInterval = timefilter.getRefreshInterval();
$scope.intervalEnabled = function(interval) { $scope.intervalEnabled = function (interval) {
return interval.val !== 'custom'; return interval.val !== 'custom';
}; };
@ -198,9 +191,9 @@ function discoverController(
if (filterUpdateSubscription) filterUpdateSubscription.unsubscribe(); if (filterUpdateSubscription) filterUpdateSubscription.unsubscribe();
}); });
const $appStatus = ($scope.appStatus = this.appStatus = { const $appStatus = $scope.appStatus = this.appStatus = {
dirty: !savedSearch.id dirty: !savedSearch.id
}); };
// WAZUH MODIFIED // WAZUH MODIFIED
$scope.topNavMenu = []; $scope.topNavMenu = [];
@ -228,35 +221,26 @@ function discoverController(
$scope.searchSource.setParent(timeRangeSearchSource); $scope.searchSource.setParent(timeRangeSearchSource);
const pageTitleSuffix = const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : '';
savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : '';
docTitle.change(`Wazuh${pageTitleSuffix}`); docTitle.change(`Wazuh${pageTitleSuffix}`);
const discoverBreadcrumbsTitle = i18n.translate( const discoverBreadcrumbsTitle = i18n.translate('kbn.discover.discoverBreadcrumbTitle', {
'kbn.discover.discoverBreadcrumbTitle', defaultMessage: 'Wazuh',
{ });
defaultMessage: 'Wazuh'
}
);
if (savedSearch.id && savedSearch.title) { if (savedSearch.id && savedSearch.title) {
chrome.breadcrumbs.set([ chrome.breadcrumbs.set([{
{
text: discoverBreadcrumbsTitle, text: discoverBreadcrumbsTitle,
href: '#/discover' href: '#/discover'
}, }, { text: savedSearch.title }]);
{ text: savedSearch.title }
]);
} else { } else {
chrome.breadcrumbs.set([ chrome.breadcrumbs.set([{
{ text: discoverBreadcrumbsTitle,
text: discoverBreadcrumbsTitle }]);
}
]);
} }
let stateMonitor; let stateMonitor;
const $state = ($scope.state = new AppState(getStateDefaults())); const $state = $scope.state = new AppState(getStateDefaults());
$scope.filters = queryFilter.getFilters(); $scope.filters = queryFilter.getFilters();
$scope.screenTitle = savedSearch.title; $scope.screenTitle = savedSearch.title;
@ -308,7 +292,7 @@ function discoverController(
} }
return await new Promise(resolve => { return await new Promise(resolve => {
const unwatch = $scope.$watch('fetchStatus', newValue => { const unwatch = $scope.$watch('fetchStatus', (newValue) => {
if (newValue === fetchStatuses.COMPLETE) { if (newValue === fetchStatuses.COMPLETE) {
unwatch(); unwatch();
resolve($scope.fieldCounts); resolve($scope.fieldCounts);
@ -329,10 +313,7 @@ function discoverController(
const timeFieldName = $scope.indexPattern.timeFieldName; const timeFieldName = $scope.indexPattern.timeFieldName;
const hideTimeColumn = config.get('doc_table:hideTimeColumn'); const hideTimeColumn = config.get('doc_table:hideTimeColumn');
const fields = const fields = (timeFieldName && !hideTimeColumn) ? [timeFieldName, ...selectedFields] : selectedFields;
timeFieldName && !hideTimeColumn
? [timeFieldName, ...selectedFields]
: selectedFields;
return { return {
searchFields: fields, searchFields: fields,
selectFields: fields selectFields: fields
@ -358,9 +339,7 @@ function discoverController(
}, },
fields: selectFields, fields: selectFields,
metaFields: $scope.indexPattern.metaFields, metaFields: $scope.indexPattern.metaFields,
conflictedTypesFields: $scope.indexPattern.fields conflictedTypesFields: $scope.indexPattern.fields.filter(f => f.type === 'conflict').map(f => f.name),
.filter(f => f.type === 'conflict')
.map(f => f.name),
indexPatternId: searchSource.getField('index').id indexPatternId: searchSource.getField('index').id
}; };
}; };
@ -371,19 +350,10 @@ function discoverController(
return { return {
query: $scope.searchSource.getField('query') || { query: $scope.searchSource.getField('query') || {
query: '', query: '',
language: language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage')
localStorage.get('kibana.userQueryLanguage') ||
config.get('search:queryLanguage')
}, },
sort: getSort.array( sort: getSort.array(savedSearch.sort, $scope.indexPattern, config.get('discover:sort:defaultOrder')),
savedSearch.sort, columns: savedSearch.columns.length > 0 ? savedSearch.columns : config.get('defaultColumns').slice(),
$scope.indexPattern,
config.get('discover:sort:defaultOrder')
),
columns:
savedSearch.columns.length > 0
? savedSearch.columns
: config.get('defaultColumns').slice(),
index: $scope.indexPattern.id, index: $scope.indexPattern.id,
interval: 'auto', interval: 'auto',
filters: _.cloneDeep($scope.searchSource.getOwnField('filter')) filters: _.cloneDeep($scope.searchSource.getOwnField('filter'))
@ -396,64 +366,46 @@ function discoverController(
$scope.getBucketIntervalToolTipText = () => { $scope.getBucketIntervalToolTipText = () => {
return i18n.translate('kbn.discover.bucketIntervalTooltip', { return i18n.translate('kbn.discover.bucketIntervalTooltip', {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
defaultMessage: defaultMessage: 'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}',
'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}',
values: { values: {
bucketsDescription: bucketsDescription: $scope.bucketInterval.scale > 1
$scope.bucketInterval.scale > 1 ? i18n.translate('kbn.discover.bucketIntervalTooltip.tooLargeBucketsText', {
? i18n.translate( defaultMessage: 'buckets that are too large',
'kbn.discover.bucketIntervalTooltip.tooLargeBucketsText', })
{ : i18n.translate('kbn.discover.bucketIntervalTooltip.tooManyBucketsText', {
defaultMessage: 'buckets that are too large' defaultMessage: 'too many buckets',
} }),
) bucketIntervalDescription: $scope.bucketInterval.description,
: i18n.translate( },
'kbn.discover.bucketIntervalTooltip.tooManyBucketsText',
{
defaultMessage: 'too many buckets'
}
),
bucketIntervalDescription: $scope.bucketInterval.description
}
}); });
}; };
$scope.$watchCollection('state.columns', function() { $scope.$watchCollection('state.columns', function () {
$state.save(); $state.save();
}); });
$scope.opts = { $scope.opts = {
// number of records to fetch, then paginate through // number of records to fetch, then paginate through
sampleSize: config.get('discover:sampleSize'), sampleSize: config.get('discover:sampleSize'),
timefield: timefield: isDefaultTypeIndexPattern($scope.indexPattern) && $scope.indexPattern.timeFieldName,
isDefaultTypeIndexPattern($scope.indexPattern) &&
$scope.indexPattern.timeFieldName,
savedSearch: savedSearch, savedSearch: savedSearch,
indexPatternList: $route.current.locals.ip.list indexPatternList: $route.current.locals.ip.list,
}; };
const init = _.once(function() { const init = _.once(function () {
stateMonitor = stateMonitorFactory.create($state, getStateDefaults()); stateMonitor = stateMonitorFactory.create($state, getStateDefaults());
stateMonitor.onChange(status => { stateMonitor.onChange((status) => {
$appStatus.dirty = status.dirty || !savedSearch.id; $appStatus.dirty = status.dirty || !savedSearch.id;
}); });
$scope.$on('$destroy', () => stateMonitor.destroy()); $scope.$on('$destroy', () => stateMonitor.destroy());
$scope.updateDataSource().then(function() { $scope.updateDataSource()
$scope.$listen(timefilter, 'fetch', function() { .then(function () {
// WAZUH $scope.$listen(timefilter, 'autoRefreshFetch', $scope.fetch);
$rootScope.$broadcast('updateVis'); $scope.$listen(timefilter, 'refreshIntervalUpdate', $scope.updateRefreshInterval);
$scope.fetch(); $scope.$listen(timefilter, 'timeUpdate', $scope.updateTime);
});
$scope.$listen(timefilter, 'refreshIntervalUpdate', () => { $scope.$watchCollection('state.sort', function (sort) {
$scope.updateRefreshInterval();
});
$scope.$listen(timefilter, 'timeUpdate', () => {
$scope.updateTime();
});
$scope.$watchCollection('state.sort', function(sort) {
if (!sort) return; if (!sort) return;
// get the current sort from {key: val} to ["key", "val"]; // get the current sort from {key: val} to ["key", "val"];
@ -466,11 +418,12 @@ function discoverController(
}); });
// update data source when filters update // update data source when filters update
filterUpdateSubscription = queryFilter.getUpdates$().subscribe(() => { filterUpdateSubscription = subscribeWithScope($scope, queryFilter.getUpdates$(), {
next: () => {
$scope.filters = queryFilter.getFilters(); $scope.filters = queryFilter.getFilters();
$scope $scope
.updateDataSource() .updateDataSource()
.then(function() { .then(function () {
/////////////////////////////// WAZUH /////////////////////////////////// /////////////////////////////// WAZUH ///////////////////////////////////
if (!filtersAreReady()) return; if (!filtersAreReady()) return;
discoverPendingUpdates.removeAll(); discoverPendingUpdates.removeAll();
@ -489,27 +442,28 @@ function discoverController(
$state.save(); $state.save();
}) })
.catch(console.error); // eslint-disable-line .catch(console.error); // eslint-disable-line
}
}); });
// fetch data when filters fire fetch event // fetch data when filters fire fetch event
filterFetchSubscription = queryFilter filterFetchSubscription = subscribeWithScope($scope, queryFilter.getUpdates$(), {
.getFetches$() next: $scope.fetch
.subscribe($scope.fetch); });
// update data source when hitting forward/back and the query changes // update data source when hitting forward/back and the query changes
$scope.$listen($state, 'fetch_with_changes', function(diff) { $scope.$listen($state, 'fetch_with_changes', function (diff) {
if (diff.indexOf('query') >= 0) $scope.fetch(); if (diff.indexOf('query') >= 0) $scope.fetch();
}); });
$scope.$watch('opts.timefield', function(timefield) { $scope.$watch('opts.timefield', function (timefield) {
$scope.enableTimeRangeSelector = !!timefield; $scope.enableTimeRangeSelector = !!timefield;
}); });
$scope.$watch('state.interval', function() { $scope.$watch('state.interval', function () {
$scope.fetch(); $scope.fetch();
}); });
$scope.$watch('vis.aggs', function() { $scope.$watch('vis.aggs', function () {
// no timefield, no vis, nothing to update // no timefield, no vis, nothing to update
if (!$scope.opts.timefield) return; if (!$scope.opts.timefield) return;
@ -520,14 +474,15 @@ function discoverController(
} }
}); });
$scope.$watch('state.query', newQuery => { $scope.$watch('state.query', (newQuery) => {
const query = migrateLegacyQuery(newQuery); const query = migrateLegacyQuery(newQuery);
$scope.updateQueryAndFetch({ query }); $scope.updateQueryAndFetch({ query });
}); });
$scope.$watchMulti( $scope.$watchMulti([
['rows', 'fetchStatus'], 'rows',
(function updateResultState() { 'fetchStatus'
], (function updateResultState() {
let prev = {}; let prev = {};
const status = { const status = {
LOADING: 'loading', // initial data load LOADING: 'loading', // initial data load
@ -540,16 +495,14 @@ function discoverController(
if (rows == null && oldRows == null) return status.LOADING; if (rows == null && oldRows == null) return status.LOADING;
const rowsEmpty = _.isEmpty(rows); const rowsEmpty = _.isEmpty(rows);
const preparingForFetch = const preparingForFetch = fetchStatus === fetchStatuses.UNINITIALIZED;
fetchStatus === fetchStatuses.UNINITIALIZED;
if (preparingForFetch) return status.LOADING; if (preparingForFetch) return status.LOADING;
else if (rowsEmpty && fetchStatus === fetchStatuses.LOADING) else if (rowsEmpty && fetchStatus === fetchStatuses.LOADING) return status.LOADING;
return status.LOADING;
else if (!rowsEmpty) return status.READY; else if (!rowsEmpty) return status.READY;
else return status.NO_RESULTS; else return status.NO_RESULTS;
} }
return function() { return function () {
const current = { const current = {
rows: $scope.rows, rows: $scope.rows,
fetchStatus: $scope.fetchStatus fetchStatus: $scope.fetchStatus
@ -561,15 +514,12 @@ function discoverController(
current.fetchStatus, current.fetchStatus,
prev.fetchStatus prev.fetchStatus
); );
// Copying it to the rootScope to access it from the Wazuh App // // Copying it to the rootScope to access it from the Wazuh App //
$rootScope.resultState = $scope.resultState; $rootScope.resultState = $scope.resultState;
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
prev = current; prev = current;
}; };
})() }()));
);
if ($scope.opts.timefield) { if ($scope.opts.timefield) {
setupVisualization(); setupVisualization();
@ -602,10 +552,9 @@ function discoverController(
return true; return true;
}; };
$scope.opts.fetch = $scope.fetch = function() { $scope.opts.fetch = $scope.fetch = function () {
// Wazuh filters are not ready yet // Wazuh filters are not ready yet
if (!filtersAreReady()) return; if (!filtersAreReady()) return;
// ignore requests to fetch before the app inits // ignore requests to fetch before the app inits
if (!init.complete) return; if (!init.complete) return;
@ -613,19 +562,34 @@ function discoverController(
$scope.updateTime(); $scope.updateTime();
$scope // Abort any in-progress requests before fetching again
.updateDataSource() $scope.searchSource.cancelQueued();
$scope.updateDataSource()
.then(setupVisualization) .then(setupVisualization)
.then(function() { .then(function () {
$state.save(); $state.save();
$scope.fetchStatus = fetchStatuses.LOADING; $scope.fetchStatus = fetchStatuses.LOADING;
logInspectorRequest(); logInspectorRequest();
return courier.fetch(); return $scope.searchSource.fetch();
}) })
.catch(notify.error); .then(onResults)
.catch((error) => {
const fetchError = getPainlessError(error);
if (fetchError) {
$scope.fetchError = fetchError;
} else {
toastNotifications.addError(error, {
title: i18n.translate('kbn.discover.errorLoadingData', {
defaultMessage: 'Error loading data',
}),
});
}
});
}; };
$scope.updateQueryAndFetch = function({ query, dateRange }) { $scope.updateQueryAndFetch = function ({ query, dateRange }) {
// Wazuh filters are not ready yet // Wazuh filters are not ready yet
if (!filtersAreReady()) return; if (!filtersAreReady()) return;
@ -635,7 +599,6 @@ function discoverController(
$rootScope.$broadcast('updateVis'); $rootScope.$broadcast('updateVis');
$rootScope.$broadcast('fetch'); $rootScope.$broadcast('fetch');
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
timefilter.setTime(dateRange); timefilter.setTime(dateRange);
$state.query = query; $state.query = query;
$scope.fetch(); $scope.fetch();
@ -647,12 +610,8 @@ function discoverController(
if ($scope.opts.timefield) { if ($scope.opts.timefield) {
const tabifiedData = tabifyAggResponse($scope.vis.aggs, resp); const tabifiedData = tabifyAggResponse($scope.vis.aggs, resp);
$scope.searchSource.rawResponse = resp; $scope.searchSource.rawResponse = resp;
Promise.resolve( Promise
buildVislibDimensions($scope.vis, { .resolve(buildVislibDimensions($scope.vis, { timeRange: $scope.timeRange, searchSource: $scope.searchSource }))
timeRange: $scope.timeRange,
searchSource: $scope.searchSource
})
)
.then(resp => responseHandler(tabifiedData, resp)) .then(resp => responseHandler(tabifiedData, resp))
.then(resp => { .then(resp => {
if (visualizeHandler) { if (visualizeHandler) {
@ -662,7 +621,7 @@ function discoverController(
visType: $scope.vis.type.name, visType: $scope.vis.type.name,
visData: resp, visData: resp,
visConfig: $scope.vis.params, visConfig: $scope.vis.params,
params: {} params: {},
} }
}); });
} }
@ -673,7 +632,7 @@ function discoverController(
$scope.rows = resp.hits.hits; $scope.rows = resp.hits.hits;
// if we haven't counted yet, reset the counts // if we haven't counted yet, reset the counts
const counts = ($scope.fieldCounts = $scope.fieldCounts || {}); const counts = $scope.fieldCounts = $scope.fieldCounts || {};
$scope.rows.forEach(hit => { $scope.rows.forEach(hit => {
const fields = Object.keys($scope.indexPattern.flattenHit(hit)); const fields = Object.keys($scope.indexPattern.flattenHit(hit));
@ -683,8 +642,6 @@ function discoverController(
}); });
$scope.fetchStatus = fetchStatuses.COMPLETE; $scope.fetchStatus = fetchStatuses.COMPLETE;
return $scope.searchSource.onResults().then(onResults);
} }
let inspectorRequest; let inspectorRequest;
@ -692,15 +649,11 @@ function discoverController(
function logInspectorRequest() { function logInspectorRequest() {
inspectorAdapters.requests.reset(); inspectorAdapters.requests.reset();
const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', { const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', {
defaultMessage: 'Data' defaultMessage: 'Data',
});
const description = i18n.translate('kbn.discover.inspectorRequestDescription', {
defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.',
}); });
const description = i18n.translate(
'kbn.discover.inspectorRequestDescription',
{
defaultMessage:
'This request queries Elasticsearch to fetch the data for the search.'
}
);
inspectorRequest = inspectorAdapters.requests.start(title, { description }); inspectorRequest = inspectorAdapters.requests.start(title, { description });
inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); inspectorRequest.stats(getRequestInspectorStats($scope.searchSource));
$scope.searchSource.getSearchRequestBody().then(body => { $scope.searchSource.getSearchRequestBody().then(body => {
@ -714,27 +667,7 @@ function discoverController(
.ok({ json: resp }); .ok({ json: resp });
} }
function startSearching() { $scope.updateTime = function () {
return $scope.searchSource
.onResults()
.then(onResults)
.catch(error => {
const fetchError = getPainlessError(error);
if (fetchError) {
$scope.fetchError = fetchError;
} else {
notify.error(error);
}
// Restart. This enables auto-refresh functionality.
startSearching();
});
}
startSearching();
$scope.updateTime = function() {
/////////////////////////////// WAZUH /////////////////////////////////// /////////////////////////////// WAZUH ///////////////////////////////////
if ($location.search().tab != 'configuration') { if ($location.search().tab != 'configuration') {
loadedVisualizations.removeAll(); loadedVisualizations.removeAll();
@ -742,7 +675,6 @@ function discoverController(
$rootScope.loadingStatus = 'Fetching data...'; $rootScope.loadingStatus = 'Fetching data...';
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
$scope.timeRange = { $scope.timeRange = {
from: dateMath.parse(timefilter.getTime().from), from: dateMath.parse(timefilter.getTime().from),
to: dateMath.parse(timefilter.getTime().to, { roundUp: true }) to: dateMath.parse(timefilter.getTime().to, { roundUp: true })
@ -750,26 +682,26 @@ function discoverController(
$scope.time = timefilter.getTime(); $scope.time = timefilter.getTime();
}; };
$scope.toMoment = function(datetime) { $scope.toMoment = function (datetime) {
return moment(datetime).format(config.get('dateFormat')); return moment(datetime).format(config.get('dateFormat'));
}; };
$scope.updateRefreshInterval = function() { $scope.updateRefreshInterval = function () {
$scope.refreshInterval = timefilter.getRefreshInterval(); $scope.refreshInterval = timefilter.getRefreshInterval();
}; };
$scope.onRefreshChange = function({ isPaused, refreshInterval }) { $scope.onRefreshChange = function ({ isPaused, refreshInterval }) {
timefilter.setRefreshInterval({ timefilter.setRefreshInterval({
pause: isPaused, pause: isPaused,
value: refreshInterval ? refreshInterval : $scope.refreshInterval.value value: refreshInterval ? refreshInterval : $scope.refreshInterval.value
}); });
}; };
$scope.resetQuery = function() { $scope.resetQuery = function () {
kbnUrl.change('/discover/{{id}}', { id: $route.current.params.id }); kbnUrl.change('/discover/{{id}}', { id: $route.current.params.id });
}; };
$scope.newQuery = function() { $scope.newQuery = function () {
kbnUrl.change('/discover'); kbnUrl.change('/discover');
}; };
@ -786,47 +718,39 @@ function discoverController(
}; };
// TODO: On array fields, negating does not negate the combination, rather all terms // TODO: On array fields, negating does not negate the combination, rather all terms
$scope.filterQuery = function(field, values, operation) { $scope.filterQuery = function (field, values, operation) {
// Commented due to https://github.com/elastic/kibana/issues/22426 // Commented due to https://github.com/elastic/kibana/issues/22426
//$scope.indexPattern.popularizeField(field, 1); //$scope.indexPattern.popularizeField(field, 1);
filterActions.addFilter( filterActions.addFilter(field, values, operation, $scope.indexPattern.id, $scope.state, filterGen);
field,
values,
operation,
$scope.indexPattern.id,
$scope.state,
filterManager
);
}; };
$scope.addColumn = function addColumn(columnName) { $scope.addColumn = function addColumn(columnName) {
// Commented due to https://github.com/elastic/kibana/issues/22426 // Commented due to https://github.com/elastic/kibana/issues/22426
//$scope.indexPattern.popularizeField(columnName, 1); //$scope.indexPattern.popularizeField(field, 1);
columnActions.addColumn($scope.state.columns, columnName); columnActions.addColumn($scope.state.columns, columnName);
}; };
$scope.removeColumn = function removeColumn(columnName) { $scope.removeColumn = function removeColumn(columnName) {
// Commented due to https://github.com/elastic/kibana/issues/22426 // Commented due to https://github.com/elastic/kibana/issues/22426
//$scope.indexPattern.popularizeField(columnName, 1); //$scope.indexPattern.popularizeField(field, 1); columnActions.removeColumn($scope.state.columns, columnName);
columnActions.removeColumn($scope.state.columns, columnName);
}; };
$scope.moveColumn = function moveColumn(columnName, newIndex) { $scope.moveColumn = function moveColumn(columnName, newIndex) {
columnActions.moveColumn($scope.state.columns, columnName, newIndex); columnActions.moveColumn($scope.state.columns, columnName, newIndex);
}; };
$scope.scrollToTop = function() { $scope.scrollToTop = function () {
$window.scrollTo(0, 0); $window.scrollTo(0, 0);
}; };
$scope.scrollToBottom = function() { $scope.scrollToBottom = function () {
// delay scrolling to after the rows have been rendered // delay scrolling to after the rows have been rendered
$timeout(() => { $timeout(() => {
$element.find('#discoverBottomMarker').focus(); $element.find('#discoverBottomMarker').focus();
}, 0); }, 0);
}; };
$scope.showAllRows = function() { $scope.showAllRows = function () {
$scope.minimumVisibleRows = $scope.hits; $scope.minimumVisibleRows = $scope.hits;
}; };
@ -845,12 +769,11 @@ function discoverController(
params: { params: {
field: $scope.opts.timefield, field: $scope.opts.timefield,
interval: $state.interval, interval: $state.interval,
timeRange: timefilter.getTime() timeRange: timefilter.getTime(),
} }
} }
]; ];
// we have a vis, just modify the aggs
if ($scope.vis) { if ($scope.vis) {
const visState = $scope.vis.getEnabledState(); const visState = $scope.vis.getEnabledState();
visState.aggs = visStateAggs; visState.aggs = visStateAggs;
@ -859,6 +782,7 @@ function discoverController(
return; return;
} }
const visSavedObject = { const visSavedObject = {
indexPattern: $scope.indexPattern.id, indexPattern: $scope.indexPattern.id,
visState: { visState: {
@ -879,15 +803,11 @@ function discoverController(
visSavedObject.vis = $scope.vis; visSavedObject.vis = $scope.vis;
$scope.searchSource.onRequestStart((searchSource, searchRequest) => { $scope.searchSource.onRequestStart((searchSource, searchRequest) => {
return $scope.vis return $scope.vis.getAggConfig().onSearchRequestStart(searchSource, searchRequest);
.getAggConfig()
.onSearchRequestStart(searchSource, searchRequest);
}); });
$scope.searchSource.setField('aggs', function() { $scope.searchSource.setField('aggs', function () {
//////////////////// WAZUH //////////////////////////////// //////////////////// WAZUH ////////////////////////////////
// Old code: //
// return $scope.vis.getAggConfig().toDsl(); //
const result = $scope.vis.getAggConfig().toDsl(); const result = $scope.vis.getAggConfig().toDsl();
if (((result[2] || {}).date_histogram || {}).interval === '0ms') { if (((result[2] || {}).date_histogram || {}).interval === '0ms') {
result[2].date_histogram.interval = '1d'; result[2].date_histogram.interval = '1d';
@ -898,15 +818,9 @@ function discoverController(
$timeout(async () => { $timeout(async () => {
const visEl = $element.find('#discoverHistogram')[0]; const visEl = $element.find('#discoverHistogram')[0];
if (visEl) { visualizeHandler = await visualizeLoader.embedVisualizationWithSavedObject(visEl, visSavedObject, {
visualizeHandler = await visualizeLoader.embedVisualizationWithSavedObject( autoFetch: false,
visEl, });
visSavedObject,
{
autoFetch: false
}
);
}
}); });
} }
@ -914,7 +828,7 @@ function discoverController(
const { const {
loaded: loadedIndexPattern, loaded: loadedIndexPattern,
stateVal, stateVal,
stateValFound stateValFound,
} = $route.current.locals.ip; } = $route.current.locals.ip;
const ownIndexPattern = $scope.searchSource.getOwnField('index'); const ownIndexPattern = $scope.searchSource.getOwnField('index');
@ -924,47 +838,36 @@ function discoverController(
} }
if (stateVal && !stateValFound) { if (stateVal && !stateValFound) {
const warningTitle = i18n( const warningTitle = i18n.translate('kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle', {
'kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle',
{
defaultMessage: '{stateVal} is not a configured index pattern ID', defaultMessage: '{stateVal} is not a configured index pattern ID',
values: { values: {
stateVal: `"${stateVal}"` stateVal: `"${stateVal}"`,
} },
} });
);
if (ownIndexPattern) { if (ownIndexPattern) {
toastNotifications.addWarning({ toastNotifications.addWarning({
title: warningTitle, title: warningTitle,
text: i18n( text: i18n.translate('kbn.discover.showingSavedIndexPatternWarningDescription', {
'kbn.discover.showingSavedIndexPatternWarningDescription', defaultMessage: 'Showing the saved index pattern: "{ownIndexPatternTitle}" ({ownIndexPatternId})',
{
defaultMessage:
'Showing the saved index pattern: "{ownIndexPatternTitle}" ({ownIndexPatternId})',
values: { values: {
ownIndexPatternTitle: ownIndexPattern.title, ownIndexPatternTitle: ownIndexPattern.title,
ownIndexPatternId: ownIndexPattern.id ownIndexPatternId: ownIndexPattern.id,
} },
} }),
)
}); });
return ownIndexPattern; return ownIndexPattern;
} }
toastNotifications.addWarning({ toastNotifications.addWarning({
title: warningTitle, title: warningTitle,
text: i18n( text: i18n.translate('kbn.discover.showingDefaultIndexPatternWarningDescription', {
'kbn.discover.showingDefaultIndexPatternWarningDescription', defaultMessage: 'Showing the default index pattern: "{loadedIndexPatternTitle}" ({loadedIndexPatternId})',
{
defaultMessage:
'Showing the default index pattern: "{loadedIndexPatternTitle}" ({loadedIndexPatternId})',
values: { values: {
loadedIndexPatternTitle: loadedIndexPattern.title, loadedIndexPatternTitle: loadedIndexPattern.title,
loadedIndexPatternId: loadedIndexPattern.id loadedIndexPatternId: loadedIndexPattern.id,
} },
} }),
)
}); });
} }
@ -973,9 +876,10 @@ function discoverController(
// Block the UI from loading if the user has loaded a rollup index pattern but it isn't // Block the UI from loading if the user has loaded a rollup index pattern but it isn't
// supported. // supported.
$scope.isUnsupportedIndexPattern = $scope.isUnsupportedIndexPattern = (
!isDefaultTypeIndexPattern($route.current.locals.ip.loaded) && !isDefaultTypeIndexPattern($route.current.locals.ip.loaded)
!hasSearchStategyForIndexPattern($route.current.locals.ip.loaded); && !hasSearchStategyForIndexPattern($route.current.locals.ip.loaded)
);
if ($scope.isUnsupportedIndexPattern) { if ($scope.isUnsupportedIndexPattern) {
$scope.unsupportedIndexPatternType = $route.current.locals.ip.loaded.type; $scope.unsupportedIndexPatternType = $route.current.locals.ip.loaded.type;
@ -1017,7 +921,7 @@ function discoverController(
queryFilter queryFilter
.addFilters(wzCurrentFilters) .addFilters(wzCurrentFilters)
.then(() => {}) .then(() => { })
.catch(error => console.log(error.message || error)); // eslint-disable-line .catch(error => console.log(error.message || error)); // eslint-disable-line
} }
}; };
@ -1046,6 +950,5 @@ function discoverController(
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
init(); init();
} }

View File

@ -25,7 +25,7 @@
// @ts-ignore // @ts-ignore
import chrome from 'ui/chrome'; import chrome from 'ui/chrome';
// @ts-ignore // @ts-ignore
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter';
// @ts-ignore // @ts-ignore
import { IPrivate } from 'ui/private'; import { IPrivate } from 'ui/private';
import { EmbeddedVisualizeHandler } from './embedded_visualize_handler'; import { EmbeddedVisualizeHandler } from './embedded_visualize_handler';

View File

@ -27,9 +27,9 @@ import classNames from 'classnames';
import React, { Component } from 'react'; import React, { Component } from 'react';
import chrome from 'ui/chrome'; import chrome from 'ui/chrome';
import { IndexPattern } from 'ui/index_patterns'; import { IndexPattern } from 'ui/index_patterns';
import { FilterEditor } from 'ui/filter_bar/filter_editor'; import { FilterEditor } from 'plugins/data/filter/filter_bar/filter_editor';
import { FilterItem } from './filter_item'; import { FilterItem } from './filter_item';
import { FilterOptions } from 'ui/filter_bar/filter_options'; import { FilterOptions } from 'plugins/data/filter/filter_bar/filter_options';
const config = chrome.getUiSettingsClient(); const config = chrome.getUiSettingsClient();

View File

@ -24,7 +24,7 @@ import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { IndexPattern } from 'ui/index_patterns'; import { IndexPattern } from 'ui/index_patterns';
import { FilterEditor } from 'ui/filter_bar/filter_editor'; import { FilterEditor } from 'plugins/data/filter/filter_bar/filter_editor';
import { FilterView } from './filter_view'; import { FilterView } from './filter_view';
interface Props { interface Props {

View File

@ -19,7 +19,7 @@ import React, { SFC } from 'react';
import { import {
existsOperator, existsOperator,
isOneOfOperator isOneOfOperator
} from 'ui/filter_bar/filter_editor/lib/filter_operators'; } from 'plugins/data/filter/filter_bar/filter_editor/lib/filter_operators';
interface Props { interface Props {
filter: Filter; filter: Filter;

View File

@ -0,0 +1,380 @@
/*
* Author: Elasticsearch B.V.
* Updated by Wazuh, Inc.
*
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query';
import { IndexPattern } from 'ui/index_patterns';
import classNames from 'classnames';
import _ from 'lodash';
import { get, isEqual } from 'lodash';
import React, { Component } from 'react';
import { Storage } from 'ui/storage';
import { timeHistory } from 'ui/timefilter/time_history';
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSuperDatePicker } from '@elastic/eui';
// @ts-ignore
import { EuiSuperUpdateButton } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { documentationLinks } from 'ui/documentation_links';
import { Toast, toastNotifications } from 'ui/notify';
import chrome from 'ui/chrome';
import { PersistedLog } from 'ui/persisted_log';
import { QueryBarInput } from 'plugins/data/query/query_bar/components/query_bar_input';
import { getQueryLog } from 'plugins/data/query/query_bar/lib/get_query_log';
import { Query } from 'plugins/data/query/query_bar/index';
const config = chrome.getUiSettingsClient();
interface DateRange {
from: string;
to: string;
}
interface Props {
query: Query;
onSubmit: (payload: { dateRange: DateRange; query: Query }) => void;
disableAutoFocus?: boolean;
appName: string;
screenTitle: string;
indexPatterns: Array<IndexPattern | string>;
store: Storage;
intl: InjectedIntl;
prepend?: any;
showDatePicker?: boolean;
dateRangeFrom?: string;
dateRangeTo?: string;
isRefreshPaused?: boolean;
refreshInterval?: number;
showAutoRefreshOnly?: boolean;
onRefreshChange?: (options: { isPaused: boolean; refreshInterval: number }) => void;
customSubmitButton?: any;
}
interface State {
query: Query;
inputIsPristine: boolean;
currentProps?: Props;
dateRangeFrom: string;
dateRangeTo: string;
isDateRangeInvalid: boolean;
}
export class QueryBarUI extends Component<Props, State> {
public static getDerivedStateFromProps(nextProps: Props, prevState: State) {
if (isEqual(prevState.currentProps, nextProps)) {
return null;
}
let nextQuery = null;
if (nextProps.query.query !== prevState.query.query) {
nextQuery = {
query: nextProps.query.query,
language: nextProps.query.language,
};
} else if (nextProps.query.language !== prevState.query.language) {
nextQuery = {
query: '',
language: nextProps.query.language,
};
}
let nextDateRange = null;
if (
nextProps.dateRangeFrom !== get(prevState, 'currentProps.dateRangeFrom') ||
nextProps.dateRangeTo !== get(prevState, 'currentProps.dateRangeTo')
) {
nextDateRange = {
dateRangeFrom: nextProps.dateRangeFrom,
dateRangeTo: nextProps.dateRangeTo,
};
}
const nextState: any = {
currentProps: nextProps,
};
if (nextQuery) {
nextState.query = nextQuery;
}
if (nextDateRange) {
nextState.dateRangeFrom = nextDateRange.dateRangeFrom;
nextState.dateRangeTo = nextDateRange.dateRangeTo;
}
return nextState;
}
/*
Keep the "draft" value in local state until the user actually submits the query. There are a couple advantages:
1. Each app doesn't have to maintain its own "draft" value if it wants to put off updating the query in app state
until the user manually submits their changes. Most apps have watches on the query value in app state so we don't
want to trigger those on every keypress. Also, some apps (e.g. dashboard) already juggle multiple query values,
each with slightly different semantics and I'd rather not add yet another variable to the mix.
2. Changes to the local component state won't trigger an Angular digest cycle. Triggering digest cycles on every
keypress has been a major source of performance issues for us in previous implementations of the query bar.
See https://github.com/elastic/kibana/issues/14086
*/
public state = {
query: {
query: this.props.query.query,
language: this.props.query.language,
},
inputIsPristine: true,
currentProps: this.props,
dateRangeFrom: _.get(this.props, 'dateRangeFrom', 'now-15m'),
dateRangeTo: _.get(this.props, 'dateRangeTo', 'now'),
isDateRangeInvalid: false,
};
public inputRef: HTMLInputElement | null = null;
private persistedLog: PersistedLog | undefined;
public isDirty = () => {
if (!this.props.showDatePicker) {
return this.state.query.query !== this.props.query.query;
}
return (
this.state.query.query !== this.props.query.query ||
this.state.dateRangeFrom !== this.props.dateRangeFrom ||
this.state.dateRangeTo !== this.props.dateRangeTo
);
};
public onClickSubmitButton = (event: React.MouseEvent<HTMLButtonElement>) => {
if (this.persistedLog) {
this.persistedLog.add(this.state.query.query);
}
this.onSubmit(() => event.preventDefault());
};
public onChange = (query: Query) => {
this.setState({
query,
inputIsPristine: false,
});
};
public onTimeChange = ({
start,
end,
isInvalid,
isQuickSelection,
}: {
start: string;
end: string;
isInvalid: boolean;
isQuickSelection: boolean;
}) => {
this.setState(
{
dateRangeFrom: start,
dateRangeTo: end,
isDateRangeInvalid: isInvalid,
},
() => isQuickSelection && this.onSubmit()
);
};
public onSubmit = (preventDefault?: () => void) => {
if (preventDefault) {
preventDefault();
}
this.handleLuceneSyntaxWarning();
timeHistory.add({
from: this.state.dateRangeFrom,
to: this.state.dateRangeTo,
});
this.props.onSubmit({
query: {
query: this.state.query.query,
language: this.state.query.language,
},
dateRange: {
from: this.state.dateRangeFrom,
to: this.state.dateRangeTo,
},
});
};
private onInputSubmit = (query: Query) => {
this.setState({ query }, () => {
this.onSubmit();
});
};
public componentDidMount() {
this.persistedLog = getQueryLog(this.props.appName, this.props.query.language);
}
public componentDidUpdate(prevProps: Props) {
if (prevProps.query.language !== this.props.query.language) {
this.persistedLog = getQueryLog(this.props.appName, this.props.query.language);
}
}
public render() {
const classes = classNames('kbnQueryBar', {
'kbnQueryBar--withDatePicker': this.props.showDatePicker,
});
return (
<EuiFlexGroup className={classes} responsive={!!this.props.showDatePicker} gutterSize="s">
<EuiFlexItem>
<QueryBarInput
appName={this.props.appName}
disableAutoFocus={this.props.disableAutoFocus}
indexPatterns={this.props.indexPatterns}
prepend={this.props.prepend}
query={this.state.query}
screenTitle={this.props.screenTitle}
store={this.props.store}
onChange={this.onChange}
onSubmit={this.onInputSubmit}
persistedLog={this.persistedLog}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>{this.renderUpdateButton()}</EuiFlexItem>
</EuiFlexGroup>
);
}
private renderUpdateButton() {
const button = this.props.customSubmitButton ? (
React.cloneElement(this.props.customSubmitButton, { onClick: this.onClickSubmitButton })
) : (
<EuiSuperUpdateButton
needsUpdate={this.isDirty()}
isDisabled={this.state.isDateRangeInvalid}
onClick={this.onClickSubmitButton}
data-test-subj="querySubmitButton"
/>
);
if (!this.props.showDatePicker) {
return button;
}
return (
<EuiFlexGroup responsive={false} gutterSize="s">
{this.renderDatePicker()}
<EuiFlexItem grow={false}>{button}</EuiFlexItem>
</EuiFlexGroup>
);
}
private renderDatePicker() {
if (!this.props.showDatePicker) {
return null;
}
const recentlyUsedRanges = timeHistory
.get()
.map(({ from, to }: { from: string; to: string }) => {
return {
start: from,
end: to,
};
});
const commonlyUsedRanges = config
.get('timepicker:quickRanges')
.map(({ from, to, display }: { from: string; to: string; display: string }) => {
return {
start: from,
end: to,
label: display,
};
});
return (
<EuiFlexItem className="kbnQueryBar__datePickerWrapper">
<EuiSuperDatePicker
start={this.state.dateRangeFrom}
end={this.state.dateRangeTo}
isPaused={this.props.isRefreshPaused}
refreshInterval={this.props.refreshInterval}
onTimeChange={this.onTimeChange}
onRefreshChange={this.props.onRefreshChange}
showUpdateButton={false}
recentlyUsedRanges={recentlyUsedRanges}
commonlyUsedRanges={commonlyUsedRanges}
dateFormat={config.get('dateFormat')}
isAutoRefreshOnly={this.props.showAutoRefreshOnly}
/>
</EuiFlexItem>
);
}
private handleLuceneSyntaxWarning() {
const { intl, store } = this.props;
const { query, language } = this.state.query;
if (
language === 'kuery' &&
typeof query === 'string' &&
!store.get('kibana.luceneSyntaxWarningOptOut') &&
doesKueryExpressionHaveLuceneSyntaxError(query)
) {
const toast = toastNotifications.addWarning({
title: intl.formatMessage({
id: 'data.query.queryBar.luceneSyntaxWarningTitle',
defaultMessage: 'Lucene syntax warning',
}),
text: (
<div>
<p>
<FormattedMessage
id="data.query.queryBar.luceneSyntaxWarningMessage"
defaultMessage="It looks like you may be trying to use Lucene query syntax, although you
have Kibana Query Language (KQL) selected. Please review the KQL docs {link}."
values={{
link: (
<EuiLink href={documentationLinks.query.kueryQuerySyntax} target="_blank">
<FormattedMessage
id="data.query.queryBar.syntaxOptionsDescription.docsLinkText"
defaultMessage="here"
/>
</EuiLink>
),
}}
/>
</p>
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButton size="s" onClick={() => this.onLuceneSyntaxWarningOptOut(toast)}>
Don't show again
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</div>
),
});
}
}
private onLuceneSyntaxWarningOptOut(toast: Toast) {
this.props.store.set('kibana.luceneSyntaxWarningOptOut', true);
toastNotifications.remove(toast);
}
}
// @ts-ignore
export const QueryBar = injectI18n(QueryBarUI);

View File

@ -13,36 +13,34 @@
*/ */
// @ts-ignore // @ts-ignore
import { data } from 'plugins/data';
import { EuiFilterButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EuiFilterButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Filter } from '@kbn/es-query'; import { Filter } from '@kbn/es-query';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ResizeObserver from 'resize-observer-polyfill'; import ResizeObserver from 'resize-observer-polyfill';
import { FilterBar } from './filter_bar';
import { IndexPattern } from 'ui/index_patterns'; import { IndexPattern } from 'ui/index_patterns';
const { QueryBar } = data.query.ui;
import { Storage } from 'ui/storage'; import { Storage } from 'ui/storage';
interface Query { import { QueryBar } from './query_bar';
query: string; import { Query } from 'plugins/data/query/query_bar/index';
language: string; import { FilterBar } from './filter_bar';
}
interface DateRange { interface DateRange {
from: string; from: string;
to: string; to: string;
} }
/**
* NgReact lib requires that changes to the props need to be made in the directive config as well
* See [search_bar\directive\index.js] file
*/
interface Props { interface Props {
query: { query: Query;
query: string;
language: string;
};
onQuerySubmit: (payload: { dateRange: DateRange; query: Query }) => void; onQuerySubmit: (payload: { dateRange: DateRange; query: Query }) => void;
disableAutoFocus?: boolean; disableAutoFocus?: boolean;
appName: string; appName: string;
screenTitle: string;
indexPatterns: IndexPattern[]; indexPatterns: IndexPattern[];
store: Storage; store: Storage;
filters: Filter[]; filters: Filter[];
@ -87,9 +85,9 @@ class SearchBarUI extends Component<Props, State> {
}; };
// member-ordering rules conflict with use-before-declaration rules // member-ordering rules conflict with use-before-declaration rules
/* tslint:disable */ /* eslint-disable */
public ro = new ResizeObserver(this.setFilterBarHeight); public ro = new ResizeObserver(this.setFilterBarHeight);
/* tslint:enable */ /* eslint-enable */
public toggleFiltersVisible = () => { public toggleFiltersVisible = () => {
this.setState({ this.setState({
@ -113,16 +111,16 @@ class SearchBarUI extends Component<Props, State> {
public render() { public render() {
const filtersAppliedText = this.props.intl.formatMessage({ const filtersAppliedText = this.props.intl.formatMessage({
id: 'common.ui.searchBar.filtersButtonFiltersAppliedTitle', id: 'data.search.searchBar.filtersButtonFiltersAppliedTitle',
defaultMessage: 'filters applied.', defaultMessage: 'filters applied.',
}); });
const clickToShowOrHideText = this.state.isFiltersVisible const clickToShowOrHideText = this.state.isFiltersVisible
? this.props.intl.formatMessage({ ? this.props.intl.formatMessage({
id: 'common.ui.searchBar.filtersButtonClickToShowTitle', id: 'data.search.searchBar.filtersButtonClickToShowTitle',
defaultMessage: 'Select to hide', defaultMessage: 'Select to hide',
}) })
: this.props.intl.formatMessage({ : this.props.intl.formatMessage({
id: 'common.ui.searchBar.filtersButtonClickToHideTitle', id: 'data.search.searchBar.filtersButtonClickToHideTitle',
defaultMessage: 'Select to show', defaultMessage: 'Select to show',
}); });
@ -131,7 +129,7 @@ class SearchBarUI extends Component<Props, State> {
onClick={this.toggleFiltersVisible} onClick={this.toggleFiltersVisible}
isSelected={this.state.isFiltersVisible} isSelected={this.state.isFiltersVisible}
hasActiveFilters={this.state.isFiltersVisible} hasActiveFilters={this.state.isFiltersVisible}
numFilters={this.props.filters.length > 0 ? this.props.filters.length : null} numFilters={this.props.filters.length > 0 ? this.props.filters.length : undefined}
aria-controls="GlobalFilterGroup" aria-controls="GlobalFilterGroup"
aria-expanded={!!this.state.isFiltersVisible} aria-expanded={!!this.state.isFiltersVisible}
title={`${this.props.filters.length} ${filtersAppliedText} ${clickToShowOrHideText}`} title={`${this.props.filters.length} ${filtersAppliedText} ${clickToShowOrHideText}`}
@ -149,6 +147,7 @@ class SearchBarUI extends Component<Props, State> {
{this.props.showQueryBar ? ( {this.props.showQueryBar ? (
<QueryBar <QueryBar
query={this.props.query} query={this.props.query}
screenTitle={this.props.screenTitle}
onSubmit={this.props.onQuerySubmit} onSubmit={this.props.onQuerySubmit}
appName={this.props.appName} appName={this.props.appName}
indexPatterns={this.props.indexPatterns} indexPatterns={this.props.indexPatterns}

View File

@ -5,101 +5,56 @@
<div data-transclude-slots> <div data-transclude-slots>
<!-- Breadcrumbs. --> <!-- Breadcrumbs. -->
<div data-transclude-slot="topLeftCorner" class="kuiLocalBreadcrumbs"> <div data-transclude-slot="topLeftCorner" class="kuiLocalBreadcrumbs">
<h1 tabindex="0" id="kui_local_breadcrumb" class="kuiLocalBreadcrumb" ng-if="opts.savedSearch.id"> <h1 id="kui_local_breadcrumb" class="kuiLocalBreadcrumb" ng-if="opts.savedSearch.id">
<span class="kuiLocalBreadcrumb__emphasis"> <span class="kuiLocalBreadcrumb__emphasis">
<span <button class="kuiLink" type="button" id="reload_saved_search" aria-label="{{::'kbn.discover.reloadSavedSearchAriaLabel' | i18n: {defaultMessage: 'Reload saved search'} }}"
id="reload_saved_search"
aria-label="{{::'kbn.discover.reloadSavedSearchAriaLabel' | i18n: {defaultMessage: 'Reload saved search'} }}"
tooltip="{{::'kbn.discover.reloadSavedSearchTooltip' | i18n: {defaultMessage: 'Reload saved search'} }}" tooltip="{{::'kbn.discover.reloadSavedSearchTooltip' | i18n: {defaultMessage: 'Reload saved search'} }}"
tooltip-placement="right" tooltip-placement="right" tooltip-append-to-body="1" ng-click="resetQuery()">
tooltip-append-to-body="1" <span class="kuiIcon fa-undo small"></span>
ng-click="resetQuery()" {{::'kbn.discover.reloadSavedSearchButton' | i18n: {defaultMessage: 'Reload'} }}
kbn-accessible-click </button>
class="kuiIcon fa-undo small"
></span>&nbsp;
</span> </span>
</h1> </h1>
<div class="kuiLocalBreadcrumb"> <div class="kuiLocalBreadcrumb">
<span data-test-subj="discoverQueryHits" class="kuiLocalBreadcrumb__emphasis">{{(hits || 0) | number:0}}</span> <span data-test-subj="discoverQueryHits" class="kuiLocalBreadcrumb__emphasis">{{(hits || 0) | number:0}}</span>
<span <span i18n-id="kbn.discover.hitsPluralTitle" i18n-default-message="{hits, plural, one {hit} other {hits}}"
i18n-id="kbn.discover.hitsPluralTitle" i18n-values="{ hits }"></span>
i18n-default-message="{hits, plural, one {hit} other {hits}}"
i18n-values="{ hits }"
></span>
</div> </div>
</div> </div>
<!-- Search. --> <!-- Search. -->
<div data-transclude-slot="bottomRow" class="fullWidth"> <div data-transclude-slot="bottomRow" class="fullWidth">
<wz-search-bar <wz-search-bar query="state.query" screen-title="screenTitle" on-query-submit="updateQueryAndFetch" app-name="'discover'"
query="state.query" index-patterns="[indexPattern]" filters="filters" on-filters-updated="onFiltersUpdated" show-date-picker="enableTimeRangeSelector"
screen-title="screenTitle" date-range-from="time.from" date-range-to="time.to" is-refresh-paused="refreshInterval.pause"
on-query-submit="updateQueryAndFetch" refresh-interval="refreshInterval.value" on-refresh-change="onRefreshChange">
app-name="'discover'" </wz-search-bar>
index-patterns="[indexPattern]"
filters="filters"
on-filters-updated="onFiltersUpdated"
show-date-picker="enableTimeRangeSelector"
date-range-from="time.from"
date-range-to="time.to"
is-refresh-paused="refreshInterval.pause"
refresh-interval="refreshInterval.value"
on-refresh-change="onRefreshChange"
></wz-search-bar>
</div> </div>
</div> </div>
</kbn-top-nav> </kbn-top-nav>
<main class="container-fluid"> <main class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-2 sidebar-container collapsible-sidebar" id="discover-sidebar"> <div class="col-md-2 sidebar-container collapsible-sidebar" id="discover-sidebar">
<disc-field-chooser <disc-field-chooser class="dscFieldChooser" columns="state.columns" hits="rows" field-counts="fieldCounts"
class="dscFieldChooser" index-pattern="searchSource.getField('index')" index-pattern-list="opts.indexPatternList" state="state"
columns="state.columns" on-add-field="addColumn" on-add-filter="filterQuery" on-remove-field="removeColumn">
hits="rows"
field-counts="fieldCounts"
index-pattern="searchSource.getField('index')"
index-pattern-list="opts.indexPatternList"
state="state"
on-add-field="addColumn"
on-add-filter="filterQuery"
on-remove-field="removeColumn"
>
</disc-field-chooser> </disc-field-chooser>
</div> </div>
<div class="dscWrapper col-md-10"> <div class="dscWrapper col-md-10">
<div class="dscWrapper__content"> <div class="dscWrapper__content">
<discover-unsupported-index-pattern <discover-unsupported-index-pattern ng-if="isUnsupportedIndexPattern" unsupported-type="unsupportedIndexPatternType"></discover-unsupported-index-pattern>
ng-if="isUnsupportedIndexPattern"
unsupported-type="unsupportedIndexPatternType"
></discover-unsupported-index-pattern>
<discover-no-results <discover-no-results ng-show="resultState === 'none'" shard-failures="failures" time-field-name="opts.timefield"
ng-show="resultState === 'none'" query-language="state.query.language" get-doc-link="getDocLink"></discover-no-results>
shard-failures="failures"
time-field-name="opts.timefield"
query-language="state.query.language"
get-doc-link="getDocLink"
></discover-no-results>
<!-- loading --> <!-- loading -->
<div ng-show="resultState === 'loading'"> <div ng-show="resultState === 'loading'">
<discover-fetch-error <discover-fetch-error ng-show="fetchError" fetch-error="fetchError"></discover-fetch-error>
ng-show="fetchError"
fetch-error="fetchError"
></discover-fetch-error>
<div <div ng-hide="fetchError" class="dscOverlay">
ng-hide="fetchError" <h2 i18n-id="kbn.discover.searchingTitle" i18n-default-message="Searching" class="euiTitle euiTitle--small"></h2>
class="dscOverlay"
>
<h2
i18n-id="kbn.discover.searchingTitle"
i18n-default-message="Searching"
class="euiTitle euiTitle--small"
></h2>
<div class="euiSpacer euiSpacer--m"></div> <div class="euiSpacer euiSpacer--m"></div>
<div class="euiLoadingSpinner euiLoadingSpinner--large" data-test-subj="loadingSpinner"></div> <div class="euiLoadingSpinner euiLoadingSpinner--large" data-test-subj="loadingSpinner"></div>
</div> </div>
@ -107,112 +62,61 @@
<!-- result --> <!-- result -->
<div class="dscResults" ng-show="resultState === 'ready'"> <div class="dscResults" ng-show="resultState === 'ready'">
<button <button class="kuiButton kuiButton--basic kuiButton--iconText dscSkipButton" ng-click="showAllRows(); scrollToBottom()">
class="kuiButton kuiButton--basic kuiButton--iconText dscSkipButton"
ng-click="showAllRows(); scrollToBottom()"
>
<span class="kuiButton__inner"> <span class="kuiButton__inner">
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-chevron-down"></span> <span aria-hidden="true" class="kuiButton__icon kuiIcon fa-chevron-down"></span>
<span <span i18n-id="kbn.discover.skipToBottomButtonLabel" i18n-default-message="Skip to bottom"></span>
i18n-id="kbn.discover.skipToBottomButtonLabel"
i18n-default-message="Skip to bottom"
></span>
</span> </span>
</button> </button>
<section <section aria-label="{{::'kbn.discover.histogramOfFoundDocumentsAriaLabel' | i18n: {defaultMessage: 'Histogram of found documents'} }}"
aria-label="{{::'kbn.discover.histogramOfFoundDocumentsAriaLabel' | i18n: {defaultMessage: 'Histogram of found documents'} }}" class="dscTimechart" ng-if="opts.timefield">
class="dscTimechart"
ng-if="opts.timefield"
>
<header class="dscTimechart__header"> <header class="dscTimechart__header">
<div class="small"> <div class="small">
<span <label for="dscResultsIntervalSelector" tooltip="{{::'kbn.discover.howToChangeTheTimeTooltip' | i18n: {defaultMessage: 'To change the time, click the clock icon in the navigation bar'} }}">
tooltip="{{::'kbn.discover.howToChangeTheTimeTooltip' | i18n: {defaultMessage: 'To change the time, click the clock icon in the navigation bar'} }}"
>
{{toMoment(timeRange.from)}} - {{toMoment(timeRange.to)}} {{toMoment(timeRange.from)}} - {{toMoment(timeRange.to)}}
</span> </label>
&mdash; &mdash;
<span class="form-inline"> <span class="form-inline">
<select <select id="dscResultsIntervalSelector" class="dscResults__interval form-control" ng-model="state.interval"
class="dscResults__interval form-control"
ng-model="state.interval"
ng-options="interval.val as interval.display for interval in intervalOptions | filter: intervalEnabled" ng-options="interval.val as interval.display for interval in intervalOptions | filter: intervalEnabled"
ng-blur="toggleInterval()" ng-blur="toggleInterval()" data-test-subj="discoverIntervalSelect">
data-test-subj="discoverIntervalSelect"
>
</select> </select>
<span ng-if="bucketInterval.scaled"> <span ng-if="bucketInterval.scaled">
<icon-tip <icon-tip content="getBucketIntervalToolTipText()" position="'top'"></icon-tip>
content="getBucketIntervalToolTipText()" <span i18n-id="kbn.discover.scaledToDescription" i18n-default-message="Scaled to {bucketIntervalDescription}"
position="'top'"
></icon-tip>
<span
i18n-id="kbn.discover.scaledToDescription"
i18n-default-message="Scaled to {bucketIntervalDescription}"
i18n-values="{ i18n-values="{
bucketIntervalDescription: bucketInterval.description bucketIntervalDescription: bucketInterval.description
}" }"></span>
></span>
</span> </span>
</span> </span>
</div> </div>
</header> </header>
<div id="discoverHistogram" <div id="discoverHistogram" ng-show="vis && rows.length !== 0" style="display: flex; height: 200px">
ng-show="vis && rows.length !== 0"
style="display: flex; height: 200px"
>
</div> </div>
</section> </section>
<section <section class="dscTable" fixed-scroll aria-label="{{::'kbn.discover.documentsAriaLabel' | i18n: {defaultMessage: 'Documents'} }}">
class="dscTable" <doc-table hits="rows" index-pattern="indexPattern" sorting="state.sort" columns="state.columns"
fixed-scroll infinite-scroll="true" filter="filterQuery" filters="state.filters" data-shared-item data-title="{{opts.savedSearch.lastSavedTitle}}"
aria-label="{{::'kbn.discover.documentsAriaLabel' | i18n: {defaultMessage: 'Documents'} }}" data-description="{{opts.savedSearch.description}}" minimum-visible-rows="minimumVisibleRows"
> render-complete on-add-column="addColumn" on-change-sort-order="setSortOrder" on-move-column="moveColumn"
<doc-table on-remove-column="removeColumn"></doc-table>
hits="rows"
index-pattern="indexPattern"
sorting="state.sort"
columns="state.columns"
infinite-scroll="true"
filter="filterQuery"
filters="state.filters"
data-shared-item
data-title="{{opts.savedSearch.lastSavedTitle}}"
data-description="{{opts.savedSearch.description}}"
minimum-visible-rows="minimumVisibleRows"
render-complete
on-add-column="addColumn"
on-change-sort-order="setSortOrder"
on-move-column="moveColumn"
on-remove-column="removeColumn"
></doc-table>
<a tabindex="0" id="discoverBottomMarker"></a> <a tabindex="0" id="discoverBottomMarker"></a>
<div <div ng-if="rows.length == opts.sampleSize" class="dscTable__footer">
ng-if="rows.length == opts.sampleSize" <span i18n-id="kbn.discover.howToSeeOtherMatchingDocumentsDescription" i18n-default-message="These are the first {sampleSize} documents matching
class="dscTable__footer"
>
<span
i18n-id="kbn.discover.howToSeeOtherMatchingDocumentsDescription"
i18n-default-message="These are the first {sampleSize} documents matching
your search, refine your search to see others. " your search, refine your search to see others. "
i18n-values="{ i18n-values="{
sampleSize: opts.sampleSize, sampleSize: opts.sampleSize,
}" }"></span>
></span> <a kbn-accessible-click ng-click="scrollToTop()" i18n-id="kbn.discover.backToTopLinkText"
<a i18n-default-message="Back to top."></a>
kbn-accessible-click
ng-click="scrollToTop()"
i18n-id="kbn.discover.backToTopLinkText"
i18n-default-message="Back to top."
></a>
</div> </div>
</section> </section>
</div> </div>

View File

@ -1,11 +1,15 @@
<discover-app-w class="app-container"> <discover-app-w class="app-container">
<!-- Local nav. --> <!-- Local nav. -->
<div data-transclude-slot="topLeftCorner" class="kuiLocalBreadcrumbs" ng-if="tabView === 'discover'"> <div data-transclude-slot="topLeftCorner" class="kuiLocalBreadcrumbs" ng-if="tabView === 'discover'">
<h1 tabindex="0" id="kui_local_breadcrumb" class="kuiLocalBreadcrumb" ng-if="opts.savedSearch.id"> <!-- Breadcrumbs. -->
<h1 id="kui_local_breadcrumb" class="kuiLocalBreadcrumb" ng-if="opts.savedSearch.id">
<span class="kuiLocalBreadcrumb__emphasis"> <span class="kuiLocalBreadcrumb__emphasis">
<span id="reload_saved_search" aria-label="{{::'kbn.discover.reloadSavedSearchAriaLabel' | i18n: {defaultMessage: 'Reload saved search'} }}" <button class="kuiLink" type="button" id="reload_saved_search" aria-label="{{::'kbn.discover.reloadSavedSearchAriaLabel' | i18n: {defaultMessage: 'Reload saved search'} }}"
tooltip="{{::'kbn.discover.reloadSavedSearchTooltip' | i18n: {defaultMessage: 'Reload saved search'} }}" tooltip="{{::'kbn.discover.reloadSavedSearchTooltip' | i18n: {defaultMessage: 'Reload saved search'} }}"
tooltip-placement="right" tooltip-append-to-body="1" ng-click="resetQuery()" kbn-accessible-click class="kuiIcon fa-undo small"></span>&nbsp; tooltip-placement="right" tooltip-append-to-body="1" ng-click="resetQuery()">
<span class="kuiIcon fa-undo small"></span>
{{::'kbn.discover.reloadSavedSearchButton' | i18n: {defaultMessage: 'Reload'} }}
</button>
</span> </span>
</h1> </h1>
<div class="kuiLocalBreadcrumb"> <div class="kuiLocalBreadcrumb">
@ -19,8 +23,8 @@
<div data-transclude-slots> <div data-transclude-slots>
<!-- Search. --> <!-- Search. -->
<div data-transclude-slot="bottomRow" class="fullWidth"> <div data-transclude-slot="bottomRow" class="fullWidth">
<wz-search-bar query="state.query" on-query-submit="updateQueryAndFetch" app-name="'discover'" index-patterns="[indexPattern]" <wz-search-bar query="state.query" screen-title="screenTitle" on-query-submit="updateQueryAndFetch" app-name="'discover'"
filters="filters" on-filters-updated="onFiltersUpdated" show-date-picker="enableTimeRangeSelector" index-patterns="[indexPattern]" filters="filters" on-filters-updated="onFiltersUpdated" show-date-picker="enableTimeRangeSelector"
date-range-from="time.from" date-range-to="time.to" is-refresh-paused="refreshInterval.pause" date-range-from="time.from" date-range-to="time.to" is-refresh-paused="refreshInterval.pause"
refresh-interval="refreshInterval.value" on-refresh-change="onRefreshChange" watch-depth="reference" refresh-interval="refreshInterval.value" on-refresh-change="onRefreshChange" watch-depth="reference"
show-filter-bar="tabView !== 'cluster-monitoring'"></wz-search-bar> show-filter-bar="tabView !== 'cluster-monitoring'"></wz-search-bar>
@ -51,7 +55,7 @@
<div ng-hide="fetchError" class="dscOverlay"> <div ng-hide="fetchError" class="dscOverlay">
<h2 i18n-id="kbn.discover.searchingTitle" i18n-default-message="Searching" class="euiTitle euiTitle--small"></h2> <h2 i18n-id="kbn.discover.searchingTitle" i18n-default-message="Searching" class="euiTitle euiTitle--small"></h2>
<div class="euiSpacer euiSpacer--m"></div> <div class="euiSpacer euiSpacer--m"></div>
<div class="euiLoadingSpinner euiLoadingSpinner--large"></div> <div class="euiLoadingSpinner euiLoadingSpinner--large" data-test-subj="loadingSpinner"></div>
<div class="euiSpacer euiSpacer--m"></div> <div class="euiSpacer euiSpacer--m"></div>
<div ng-show="fetchStatus">{{fetchStatus.complete}} / {{fetchStatus.total}}</div> <div ng-show="fetchStatus">{{fetchStatus.complete}} / {{fetchStatus.total}}</div>
</div> </div>
@ -70,14 +74,15 @@
class="dscTimechart" ng-if="opts.timefield"> class="dscTimechart" ng-if="opts.timefield">
<header class="dscTimechart__header"> <header class="dscTimechart__header">
<div class="small"> <div class="small">
<span tooltip="{{::'kbn.discover.howToChangeTheTimeTooltip' | i18n: {defaultMessage: 'To change the time, click the clock icon in the navigation bar'} }}"> <label for="dscResultsIntervalSelector" tooltip="{{::'kbn.discover.howToChangeTheTimeTooltip' | i18n: {defaultMessage: 'To change the time, click the clock icon in the navigation bar'} }}">
{{toMoment(timeRange.from)}} - {{toMoment(timeRange.to)}} {{toMoment(timeRange.from)}} - {{toMoment(timeRange.to)}}
</span> </label>
&mdash; &mdash;
<span class="form-inline"> <span class="form-inline">
<select class="dscResults__interval form-control" ng-model="state.interval" ng-options="interval.val as interval.display for interval in intervalOptions | filter: intervalEnabled" <select id="dscResultsIntervalSelector" class="dscResults__interval form-control" ng-model="state.interval"
ng-options="interval.val as interval.display for interval in intervalOptions | filter: intervalEnabled"
ng-blur="toggleInterval()" data-test-subj="discoverIntervalSelect"> ng-blur="toggleInterval()" data-test-subj="discoverIntervalSelect">
</select> </select>
<span ng-if="bucketInterval.scaled"> <span ng-if="bucketInterval.scaled">