From a9d4e5f37ce9c9ed67039f0ead390b89634e6fc4 Mon Sep 17 00:00:00 2001 From: stefano bovio Date: Wed, 7 Jun 2023 12:48:38 +0200 Subject: [PATCH] Fix #9208 Timeline map sync does not update for time ranges visualization (#9209) (#9211) * Fix #9208 Timeline map sync does not update for time ranges visualization * move the default expand limit value to selector --- web/client/epics/__tests__/timeline-test.js | 23 ++++++++++- web/client/epics/dimension.js | 13 ++----- web/client/epics/timeline.js | 6 ++- .../selectors/__tests__/timeline-test.js | 38 ++++++++++++++++++- web/client/selectors/timeline.js | 10 +++-- 5 files changed, 73 insertions(+), 17 deletions(-) diff --git a/web/client/epics/__tests__/timeline-test.js b/web/client/epics/__tests__/timeline-test.js index 15f90a40b6..a728eb830c 100644 --- a/web/client/epics/__tests__/timeline-test.js +++ b/web/client/epics/__tests__/timeline-test.js @@ -48,7 +48,8 @@ import { SET_SNAP_RADIO_BUTTON_ENABLED, AUTOSELECT, SELECT_TIME, - SET_TIME_LAYERS + SET_TIME_LAYERS, + setMapSync } from '../../actions/timeline'; import { changeLayerProperties, removeNode } from '../../actions/layers'; import { SET_CURRENT_TIME, SET_OFFSET_TIME, updateLayerDimensionData } from '../../actions/dimension'; @@ -1123,6 +1124,26 @@ describe('timeline Epics', () => { done(); }, MAPSYNC_OFF_STATE); }); + it('mapSync off via action should trigger update range', done => { + // tests with mapsync on take around 400 ms to run, so this have to be set with a bigger time + // TODO: use mock-axios or other tools to speed up these tests + testEpic(addTimeoutEpic(updateRangeDataOnRangeChange, 500), 4, setMapSync(false), ([action1, action2, action3, action4]) => { + const { type: startType } = action1; + const { type: range1Type, range } = action2; + const { type: range2Type } = action3; + const { type: endType } = action4; + // first action moves the current timeline view to center the current time + expect(startType).toBe(LOADING); + expect(endType).toBe(LOADING); + // in this case loading fixed file of domain values causes double trigger of range data loaded with domain + // in real world the 2nd response is histogram. TODO: test also histogram + expect(range1Type).toBe(RANGE_DATA_LOADED); + expect(range2Type).toBe(RANGE_DATA_LOADED); + expect(range.start).toBe("2000-01-01T00:00:00.000Z"); + expect(range.end).toBe("2001-12-31T00:00:00.000Z"); + done(); + }, MAPSYNC_OFF_STATE); + }); describe('fixed domain values', () => { const MAPSYNC_ON_DOMAIN_VALUES_STATE = set('dimension.data.time.TEST_LAYER.domain', "2000-03-01T00:00:00.000Z,2000-06-08T00:00:00.000Z" diff --git a/web/client/epics/dimension.js b/web/client/epics/dimension.js index cdedf67728..7d2698b19a 100644 --- a/web/client/epics/dimension.js +++ b/web/client/epics/dimension.js @@ -25,12 +25,7 @@ import { layersWithTimeDataSelector, offsetTimeSelector, currentTimeSelector } f import { describeDomains, getMultidimURL } from '../api/MultiDim'; import { domainsToDimensionsObject } from '../utils/TimeUtils'; import { pick, find, get, flatten } from 'lodash'; - - -const DESCRIBE_DOMAIN_OPTIONS = { - expandLimit: 10 // TODO: increase this limit to max client allowed -}; - +import { expandLimitSelector } from '../selectors/timeline'; const getTimeMultidimURL = (l = {}) => get(find(l.dimensions || [], d => d && d.source && d.source.type === "multidim-extension"), "source.url"); @@ -50,7 +45,7 @@ export const updateLayerDimensionOnCurrentTimeSelection = (action$, { getState = * Check the presence of Multidimensional API extension, then setup layers properly. * Updates also current dimension state */ -export const queryMultidimensionalAPIExtensionOnAddLayer = (action$) => +export const queryMultidimensionalAPIExtensionOnAddLayer = (action$, {getState = () => {}} = {}) => action$ .ofType(ADD_LAYER) .filter( @@ -61,7 +56,7 @@ export const queryMultidimensionalAPIExtensionOnAddLayer = (action$) => .map(({ layer = {} } = {}) => ({ layer, multidimURL: getMultidimURL(layer)})) // every add layer has it's own flow, this is why it uses .flatMap(({ layer = {}, multidimURL } = {}) => - describeDomains(multidimURL, layer.name, undefined, DESCRIBE_DOMAIN_OPTIONS) + describeDomains(multidimURL, layer.name, undefined, { expandLimit: expandLimitSelector(getState()) }) .switchMap( domains => { const dimensions = domainsToDimensionsObject(domains, multidimURL) || []; if (dimensions && dimensions.length > 0) { @@ -106,7 +101,7 @@ export const updateLayerDimensionDataOnMapLoad = (action$, {getState = () => {}} .concat(Observable.from(layersWithMultidim) // one flow for each dimension .mergeMap(l => - describeDomains(getTimeMultidimURL(l), l.name, undefined, DESCRIBE_DOMAIN_OPTIONS) + describeDomains(getTimeMultidimURL(l), l.name, undefined, { expandLimit: expandLimitSelector(getState()) }) .switchMap( domains => Observable.from(flatten(domainsToDimensionsObject(domains, getTimeMultidimURL(l)) .map(d => [ diff --git a/web/client/epics/timeline.js b/web/client/epics/timeline.js index 9628e9febf..adc4c9b6db 100644 --- a/web/client/epics/timeline.js +++ b/web/client/epics/timeline.js @@ -427,11 +427,13 @@ export const updateRangeDataOnRangeChange = (action$, { getState = () => { } } = ) .debounceTime(400) .merge(action$.ofType(UPDATE_LAYER_DIMENSION_DATA).debounceTime(50)) - .switchMap( () => { + .switchMap( (action) => { + // we should force the range data update when we turn off the map sync + const resetMapSync = action.mapSync === false; const timeData = timeDataSelector(getState()) || {}; const layerIds = Object.keys(timeData).filter(id => timeData[id] && timeData[id].domain // when data is already fully downloaded, no need to refresh, except if the mapSync is active - && (isTimeDomainInterval(timeData[id].domain)) || isMapSync(getState())); + && (isTimeDomainInterval(timeData[id].domain)) || isMapSync(getState()) || resetMapSync); // update range data for every layer that need to sync with histogram/domain return Rx.Observable.merge( ...layerIds.map(id => diff --git a/web/client/selectors/__tests__/timeline-test.js b/web/client/selectors/__tests__/timeline-test.js index c15365e56b..7255481933 100644 --- a/web/client/selectors/__tests__/timeline-test.js +++ b/web/client/selectors/__tests__/timeline-test.js @@ -19,7 +19,8 @@ import { multidimOptionsSelectorCreator, timelineLayersSelector, timelineLayersSetting, - timelineLayersParsedSettings + timelineLayersParsedSettings, + getTimeItems } from '../timeline'; import { set, compose } from '../../utils/ImmutableUtils'; @@ -169,7 +170,40 @@ describe('timeline selector', () => { expect(timelayer[0].id).toEqual(TEST_LAYER_ID); expect(timelayer[0].hideInTimeline).toEqual(true); }); - + it('getTimeItems should give priority to rangeData', () => { + const data = { + source: { + type: 'multidim-extension', + version: '1.2', + url: '/geoserver/gwc/service/wmts' + }, + name: 'time', + domain: '2022-06-01T00:00:00.000Z/2023-06-01T00:00:00.000Z,2023-01-01T00:00:00.000Z/2023-06-01T00:00:00.000Z,2023-01-01T00:00:00.000Z/2023-12-31T00:00:00.000Z' + }; + const range = { + start: '2022-06-01T00:00:00.000Z', + end: '2024-01-09T11:07:53.218Z' + }; + let timeItems = getTimeItems(data, range); + expect(timeItems.length).toBe(3); + const rangeData = { + range: { + start: '2022-06-01T00:00:00.000Z', + end: '2024-01-09T11:07:53.218Z' + }, + histogram: { + values: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + domain: '2022-06-01T00:00:00.000Z/2023-06-01T00:00:00.000Z/PT704H' + }, + domain: { + values: [ + '2022-06-01T00:00:00.000Z/2023-06-01T00:00:00.000Z' + ] + } + }; + timeItems = getTimeItems(data, range, rangeData); + expect(timeItems.length).toBe(1); + }); it('itemsSelector', () => { const histogramItems = itemsSelector(SAMPLE_STATE_HISTOGRAM); expect(histogramItems.length).toBe(31); diff --git a/web/client/selectors/timeline.js b/web/client/selectors/timeline.js index 79fb7d599e..be59926d67 100644 --- a/web/client/selectors/timeline.js +++ b/web/client/selectors/timeline.js @@ -30,7 +30,7 @@ export const rangeDataSelector = state => get(state, 'timeline.rangeData'); */ export const settingsSelector = state => get(state, 'timeline.settings'); const MAX_ITEMS = 50; -export const expandLimitSelector = state => get(settingsSelector(state), 'expandLimit'); +export const expandLimitSelector = state => get(settingsSelector(state), 'expandLimit') || 10; export const isCollapsed = state => get(settingsSelector(state), 'collapsed'); @@ -130,10 +130,14 @@ export const rangeDataToItems = (rangeData = {}, range) => { * @param {object} rangeData object that contains domain or histogram */ export const getTimeItems = (data = {}, range, rangeData) => { + // rangeData populates when some changes ara applied with map sync + // we should use this when available + // because represent the latest updated value + if (rangeData?.domain || rangeData?.histogram) { + return rangeDataToItems(rangeData, range); + } if (data && data.values || data && data.domain && !isTimeDomainInterval(data.domain)) { return valuesToItems(data.values || data.domain.split(','), range); - } else if (rangeData && rangeData.histogram) { - return rangeDataToItems(rangeData, range); } return []; };