Skip to content

Commit

Permalink
[Maps] add initial location option that fits to data bounds (#74583)
Browse files Browse the repository at this point in the history
* [Maps] add initial location option that fits to data bounds

* update navigation_panel snapshot

* add functional test to ensure sync is called when auto fit to bounds with no data

* add functional test for auto fit to bounds on map load

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
nreese and elasticmachine authored Aug 11, 2020
1 parent 0ef17f9 commit 75b8a3c
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 29 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
34 changes: 32 additions & 2 deletions x-pack/plugins/maps/public/actions/data_request_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -133,7 +134,7 @@ export function syncDataForAllLayers() {
};
}

export function syncDataForAllJoinLayers() {
function syncDataForAllJoinLayers() {
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
const syncPromises = getLayerList(getState())
.filter((layer) => {
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -365,6 +366,9 @@ export function fitToDataBounds() {
}

if (!corners.length) {
if (onNoBounds) {
onNoBounds();
}
return;
}

Expand All @@ -374,6 +378,32 @@ export function fitToDataBounds() {
};
}

let lastSetQueryCallId: string = '';
export function autoFitToBounds() {
return async (dispatch: Dispatch) => {
// Method can be triggered before async actions complete
// Use localSetQueryCallId to only continue execution path if method has not been re-triggered.
const localSetQueryCallId = uuid();
lastSetQueryCallId = localSetQueryCallId;

// 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.
await dispatch<any>(syncDataForAllJoinLayers());

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<any>(syncDataForAllLayers());
}
}
dispatch<any>(fitToDataBounds(onNoBounds));
}
};
}

function setGotoWithBounds(bounds: MapExtent) {
return {
type: SET_GOTO,
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/maps/public/actions/layer_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
40 changes: 16 additions & 24 deletions x-pack/plugins/maps/public/actions/map_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -44,19 +43,16 @@ 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,
MapCenterAndZoom,
MapExtent,
MapRefreshConfig,
} from '../../common/descriptor_types';
import { INITIAL_LOCATION } from '../../common/constants';
import { scaleBounds } from '../elasticsearch_geo_utils';

export function setMapInitError(errorMessage: string) {
Expand Down Expand Up @@ -98,13 +94,21 @@ export function mapReady() {
type: MAP_READY,
});

getWaitingForMapReadyLayerListRaw(getState()).forEach((layerDescriptor) => {
dispatch<any>(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<any>(addLayerWithoutDataSync(layerDescriptor));
});
dispatch<any>(autoFitToBounds());
} else {
waitingForMapReadyLayerList.forEach((layerDescriptor) => {
dispatch<any>(addLayer(layerDescriptor));
});
}
};
}

Expand Down Expand Up @@ -196,7 +200,6 @@ function generateQueryTimestamp() {
return new Date().toISOString();
}

let lastSetQueryCallId: string = '';
export function setQuery({
query,
timeFilters,
Expand Down Expand Up @@ -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<any>(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<any>(fitToDataBounds());
}
dispatch<any>(autoFitToBounds());
} else {
await dispatch<any>(syncDataForAllLayers());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down Expand Up @@ -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;
}

Expand Down
27 changes: 27 additions & 0 deletions x-pack/test/functional/apps/maps/auto_fit_to_bounds.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ export default function ({ getPageObjects }) {
const PageObjects = getPageObjects(['maps']);

describe('auto fit map to bounds', () => {
describe('initial location', () => {
before(async () => {
await PageObjects.maps.loadSavedMap(
'document example - auto fit to bounds for initial location'
);
});

it('should automatically fit to bounds on initial map load', async () => {
const hits = await PageObjects.maps.getHits();
expect(hits).to.equal('6');

const { lat, lon } = await PageObjects.maps.getView();
expect(Math.round(lat)).to.equal(41);
expect(Math.round(lon)).to.equal(-99);
});
});

describe('without joins', () => {
before(async () => {
await PageObjects.maps.loadSavedMap('document example');
Expand All @@ -25,10 +42,20 @@ export default function ({ getPageObjects }) {
await PageObjects.maps.setAndSubmitQuery('machine.os.raw : "ios"');
await PageObjects.maps.waitForMapPanAndZoom(origView);

const hits = await PageObjects.maps.getHits();
expect(hits).to.equal('2');

const { lat, lon } = await PageObjects.maps.getView();
expect(Math.round(lat)).to.equal(43);
expect(Math.round(lon)).to.equal(-102);
});

it('should sync layers even when there is not data', async () => {
await PageObjects.maps.setAndSubmitQuery('machine.os.raw : "fake_os_with_no_matches"');

const hits = await PageObjects.maps.getHits();
expect(hits).to.equal('0');
});
});

describe('with joins', () => {
Expand Down
29 changes: 29 additions & 0 deletions x-pack/test/functional/es_archives/maps/kibana/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,35 @@
}
}

{
"type": "doc",
"value": {
"id": "map:13776f20-db37-11ea-8fbb-3da39bb9bff2",
"index": ".kibana",
"source": {
"map" : {
"title" : "document example - auto fit to bounds for initial location",
"description" : "",
"mapStateJSON" : "{\"zoom\":5.2,\"center\":{\"lon\":-67.80052,\"lat\":-55.25331},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"AUTO_FIT_TO_BOUNDS\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
"layerListJSON" : "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
"uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
},
"type" : "map",
"references" : [
{
"name" : "layer_1_source_index_pattern",
"type" : "index-pattern",
"id" : "c698b940-e149-11e8-a35a-370a8516603a"
}
],
"migrationVersion" : {
"map" : "7.9.0"
},
"updated_at" : "2020-08-10T18:27:39.805Z"
}
}
}

{
"type": "doc",
"value": {
Expand Down

0 comments on commit 75b8a3c

Please sign in to comment.