From 88bb0121d46f6c14ff608a161a196b2af479a1ba Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Wed, 13 Jun 2018 04:35:07 -0500 Subject: [PATCH] explicitly passing filters and query to visualize (#19172) --- .../kibana/public/dashboard/actions/view.js | 2 + .../kibana/public/dashboard/dashboard_app.js | 3 - .../dashboard/dashboard_state_manager.js | 17 +++- .../public/dashboard/panel/dashboard_panel.js | 4 +- .../kibana/public/dashboard/reducers/view.js | 12 +++ .../public/dashboard/selectors/index.js | 6 ++ .../public/discover/controllers/discover.js | 9 ++ .../discover/embeddable/search_embeddable.js | 22 ++++- .../discover/saved_searches/_saved_search.js | 2 +- .../public/selectors/dashboard_selectors.js | 2 + .../public/visualize/editor/editor.html | 3 +- .../kibana/public/visualize/editor/editor.js | 4 +- .../embeddable/visualize_embeddable.js | 16 ++++ src/ui/public/courier/courier.js | 4 - .../data_source/_root_search_source.js | 89 ------------------- .../courier/data_source/search_source.js | 16 ++-- src/ui/public/timefilter/get_time.js | 50 +++++++++++ src/ui/public/timefilter/timefilter.js | 42 ++------- src/ui/public/vis/request_handlers/courier.js | 86 +++++++----------- .../public/visualize/__tests__/visualize.js | 7 -- .../loader/embedded_visualize_handler.js | 6 ++ src/ui/public/visualize/loader/loader.js | 4 + .../visualize/loader/loader_template.html | 2 + src/ui/public/visualize/visualize.js | 55 +++--------- 24 files changed, 215 insertions(+), 248 deletions(-) delete mode 100644 src/ui/public/courier/data_source/_root_search_source.js create mode 100644 src/ui/public/timefilter/get_time.js diff --git a/src/core_plugins/kibana/public/dashboard/actions/view.js b/src/core_plugins/kibana/public/dashboard/actions/view.js index e82761b1085833..c5971741c32075 100644 --- a/src/core_plugins/kibana/public/dashboard/actions/view.js +++ b/src/core_plugins/kibana/public/dashboard/actions/view.js @@ -27,3 +27,5 @@ export const updateIsFullScreenMode = createAction('UPDATE_IS_FULL_SCREEN_MODE') export const updateUseMargins = createAction('UPDATE_USE_MARGINS'); export const updateHidePanelTitles = createAction('HIDE_PANEL_TITLES'); export const updateTimeRange = createAction('UPDATE_TIME_RANGE'); +export const updateFilters = createAction('UPDATE_FILTERS'); +export const updateQuery = createAction('UPDATE_QUERY'); diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_app.js b/src/core_plugins/kibana/public/dashboard/dashboard_app.js index 907e33dc88c6e2..8ad6364a229c59 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_app.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_app.js @@ -160,9 +160,6 @@ app.directive('dashboardApp', function ($injector) { timefilter.enableAutoRefreshSelector(); timefilter.enableTimeRangeSelector(); - dash.searchSource.highlightAll(true); - dash.searchSource.version(true); - courier.setRootSearchSource(dash.searchSource); updateState(); diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js b/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js index 60b3c04a8bb554..94db30bfc17ae2 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js @@ -35,6 +35,8 @@ import { updateHidePanelTitles, updateTimeRange, clearStagedFilters, + updateFilters, + updateQuery, } from './actions'; import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; import { createPanelState } from './panel'; @@ -50,7 +52,9 @@ import { getHidePanelTitles, getStagedFilters, getEmbeddables, - getEmbeddableMetadata + getEmbeddableMetadata, + getQuery, + getFilters, } from '../selectors'; /** @@ -196,6 +200,17 @@ export class DashboardStateManager { if (getDescription(state) !== this.getDescription()) { store.dispatch(updateDescription(this.getDescription())); } + + if (!_.isEqual( + FilterUtils.cleanFiltersForComparison(this.appState.filters), + FilterUtils.cleanFiltersForComparison(getFilters(state)) + )) { + store.dispatch(updateFilters(this.appState.filters)); + } + + if (getQuery(state) !== this.getQuery()) { + store.dispatch(updateQuery(this.getQuery())); + } } _handleStoreChanges() { diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js index 7032368a275e06..272becef2ad008 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js @@ -161,7 +161,9 @@ DashboardPanel.propTypes = { ]), destroy: PropTypes.func.isRequired, containerState: PropTypes.shape({ - timeRange: PropTypes.object.isRequired, + timeRange: PropTypes.object, + filters: PropTypes.array, + query: PropTypes.object, embeddableCustomization: PropTypes.object, hidePanelTitles: PropTypes.bool.isRequired, }), diff --git a/src/core_plugins/kibana/public/dashboard/reducers/view.js b/src/core_plugins/kibana/public/dashboard/reducers/view.js index 0d0d53cca22145..fbc8f94f6d65cd 100644 --- a/src/core_plugins/kibana/public/dashboard/reducers/view.js +++ b/src/core_plugins/kibana/public/dashboard/reducers/view.js @@ -26,6 +26,8 @@ import { updateHidePanelTitles, updateIsFullScreenMode, updateTimeRange, + updateFilters, + updateQuery, setVisibleContextMenuPanelId, } from '../actions'; @@ -47,6 +49,16 @@ export const view = handleActions({ timeRange: payload }), + [updateFilters]: (state, { payload }) => ({ + ...state, + filters: payload + }), + + [updateQuery]: (state, { payload }) => ({ + ...state, + query: payload + }), + [updateUseMargins]: (state, { payload }) => ({ ...state, useMargins: payload diff --git a/src/core_plugins/kibana/public/dashboard/selectors/index.js b/src/core_plugins/kibana/public/dashboard/selectors/index.js index 615deb264c48bb..b7f82b4adf0663 100644 --- a/src/core_plugins/kibana/public/dashboard/selectors/index.js +++ b/src/core_plugins/kibana/public/dashboard/selectors/index.js @@ -158,6 +158,10 @@ export const getMaximizedPanelId = dashboard => dashboard.view.maximizedPanelId; */ export const getTimeRange = dashboard => dashboard.view.timeRange; +export const getFilters = dashboard => dashboard.view.filters; + +export const getQuery = dashboard => dashboard.view.query; + /** * @typedef {Object} DashboardMetadata * @property {string} title @@ -205,6 +209,8 @@ export const getContainerState = (dashboard, panelId) => { to: time.to, from: time.from, }, + filters: getFilters(dashboard), + query: getQuery(dashboard), embeddableCustomization: _.cloneDeep(getEmbeddableCustomization(dashboard, panelId) || {}), hidePanelTitles: getHidePanelTitles(dashboard), customTitle: getPanel(dashboard, panelId).title, diff --git a/src/core_plugins/kibana/public/discover/controllers/discover.js b/src/core_plugins/kibana/public/discover/controllers/discover.js index ce3b298aa9a16a..d4fb69f0545609 100644 --- a/src/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/core_plugins/kibana/public/discover/controllers/discover.js @@ -201,6 +201,15 @@ function discoverController( .highlightAll(true) .version(true); + // searchSource which applies time range + const timeRangeSearchSource = savedSearch.searchSource.new(); + timeRangeSearchSource.set('filter', () => { + return timefilter.get($scope.indexPattern); + }); + + $scope.searchSource.inherits(timeRangeSearchSource); + + const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; docTitle.change(`Discover${pageTitleSuffix}`); diff --git a/src/core_plugins/kibana/public/discover/embeddable/search_embeddable.js b/src/core_plugins/kibana/public/discover/embeddable/search_embeddable.js index 9129085e18f81c..47a94aa4e45ea2 100644 --- a/src/core_plugins/kibana/public/discover/embeddable/search_embeddable.js +++ b/src/core_plugins/kibana/public/discover/embeddable/search_embeddable.js @@ -21,6 +21,7 @@ import angular from 'angular'; import { Embeddable } from 'ui/embeddable'; import searchTemplate from './search_template.html'; import * as columnActions from 'ui/doc_table/actions/columns'; +import { getTime } from 'ui/timefilter/get_time'; export class SearchEmbeddable extends Embeddable { constructor({ onEmbeddableStateChanged, savedSearch, editUrl, loader, $rootScope, $compile }) { @@ -52,13 +53,20 @@ export class SearchEmbeddable extends Embeddable { pushContainerStateParamsToScope() { // If there is column or sort data on the panel, that means the original columns or sort settings have // been overridden in a dashboard. + this.searchScope.columns = this.customization.columns || this.savedSearch.columns; this.searchScope.sort = this.customization.sort || this.savedSearch.sort; this.searchScope.sharedItemTitle = this.panelTitle; + + this.filtersSearchSource.set('filter', this.filters); + this.filtersSearchSource.set('query', this.query); } onContainerStateChanged(containerState) { this.customization = containerState.embeddableCustomization || {}; + this.filters = containerState.filters; + this.query = containerState.query; + this.timeRange = containerState.timeRange; this.panelTitle = ''; if (!containerState.hidePanelTitles) { this.panelTitle = containerState.customTitle !== undefined ? @@ -74,11 +82,21 @@ export class SearchEmbeddable extends Embeddable { initializeSearchScope() { this.searchScope = this.$rootScope.$new(); - this.pushContainerStateParamsToScope(); - this.searchScope.description = this.savedSearch.description; this.searchScope.searchSource = this.savedSearch.searchSource; + const timeRangeSearchSource = this.searchScope.searchSource.new(); + timeRangeSearchSource.filter(() => { + return getTime(this.searchScope.searchSource.get('index'), this.timeRange); + }); + + this.filtersSearchSource = this.searchScope.searchSource.new(); + this.filtersSearchSource.inherits(timeRangeSearchSource); + + this.searchScope.searchSource.inherits(this.filtersSearchSource); + + this.pushContainerStateParamsToScope(); + this.searchScope.setSortOrder = (columnName, direction) => { this.searchScope.sort = this.customization.sort = [columnName, direction]; this.emitEmbeddableStateChange(this.getEmbeddableState()); diff --git a/src/core_plugins/kibana/public/discover/saved_searches/_saved_search.js b/src/core_plugins/kibana/public/discover/saved_searches/_saved_search.js index 8dd9e5c9d7e21f..e563663836dff7 100644 --- a/src/core_plugins/kibana/public/discover/saved_searches/_saved_search.js +++ b/src/core_plugins/kibana/public/discover/saved_searches/_saved_search.js @@ -42,7 +42,7 @@ module.factory('SavedSearch', function (courier) { hits: 0, sort: [], version: 1 - } + }, }); this.showInRecenltyAccessed = true; diff --git a/src/core_plugins/kibana/public/selectors/dashboard_selectors.js b/src/core_plugins/kibana/public/selectors/dashboard_selectors.js index 3d72b010e81b57..3795b810d85c8b 100644 --- a/src/core_plugins/kibana/public/selectors/dashboard_selectors.js +++ b/src/core_plugins/kibana/public/selectors/dashboard_selectors.js @@ -52,6 +52,8 @@ export const getMaximizedPanelId = state => DashboardSelectors.getMaximizedPanel export const getUseMargins = state => DashboardSelectors.getUseMargins(getDashboard(state)); export const getHidePanelTitles = state => DashboardSelectors.getHidePanelTitles(getDashboard(state)); export const getTimeRange = state => DashboardSelectors.getTimeRange(getDashboard(state)); +export const getFilters = state => DashboardSelectors.getFilters(getDashboard(state)); +export const getQuery = state => DashboardSelectors.getQuery(getDashboard(state)); export const getTitle = state => DashboardSelectors.getTitle(getDashboard(state)); export const getDescription = state => DashboardSelectors.getDescription(getDashboard(state)); diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.html b/src/core_plugins/kibana/public/visualize/editor/editor.html index 08bd1e39c2dda0..34a3c4d9d9df0a 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/core_plugins/kibana/public/visualize/editor/editor.html @@ -69,10 +69,9 @@ diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.js b/src/core_plugins/kibana/public/visualize/editor/editor.js index a4a0dbe96efb5a..4a0bfbb686b64f 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/core_plugins/kibana/public/visualize/editor/editor.js @@ -246,12 +246,14 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie // update the searchSource when filters update $scope.$listen(queryFilter, 'update', function () { - $state.save(); + $scope.fetch(); }); // update the searchSource when query updates $scope.fetch = function () { $state.save(); + savedVis.searchSource.set('query', $state.query); + savedVis.searchSource.set('filter', $state.filters); $scope.vis.forceReload(); }; diff --git a/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.js b/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.js index 71a63dc5ac67c1..279100983172aa 100644 --- a/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.js +++ b/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.js @@ -98,6 +98,18 @@ export class VisualizeEmbeddable extends Embeddable { this.timeRange = containerState.timeRange; } + // Check if filters has changed + if (containerState.filters !== this.filters) { + updatedParams.filters = containerState.filters; + this.filters = containerState.filters; + } + + // Check if query has changed + if (containerState.query !== this.query) { + updatedParams.query = containerState.query; + this.query = containerState.query; + } + const derivedPanelTitle = this.getPanelTitle(containerState); if (this.panelTitle !== derivedPanelTitle) { updatedParams.dataAttrs = { @@ -119,6 +131,8 @@ export class VisualizeEmbeddable extends Embeddable { render(domNode, containerState) { this.panelTitle = this.getPanelTitle(containerState); this.timeRange = containerState.timeRange; + this.query = containerState.query; + this.filters = containerState.filters; this.transferCustomizationsToUiState(containerState); @@ -127,6 +141,8 @@ export class VisualizeEmbeddable extends Embeddable { // Append visualization to container instead of replacing its content append: true, timeRange: containerState.timeRange, + query: containerState.query, + filters: containerState.filters, cssClass: `panel-content panel-content--fullWidth`, // The chrome is permanently hidden in "embed mode" in which case we don't want to show the spy pane, since // we deem that situation to be more public facing and want to hide more detailed information. diff --git a/src/ui/public/courier/courier.js b/src/ui/public/courier/courier.js index 1b53c4214de2a7..48ceedf046fcb0 100644 --- a/src/ui/public/courier/courier.js +++ b/src/ui/public/courier/courier.js @@ -29,7 +29,6 @@ import { SearchSourceProvider } from './data_source/search_source'; import { requestQueue } from './_request_queue'; import { FetchSoonProvider } from './fetch'; import { SearchLooperProvider } from './looper/search'; -import { RootSearchSourceProvider } from './data_source/_root_search_source'; import { SavedObjectProvider } from './saved_object'; import { RedirectWhenMissingProvider } from './_redirect_when_missing'; @@ -42,9 +41,6 @@ uiModules.get('kibana/courier') const fetchSoon = Private(FetchSoonProvider); const searchLooper = self.searchLooper = Private(SearchLooperProvider); - // expose some internal modules - self.setRootSearchSource = Private(RootSearchSourceProvider).set; - self.SavedObject = Private(SavedObjectProvider); self.indexPatterns = indexPatterns; self.redirectWhenMissing = Private(RedirectWhenMissingProvider); diff --git a/src/ui/public/courier/data_source/_root_search_source.js b/src/ui/public/courier/data_source/_root_search_source.js deleted file mode 100644 index 94943719c36137..00000000000000 --- a/src/ui/public/courier/data_source/_root_search_source.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { SearchSourceProvider } from './search_source'; - -export function RootSearchSourceProvider(Private, $rootScope, timefilter) { - const SearchSource = Private(SearchSourceProvider); - - const globalSource = new SearchSource(); - globalSource.inherits(false); // this is the final source, it has no parents - globalSource.filter(function (globalSource) { - // dynamic time filter will be called in the _flatten phase of things - const filter = timefilter.get(globalSource.get('index')); - // Attach a meta property to it, that we check inside visualizations - // to remove that timefilter again because we use our explicitly passed in one. - // This should be removed as soon as we got rid of inheritance in SearchSource - // across the boundary or visualization. - if (filter) { - filter.meta = { _globalTimefilter: true }; - } - return filter; - }); - - let appSource; // set in setAppSource() - resetAppSource(); - - // when the route changes, clear the appSource - $rootScope.$on('$routeChangeStart', resetAppSource); - - /** - * Get the current AppSource - * @return {Promise} - resolved with the current AppSource - */ - function getAppSource() { - return appSource; - } - - /** - * Set the current AppSource - * @param {SearchSource} source - The Source that represents the applications "root" search source object - */ - function setAppSource(source) { - appSource = source; - - // walk the parent chain until we get to the global source or nothing - // that's where we will attach to the globalSource - let literalRoot = source; - while (literalRoot._parent && literalRoot._parent !== globalSource) { - literalRoot = literalRoot._parent; - } - - literalRoot.inherits(globalSource); - } - - - - /** - * Sets the appSource to be a new, empty, SearchSource - * @return {undefined} - */ - function resetAppSource() { - setAppSource(new SearchSource()); - } - - return { - get: getAppSource, - set: setAppSource, - - getGlobalSource: function () { - return globalSource; - } - }; -} diff --git a/src/ui/public/courier/data_source/search_source.js b/src/ui/public/courier/data_source/search_source.js index 8de100592518c7..f7a8d2f3e2493e 100644 --- a/src/ui/public/courier/data_source/search_source.js +++ b/src/ui/public/courier/data_source/search_source.js @@ -75,7 +75,6 @@ import angular from 'angular'; import '../../promises'; import { NormalizeSortRequestProvider } from './_normalize_sort_request'; -import { RootSearchSourceProvider } from './_root_search_source'; import { SearchRequestProvider } from '../fetch/request'; import { SegmentedRequestProvider } from '../fetch/request/segmented'; @@ -227,11 +226,8 @@ export function SearchSourceProvider(Promise, PromiseEmitter, Private, config) { * Get the parent of this SearchSource * @return {undefined|searchSource} */ - getParent(onlyHardLinked) { - const self = this; - if (self._parent === false) return; - if (self._parent) return self._parent; - return onlyHardLinked ? undefined : Private(RootSearchSourceProvider).get(); + getParent() { + return this._parent || undefined; } /** @@ -282,6 +278,14 @@ export function SearchSourceProvider(Promise, PromiseEmitter, Private, config) { return clone; } + makeChild() { + return new SearchSource().inherits(this, { callParentStartHandlers: true }); + } + + new() { + return new SearchSource(); + } + async getSearchRequestBody() { const searchRequest = await this._flatten(); return searchRequest.body; diff --git a/src/ui/public/timefilter/get_time.js b/src/ui/public/timefilter/get_time.js new file mode 100644 index 00000000000000..813fbf2889d134 --- /dev/null +++ b/src/ui/public/timefilter/get_time.js @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import dateMath from '@kbn/datemath'; + +export function calculateBounds(timeRange, options = {}) { + return { + min: dateMath.parse(timeRange.from, { forceNow: options.forceNow }), + max: dateMath.parse(timeRange.to, { roundUp: true, forceNow: options.forceNow }) + }; +} + +export function getTime(indexPattern, timeRange, forceNow) { + if (!indexPattern) { + //in CI, we sometimes seem to fail here. + return; + } + + let filter; + const timefield = indexPattern.timeFieldName && _.find(indexPattern.fields, { name: indexPattern.timeFieldName }); + + if (timefield) { + const bounds = calculateBounds(timeRange, { forceNow }); + filter = { range: {} }; + filter.range[timefield.name] = { + gte: bounds.min.valueOf(), + lte: bounds.max.valueOf(), + format: 'epoch_millis' + }; + } + + return filter; +} diff --git a/src/ui/public/timefilter/timefilter.js b/src/ui/public/timefilter/timefilter.js index ecbd233489a996..2693e59b027fa5 100644 --- a/src/ui/public/timefilter/timefilter.js +++ b/src/ui/public/timefilter/timefilter.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import moment from 'moment'; -import dateMath from '@kbn/datemath'; +import { calculateBounds, getTime } from './get_time'; import '../state_management/global_state'; import '../config'; import { EventsProvider } from '../events'; @@ -98,33 +98,6 @@ uiModules $rootScope.$apply(); }; - Timefilter.prototype.get = function (indexPattern, timeRange) { - - if (!indexPattern) { - //in CI, we sometimes seem to fail here. - return; - } - - let filter; - const timefield = indexPattern.timeFieldName && _.find(indexPattern.fields, { name: indexPattern.timeFieldName }); - - if (timefield) { - const bounds = timeRange ? this.calculateBounds(timeRange) : this.getBounds(); - filter = { range: {} }; - filter.range[timefield.name] = { - gte: bounds.min.valueOf(), - lte: bounds.max.valueOf(), - format: 'epoch_millis' - }; - } - - return filter; - }; - - Timefilter.prototype.getBounds = function () { - return this.calculateBounds(this.time); - }; - Timefilter.prototype.getForceNow = function () { const query = $location.search().forceNow; if (!query) { @@ -138,13 +111,16 @@ uiModules return new Date(ticks); }; + Timefilter.prototype.get = function (indexPattern, timeRange) { + return getTime(indexPattern, timeRange ? timeRange : this.time, this.getForceNow()); + }; + Timefilter.prototype.calculateBounds = function (timeRange) { - const forceNow = this.getForceNow(); + return calculateBounds(timeRange, { forceNow: this.getForceNow() }); + }; - return { - min: dateMath.parse(timeRange.from, { forceNow }), - max: dateMath.parse(timeRange.to, { roundUp: true, forceNow }) - }; + Timefilter.prototype.getBounds = function () { + return this.calculateBounds(this.time); }; Timefilter.prototype.getActiveBounds = function () { diff --git a/src/ui/public/vis/request_handlers/courier.js b/src/ui/public/vis/request_handlers/courier.js index ca03bb0cec872c..6b8fb23f0e9579 100644 --- a/src/ui/public/vis/request_handlers/courier.js +++ b/src/ui/public/vis/request_handlers/courier.js @@ -25,21 +25,9 @@ import { calculateObjectHash } from '../lib/calculate_object_hash'; const CourierRequestHandlerProvider = function (Private, courier, timefilter) { const SearchSource = Private(SearchSourceProvider); - /** - * TODO: This code can be removed as soon as we got rid of inheritance in the - * searchsource and pass down every filter explicitly. - * We are filtering out the global timefilter by the meta key set by the root - * search source on that filter. - */ - function removeSearchSourceParentTimefilter(searchSource) { - searchSource.addFilterPredicate((filter) => { - return !_.get(filter, 'meta._globalTimefilter', false); - }); - } - return { name: 'courier', - handler: function (vis, { appState, queryFilter, searchSource, timeRange, forceFetch }) { + handler: function (vis, { searchSource, timeRange, query, filters, forceFetch }) { // Create a new search source that inherits the original search source // but has the propriate timeRange applied via a filter. @@ -48,16 +36,17 @@ const CourierRequestHandlerProvider = function (Private, courier, timefilter) { // Using callParentStartHandlers: true we make sure, that the parent searchSource // onSearchRequestStart will be called properly even though we use an inherited // search source. - const requestSearchSource = new SearchSource().inherits(searchSource, { callParentStartHandlers: true }); + const timeFilterSearchSource = searchSource.makeChild(); + const requestSearchSource = timeFilterSearchSource.makeChild(); // For now we need to mirror the history of the passed search source, since // the spy panel wouldn't work otherwise. Object.defineProperty(requestSearchSource, 'history', { get() { - return requestSearchSource._parent.history; + return searchSource.history; }, set(history) { - return requestSearchSource._parent.history = history; + return searchSource.history = history; } }); @@ -69,57 +58,44 @@ const CourierRequestHandlerProvider = function (Private, courier, timefilter) { return vis.onSearchRequestStart(searchSource, searchRequest); }); - // Add the explicit passed timeRange as a filter to the requestSearchSource. - requestSearchSource.filter(() => { + timeFilterSearchSource.set('filter', () => { return timefilter.get(searchSource.get('index'), timeRange); }); - removeSearchSourceParentTimefilter(requestSearchSource); - - if (queryFilter && vis.editorMode) { - searchSource.set('filter', queryFilter.getFilters()); - searchSource.set('query', appState.query); - } + requestSearchSource.set('filter', filters); + requestSearchSource.set('query', query); - const shouldQuery = () => { + const shouldQuery = (requestBodyHash) => { if (!searchSource.lastQuery || forceFetch) return true; - if (!_.isEqual(_.cloneDeep(searchSource.get('filter')), searchSource.lastQuery.filter)) return true; - if (!_.isEqual(_.cloneDeep(searchSource.get('query')), searchSource.lastQuery.query)) return true; - if (!_.isEqual(calculateObjectHash(vis.getAggConfig()), searchSource.lastQuery.aggs)) return true; - if (!_.isEqual(_.cloneDeep(timeRange), searchSource.lastQuery.timeRange)) return true; - + if (searchSource.lastQuery !== requestBodyHash) return true; return false; }; return new Promise((resolve, reject) => { - if (shouldQuery()) { - requestSearchSource.onResults().then(resp => { - searchSource.lastQuery = { - filter: _.cloneDeep(searchSource.get('filter')), - query: _.cloneDeep(searchSource.get('query')), - aggs: calculateObjectHash(vis.getAggConfig()), - timeRange: _.cloneDeep(timeRange) - }; - - searchSource.rawResponse = resp; - - return _.cloneDeep(resp); - }).then(async resp => { - for (const agg of vis.getAggConfig()) { - if (_.has(agg, 'type.postFlightRequest')) { - const nestedSearchSource = new SearchSource().inherits(requestSearchSource); - resp = await agg.type.postFlightRequest(resp, vis.aggs, agg, nestedSearchSource); + return requestSearchSource.getSearchRequestBody().then(q => { + const queryHash = calculateObjectHash(q); + if (shouldQuery(queryHash)) { + requestSearchSource.onResults().then(resp => { + searchSource.lastQuery = queryHash; + searchSource.rawResponse = resp; + return _.cloneDeep(resp); + }).then(async resp => { + for (const agg of vis.getAggConfig()) { + if (_.has(agg, 'type.postFlightRequest')) { + const nestedSearchSource = new SearchSource().inherits(requestSearchSource); + resp = await agg.type.postFlightRequest(resp, vis.aggs, agg, nestedSearchSource); + } } - } - searchSource.finalResponse = resp; - resolve(resp); - }).catch(e => reject(e)); + searchSource.finalResponse = resp; + resolve(resp); + }).catch(e => reject(e)); - courier.fetch(); - } else { - resolve(searchSource.finalResponse); - } + courier.fetch(); + } else { + resolve(searchSource.finalResponse); + } + }); }); } }; diff --git a/src/ui/public/visualize/__tests__/visualize.js b/src/ui/public/visualize/__tests__/visualize.js index 77a9f288be817c..712bd42d2b65aa 100644 --- a/src/ui/public/visualize/__tests__/visualize.js +++ b/src/ui/public/visualize/__tests__/visualize.js @@ -167,13 +167,6 @@ describe('visualize directive', function () { assertParam({ forceFetch: true }); }); - it('should be true if triggered via fetch event', async () => { - $scope.$emit('fetch'); - await waitForFetch(); - sinon.assert.calledOnce(requestHandler); - assertParam({ forceFetch: true }); - }); - it('should be false if triggered via resize event', async () => { $el.width(400); $el.height(500); diff --git a/src/ui/public/visualize/loader/embedded_visualize_handler.js b/src/ui/public/visualize/loader/embedded_visualize_handler.js index f3641aeccd8118..25866e53c0fdd6 100644 --- a/src/ui/public/visualize/loader/embedded_visualize_handler.js +++ b/src/ui/public/visualize/loader/embedded_visualize_handler.js @@ -55,6 +55,12 @@ export class EmbeddedVisualizeHandler { if (params.hasOwnProperty('timeRange')) { this._scope.timeRange = params.timeRange; } + if (params.hasOwnProperty('filters')) { + this._scope.filters = params.filters; + } + if (params.hasOwnProperty('query')) { + this._scope.query = params.query; + } // Apply data- attributes to the element if specified if (params.dataAttrs) { diff --git a/src/ui/public/visualize/loader/loader.js b/src/ui/public/visualize/loader/loader.js index 8fa679de49dac7..26a0b3c194e8f0 100644 --- a/src/ui/public/visualize/loader/loader.js +++ b/src/ui/public/visualize/loader/loader.js @@ -52,6 +52,8 @@ import { EmbeddedVisualizeHandler } from './embedded_visualize_handler'; * will be set to the root visuzalize element. * @property {object} dataAttrs An object of key-value pairs, that will be set * as data-{key}="{value}" attributes on the visualization element. + * @property {array} filters Specifies the filters that should be applied to that visualization. + * @property {object} query The query that should apply to that visualization. */ const VisualizeLoaderProvider = ($compile, $rootScope, savedVisualizations) => { @@ -62,6 +64,8 @@ const VisualizeLoaderProvider = ($compile, $rootScope, savedVisualizations) => { scope.appState = params.appState; scope.uiState = params.uiState; scope.timeRange = params.timeRange; + scope.filters = params.filters; + scope.query = params.query; scope.showSpyPanel = params.showSpyPanel; const container = angular.element(el); diff --git a/src/ui/public/visualize/loader/loader_template.html b/src/ui/public/visualize/loader/loader_template.html index a9e3269f1471b3..bc5084598ce6ca 100644 --- a/src/ui/public/visualize/loader/loader_template.html +++ b/src/ui/public/visualize/loader/loader_template.html @@ -3,6 +3,8 @@ app-state="appState" ui-state="uiState" time-range="timeRange" + filters="filters" + query="query" show-spy-panel="showSpyPanel" render-complete > diff --git a/src/ui/public/visualize/visualize.js b/src/ui/public/visualize/visualize.js index bde03e3bf88400..dd65d58ffbbae6 100644 --- a/src/ui/public/visualize/visualize.js +++ b/src/ui/public/visualize/visualize.js @@ -19,7 +19,6 @@ import _ from 'lodash'; import { uiModules } from '../modules'; -import { stateMonitorFactory } from '../state_management/state_monitor_factory'; import visualizeTemplate from './visualize.html'; import { VisRequestHandlersRegistryProvider } from '../registry/vis_request_handlers'; import { VisResponseHandlersRegistryProvider } from '../registry/vis_response_handlers'; @@ -55,6 +54,8 @@ uiModules appState: '=?', uiState: '=?', timeRange: '=?', + filters: '=?', + query: '=?', }, template: visualizeTemplate, link: async function ($scope, $el) { @@ -92,21 +93,16 @@ uiModules // fetching new data and rendering. if (!$scope.vis.initialized || !$scope.savedObj || destroyed) return; - // TODO: This should ALWAYS be passed into this component via the loader - // in the future. Unfortunately we need some refactoring in dashboard - // to make this working and correctly rerender, so for now we will either - // use the one passed in to us or look into the timefilter ourselfs (which - // will be removed in the future). - const timeRange = $scope.timeRange || timefilter.time; - - $scope.vis.filters = { timeRange }; + $scope.vis.filters = { timeRange: $scope.timeRange }; const handlerParams = { appState: $scope.appState, uiState: $scope.uiState, queryFilter: queryFilter, searchSource: $scope.savedObj.searchSource, - timeRange: timeRange, + timeRange: $scope.timeRange, + filters: $scope.filters, + query: $scope.query, forceFetch, }; @@ -147,14 +143,12 @@ uiModules }); }, 100); - //todo: clean this one up as well const handleVisUpdate = () => { - if ($scope.editorMode) { + if ($scope.appState.vis) { $scope.appState.vis = $scope.vis.getState(); $scope.appState.save(); - } else { - $scope.fetch(); } + $scope.fetch(); }; $scope.vis.on('update', handleVisUpdate); @@ -166,32 +160,10 @@ uiModules $scope.vis.on('reload', reload); // auto reload will trigger this event $scope.$on('courier:searchRefresh', reload); - // dashboard will fire fetch event when it wants to refresh - $scope.$on('fetch', reload); - - - const handleQueryUpdate = ()=> { - $scope.fetch(); - }; - queryFilter.on('update', handleQueryUpdate); - - if ($scope.appState) { - const stateMonitor = stateMonitorFactory.create($scope.appState); - stateMonitor.onChange((status, type, keys) => { - if (keys[0] === 'vis') { - if ($scope.appState.vis) $scope.vis.setState($scope.appState.vis); - $scope.fetch(); - } - if ($scope.vis.type.requiresSearch && ['query', 'filters'].includes(keys[0])) { - $scope.fetch(); - } - }); - - $scope.$on('$destroy', () => { - stateMonitor.destroy(); - }); - } + $scope.$watch('filters', $scope.fetch, true); + $scope.$watch('query', $scope.fetch, true); + $scope.$watch('timeRange', $scope.fetch, true); // Listen on uiState changes to start fetching new data again. // Some visualizations might need different data depending on their uiState, @@ -199,16 +171,13 @@ uiModules // checking if anything changed, that actually require a new fetch or return // cached data otherwise. $scope.uiState.on('change', $scope.fetch); - resizeChecker.on('resize', $scope.fetch); - // visualize needs to know about timeFilter - $scope.$listen(timefilter, 'fetch', $scope.fetch); + resizeChecker.on('resize', $scope.fetch); $scope.$on('$destroy', () => { destroyed = true; $scope.vis.removeListener('reload', reload); $scope.vis.removeListener('update', handleVisUpdate); - queryFilter.off('update', handleQueryUpdate); $scope.uiState.off('change', $scope.fetch); resizeChecker.destroy(); });