diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index 07de57d0ac832..d1690ddfff43d 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -66,6 +66,7 @@ export type VectorSourceRequestMeta = MapFilters & { applyGlobalTime: boolean; fieldNames: string[]; geogridPrecision?: number; + timesiceMaskField?: string; sourceQuery?: MapQuery; sourceMeta: VectorSourceSyncMeta; }; @@ -84,6 +85,9 @@ export type VectorStyleRequestMeta = MapFilters & { export type ESSearchSourceResponseMeta = { areResultsTrimmed?: boolean; resultsCount?: number; + // results time extent, either Kibana time range or timeslider time slice + timeExtent?: Timeslice; + isTimeExtentForTimeslice?: boolean; // top hits meta areEntitiesTrimmed?: boolean; diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index 6dd454137be7d..9bfa74825c338 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -22,7 +22,6 @@ import { LAYER_STYLE_TYPE, FIELD_ORIGIN, } from '../../../../common/constants'; -import { isTotalHitsGreaterThan, TotalHits } from '../../../../common/elasticsearch_util'; import { ESGeoGridSource } from '../../sources/es_geo_grid_source/es_geo_grid_source'; import { canSkipSourceUpdate } from '../../util/can_skip_fetch'; import { IESSource } from '../../sources/es_source'; @@ -35,6 +34,7 @@ import { DynamicStylePropertyOptions, StylePropertyOptions, LayerDescriptor, + Timeslice, VectorLayerDescriptor, VectorSourceRequestMeta, VectorStylePropertiesDescriptor, @@ -46,10 +46,6 @@ import { isSearchSourceAbortError } from '../../sources/es_source/es_source'; const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID'; -interface CountData { - isSyncClustered: boolean; -} - function getAggType( dynamicProperty: IDynamicStyleProperty ): AGG_TYPE.AVG | AGG_TYPE.TERMS { @@ -216,7 +212,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { let isClustered = false; const countDataRequest = this.getDataRequest(ACTIVE_COUNT_DATA_ID); if (countDataRequest) { - const requestData = countDataRequest.getData() as CountData; + const requestData = countDataRequest.getData() as { isSyncClustered: boolean }; if (requestData && requestData.isSyncClustered) { isClustered = true; } @@ -294,7 +290,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { async syncData(syncContext: DataRequestContext) { const dataRequestId = ACTIVE_COUNT_DATA_ID; const requestToken = Symbol(`layer-active-count:${this.getId()}`); - const searchFilters: VectorSourceRequestMeta = this._getSearchFilters( + const searchFilters: VectorSourceRequestMeta = await this._getSearchFilters( syncContext.dataFilters, this.getSource(), this.getCurrentStyle() @@ -305,6 +301,9 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { prevDataRequest: this.getDataRequest(dataRequestId), nextMeta: searchFilters, extentAware: source.isFilterByMapBounds(), + getUpdateDueToTimeslice: (timeslice?: Timeslice) => { + return this._getUpdateDueToTimesliceFromSourceRequestMeta(source, timeslice); + }, }); let activeSource; @@ -322,22 +321,11 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { let isSyncClustered; try { syncContext.startLoading(dataRequestId, requestToken, searchFilters); - const abortController = new AbortController(); - syncContext.registerCancelCallback(requestToken, () => abortController.abort()); - const maxResultWindow = await this._documentSource.getMaxResultWindow(); - const searchSource = await this._documentSource.makeSearchSource(searchFilters, 0); - searchSource.setField('trackTotalHits', maxResultWindow + 1); - const resp = await searchSource.fetch({ - abortSignal: abortController.signal, - sessionId: syncContext.dataFilters.searchSessionId, - legacyHitsTotal: false, - }); - isSyncClustered = isTotalHitsGreaterThan( - (resp.hits.total as unknown) as TotalHits, - maxResultWindow - ); - const countData = { isSyncClustered } as CountData; - syncContext.stopLoading(dataRequestId, requestToken, countData, searchFilters); + isSyncClustered = !(await this._documentSource.canLoadAllDocuments( + searchFilters, + syncContext.registerCancelCallback.bind(null, requestToken) + )); + syncContext.stopLoading(dataRequestId, requestToken, { isSyncClustered }, searchFilters); } catch (error) { if (!(error instanceof DataRequestAbortError) || !isSearchSourceAbortError(error)) { syncContext.onLoadError(dataRequestId, requestToken, error.message); diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts index 368ff8bebcdd1..d12c8432a4191 100644 --- a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts @@ -111,6 +111,9 @@ export class HeatmapLayer extends AbstractLayer { }, syncContext, source: this.getSource(), + getUpdateDueToTimeslice: () => { + return true; + }, }); } catch (error) { if (!(error instanceof DataRequestAbortError)) { diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index be113ab4cc2c9..ef41c157a2b17 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -36,6 +36,7 @@ import { LayerDescriptor, MapExtent, StyleDescriptor, + Timeslice, } from '../../../common/descriptor_types'; import { ImmutableSourceProperty, ISource, SourceEditorArgs } from '../sources/source'; import { DataRequestContext } from '../../actions'; @@ -78,7 +79,7 @@ export interface ILayer { getMbLayerIds(): string[]; ownsMbLayerId(mbLayerId: string): boolean; ownsMbSourceId(mbSourceId: string): boolean; - syncLayerWithMB(mbMap: MbMap): void; + syncLayerWithMB(mbMap: MbMap, timeslice?: Timeslice): void; getLayerTypeIconName(): string; isInitialDataLoadComplete(): boolean; getIndexPatternIds(): string[]; diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index 6dba935ccc87d..2ad6a5ef73c6d 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -21,6 +21,7 @@ import { VectorLayer, VectorLayerArguments } from '../vector_layer'; import { ITiledSingleLayerVectorSource } from '../../sources/tiled_single_layer_vector_source'; import { DataRequestContext } from '../../../actions'; import { + Timeslice, VectorLayerDescriptor, VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; @@ -66,7 +67,7 @@ export class TiledVectorLayer extends VectorLayer { dataFilters, }: DataRequestContext) { const requestToken: symbol = Symbol(`layer-${this.getId()}-${SOURCE_DATA_REQUEST_ID}`); - const searchFilters: VectorSourceRequestMeta = this._getSearchFilters( + const searchFilters: VectorSourceRequestMeta = await this._getSearchFilters( dataFilters, this.getSource(), this._style as IVectorStyle @@ -84,6 +85,10 @@ export class TiledVectorLayer extends VectorLayer { source: this.getSource(), prevDataRequest, nextMeta: searchFilters, + getUpdateDueToTimeslice: (timeslice?: Timeslice) => { + // TODO use meta features to determine if tiles already contain features for timeslice. + return true; + }, }); const canSkip = noChangesInSourceState && noChangesInSearchState; if (canSkip) { diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx index d305bb920b2ad..346e59f60af32 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx @@ -13,7 +13,13 @@ import { SOURCE_DATA_REQUEST_ID, VECTOR_SHAPE_TYPE, } from '../../../../common/constants'; -import { MapExtent, MapQuery, VectorSourceRequestMeta } from '../../../../common/descriptor_types'; +import { + DataMeta, + MapExtent, + MapQuery, + Timeslice, + VectorSourceRequestMeta, +} from '../../../../common/descriptor_types'; import { DataRequestContext } from '../../../actions'; import { IVectorSource } from '../../sources/vector_source'; import { DataRequestAbortError } from '../../util/data_request'; @@ -52,6 +58,7 @@ export async function syncVectorSource({ requestMeta, syncContext, source, + getUpdateDueToTimeslice, }: { layerId: string; layerName: string; @@ -59,6 +66,7 @@ export async function syncVectorSource({ requestMeta: VectorSourceRequestMeta; syncContext: DataRequestContext; source: IVectorSource; + getUpdateDueToTimeslice: (timeslice?: Timeslice) => boolean; }): Promise<{ refreshed: boolean; featureCollection: FeatureCollection }> { const { startLoading, @@ -76,6 +84,7 @@ export async function syncVectorSource({ prevDataRequest, nextMeta: requestMeta, extentAware: source.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); if (canSkipFetch) { return { @@ -104,7 +113,14 @@ export async function syncVectorSource({ ) { layerFeatureCollection.features.push(...getCentroidFeatures(layerFeatureCollection)); } - stopLoading(dataRequestId, requestToken, layerFeatureCollection, meta); + const responseMeta: DataMeta = meta ? { ...meta } : {}; + if (requestMeta.applyGlobalTime && (await source.isTimeAware())) { + const timesiceMaskField = await source.getTimesliceMaskFieldName(); + if (timesiceMaskField) { + responseMeta.timesiceMaskField = timesiceMaskField; + } + } + stopLoading(dataRequestId, requestToken, layerFeatureCollection, responseMeta); return { refreshed: true, featureCollection: layerFeatureCollection, diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 8b4d25f4612cc..49a0878ef80b2 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -43,16 +43,19 @@ import { getFillFilterExpression, getLineFilterExpression, getPointFilterExpression, + TimesliceMaskConfig, } from '../../util/mb_filter_expressions'; import { DynamicStylePropertyOptions, MapFilters, MapQuery, + Timeslice, VectorJoinSourceRequestMeta, VectorLayerDescriptor, VectorSourceRequestMeta, VectorStyleRequestMeta, } from '../../../../common/descriptor_types'; +import { ISource } from '../../sources/source'; import { IVectorSource } from '../../sources/vector_source'; import { CustomIconAndTooltipContent, ILayer } from '../layer'; import { InnerJoin } from '../../joins/inner_join'; @@ -347,6 +350,9 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { prevDataRequest, nextMeta: searchFilters, extentAware: false, // join-sources are term-aggs that are spatially unaware (e.g. ESTermSource/TableSource). + getUpdateDueToTimeslice: () => { + return true; + }, }); if (canSkipFetch) { return { @@ -389,17 +395,22 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return await Promise.all(joinSyncs); } - _getSearchFilters( + async _getSearchFilters( dataFilters: MapFilters, source: IVectorSource, style: IVectorStyle - ): VectorSourceRequestMeta { + ): Promise { const fieldNames = [ ...source.getFieldNames(), ...style.getSourceFieldNames(), ...this.getValidJoins().map((join) => join.getLeftField().getName()), ]; + const timesliceMaskFieldName = await source.getTimesliceMaskFieldName(); + if (timesliceMaskFieldName) { + fieldNames.push(timesliceMaskFieldName); + } + const sourceQuery = this.getQuery() as MapQuery; return { ...dataFilters, @@ -674,9 +685,12 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { layerId: this.getId(), layerName: await this.getDisplayName(source), prevDataRequest: this.getSourceDataRequest(), - requestMeta: this._getSearchFilters(syncContext.dataFilters, source, style), + requestMeta: await this._getSearchFilters(syncContext.dataFilters, source, style), syncContext, source, + getUpdateDueToTimeslice: (timeslice?: Timeslice) => { + return this._getUpdateDueToTimesliceFromSourceRequestMeta(source, timeslice); + }, }); await this._syncSupportsFeatureEditing({ syncContext, source }); if ( @@ -754,7 +768,11 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } } - _setMbPointsProperties(mbMap: MbMap, mvtSourceLayer?: string) { + _setMbPointsProperties( + mbMap: MbMap, + mvtSourceLayer?: string, + timesliceMaskConfig?: TimesliceMaskConfig + ) { const pointLayerId = this._getMbPointLayerId(); const symbolLayerId = this._getMbSymbolLayerId(); const pointLayer = mbMap.getLayer(pointLayerId); @@ -771,7 +789,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { if (symbolLayer) { mbMap.setLayoutProperty(symbolLayerId, 'visibility', 'none'); } - this._setMbCircleProperties(mbMap, mvtSourceLayer); + this._setMbCircleProperties(mbMap, mvtSourceLayer, timesliceMaskConfig); } else { markerLayerId = symbolLayerId; textLayerId = symbolLayerId; @@ -779,7 +797,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { mbMap.setLayoutProperty(pointLayerId, 'visibility', 'none'); mbMap.setLayoutProperty(this._getMbTextLayerId(), 'visibility', 'none'); } - this._setMbSymbolProperties(mbMap, mvtSourceLayer); + this._setMbSymbolProperties(mbMap, mvtSourceLayer, timesliceMaskConfig); } this.syncVisibilityWithMb(mbMap, markerLayerId); @@ -790,7 +808,11 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } } - _setMbCircleProperties(mbMap: MbMap, mvtSourceLayer?: string) { + _setMbCircleProperties( + mbMap: MbMap, + mvtSourceLayer?: string, + timesliceMaskConfig?: TimesliceMaskConfig + ) { const sourceId = this.getId(); const pointLayerId = this._getMbPointLayerId(); const pointLayer = mbMap.getLayer(pointLayerId); @@ -822,7 +844,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { mbMap.addLayer(mbLayer); } - const filterExpr = getPointFilterExpression(this.hasJoins()); + const filterExpr = getPointFilterExpression(this.hasJoins(), timesliceMaskConfig); if (!_.isEqual(filterExpr, mbMap.getFilter(pointLayerId))) { mbMap.setFilter(pointLayerId, filterExpr); mbMap.setFilter(textLayerId, filterExpr); @@ -841,7 +863,11 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { }); } - _setMbSymbolProperties(mbMap: MbMap, mvtSourceLayer?: string) { + _setMbSymbolProperties( + mbMap: MbMap, + mvtSourceLayer?: string, + timesliceMaskConfig?: TimesliceMaskConfig + ) { const sourceId = this.getId(); const symbolLayerId = this._getMbSymbolLayerId(); const symbolLayer = mbMap.getLayer(symbolLayerId); @@ -858,7 +884,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { mbMap.addLayer(mbLayer); } - const filterExpr = getPointFilterExpression(this.hasJoins()); + const filterExpr = getPointFilterExpression(this.hasJoins(), timesliceMaskConfig); if (!_.isEqual(filterExpr, mbMap.getFilter(symbolLayerId))) { mbMap.setFilter(symbolLayerId, filterExpr); } @@ -876,7 +902,11 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { }); } - _setMbLinePolygonProperties(mbMap: MbMap, mvtSourceLayer?: string) { + _setMbLinePolygonProperties( + mbMap: MbMap, + mvtSourceLayer?: string, + timesliceMaskConfig?: TimesliceMaskConfig + ) { const sourceId = this.getId(); const fillLayerId = this._getMbPolygonLayerId(); const lineLayerId = this._getMbLineLayerId(); @@ -940,14 +970,14 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { this.syncVisibilityWithMb(mbMap, fillLayerId); mbMap.setLayerZoomRange(fillLayerId, this.getMinZoom(), this.getMaxZoom()); - const fillFilterExpr = getFillFilterExpression(hasJoins); + const fillFilterExpr = getFillFilterExpression(hasJoins, timesliceMaskConfig); if (!_.isEqual(fillFilterExpr, mbMap.getFilter(fillLayerId))) { mbMap.setFilter(fillLayerId, fillFilterExpr); } this.syncVisibilityWithMb(mbMap, lineLayerId); mbMap.setLayerZoomRange(lineLayerId, this.getMinZoom(), this.getMaxZoom()); - const lineFilterExpr = getLineFilterExpression(hasJoins); + const lineFilterExpr = getLineFilterExpression(hasJoins, timesliceMaskConfig); if (!_.isEqual(lineFilterExpr, mbMap.getFilter(lineLayerId))) { mbMap.setFilter(lineLayerId, lineFilterExpr); } @@ -956,7 +986,11 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { mbMap.setLayerZoomRange(tooManyFeaturesLayerId, this.getMinZoom(), this.getMaxZoom()); } - _setMbCentroidProperties(mbMap: MbMap, mvtSourceLayer?: string) { + _setMbCentroidProperties( + mbMap: MbMap, + mvtSourceLayer?: string, + timesliceMaskConfig?: TimesliceMaskConfig + ) { const centroidLayerId = this._getMbCentroidLayerId(); const centroidLayer = mbMap.getLayer(centroidLayerId); if (!centroidLayer) { @@ -971,7 +1005,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { mbMap.addLayer(mbLayer); } - const filterExpr = getCentroidFilterExpression(this.hasJoins()); + const filterExpr = getCentroidFilterExpression(this.hasJoins(), timesliceMaskConfig); if (!_.isEqual(filterExpr, mbMap.getFilter(centroidLayerId))) { mbMap.setFilter(centroidLayerId, filterExpr); } @@ -986,17 +1020,32 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { mbMap.setLayerZoomRange(centroidLayerId, this.getMinZoom(), this.getMaxZoom()); } - _syncStylePropertiesWithMb(mbMap: MbMap) { - this._setMbPointsProperties(mbMap); - this._setMbLinePolygonProperties(mbMap); + _syncStylePropertiesWithMb(mbMap: MbMap, timeslice?: Timeslice) { + const timesliceMaskConfig = this._getTimesliceMaskConfig(timeslice); + this._setMbPointsProperties(mbMap, undefined, timesliceMaskConfig); + this._setMbLinePolygonProperties(mbMap, undefined, timesliceMaskConfig); // centroid layers added after polygon layers to ensure they are on top of polygon layers - this._setMbCentroidProperties(mbMap); + this._setMbCentroidProperties(mbMap, undefined, timesliceMaskConfig); } - syncLayerWithMB(mbMap: MbMap) { + _getTimesliceMaskConfig(timeslice?: Timeslice): TimesliceMaskConfig | undefined { + if (!timeslice || this.hasJoins()) { + return; + } + + const prevMeta = this.getSourceDataRequest()?.getMeta(); + return prevMeta !== undefined && prevMeta.timesiceMaskField !== undefined + ? { + timesiceMaskField: prevMeta.timesiceMaskField, + timeslice, + } + : undefined; + } + + syncLayerWithMB(mbMap: MbMap, timeslice?: Timeslice) { addGeoJsonMbSource(this._getMbSourceId(), this.getMbLayerIds(), mbMap); this._syncFeatureCollectionWithMb(mbMap); - this._syncStylePropertiesWithMb(mbMap); + this._syncStylePropertiesWithMb(mbMap, timeslice); } _getMbPointLayerId() { @@ -1094,6 +1143,15 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return await this._source.getLicensedFeatures(); } + _getUpdateDueToTimesliceFromSourceRequestMeta(source: ISource, timeslice?: Timeslice) { + const prevDataRequest = this.getSourceDataRequest(); + const prevMeta = prevDataRequest?.getMeta(); + if (!prevMeta) { + return true; + } + return source.getUpdateDueToTimeslice(prevMeta, timeslice); + } + async addFeature(geometry: Geometry | Position[]) { const layerSource = this.getSource(); await layerSource.addFeature(geometry); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index a51e291574b70..9f7bd1260ca22 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -12,13 +12,19 @@ import { i18n } from '@kbn/i18n'; import { IFieldType, IndexPattern } from 'src/plugins/data/public'; import { GeoJsonProperties, Geometry, Position } from 'geojson'; import { AbstractESSource } from '../es_source'; -import { getHttp, getMapAppConfig, getSearchService } from '../../../kibana_services'; +import { + getHttp, + getMapAppConfig, + getSearchService, + getTimeFilter, +} from '../../../kibana_services'; import { addFieldToDSL, getField, hitsToGeoJson, isTotalHitsGreaterThan, PreIndexedShape, + TotalHits, } from '../../../../common/elasticsearch_util'; // @ts-expect-error import { UpdateSourceEditor } from './update_source_editor'; @@ -41,11 +47,14 @@ import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { ESDocField } from '../../fields/es_doc_field'; import { registerSource } from '../source_registry'; import { + DataMeta, ESSearchSourceDescriptor, + Timeslice, VectorSourceRequestMeta, VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; +import { TimeRange } from '../../../../../../../src/plugins/data/common'; import { ImmutableSourceProperty, SourceEditorArgs } from '../source'; import { IField } from '../../fields/field'; import { GeoJsonWithMeta, SourceTooltipConfig } from '../vector_source'; @@ -59,6 +68,16 @@ import { getDocValueAndSourceFields, ScriptField } from './util/get_docvalue_sou import { ITiledSingleLayerMvtParams } from '../tiled_single_layer_vector_source/tiled_single_layer_vector_source'; import { addFeatureToIndex, getMatchingIndexes } from './util/feature_edit'; +export function timerangeToTimeextent(timerange: TimeRange): Timeslice | undefined { + const timeRangeBounds = getTimeFilter().calculateBounds(timerange); + return timeRangeBounds.min !== undefined && timeRangeBounds.max !== undefined + ? { + from: timeRangeBounds.min.valueOf(), + to: timeRangeBounds.max.valueOf(), + } + : undefined; +} + export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', { defaultMessage: 'Documents', }); @@ -338,7 +357,6 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye async _getSearchHits( layerName: string, searchFilters: VectorSourceRequestMeta, - maxResultWindow: number, registerCancelCallback: (callback: () => void) => void ) { const indexPattern = await this.getIndexPattern(); @@ -350,8 +368,18 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye ); const initialSearchContext = { docvalue_fields: docValueFields }; // Request fields in docvalue_fields insted of _source + + // Use Kibana global time extent instead of timeslice extent when all documents for global time extent can be loaded + // to allow for client-side masking of timeslice + const searchFiltersWithoutTimeslice = { ...searchFilters }; + delete searchFiltersWithoutTimeslice.timeslice; + const useSearchFiltersWithoutTimeslice = + searchFilters.timeslice !== undefined && + (await this.canLoadAllDocuments(searchFiltersWithoutTimeslice, registerCancelCallback)); + + const maxResultWindow = await this.getMaxResultWindow(); const searchSource = await this.makeSearchSource( - searchFilters, + useSearchFiltersWithoutTimeslice ? searchFiltersWithoutTimeslice : searchFilters, maxResultWindow, initialSearchContext ); @@ -375,11 +403,17 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye searchSessionId: searchFilters.searchSessionId, }); + const isTimeExtentForTimeslice = + searchFilters.timeslice !== undefined && !useSearchFiltersWithoutTimeslice; return { hits: resp.hits.hits.reverse(), // Reverse hits so top documents by sort are drawn on top meta: { resultsCount: resp.hits.hits.length, areResultsTrimmed: isTotalHitsGreaterThan(resp.hits.total, resp.hits.hits.length), + timeExtent: isTimeExtentForTimeslice + ? searchFilters.timeslice + : timerangeToTimeextent(searchFilters.timeFilters), + isTimeExtentForTimeslice, }, }; } @@ -424,16 +458,9 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye ): Promise { const indexPattern = await this.getIndexPattern(); - const indexSettings = await loadIndexSettings(indexPattern.title); - const { hits, meta } = this._isTopHits() ? await this._getTopHits(layerName, searchFilters, registerCancelCallback) - : await this._getSearchHits( - layerName, - searchFilters, - indexSettings.maxResultWindow, - registerCancelCallback - ); + : await this._getSearchHits(layerName, searchFilters, registerCancelCallback); const unusedMetaFields = indexPattern.metaFields.filter((metaField) => { return !['_id', '_index'].includes(metaField); @@ -743,6 +770,62 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye : urlTemplate, }; } + + async getTimesliceMaskFieldName(): Promise { + if (this._isTopHits() || this._descriptor.scalingType === SCALING_TYPES.MVT) { + return null; + } + + const indexPattern = await this.getIndexPattern(); + return indexPattern.timeFieldName ? indexPattern.timeFieldName : null; + } + + getUpdateDueToTimeslice(prevMeta: DataMeta, timeslice?: Timeslice): boolean { + if (this._isTopHits() || this._descriptor.scalingType === SCALING_TYPES.MVT) { + return true; + } + + if ( + prevMeta.timeExtent === undefined || + prevMeta.areResultsTrimmed === undefined || + prevMeta.areResultsTrimmed + ) { + return true; + } + + const isTimeExtentForTimeslice = + prevMeta.isTimeExtentForTimeslice !== undefined ? prevMeta.isTimeExtentForTimeslice : false; + if (!timeslice) { + return isTimeExtentForTimeslice + ? // Previous request only covers timeslice extent. Will need to re-fetch data to cover global time extent + true + : // Previous request covers global time extent. + // No need to re-fetch data since previous request already has data for the entire global time extent. + false; + } + + const isWithin = isTimeExtentForTimeslice + ? timeslice.from >= prevMeta.timeExtent.from && timeslice.to <= prevMeta.timeExtent.to + : true; + return !isWithin; + } + + async canLoadAllDocuments( + searchFilters: VectorSourceRequestMeta, + registerCancelCallback: (callback: () => void) => void + ) { + const abortController = new AbortController(); + registerCancelCallback(() => abortController.abort()); + const maxResultWindow = await this.getMaxResultWindow(); + const searchSource = await this.makeSearchSource(searchFilters, 0); + searchSource.setField('trackTotalHits', maxResultWindow + 1); + const resp = await searchSource.fetch({ + abortSignal: abortController.signal, + sessionId: searchFilters.searchSessionId, + legacyHitsTotal: false, + }); + return !isTotalHitsGreaterThan((resp.hits.total as unknown) as TotalHits, maxResultWindow); + } } registerSource({ diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index d58e71db2a9ab..5bf7a2e47cc66 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -228,6 +228,10 @@ export class MVTSingleLayerVectorSource return tooltips; } + async getTimesliceMaskFieldName() { + return null; + } + async supportsFeatureEditing(): Promise { return false; } diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 7a8fca337fd2e..0ecbde06cf3e2 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -13,7 +13,12 @@ import { GeoJsonProperties } from 'geojson'; import { copyPersistentState } from '../../reducers/copy_persistent_state'; import { IField } from '../fields/field'; import { FieldFormatter, LAYER_TYPE, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; -import { AbstractSourceDescriptor, Attribution } from '../../../common/descriptor_types'; +import { + AbstractSourceDescriptor, + Attribution, + DataMeta, + Timeslice, +} from '../../../common/descriptor_types'; import { LICENSED_FEATURES } from '../../licensed_features'; import { PreIndexedShape } from '../../../common/elasticsearch_util'; @@ -64,6 +69,7 @@ export interface ISource { getMinZoom(): number; getMaxZoom(): number; getLicensedFeatures(): Promise; + getUpdateDueToTimeslice(prevMeta: DataMeta, timeslice?: Timeslice): boolean; } export class AbstractSource implements ISource { @@ -194,4 +200,8 @@ export class AbstractSource implements ISource { async getLicensedFeatures(): Promise { return []; } + + getUpdateDueToTimeslice(prevMeta: DataMeta, timeslice?: Timeslice): boolean { + return true; + } } diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index 1194d571e344b..8f93de705e365 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -66,6 +66,7 @@ export interface IVectorSource extends ISource { getSupportedShapeTypes(): Promise; isBoundsAware(): boolean; getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig; + getTimesliceMaskFieldName(): Promise; supportsFeatureEditing(): Promise; addFeature(geometry: Geometry | Position[]): Promise; } @@ -156,6 +157,10 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return null; } + async getTimesliceMaskFieldName(): Promise { + return null; + } + async addFeature(geometry: Geometry | Position[]) { throw new Error('Should implement VectorSource#addFeature'); } diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js index c13b2fd441cad..da3cbb9055d43 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.js @@ -82,6 +82,9 @@ describe('updateDueToExtent', () => { describe('canSkipSourceUpdate', () => { const SOURCE_DATA_REQUEST_ID = 'foo'; + const getUpdateDueToTimeslice = () => { + return true; + }; describe('isQueryAware', () => { const queryAwareSourceMock = { @@ -136,6 +139,7 @@ describe('canSkipSourceUpdate', () => { prevDataRequest, nextMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(true); @@ -156,6 +160,7 @@ describe('canSkipSourceUpdate', () => { prevDataRequest, nextMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(true); @@ -176,6 +181,7 @@ describe('canSkipSourceUpdate', () => { prevDataRequest, nextMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -193,6 +199,7 @@ describe('canSkipSourceUpdate', () => { prevDataRequest, nextMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -224,6 +231,7 @@ describe('canSkipSourceUpdate', () => { prevDataRequest, nextMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -244,6 +252,7 @@ describe('canSkipSourceUpdate', () => { prevDataRequest, nextMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -264,6 +273,7 @@ describe('canSkipSourceUpdate', () => { prevDataRequest, nextMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -281,6 +291,7 @@ describe('canSkipSourceUpdate', () => { prevDataRequest, nextMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -327,6 +338,7 @@ describe('canSkipSourceUpdate', () => { applyGlobalTime: false, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -346,6 +358,7 @@ describe('canSkipSourceUpdate', () => { applyGlobalTime: true, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(true); @@ -375,6 +388,7 @@ describe('canSkipSourceUpdate', () => { }, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -402,6 +416,7 @@ describe('canSkipSourceUpdate', () => { }, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(true); @@ -429,6 +444,7 @@ describe('canSkipSourceUpdate', () => { }, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(true); @@ -463,6 +479,7 @@ describe('canSkipSourceUpdate', () => { }, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -498,6 +515,7 @@ describe('canSkipSourceUpdate', () => { }, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -529,6 +547,7 @@ describe('canSkipSourceUpdate', () => { }, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(false); @@ -564,6 +583,7 @@ describe('canSkipSourceUpdate', () => { }, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(true); @@ -599,6 +619,7 @@ describe('canSkipSourceUpdate', () => { }, }, extentAware: false, + getUpdateDueToTimeslice, }); expect(canSkipUpdate).toBe(true); diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts index 1f2678f40eecd..b6f03ef3d1c63 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts @@ -10,7 +10,7 @@ import turfBboxPolygon from '@turf/bbox-polygon'; import turfBooleanContains from '@turf/boolean-contains'; import { isRefreshOnlyQuery } from './is_refresh_only_query'; import { ISource } from '../sources/source'; -import { DataMeta } from '../../../common/descriptor_types'; +import { DataMeta, Timeslice } from '../../../common/descriptor_types'; import { DataRequest } from './data_request'; const SOURCE_UPDATE_REQUIRED = true; @@ -56,11 +56,13 @@ export async function canSkipSourceUpdate({ prevDataRequest, nextMeta, extentAware, + getUpdateDueToTimeslice, }: { source: ISource; prevDataRequest: DataRequest | undefined; nextMeta: DataMeta; extentAware: boolean; + getUpdateDueToTimeslice: (timeslice?: Timeslice) => boolean; }): Promise { const timeAware = await source.isTimeAware(); const refreshTimerAware = await source.isRefreshTimerAware(); @@ -94,7 +96,9 @@ export async function canSkipSourceUpdate({ updateDueToApplyGlobalTime = prevMeta.applyGlobalTime !== nextMeta.applyGlobalTime; if (nextMeta.applyGlobalTime) { updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters); - updateDueToTimeslice = !_.isEqual(prevMeta.timeslice, nextMeta.timeslice); + if (!_.isEqual(prevMeta.timeslice, nextMeta.timeslice)) { + updateDueToTimeslice = getUpdateDueToTimeslice(nextMeta.timeslice); + } } } diff --git a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts index f5df741759cb3..6a193216c7c1e 100644 --- a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts +++ b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts @@ -12,67 +12,110 @@ import { KBN_TOO_MANY_FEATURES_PROPERTY, } from '../../../common/constants'; +import { Timeslice } from '../../../common/descriptor_types'; + +export interface TimesliceMaskConfig { + timesiceMaskField: string; + timeslice: Timeslice; +} + export const EXCLUDE_TOO_MANY_FEATURES_BOX = ['!=', ['get', KBN_TOO_MANY_FEATURES_PROPERTY], true]; const EXCLUDE_CENTROID_FEATURES = ['!=', ['get', KBN_IS_CENTROID_FEATURE], true]; -function getFilterExpression(geometryFilter: unknown[], hasJoins: boolean) { - const filters: unknown[] = [ - EXCLUDE_TOO_MANY_FEATURES_BOX, - EXCLUDE_CENTROID_FEATURES, - geometryFilter, - ]; +function getFilterExpression( + filters: unknown[], + hasJoins: boolean, + timesliceMaskConfig?: TimesliceMaskConfig +) { + const allFilters: unknown[] = [...filters]; if (hasJoins) { - filters.push(['==', ['get', FEATURE_VISIBLE_PROPERTY_NAME], true]); + allFilters.push(['==', ['get', FEATURE_VISIBLE_PROPERTY_NAME], true]); } - return ['all', ...filters]; + if (timesliceMaskConfig) { + allFilters.push(['has', timesliceMaskConfig.timesiceMaskField]); + allFilters.push([ + '>=', + ['get', timesliceMaskConfig.timesiceMaskField], + timesliceMaskConfig.timeslice.from, + ]); + allFilters.push([ + '<', + ['get', timesliceMaskConfig.timesiceMaskField], + timesliceMaskConfig.timeslice.to, + ]); + } + + return ['all', ...allFilters]; } -export function getFillFilterExpression(hasJoins: boolean): unknown[] { +export function getFillFilterExpression( + hasJoins: boolean, + timesliceMaskConfig?: TimesliceMaskConfig +): unknown[] { return getFilterExpression( [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], + EXCLUDE_TOO_MANY_FEATURES_BOX, + EXCLUDE_CENTROID_FEATURES, + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], + ], ], - hasJoins + hasJoins, + timesliceMaskConfig ); } -export function getLineFilterExpression(hasJoins: boolean): unknown[] { +export function getLineFilterExpression( + hasJoins: boolean, + timesliceMaskConfig?: TimesliceMaskConfig +): unknown[] { return getFilterExpression( [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], - ['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING], + EXCLUDE_TOO_MANY_FEATURES_BOX, + EXCLUDE_CENTROID_FEATURES, + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON], + ['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING], + ], ], - hasJoins + hasJoins, + timesliceMaskConfig ); } -export function getPointFilterExpression(hasJoins: boolean): unknown[] { +export function getPointFilterExpression( + hasJoins: boolean, + timesliceMaskConfig?: TimesliceMaskConfig +): unknown[] { return getFilterExpression( [ - 'any', - ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], - ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT], + EXCLUDE_TOO_MANY_FEATURES_BOX, + EXCLUDE_CENTROID_FEATURES, + [ + 'any', + ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], + ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT], + ], ], - hasJoins + hasJoins, + timesliceMaskConfig ); } -export function getCentroidFilterExpression(hasJoins: boolean): unknown[] { - const filters: unknown[] = [ - EXCLUDE_TOO_MANY_FEATURES_BOX, - ['==', ['get', KBN_IS_CENTROID_FEATURE], true], - ]; - - if (hasJoins) { - filters.push(['==', ['get', FEATURE_VISIBLE_PROPERTY_NAME], true]); - } - - return ['all', ...filters]; +export function getCentroidFilterExpression( + hasJoins: boolean, + timesliceMaskConfig?: TimesliceMaskConfig +): unknown[] { + return getFilterExpression( + [EXCLUDE_TOO_MANY_FEATURES_BOX, ['==', ['get', KBN_IS_CENTROID_FEATURE], true]], + hasJoins, + timesliceMaskConfig + ); } diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/index.ts index 4f94cbc7b7458..b9b4b184318f5 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/index.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/index.ts @@ -27,6 +27,7 @@ import { getMapSettings, getScrollZoom, getSpatialFiltersLayer, + getTimeslice, } from '../../selectors/map_selectors'; import { getDrawMode, getIsFullScreen } from '../../selectors/ui_selectors'; import { getInspectorAdapters } from '../../reducers/non_serializable_instances'; @@ -43,6 +44,7 @@ function mapStateToProps(state: MapStoreState) { inspectorAdapters: getInspectorAdapters(state), scrollZoom: getScrollZoom(state), isFullScreen: getIsFullScreen(state), + timeslice: getTimeslice(state), featureModeActive: getDrawMode(state) === DRAW_MODE.DRAW_SHAPES || getDrawMode(state) === DRAW_MODE.DRAW_POINTS, filterModeActive: getDrawMode(state) === DRAW_MODE.DRAW_FILTERS, diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 96ff7b7dcf882..2ce4e2d98ce5f 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -25,7 +25,7 @@ import { getInitialView } from './get_initial_view'; import { getPreserveDrawingBuffer } from '../../kibana_services'; import { ILayer } from '../../classes/layers/layer'; import { MapSettings } from '../../reducers/map'; -import { Goto, MapCenterAndZoom } from '../../../common/descriptor_types'; +import { Goto, MapCenterAndZoom, Timeslice } from '../../../common/descriptor_types'; import { DECIMAL_DEGREES_PRECISION, KBN_TOO_MANY_FEATURES_IMAGE_ID, @@ -68,13 +68,12 @@ export interface Props { onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void; renderTooltipContent?: RenderToolTipContent; setAreTilesLoaded: (layerId: string, areTilesLoaded: boolean) => void; + timeslice?: Timeslice; featureModeActive: boolean; filterModeActive: boolean; } interface State { - prevLayerList: ILayer[] | undefined; - hasSyncedLayerList: boolean; mbMap: MapboxMap | undefined; } @@ -83,38 +82,23 @@ export class MBMap extends Component { private _isMounted: boolean = false; private _containerRef: HTMLDivElement | null = null; private _prevDisableInteractive?: boolean; + private _prevLayerList?: ILayer[]; + private _prevTimeslice?: Timeslice; private _navigationControl = new mapboxgl.NavigationControl({ showCompass: false }); private _tileStatusTracker?: TileStatusTracker; state: State = { - prevLayerList: undefined, - hasSyncedLayerList: false, mbMap: undefined, }; - static getDerivedStateFromProps(nextProps: Props, prevState: State) { - const nextLayerList = nextProps.layerList; - if (nextLayerList !== prevState.prevLayerList) { - return { - prevLayerList: nextLayerList, - hasSyncedLayerList: false, - }; - } - - return null; - } - componentDidMount() { this._initializeMap(); this._isMounted = true; } componentDidUpdate() { - if (this.state.mbMap) { - // do not debounce syncing of map-state - this._syncMbMapWithMapState(); - this._debouncedSync(); - } + this._syncMbMapWithMapState(); // do not debounce syncing of map-state + this._debouncedSync(); } componentWillUnmount() { @@ -134,16 +118,13 @@ export class MBMap extends Component { _debouncedSync = _.debounce(() => { if (this._isMounted && this.props.isMapReady && this.state.mbMap) { - if (!this.state.hasSyncedLayerList) { - this.setState( - { - hasSyncedLayerList: true, - }, - () => { - this._syncMbMapWithLayerList(); - this._syncMbMapWithInspector(); - } - ); + const hasLayerListChanged = this._prevLayerList !== this.props.layerList; // Comparing re-select memoized instance so no deep equals needed + const hasTimesliceChanged = !_.isEqual(this._prevTimeslice, this.props.timeslice); + if (hasLayerListChanged || hasTimesliceChanged) { + this._prevLayerList = this.props.layerList; + this._prevTimeslice = this.props.timeslice; + this._syncMbMapWithLayerList(); + this._syncMbMapWithInspector(); } this.props.spatialFiltersLayer.syncLayerWithMB(this.state.mbMap); this._syncSettings(); @@ -346,7 +327,9 @@ export class MBMap extends Component { this.props.layerList, this.props.spatialFiltersLayer ); - this.props.layerList.forEach((layer) => layer.syncLayerWithMB(this.state.mbMap!)); + this.props.layerList.forEach((layer) => + layer.syncLayerWithMB(this.state.mbMap!, this.props.timeslice) + ); syncLayerOrder(this.state.mbMap, this.props.spatialFiltersLayer, this.props.layerList); }; diff --git a/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js b/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js index fb0fdcf333cf2..3479f292374d2 100644 --- a/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js +++ b/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js @@ -21,12 +21,12 @@ export default function ({ getPageObjects, getService }) { await security.testUser.restoreDefaults(); }); - it('should only fetch geo_point field and nothing else when source does not have data driven styling', async () => { + it('should only fetch geo_point field and time field and nothing else when source does not have data driven styling', async () => { await PageObjects.maps.loadSavedMap('document example'); const { rawResponse: response } = await PageObjects.maps.getResponse(); const firstHit = response.hits.hits[0]; expect(firstHit).to.only.have.keys(['_id', '_index', '_score', 'fields']); - expect(firstHit.fields).to.only.have.keys(['geo.coordinates']); + expect(firstHit.fields).to.only.have.keys(['@timestamp', 'geo.coordinates']); }); it('should only fetch geo_point field and data driven styling fields', async () => { @@ -34,7 +34,12 @@ export default function ({ getPageObjects, getService }) { const { rawResponse: response } = await PageObjects.maps.getResponse(); const firstHit = response.hits.hits[0]; expect(firstHit).to.only.have.keys(['_id', '_index', '_score', 'fields']); - expect(firstHit.fields).to.only.have.keys(['bytes', 'geo.coordinates', 'hour_of_day']); + expect(firstHit.fields).to.only.have.keys([ + '@timestamp', + 'bytes', + 'geo.coordinates', + 'hour_of_day', + ]); }); it('should format date fields as epoch_millis when data driven styling is applied to a date field', async () => {