Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Maps] add initial location option that fits to data bounds (#74583) #74776

Merged
merged 1 commit into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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