Skip to content

Commit 4064803

Browse files
authored
Fix #2615. Avoid widgets clear while saving (#2649)
1 parent b4e1dfc commit 4064803

7 files changed

Lines changed: 181 additions & 25 deletions

File tree

web/client/actions/maps.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const MAP_ERROR = 'MAP_ERROR';
3535
const SAVE_ALL = 'SAVE_ALL';
3636
const DISPLAY_METADATA_EDIT = 'DISPLAY_METADATA_EDIT';
3737
const RESET_UPDATING = 'RESET_UPDATING';
38+
const SAVING_MAP = 'SAVING_MAP';
3839
const SAVE_MAP = 'SAVE_MAP';
3940
const PERMISSIONS_LIST_LOADING = 'PERMISSIONS_LIST_LOADING';
4041
const PERMISSIONS_LIST_LOADED = 'PERMISSIONS_LIST_LOADED';
@@ -325,9 +326,21 @@ function saveMap(map, resourceId) {
325326
map
326327
};
327328
}
329+
/**
330+
* Performed before start saving a new map
331+
* @memberof actions.maps
332+
* @param {object} metadata
333+
* @return {action} type SAVING_MAP action
334+
*/
335+
function savingMap(metadata) {
336+
return {
337+
type: SAVING_MAP,
338+
metadata
339+
};
340+
}
328341

