mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Maps] ignore global query layer setting (#35542)
* [Maps] ignore global query layer setting * do not include index pattern id in query bar indexPatterns when applyGlobalQuery is disabled * update how embeddable factory extracts indexPatterns so only queryable index patterns are included in list * support applyGlobalQuery for heatmap and getBounds * add functional tests to join layer * show filter section when layer has join * move checkbox to layer settings panel * text review * set checkbox to false when disabled * do not trigger refetch when global query and global filter changes and applyGlobalQuery is disabled * rename applyGlobalQuery to getApplyGlobalQuery * throw error in map embeddable factory if layerListJSON can not be parsed * remove extra space * update zoom range slider label and remove nested EuiFormRow
This commit is contained in:
parent
27de17abbb
commit
8c687ed85a
21 changed files with 458 additions and 79 deletions
|
@ -156,6 +156,15 @@ export function addLayer(layerDescriptor) {
|
|||
};
|
||||
}
|
||||
|
||||
// Do not use when rendering a map. Method exists to enable selectors for getLayerList when
|
||||
// rendering is not needed.
|
||||
export function addLayerWithoutDataSync(layerDescriptor) {
|
||||
return {
|
||||
type: ADD_LAYER,
|
||||
layer: layerDescriptor,
|
||||
};
|
||||
}
|
||||
|
||||
function setLayerDataLoadErrorStatus(layerId, errorMessage) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
|
@ -511,6 +520,19 @@ export function setLayerQuery(id, query) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setLayerApplyGlobalQuery(id, applyGlobalQuery) {
|
||||
return (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_LAYER_PROP,
|
||||
id,
|
||||
propName: 'applyGlobalQuery',
|
||||
newValue: applyGlobalQuery,
|
||||
});
|
||||
|
||||
dispatch(syncDataForLayer(id));
|
||||
};
|
||||
}
|
||||
|
||||
export function removeSelectedLayer() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
|
|
@ -62,22 +62,23 @@ describe('Saved object does not have layer list', () => {
|
|||
};
|
||||
const layers = getInitialLayers(null);
|
||||
expect(layers).toEqual([{
|
||||
'alpha': 1,
|
||||
'__dataRequests': [],
|
||||
'id': layers[0].id,
|
||||
'label': null,
|
||||
'maxZoom': 24,
|
||||
'minZoom': 0,
|
||||
'sourceDescriptor': {
|
||||
'type': 'EMS_TMS',
|
||||
'id': 'road_map',
|
||||
alpha: 1,
|
||||
__dataRequests: [],
|
||||
id: layers[0].id,
|
||||
applyGlobalQuery: true,
|
||||
label: null,
|
||||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
sourceDescriptor: {
|
||||
type: 'EMS_TMS',
|
||||
id: 'road_map',
|
||||
},
|
||||
'style': {
|
||||
'properties': {},
|
||||
'type': 'TILE',
|
||||
style: {
|
||||
properties: {},
|
||||
type: 'TILE',
|
||||
},
|
||||
'type': 'TILE',
|
||||
'visible': true,
|
||||
type: 'TILE',
|
||||
visible: true,
|
||||
}]);
|
||||
});
|
||||
|
||||
|
@ -91,9 +92,10 @@ describe('Saved object does not have layer list', () => {
|
|||
|
||||
const layers = getInitialLayers(null);
|
||||
expect(layers).toEqual([{
|
||||
'alpha': 1,
|
||||
alpha: 1,
|
||||
__dataRequests: [],
|
||||
id: layers[0].id,
|
||||
applyGlobalQuery: true,
|
||||
label: null,
|
||||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
|
@ -117,9 +119,10 @@ describe('Saved object does not have layer list', () => {
|
|||
|
||||
const layers = getInitialLayers(null);
|
||||
expect(layers).toEqual([{
|
||||
'alpha': 1,
|
||||
alpha: 1,
|
||||
__dataRequests: [],
|
||||
id: layers[0].id,
|
||||
applyGlobalQuery: true,
|
||||
label: null,
|
||||
maxZoom: 24,
|
||||
minZoom: 0,
|
||||
|
|
|
@ -35,7 +35,7 @@ import {
|
|||
setReadOnly,
|
||||
setIsLayerTOCOpen
|
||||
} from '../store/ui';
|
||||
import { getUniqueIndexPatternIds } from '../selectors/map_selectors';
|
||||
import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors';
|
||||
import { getInspectorAdapters } from '../store/non_serializable_instances';
|
||||
import { Inspector } from 'ui/inspector';
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
|
@ -197,7 +197,7 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
|
|||
});
|
||||
}
|
||||
|
||||
const nextIndexPatternIds = getUniqueIndexPatternIds(store.getState());
|
||||
const nextIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState());
|
||||
if (nextIndexPatternIds !== prevIndexPatternIds) {
|
||||
prevIndexPatternIds = nextIndexPatternIds;
|
||||
updateIndexPatterns(nextIndexPatternIds);
|
||||
|
|
|
@ -128,6 +128,7 @@ export class FilterEditor extends Component {
|
|||
</EuiTextColor>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -174,8 +175,13 @@ export class FilterEditor extends Component {
|
|||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiSpacer size="m"/>
|
||||
|
||||
{this._renderQuery()}
|
||||
|
||||
{this._renderQueryPopover()}
|
||||
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ exports[`Should render errors when layer has errors 1`] = `
|
|||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer
|
||||
margin="m"
|
||||
size="m"
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
|
|
@ -31,7 +31,7 @@ export function LayerErrors({ layer }) {
|
|||
{layer.getErrors()}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer margin="m"/>
|
||||
<EuiSpacer size="m"/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@ import {
|
|||
updateLayerMaxZoom,
|
||||
updateLayerMinZoom,
|
||||
updateLayerAlpha,
|
||||
setLayerApplyGlobalQuery,
|
||||
} from '../../../actions/store_actions';
|
||||
|
||||
function mapStateToProps(state = {}) {
|
||||
const selectedLayer = getSelectedLayer(state);
|
||||
return {
|
||||
alpha: selectedLayer.getAlpha(),
|
||||
applyGlobalQuery: selectedLayer.getApplyGlobalQuery(),
|
||||
label: selectedLayer.getLabel(),
|
||||
layerId: selectedLayer.getId(),
|
||||
maxZoom: selectedLayer.getMaxZoom(),
|
||||
|
@ -32,6 +34,9 @@ function mapDispatchToProps(dispatch) {
|
|||
updateMinZoom: (id, minZoom) => dispatch(updateLayerMinZoom(id, minZoom)),
|
||||
updateMaxZoom: (id, maxZoom) => dispatch(updateLayerMaxZoom(id, maxZoom)),
|
||||
updateAlpha: (id, alpha) => dispatch(updateLayerAlpha(id, alpha)),
|
||||
setLayerApplyGlobalQuery: (layerId, applyGlobalQuery) => {
|
||||
dispatch(setLayerApplyGlobalQuery(layerId, applyGlobalQuery));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
EuiFormRow,
|
||||
EuiFieldText,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ValidatedRange } from '../../../shared/components/validated_range';
|
||||
|
@ -40,35 +42,26 @@ export function LayerSettings(props) {
|
|||
props.updateAlpha(props.layerId, alpha);
|
||||
};
|
||||
|
||||
const onApplyGlobalQueryChange = event => {
|
||||
props.setLayerApplyGlobalQuery(props.layerId, event.target.checked);
|
||||
};
|
||||
|
||||
const renderZoomSliders = () => {
|
||||
return (
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
i18n.translate('xpack.maps.layerPanel.settingsPanel.zoomFeedbackHelptext', {
|
||||
defaultMessage: 'Display layer when map is in zoom range.'
|
||||
})
|
||||
}
|
||||
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.visibleZoomLabel', {
|
||||
defaultMessage: 'Zoom range for layer visibility'
|
||||
})}
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.visibleZoomLabel', {
|
||||
defaultMessage: 'Visible zoom range'
|
||||
})}
|
||||
>
|
||||
<ValidatedDualRange
|
||||
min={MIN_ZOOM}
|
||||
max={MAX_ZOOM}
|
||||
value={[props.minZoom, props.maxZoom]}
|
||||
showInput
|
||||
showRange
|
||||
onChange={onZoomChange}
|
||||
allowEmptyRange={false}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<ValidatedDualRange
|
||||
min={MIN_ZOOM}
|
||||
max={MAX_ZOOM}
|
||||
value={[props.minZoom, props.maxZoom]}
|
||||
showInput
|
||||
showRange
|
||||
onChange={onZoomChange}
|
||||
allowEmptyRange={false}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
};
|
||||
|
@ -115,6 +108,43 @@ export function LayerSettings(props) {
|
|||
);
|
||||
};
|
||||
|
||||
const renderApplyGlobalQueryCheckbox = () => {
|
||||
const layerSupportsGlobalQuery = props.layer.getIndexPatternIds().length;
|
||||
|
||||
const applyGlobalQueryCheckbox = (
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={
|
||||
i18n.translate('xpack.maps.layerPanel.applyGlobalQueryCheckboxLabel', {
|
||||
defaultMessage: `Apply global filter to layer`
|
||||
})
|
||||
}
|
||||
checked={layerSupportsGlobalQuery ? props.applyGlobalQuery : false}
|
||||
onChange={onApplyGlobalQueryChange}
|
||||
disabled={!layerSupportsGlobalQuery}
|
||||
data-test-subj="mapLayerPanelApplyGlobalQueryCheckbox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
||||
if (layerSupportsGlobalQuery) {
|
||||
return applyGlobalQueryCheckbox;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
i18n.translate('xpack.maps.layerPanel.applyGlobalQueryCheckbox.disableTooltip', {
|
||||
defaultMessage: `Layer does not support filtering.`
|
||||
})
|
||||
}
|
||||
>
|
||||
{applyGlobalQueryCheckbox}
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiPanel>
|
||||
|
@ -131,13 +161,15 @@ export function LayerSettings(props) {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer margin="m"/>
|
||||
<EuiSpacer size="m"/>
|
||||
|
||||
{renderLabel()}
|
||||
|
||||
{renderZoomSliders()}
|
||||
|
||||
{renderAlphaSlider()}
|
||||
|
||||
{renderApplyGlobalQueryCheckbox()}
|
||||
</EuiPanel>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
|
|
@ -23,7 +23,7 @@ exports[`Should render source settings editor 1`] = `
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer
|
||||
margin="m"
|
||||
size="m"
|
||||
/>
|
||||
<div>
|
||||
mockSourceEditor
|
||||
|
|
|
@ -44,7 +44,7 @@ export function SourceSettings({ layer, updateSourceProp }) {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer margin="m"/>
|
||||
<EuiSpacer size="m"/>
|
||||
|
||||
{sourceSettingsEditor}
|
||||
</EuiPanel>
|
||||
|
|
|
@ -40,7 +40,7 @@ export function StyleTabs({ layer, updateStyle }) {
|
|||
<EuiPanel key={index}>
|
||||
<EuiTitle size="xs"><h5>{Style.getDisplayName()}</h5></EuiTitle>
|
||||
{description}
|
||||
<EuiSpacer margin="m"/>
|
||||
<EuiSpacer size="m"/>
|
||||
{styleEditor}
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
|
@ -11,6 +11,9 @@ import { MapEmbeddable } from './map_embeddable';
|
|||
import { indexPatternService } from '../kibana_services';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants';
|
||||
import { createMapStore } from '../store/store';
|
||||
import { addLayerWithoutDataSync } from '../actions/store_actions';
|
||||
import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors';
|
||||
|
||||
export class MapEmbeddableFactory extends EmbeddableFactory {
|
||||
|
||||
|
@ -28,8 +31,21 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
|
|||
this._savedObjectLoader = gisMapSavedObjectLoader;
|
||||
}
|
||||
|
||||
async _getIndexPatterns(indexPatternIds = []) {
|
||||
const promises = indexPatternIds.map(async (indexPatternId) => {
|
||||
async _getIndexPatterns(layerListJSON) {
|
||||
// Need to extract layerList from store to get queryable index pattern ids
|
||||
const store = createMapStore();
|
||||
try {
|
||||
JSON.parse(layerListJSON).forEach(layerDescriptor => {
|
||||
store.dispatch(addLayerWithoutDataSync(layerDescriptor));
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(i18n.translate('xpack.maps.mapEmbeddableFactory', {
|
||||
defaultMessage: 'Unable to load map, malformed saved object',
|
||||
}));
|
||||
}
|
||||
const queryableIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState());
|
||||
|
||||
const promises = queryableIndexPatternIds.map(async (indexPatternId) => {
|
||||
try {
|
||||
return await indexPatternService.get(indexPatternId);
|
||||
} catch (error) {
|
||||
|
@ -44,7 +60,8 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
|
|||
|
||||
async create(panelMetadata, onEmbeddableStateChanged) {
|
||||
const savedMap = await this._savedObjectLoader.get(panelMetadata.id);
|
||||
const indexPatterns = await this._getIndexPatterns(savedMap.indexPatternIds);
|
||||
|
||||
const indexPatterns = await this._getIndexPatterns(savedMap.layerListJSON);
|
||||
|
||||
return new MapEmbeddable({
|
||||
onEmbeddableStateChanged,
|
||||
|
|
|
@ -170,6 +170,7 @@ export const getSelectedLayerJoinDescriptors = createSelector(
|
|||
});
|
||||
});
|
||||
|
||||
// Get list of unique index patterns used by all layers
|
||||
export const getUniqueIndexPatternIds = createSelector(
|
||||
getLayerList,
|
||||
(layerList) => {
|
||||
|
@ -181,6 +182,18 @@ export const getUniqueIndexPatternIds = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
// Get list of unique index patterns, excluding index patterns from layers that disable applyGlobalQuery
|
||||
export const getQueryableUniqueIndexPatternIds = createSelector(
|
||||
getLayerList,
|
||||
(layerList) => {
|
||||
const indexPatternIds = [];
|
||||
layerList.forEach(layer => {
|
||||
indexPatternIds.push(...layer.getQueryableIndexPatternIds());
|
||||
});
|
||||
return _.uniq(indexPatternIds);
|
||||
}
|
||||
);
|
||||
|
||||
export const hasDirtyState = createSelector(getLayerListRaw, (layerListRaw) => {
|
||||
return layerListRaw.some(layerDescriptor => {
|
||||
const currentState = copyPersistentState(layerDescriptor);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { AbstractLayer } from './layer';
|
|||
import { EuiIcon } from '@elastic/eui';
|
||||
import { HeatmapStyle } from './styles/heatmap_style';
|
||||
import { SOURCE_DATA_ID_ORIGIN } from '../../../common/constants';
|
||||
import { isRefreshOnlyQuery } from './util/is_refresh_only_query';
|
||||
|
||||
const SCALED_PROPERTY_NAME = '__kbn_heatmap_weight__';//unique name to store scaled value for weighting
|
||||
|
||||
|
@ -45,7 +46,6 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
return metricfields[0].propertyKey;
|
||||
}
|
||||
|
||||
|
||||
_getMbLayerId() {
|
||||
return this.getId() + '_heatmap';
|
||||
}
|
||||
|
@ -134,13 +134,19 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
|
||||
const updateDueToExtent = this.updateDueToExtent(this._source, meta, searchFilters);
|
||||
|
||||
const updateDueToQuery = searchFilters.query
|
||||
&& !_.isEqual(meta.query, searchFilters.query);
|
||||
let updateDueToQuery = false;
|
||||
let updateDueToFilters = false;
|
||||
if (searchFilters.applyGlobalQuery) {
|
||||
updateDueToQuery = !_.isEqual(meta.query, searchFilters.query);
|
||||
updateDueToFilters = !_.isEqual(meta.filters, searchFilters.filters);
|
||||
} else {
|
||||
// Global filters and query are not applied to layer search request so no re-fetch required.
|
||||
// Exception is "Refresh" query.
|
||||
updateDueToQuery = isRefreshOnlyQuery(meta.query, searchFilters.query);
|
||||
}
|
||||
const updateDueToLayerQuery = searchFilters.layerQuery
|
||||
&& !_.isEqual(meta.layerQuery, searchFilters.layerQuery);
|
||||
|
||||
const updateDueToFilters = searchFilters.filters
|
||||
&& !_.isEqual(meta.filters, searchFilters.filters);
|
||||
const updateDueToApplyGlobalQuery = meta.applyGlobalQuery !== searchFilters.applyGlobalQuery;
|
||||
|
||||
const updateDueToMetricChange = !_.isEqual(meta.metric, searchFilters.metric);
|
||||
|
||||
|
@ -150,6 +156,7 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
&& !updateDueToRefreshTimer
|
||||
&& !updateDueToQuery
|
||||
&& !updateDueToLayerQuery
|
||||
&& !updateDueToApplyGlobalQuery
|
||||
&& !updateDueToFilters
|
||||
&& !updateDueToMetricChange
|
||||
) {
|
||||
|
@ -163,6 +170,7 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
return {
|
||||
...dataFilters,
|
||||
layerQuery: this.getQuery(),
|
||||
applyGlobalQuery: this.getApplyGlobalQuery(),
|
||||
geogridPrecision: this._source.getGeoGridPrecision(dataFilters.zoom),
|
||||
metric: this._getPropKeyOfSelectedMetric()
|
||||
};
|
||||
|
|
|
@ -42,7 +42,9 @@ export class AbstractLayer {
|
|||
layerDescriptor.maxZoom = _.get(options, 'maxZoom', 24);
|
||||
layerDescriptor.alpha = _.get(options, 'alpha', 0.75);
|
||||
layerDescriptor.visible = _.get(options, 'visible', true);
|
||||
layerDescriptor.applyGlobalQuery = _.get(options, 'applyGlobalQuery', true);
|
||||
layerDescriptor.style = _.get(options, 'style', {});
|
||||
|
||||
return layerDescriptor;
|
||||
}
|
||||
|
||||
|
@ -144,6 +146,10 @@ export class AbstractLayer {
|
|||
return this._descriptor.query;
|
||||
}
|
||||
|
||||
getApplyGlobalQuery() {
|
||||
return this._descriptor.applyGlobalQuery;
|
||||
}
|
||||
|
||||
getZoomConfig() {
|
||||
return {
|
||||
minZoom: this._descriptor.minZoom,
|
||||
|
@ -262,6 +268,14 @@ export class AbstractLayer {
|
|||
return [];
|
||||
}
|
||||
|
||||
getQueryableIndexPatternIds() {
|
||||
if (this.getApplyGlobalQuery()) {
|
||||
return this.getIndexPatternIds();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async getOrdinalFields() {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -142,7 +142,9 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
async _makeSearchSource(searchFilters, limit) {
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
const isTimeAware = await this.isTimeAware();
|
||||
const allFilters = [...searchFilters.filters];
|
||||
const applyGlobalQuery = _.get(searchFilters, 'applyGlobalQuery', true);
|
||||
const globalFilters = applyGlobalQuery ? searchFilters.filters : [];
|
||||
const allFilters = [...globalFilters];
|
||||
if (this.isFilterByMapBounds() && searchFilters.buffer) {//buffer can be empty
|
||||
const geoField = await this._getGeoField();
|
||||
allFilters.push(createExtentFilter(searchFilters.buffer, geoField.name, geoField.type));
|
||||
|
@ -155,7 +157,9 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
searchSource.setField('index', indexPattern);
|
||||
searchSource.setField('size', limit);
|
||||
searchSource.setField('filter', allFilters);
|
||||
searchSource.setField('query', searchFilters.query);
|
||||
if (applyGlobalQuery) {
|
||||
searchSource.setField('query', searchFilters.query);
|
||||
}
|
||||
|
||||
if (searchFilters.layerQuery) {
|
||||
const layerSearchSource = new SearchSource();
|
||||
|
@ -167,9 +171,9 @@ export class AbstractESSource extends AbstractVectorSource {
|
|||
return searchSource;
|
||||
}
|
||||
|
||||
async getBoundsForFilters({ layerQuery, query, timeFilters, filters }) {
|
||||
async getBoundsForFilters({ layerQuery, query, timeFilters, filters, applyGlobalQuery }) {
|
||||
|
||||
const searchSource = await this._makeSearchSource({ layerQuery, query, timeFilters, filters }, 0);
|
||||
const searchSource = await this._makeSearchSource({ layerQuery, query, timeFilters, filters, applyGlobalQuery }, 0);
|
||||
const geoField = await this._getGeoField();
|
||||
const indexPattern = await this._getIndexPattern();
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// Refresh only query is query where timestamps are different but query is the same.
|
||||
// Triggered by clicking "Refresh" button in QueryBar
|
||||
export function isRefreshOnlyQuery(prevQuery, newQuery) {
|
||||
return prevQuery.queryLastTriggeredAt !== newQuery.queryLastTriggeredAt
|
||||
&& prevQuery.language === newQuery.language
|
||||
&& prevQuery.query === newQuery.query;
|
||||
}
|
|
@ -11,6 +11,7 @@ import { LeftInnerJoin } from './joins/left_inner_join';
|
|||
import { FEATURE_ID_PROPERTY_NAME, SOURCE_DATA_ID_ORIGIN } from '../../../common/constants';
|
||||
import _ from 'lodash';
|
||||
import { JoinTooltipProperty } from './tooltips/join_tooltip_property';
|
||||
import { isRefreshOnlyQuery } from './util/is_refresh_only_query';
|
||||
|
||||
const EMPTY_FEATURE_COLLECTION = {
|
||||
type: 'FeatureCollection',
|
||||
|
@ -207,10 +208,18 @@ export class VectorLayer extends AbstractLayer {
|
|||
let updateDueToQuery = false;
|
||||
let updateDueToFilters = false;
|
||||
let updateDueToLayerQuery = false;
|
||||
let updateDueToApplyGlobalQuery = false;
|
||||
if (isQueryAware) {
|
||||
updateDueToQuery = !_.isEqual(meta.query, searchFilters.query);
|
||||
updateDueToFilters = !_.isEqual(meta.filters, searchFilters.filters);
|
||||
updateDueToApplyGlobalQuery = meta.applyGlobalQuery !== searchFilters.applyGlobalQuery;
|
||||
updateDueToLayerQuery = !_.isEqual(meta.layerQuery, searchFilters.layerQuery);
|
||||
if (searchFilters.applyGlobalQuery) {
|
||||
updateDueToQuery = !_.isEqual(meta.query, searchFilters.query);
|
||||
updateDueToFilters = !_.isEqual(meta.filters, searchFilters.filters);
|
||||
} else {
|
||||
// Global filters and query are not applied to layer search request so no re-fetch required.
|
||||
// Exception is "Refresh" query.
|
||||
updateDueToQuery = isRefreshOnlyQuery(meta.query, searchFilters.query);
|
||||
}
|
||||
}
|
||||
|
||||
let updateDueToPrecisionChange = false;
|
||||
|
@ -227,6 +236,7 @@ export class VectorLayer extends AbstractLayer {
|
|||
&& !updateDueToQuery
|
||||
&& !updateDueToFilters
|
||||
&& !updateDueToLayerQuery
|
||||
&& !updateDueToApplyGlobalQuery
|
||||
&& !updateDueToPrecisionChange;
|
||||
}
|
||||
|
||||
|
@ -236,22 +246,27 @@ export class VectorLayer extends AbstractLayer {
|
|||
const sourceDataId = join.getSourceId();
|
||||
const requestToken = Symbol(`layer-join-refresh:${ this.getId()} - ${sourceDataId}`);
|
||||
|
||||
const searchFilters = {
|
||||
...dataFilters,
|
||||
applyGlobalQuery: this.getApplyGlobalQuery(),
|
||||
};
|
||||
const canSkip = await this._canSkipSourceUpdate(joinSource, sourceDataId, searchFilters);
|
||||
if (canSkip) {
|
||||
const sourceDataRequest = this._findDataRequestForSource(sourceDataId);
|
||||
const propertiesMap = sourceDataRequest ? sourceDataRequest.getData() : null;
|
||||
return {
|
||||
dataHasChanged: false,
|
||||
join: join,
|
||||
propertiesMap: propertiesMap
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const canSkip = await this._canSkipSourceUpdate(joinSource, sourceDataId, dataFilters);
|
||||
if (canSkip) {
|
||||
const sourceDataRequest = this._findDataRequestForSource(sourceDataId);
|
||||
const propertiesMap = sourceDataRequest ? sourceDataRequest.getData() : null;
|
||||
return {
|
||||
dataHasChanged: false,
|
||||
join: join,
|
||||
propertiesMap: propertiesMap
|
||||
};
|
||||
}
|
||||
startLoading(sourceDataId, requestToken, dataFilters);
|
||||
startLoading(sourceDataId, requestToken, searchFilters);
|
||||
const leftSourceName = await this.getSourceName();
|
||||
const {
|
||||
propertiesMap
|
||||
} = await joinSource.getPropertiesMap(dataFilters, leftSourceName, join.getLeftFieldName());
|
||||
} = await joinSource.getPropertiesMap(searchFilters, leftSourceName, join.getLeftFieldName());
|
||||
stopLoading(sourceDataId, requestToken, propertiesMap);
|
||||
return {
|
||||
dataHasChanged: true,
|
||||
|
@ -289,7 +304,8 @@ export class VectorLayer extends AbstractLayer {
|
|||
...dataFilters,
|
||||
fieldNames: _.uniq(fieldNames).sort(),
|
||||
geogridPrecision: this._source.getGeoGridPrecision(dataFilters.zoom),
|
||||
layerQuery: this.getQuery()
|
||||
layerQuery: this.getQuery(),
|
||||
applyGlobalQuery: this.getApplyGlobalQuery(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
185
x-pack/plugins/maps/public/shared/layers/vector_layer.test.js
Normal file
185
x-pack/plugins/maps/public/shared/layers/vector_layer.test.js
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('./joins/left_inner_join', () => ({
|
||||
LeftInnerJoin: Object
|
||||
}));
|
||||
|
||||
jest.mock('./tooltips/join_tooltip_property', () => ({
|
||||
JoinTooltipProperty: Object
|
||||
}));
|
||||
|
||||
import { VectorLayer } from './vector_layer';
|
||||
|
||||
describe('_canSkipSourceUpdate', () => {
|
||||
const SOURCE_DATA_REQUEST_ID = 'foo';
|
||||
|
||||
describe('isQueryAware', () => {
|
||||
|
||||
const queryAwareSourceMock = {
|
||||
isTimeAware: () => { return false; },
|
||||
isRefreshTimerAware: () => { return false; },
|
||||
isFilterByMapBounds: () => { return false; },
|
||||
isFieldAware: () => { return false; },
|
||||
isQueryAware: () => { return true; },
|
||||
isGeoGridPrecisionAware: () => { return false; },
|
||||
};
|
||||
const prevFilters = [];
|
||||
const prevQuery = {
|
||||
language: 'kuery',
|
||||
query: 'machine.os.keyword : "win 7"',
|
||||
queryLastTriggeredAt: '2019-04-25T20:53:22.331Z'
|
||||
};
|
||||
|
||||
describe('applyGlobalQuery is false', () => {
|
||||
|
||||
const prevApplyGlobalQuery = false;
|
||||
|
||||
const vectorLayer = new VectorLayer({
|
||||
layerDescriptor: {
|
||||
__dataRequests: [
|
||||
{
|
||||
dataId: SOURCE_DATA_REQUEST_ID,
|
||||
dataMeta: {
|
||||
applyGlobalQuery: prevApplyGlobalQuery,
|
||||
filters: prevFilters,
|
||||
query: prevQuery,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
it('can skip update when filter changes', async () => {
|
||||
const searchFilters = {
|
||||
applyGlobalQuery: prevApplyGlobalQuery,
|
||||
filters: [prevQuery],
|
||||
query: prevQuery,
|
||||
};
|
||||
|
||||
const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
|
||||
|
||||
expect(canSkipUpdate).toBe(true);
|
||||
});
|
||||
|
||||
it('can skip update when query changes', async () => {
|
||||
const searchFilters = {
|
||||
applyGlobalQuery: prevApplyGlobalQuery,
|
||||
filters: prevFilters,
|
||||
query: {
|
||||
...prevQuery,
|
||||
query: 'a new query string',
|
||||
}
|
||||
};
|
||||
|
||||
const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
|
||||
|
||||
expect(canSkipUpdate).toBe(true);
|
||||
});
|
||||
|
||||
it('can not skip update when query is refreshed', async () => {
|
||||
const searchFilters = {
|
||||
applyGlobalQuery: prevApplyGlobalQuery,
|
||||
filters: prevFilters,
|
||||
query: {
|
||||
...prevQuery,
|
||||
queryLastTriggeredAt: 'sometime layer when Refresh button is clicked'
|
||||
}
|
||||
};
|
||||
|
||||
const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
|
||||
|
||||
expect(canSkipUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('can not skip update when applyGlobalQuery changes', async () => {
|
||||
const searchFilters = {
|
||||
applyGlobalQuery: !prevApplyGlobalQuery,
|
||||
filters: prevFilters,
|
||||
query: prevQuery
|
||||
};
|
||||
|
||||
const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
|
||||
|
||||
expect(canSkipUpdate).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyGlobalQuery is true', () => {
|
||||
|
||||
const prevApplyGlobalQuery = true;
|
||||
|
||||
const vectorLayer = new VectorLayer({
|
||||
layerDescriptor: {
|
||||
__dataRequests: [
|
||||
{
|
||||
dataId: SOURCE_DATA_REQUEST_ID,
|
||||
dataMeta: {
|
||||
applyGlobalQuery: prevApplyGlobalQuery,
|
||||
filters: prevFilters,
|
||||
query: prevQuery,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
it('can not skip update when filter changes', async () => {
|
||||
const searchFilters = {
|
||||
applyGlobalQuery: prevApplyGlobalQuery,
|
||||
filters: [prevQuery],
|
||||
query: prevQuery,
|
||||
};
|
||||
|
||||
const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
|
||||
|
||||
expect(canSkipUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('can not skip update when query changes', async () => {
|
||||
const searchFilters = {
|
||||
applyGlobalQuery: prevApplyGlobalQuery,
|
||||
filters: prevFilters,
|
||||
query: {
|
||||
...prevQuery,
|
||||
query: 'a new query string',
|
||||
}
|
||||
};
|
||||
|
||||
const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
|
||||
|
||||
expect(canSkipUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('can not skip update when query is refreshed', async () => {
|
||||
const searchFilters = {
|
||||
applyGlobalQuery: prevApplyGlobalQuery,
|
||||
filters: prevFilters,
|
||||
query: {
|
||||
...prevQuery,
|
||||
queryLastTriggeredAt: 'sometime layer when Refresh button is clicked'
|
||||
}
|
||||
};
|
||||
|
||||
const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
|
||||
|
||||
expect(canSkipUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('can not skip update when applyGlobalQuery changes', async () => {
|
||||
const searchFilters = {
|
||||
applyGlobalQuery: !prevApplyGlobalQuery,
|
||||
filters: prevFilters,
|
||||
query: prevQuery
|
||||
};
|
||||
|
||||
const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
|
||||
|
||||
expect(canSkipUpdate).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -82,12 +82,30 @@ export default function ({ getPageObjects, getService }) {
|
|||
|
||||
});
|
||||
|
||||
describe('inspector', () => {
|
||||
describe('query bar', () => {
|
||||
before(async () => {
|
||||
await PageObjects.maps.setAndSubmitQuery('prop1 < 10 or _index : "geo_shapes*"');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await inspector.close();
|
||||
});
|
||||
|
||||
it('should contain terms aggregation elasticsearch request', async () => {
|
||||
it('should apply query to join request', async () => {
|
||||
await PageObjects.maps.openInspectorRequest('meta_for_geo_shapes*.shape_name');
|
||||
const requestStats = await inspector.getTableData();
|
||||
const totalHits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)');
|
||||
expect(totalHits).to.equal('3');
|
||||
const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits');
|
||||
expect(hits).to.equal('0'); // aggregation requests do not return any documents
|
||||
const indexPatternName = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Index pattern');
|
||||
expect(indexPatternName).to.equal('meta_for_geo_shapes*');
|
||||
});
|
||||
|
||||
it('should not apply query to join request when apply global query is disabled', async () => {
|
||||
await PageObjects.maps.openLayerPanel('geo_shapes*');
|
||||
await PageObjects.maps.disableApplyGlobalQuery();
|
||||
|
||||
await PageObjects.maps.openInspectorRequest('meta_for_geo_shapes*.shape_name');
|
||||
const requestStats = await inspector.getTableData();
|
||||
const totalHits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)');
|
||||
|
@ -97,6 +115,13 @@ export default function ({ getPageObjects, getService }) {
|
|||
const indexPatternName = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Index pattern');
|
||||
expect(indexPatternName).to.equal('meta_for_geo_shapes*');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('inspector', () => {
|
||||
afterEach(async () => {
|
||||
await inspector.close();
|
||||
});
|
||||
|
||||
it('should not contain any elasticsearch request after layer is deleted', async () => {
|
||||
await PageObjects.maps.removeLayer('geo_shapes*');
|
||||
|
|
|
@ -259,6 +259,22 @@ export function GisPageProvider({ getService, getPageObjects }) {
|
|||
await testSubjects.click(`mapOpenLayerButton${layerName}`);
|
||||
}
|
||||
|
||||
async disableApplyGlobalQuery() {
|
||||
const element = await testSubjects.find('mapLayerPanelApplyGlobalQueryCheckbox');
|
||||
const isSelected = await element.isSelected();
|
||||
if(isSelected) {
|
||||
await retry.try(async () => {
|
||||
log.debug(`disabling applyGlobalQuery`);
|
||||
await testSubjects.click('mapLayerPanelApplyGlobalQueryCheckbox');
|
||||
const isStillSelected = await element.isSelected();
|
||||
if (isStillSelected) {
|
||||
throw new Error('applyGlobalQuery not disabled');
|
||||
}
|
||||
});
|
||||
await this.waitForLayersToLoad();
|
||||
}
|
||||
}
|
||||
|
||||
async doesLayerExist(layerName) {
|
||||
layerName = layerName.replace(' ', '_');
|
||||
log.debug(`Open layer panel, layer: ${layerName}`);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue