explicitly passing filters and query to visualize (#19172)
This commit is contained in:
parent
43639cd9c0
commit
88bb0121d4
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}`);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -42,7 +42,7 @@ module.factory('SavedSearch', function (courier) {
|
|||
hits: 0,
|
||||
sort: [],
|
||||
version: 1
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.showInRecenltyAccessed = true;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -69,10 +69,9 @@
|
|||
<visualize
|
||||
saved-obj="savedVis"
|
||||
ui-state="uiState"
|
||||
app-state="state"
|
||||
time-range="timeRange"
|
||||
editor-mode="chrome.getVisible()"
|
||||
show-spy-panel="chrome.getVisible()"
|
||||
time-range="timeRange"
|
||||
>
|
||||
|
||||
</visualize>
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
50
src/ui/public/timefilter/get_time.js
Normal file
50
src/ui/public/timefilter/get_time.js
Normal file
|
@ -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;
|
||||
}
|
|
@ -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.calculateBounds = function (timeRange) {
|
||||
const forceNow = this.getForceNow();
|
||||
Timefilter.prototype.get = function (indexPattern, timeRange) {
|
||||
return getTime(indexPattern, timeRange ? timeRange : this.time, this.getForceNow());
|
||||
};
|
||||
|
||||
return {
|
||||
min: dateMath.parse(timeRange.from, { forceNow }),
|
||||
max: dateMath.parse(timeRange.to, { roundUp: true, forceNow })
|
||||
};
|
||||
Timefilter.prototype.calculateBounds = function (timeRange) {
|
||||
return calculateBounds(timeRange, { forceNow: this.getForceNow() });
|
||||
};
|
||||
|
||||
Timefilter.prototype.getBounds = function () {
|
||||
return this.calculateBounds(this.time);
|
||||
};
|
||||
|
||||
Timefilter.prototype.getActiveBounds = function () {
|
||||
|
|
|
@ -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);
|
||||
requestSearchSource.set('filter', filters);
|
||||
requestSearchSource.set('query', query);
|
||||
|
||||
if (queryFilter && vis.editorMode) {
|
||||
searchSource.set('filter', queryFilter.getFilters());
|
||||
searchSource.set('query', appState.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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
app-state="appState"
|
||||
ui-state="uiState"
|
||||
time-range="timeRange"
|
||||
filters="filters"
|
||||
query="query"
|
||||
show-spy-panel="showSpyPanel"
|
||||
render-complete
|
||||
></visualize>
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue