diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index cf67ac4dd999f..eec23f95bb17b 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -234,6 +234,7 @@ export enum INITIAL_LOCATION { LAST_SAVED_LOCATION = 'LAST_SAVED_LOCATION', FIXED_LOCATION = 'FIXED_LOCATION', BROWSER_LOCATION = 'BROWSER_LOCATION', + AUTO_FIT_TO_BOUNDS = 'AUTO_FIT_TO_BOUNDS', } export enum LAYER_WIZARD_CATEGORY { diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index 4c829f8e75c20..f6b8d212faf7b 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -7,6 +7,7 @@ import { Dispatch } from 'redux'; import bbox from '@turf/bbox'; +import uuid from 'uuid/v4'; import { multiPoint } from '@turf/helpers'; import { FeatureCollection } from 'geojson'; import { MapStoreState } from '../reducers/store'; @@ -133,7 +134,7 @@ export function syncDataForAllLayers() { }; } -export function syncDataForAllJoinLayers() { +function syncDataForAllJoinLayers() { return async (dispatch: Dispatch, getState: () => MapStoreState) => { const syncPromises = getLayerList(getState()) .filter((layer) => { @@ -318,7 +319,7 @@ export function fitToLayerExtent(layerId: string) { }; } -export function fitToDataBounds() { +export function fitToDataBounds(onNoBounds?: () => void) { return async (dispatch: Dispatch, getState: () => MapStoreState) => { const layerList = getFittableLayers(getState()); @@ -365,6 +366,9 @@ export function fitToDataBounds() { } if (!corners.length) { + if (onNoBounds) { + onNoBounds(); + } return; } @@ -374,6 +378,31 @@ export function fitToDataBounds() { }; } +let lastSetQueryCallId: string = ''; +export function autoFitToBounds() { + return async (dispatch: Dispatch) => { + // Joins are performed on the client. + // As a result, bounds for join layers must also be performed on the client. + // Therefore join layers need to fetch data prior to auto fitting bounds. + const localSetQueryCallId = uuid(); + lastSetQueryCallId = localSetQueryCallId; + await dispatch(syncDataForAllJoinLayers()); + + // autoFitToBounds can be triggered before async data fetching completes + // Only continue execution path if autoFitToBounds has not been re-triggered. + if (localSetQueryCallId === lastSetQueryCallId) { + // In cases where there are no bounds, such as no matching documents, fitToDataBounds does not trigger setGotoWithBounds. + // Ensure layer syncing occurs when setGotoWithBounds is not triggered. + function onNoBounds() { + if (localSetQueryCallId === lastSetQueryCallId) { + dispatch(syncDataForAllLayers()); + } + } + dispatch(fitToDataBounds(onNoBounds)); + } + }; +} + function setGotoWithBounds(bounds: MapExtent) { return { type: SET_GOTO, diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index 208f6dc6c6f85..472e42129816b 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -125,8 +125,6 @@ export function addLayer(layerDescriptor: LayerDescriptor) { }; } -// Do not use when rendering a map. Method exists to enable selectors for getLayerList when -// rendering is not needed. export function addLayerWithoutDataSync(layerDescriptor: LayerDescriptor) { return { type: ADD_LAYER, diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index 7191fb312b211..08826276c12ad 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -6,7 +6,6 @@ import { Dispatch } from 'redux'; import turfBboxPolygon from '@turf/bbox-polygon'; import turfBooleanContains from '@turf/boolean-contains'; -import uuid from 'uuid/v4'; import { Filter, Query, TimeRange } from 'src/plugins/data/public'; import { MapStoreState } from '../reducers/store'; @@ -44,12 +43,8 @@ import { UPDATE_DRAW_STATE, UPDATE_MAP_SETTING, } from './map_action_constants'; -import { - fitToDataBounds, - syncDataForAllJoinLayers, - syncDataForAllLayers, -} from './data_request_actions'; -import { addLayer } from './layer_actions'; +import { autoFitToBounds, syncDataForAllLayers } from './data_request_actions'; +import { addLayer, addLayerWithoutDataSync } from './layer_actions'; import { MapSettings } from '../reducers/map'; import { DrawState, @@ -57,6 +52,7 @@ import { MapExtent, MapRefreshConfig, } from '../../common/descriptor_types'; +import { INITIAL_LOCATION } from '../../common/constants'; import { scaleBounds } from '../elasticsearch_geo_utils'; export function setMapInitError(errorMessage: string) { @@ -98,13 +94,21 @@ export function mapReady() { type: MAP_READY, }); - getWaitingForMapReadyLayerListRaw(getState()).forEach((layerDescriptor) => { - dispatch(addLayer(layerDescriptor)); - }); - + const waitingForMapReadyLayerList = getWaitingForMapReadyLayerListRaw(getState()); dispatch({ type: CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST, }); + + if (getMapSettings(getState()).initialLocation === INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS) { + waitingForMapReadyLayerList.forEach((layerDescriptor) => { + dispatch(addLayerWithoutDataSync(layerDescriptor)); + }); + dispatch(autoFitToBounds()); + } else { + waitingForMapReadyLayerList.forEach((layerDescriptor) => { + dispatch(addLayer(layerDescriptor)); + }); + } }; } @@ -196,7 +200,6 @@ function generateQueryTimestamp() { return new Date().toISOString(); } -let lastSetQueryCallId: string = ''; export function setQuery({ query, timeFilters, @@ -227,18 +230,7 @@ export function setQuery({ }); if (getMapSettings(getState()).autoFitToDataBounds) { - // Joins are performed on the client. - // As a result, bounds for join layers must also be performed on the client. - // Therefore join layers need to fetch data prior to auto fitting bounds. - const localSetQueryCallId = uuid(); - lastSetQueryCallId = localSetQueryCallId; - await dispatch(syncDataForAllJoinLayers()); - - // setQuery can be triggered before async data fetching completes - // Only continue execution path if setQuery has not been re-triggered. - if (localSetQueryCallId === lastSetQueryCallId) { - dispatch(fitToDataBounds()); - } + dispatch(autoFitToBounds()); } else { await dispatch(syncDataForAllLayers()); } diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts b/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts index b9d446d390ffb..20fb8186f9870 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts +++ b/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts @@ -41,5 +41,10 @@ export async function getInitialView( }); } + if (settings.initialLocation === INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS) { + // map bounds pulled from data sources. Just use default map location + return null; + } + return goto && goto.center ? goto.center : null; } diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.tsx index 428a50e03515d..161c0c3576f8f 100644 --- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/navigation_panel.tsx @@ -41,6 +41,12 @@ const initialLocationOptions = [ defaultMessage: 'Map location at save', }), }, + { + id: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, + label: i18n.translate('xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel', { + defaultMessage: 'Auto fit map to data bounds', + }), + }, { id: INITIAL_LOCATION.FIXED_LOCATION, label: i18n.translate('xpack.maps.mapSettingsPanel.fixedLocationLabel', { @@ -125,7 +131,10 @@ export function NavigationPanel({ center, settings, updateMapSetting, zoom }: Pr }; function renderInitialLocationInputs() { - if (settings.initialLocation === INITIAL_LOCATION.LAST_SAVED_LOCATION) { + if ( + settings.initialLocation === INITIAL_LOCATION.LAST_SAVED_LOCATION || + settings.initialLocation === INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS + ) { return null; }