329342
/**
330-
* performed when want to disaplay/hide the metadata editing window
343+
* performed when want to display/hide the metadata editing window
331344
* @memberof actions.maps
332345
* @param {boolean} displayMetadataEditValue true to display, false to hide
333346
* @return {action} type `DISPLAY_METADATA_EDIT`, with the arguments as they are named
@@ -591,12 +604,12 @@ function createThumbnail(map, metadataMap, nameThumbnail, dataThumbnail, categor
591604
}
592605

593606
/**
594-
* Save all the metadata and thubnail, if needed.
607+
* Save all the metadata and thumbnail, if needed.
595608
* @memberof actions.maps
596609
* @param {object} map the map object
597610
* @param {object} metadataMap metadata for the map
598-
* @param {string} nameThumbnail the name for the thubnail
599-
* @param {string} dataThumbnail the data to save for the thubnail
611+
* @param {string} nameThumbnail the name for the thumbnail
612+
* @param {string} dataThumbnail the data to save for the thumbnail
600613
* @param {string} categoryThumbnail the category for the thumbnails
601614
* @param {number} resourceIdMap the id of the map
602615
* @param {object} [options] options for the request
@@ -673,6 +686,7 @@ function deleteThumbnail(resourceId, resourceIdMap, options, reset) {
673686
*/
674687
function createMap(metadata, content, thumbnail, options) {
675688
return (dispatch) => {
689+
dispatch(savingMap(metadata));
676690
GeoStoreApi.createResource(metadata, content, "MAP", options).then((response) => {
677691
let resourceId = response.data;
678692
if (thumbnail && thumbnail.data) {
@@ -898,6 +912,7 @@ module.exports = {
898912
ATTRIBUTE_UPDATED,
899913
PERMISSIONS_UPDATED,
900914
SAVE_MAP,
915+
SAVING_MAP,
901916
THUMBNAIL_ERROR,
902917
PERMISSIONS_LIST_LOADING,
903918
PERMISSIONS_LIST_LOADED,
@@ -944,6 +959,7 @@ module.exports = {
944959
permissionsLoading,
945960
permissionsLoaded,
946961
attributeUpdated,
962+
savingMap,
947963
saveMap,
948964
thumbnailError,
949965
createMap,

web/client/components/widgets/enhancers/__tests__/builderConfiguration-test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe('widgets builderConfiguration enhancer', () => {
4141
setTimeout(() => {
4242
expect(document.querySelector('.empty-state-container')).toExist();
4343
done();
44-
}, 100);
44+
}, 20);
4545

4646
}
4747
};

web/client/components/widgets/enhancers/wfsTable/__tests__/wfsTable-test.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const rxjsConfig = require('recompose/rxjsObservableConfig').default;
1616
setObservableConfig(rxjsConfig);
1717

1818

19-
describe('index enhancer', () => {
19+
describe('wfsTable enhancer', () => {
2020
beforeEach((done) => {
2121
document.body.innerHTML = '<div id="container"></div>';
2222
setTimeout(done);
@@ -60,7 +60,6 @@ describe('index enhancer', () => {
6060
} else if (props.pages && props.features.length > 0 && props.pages[0] === 40) {
6161
expect(props.pages[1]).toBe(60);
6262
done();
63-
6463
}
6564
}));
6665
ReactDOM.render(<Sink virtualScroll layer={{

web/client/epics/__tests__/epicTestUtils.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
const Rx = require('rxjs');
3+
const { isFunction } = require('lodash');
34
const { ActionsObservable, combineEpics } = require('redux-observable');
45
const TEST_TIMEOUT = "EPICTEST:TIMEOUT";
56
module.exports = {
@@ -9,12 +10,12 @@ module.exports = {
910
* @param {number} count the number of actions to wait (note, the stream)
1011
* @param {object|object[]} action the action(s) to trigger
1112
* @param {function} callback The check function, called after `count` actions received
12-
* @param {Object} [state={}] the state
13+
* @param {Object|function} [state={}] the state or a function that return it
1314
*/
1415
testEpic: (epic, count, action, callback, state = {}) => {
1516
const actions = new Rx.Subject();
1617
const actions$ = new ActionsObservable(actions);
17-
const store = { getState: () => state };
18+
const store = { getState: () => isFunction(state) ? state() : state};
1819
epic(actions$, store)
1920
.take(count)
2021
.toArray()
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2018, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
var expect = require('expect');
9+
const { testEpic, addTimeoutEpic, TEST_TIMEOUT } = require('./epicTestUtils');
10+
11+
const {
12+
clearWidgetsOnLocationChange
13+
} = require('../widgets');
14+
const {
15+
CLEAR_WIDGETS
16+
} = require('../../actions/widgets');
17+
const {
18+
savingMap,
19+
mapCreated
20+
} = require('../../actions/maps');
21+
const {
22+
configureMap
23+
} = require('../../actions/config');
24+
const { LOCATION_CHANGE } = require('react-router-redux');
25+
26+
describe('widgets Epics', () => {
27+
it('clearWidgetsOnLocationChange triggers CLEAR_WIDGETS on LOCATION_CHANGE', (done) => {
28+
const checkActions = actions => {
29+
expect(actions.length).toBe(1);
30+
const action = actions[0];
31+
expect(action.type).toBe(CLEAR_WIDGETS);
32+
done();
33+
};
34+
let count = 0;
35+
testEpic(clearWidgetsOnLocationChange,
36+
1,
37+
[configureMap(), { type: LOCATION_CHANGE, payload: {
38+
pathname: "newPath"
39+
}}],
40+
checkActions,
41+
() => {
42+
return count++
43+
? {
44+
routing: {
45+
location: "new"
46+
}
47+
}
48+
: {
49+
routing: {
50+
location: "old"
51+
}
52+
};
53+
});
54+
});
55+
it('clearWidgetsOnLocationChange stops CLEAR_WIDGETS triggers if saving', (done) => {
56+
const checkActions = actions => {
57+
expect(actions.length).toBe(1);
58+
const action = actions[0];
59+
expect(action.type).toBe(TEST_TIMEOUT);
60+
done();
61+
};
62+
let count = 0;
63+
testEpic(addTimeoutEpic(clearWidgetsOnLocationChange, 20),
64+
1,
65+
[
66+
configureMap(),
67+
savingMap(),
68+
{
69+
type: LOCATION_CHANGE,
70+
payload: {
71+
pathname: "newPath"
72+
}
73+
}
74+
],
75+
checkActions,
76+
() => {
77+
return count++
78+
? {
79+
routing: {
80+
location: "new"
81+
}
82+
}
83+
: {
84+
routing: {
85+
location: "old"
86+
}
87+
};
88+
});
89+
});
90+
it('clearWidgetsOnLocationChange restores CLEAR_WIDGETS triggers after save completed', (done) => {
91+
const checkActions = actions => {
92+
expect(actions.length).toBe(1);
93+
const action = actions[0];
94+
expect(action.type).toBe(CLEAR_WIDGETS);
95+
done();
96+
};
97+
let count = 0;
98+
testEpic(clearWidgetsOnLocationChange,
99+
1,
100+
[configureMap(),
101+
savingMap(),
102+
{
103+
type: LOCATION_CHANGE,
104+
payload: {
105+
pathname: "newPath"
106+
}
107+
},
108+
mapCreated(),
109+
{
110+
type: LOCATION_CHANGE, payload: {
111+
pathname: "newPath"
112+
}
113+
}],
114+
checkActions,
115+
() => {
116+
return count++
117+
? {
118+
routing: {
119+
location: "new"
120+
}
121+
}
122+
: {
123+
routing: {
124+
location: "old"
125+
}
126+
};
127+
});
128+
});
129+
});

web/client/epics/maps.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ const deleteMapAndAssociatedResourcesEpic = (action$, store) =>
181181
return Rx.Observable.forkJoin(
182182
// delete details
183183
deleteResourceById(thumbnailsId, options),
184-
// delete thumbanil
184+
// delete thumbnail
185185
deleteResourceById(detailsId, options),
186186
// delete map
187187
deleteResourceById(mapId, options)
@@ -255,19 +255,19 @@ const storeDetailsInfoEpic = (action$, store) =>
255255
return !mapId ?
256256
Rx.Observable.empty() :
257257
Rx.Observable.fromPromise(
258-
GeoStoreApi.getResourceAttribute(mapId, "details")
259-
.then(res => res.data).catch(() => {
260-
return null;
261-
})
262-
)
263-
.switchMap((details) => {
264-
if (!details) {
265-
return Rx.Observable.empty();
266-
}
267-
return Rx.Observable.of(
268-
detailsLoaded(mapId, details)
269-
);
270-
});
258+
GeoStoreApi.getResourceAttribute(mapId, "details")
259+
.then(res => res.data).catch(() => {
260+
return null;
261+
})
262+
)
263+
.switchMap((details) => {
264+
if (!details) {
265+
return Rx.Observable.empty();
266+
}
267+
return Rx.Observable.of(
268+
detailsLoaded(mapId, details)
269+
);
270+
});
271271
});
272272

273273

web/client/epics/widgets.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {EXPORT_CSV, EXPORT_IMAGE, clearWidgets} = require('../actions/widgets');
44
const {
55
MAP_CONFIG_LOADED
66
} = require('../actions/config');
7+
const { MAP_CREATED, SAVING_MAP, MAP_ERROR } = require('../actions/maps');
78
const {LOCATION_CHANGE} = require('react-router-redux');
89
const {saveAs} = require('file-saver');
910
const FileUtils = require('../utils/FileUtils');
@@ -15,6 +16,16 @@ const outerHTML = (node) => {
1516
parent.appendChild(node.cloneNode(true));
1617
return parent.innerHTML;
1718
};
19+
/**
20+
* Disables action emissions on the stream between SAVING_MAP and MAP_CREATED or MAP_ERROR events.
21+
* This is needed to avoid widget clear when LOCATION_CHANGE because of a map save.
22+
*/
23+
const getValidLocationChange = action$ =>
24+
action$.ofType(SAVING_MAP, MAP_CREATED, MAP_ERROR)
25+
.startWith({type: MAP_CONFIG_LOADED}) // just dummy action to trigger the first switchMap
26+
.switchMap(action => action.type === SAVING_MAP ? Rx.Observable.never() : action$)
27+
.filter(({type} = {}) => type === LOCATION_CHANGE);
28+
1829
module.exports = {
1930
exportWidgetData: action$ =>
2031
action$.ofType(EXPORT_CSV)
@@ -23,10 +34,10 @@ module.exports = {
2334
csv
2435
], {type: "text/csv"}), title + ".csv")))
2536
.filter( () => false),
26-
clearWidgetsOnlocationChange: (action$, {getState = () => {}} = {}) =>
37+
clearWidgetsOnLocationChange: (action$, {getState = () => {}} = {}) =>
2738
action$.ofType(MAP_CONFIG_LOADED).switchMap( () => {
2839
const location = get(getState(), "routing.location");
29-
return action$.ofType(LOCATION_CHANGE)
40+
return action$.let(getValidLocationChange)
3041
.filter( () => {
3142
const newLocation = get(getState(), "routing.location");
3243
return newLocation !== location;

0 commit comments

Comments
 (0)