Skip to content

Commit

Permalink
save settings to session like mapSave, able to remove specific session
Browse files Browse the repository at this point in the history
  • Loading branch information
rowheat02 committed Dec 3, 2024
1 parent f5ec4f3 commit 435be1b
Show file tree
Hide file tree
Showing 24 changed files with 968 additions and 77 deletions.
13 changes: 11 additions & 2 deletions web/client/actions/__tests__/usersession-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {SAVE_USER_SESSION, USER_SESSION_SAVED, LOAD_USER_SESSION, USER_SESSION_L
REMOVE_USER_SESSION, USER_SESSION_REMOVED, SAVE_MAP_CONFIG, USER_SESSION_START_SAVING, USER_SESSION_STOP_SAVING,
SET_USER_SESSION,
saveUserSession, userSessionSaved, loadUserSession, userSessionLoaded, loading, setUserSession,
removeUserSession, userSessionRemoved, saveMapConfig, userSessionStartSaving, userSessionStopSaving} from "../usersession";
removeUserSession, userSessionRemoved, saveMapConfig, userSessionStartSaving, userSessionStopSaving,
setCheckedSessionToClear,
SET_CHECKED_SESSION_TO_CLEAR} from "../usersession";

describe('Test correctness of the usersession actions', () => {

Expand Down Expand Up @@ -51,8 +53,9 @@ describe('Test correctness of the usersession actions', () => {
expect(action.type).toBe(REMOVE_USER_SESSION);
});
it('user session removed', () => {
const action = userSessionRemoved();
const action = userSessionRemoved({map: { zoom: 20}});
expect(action.type).toBe(USER_SESSION_REMOVED);
expect(action.newSession).toExist();
});
it('user session start saving', () => {
const action = userSessionStartSaving();
Expand All @@ -75,4 +78,10 @@ describe('Test correctness of the usersession actions', () => {
expect(action.type).toBe(SAVE_MAP_CONFIG);
expect(action.config).toExist();
});
it("set Checked session to remove", () => {
const action = setCheckedSessionToClear(["map_pos"]);
expect(action.type).toBe(SET_CHECKED_SESSION_TO_CLEAR);
expect(action.checks).toExist();
expect(action.checks.length).toBe(1);
});
});
4 changes: 3 additions & 1 deletion web/client/actions/usersession.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ export const USER_SESSION_START_SAVING = "USER_SESSION:START_SAVING";
export const USER_SESSION_STOP_SAVING = "USER_SESSION:STOP_SAVING";
export const SET_USER_SESSION = "USER_SESSION:SET";
export const ENABLE_AUTO_SAVE = "USER_SESSION:ENABLE_AUTO_SAVE";
export const SET_CHECKED_SESSION_TO_CLEAR = "USER_SESSION:SET_CHECKED_SESSION_TO_CLEAR";

export const saveUserSession = () => ({type: SAVE_USER_SESSION});
export const userSessionSaved = (id, session) => ({type: USER_SESSION_SAVED, id, session});
export const loadUserSession = (name = "") => ({type: LOAD_USER_SESSION, name});
export const userSessionLoaded = (id, session) => ({type: USER_SESSION_LOADED, id, session});
export const removeUserSession = () => ({type: REMOVE_USER_SESSION});
export const userSessionRemoved = () => ({type: USER_SESSION_REMOVED});
export const userSessionRemoved = (newSession) => ({type: USER_SESSION_REMOVED, newSession});
export const userSessionStartSaving = () => ({type: USER_SESSION_START_SAVING});
export const userSessionStopSaving = () => ({type: USER_SESSION_STOP_SAVING});
export const saveMapConfig = (config) => ({type: SAVE_MAP_CONFIG, config});
export const setUserSession = (session) => ({type: SET_USER_SESSION, session});
export const setCheckedSessionToClear = (checks) => ({type: SET_CHECKED_SESSION_TO_CLEAR, checks});
/**
* Action to enable/disable the auto-save functionality.
* @param {boolean} enabled flag to enable/disable the auto-save for session
Expand Down
60 changes: 58 additions & 2 deletions web/client/epics/__tests__/usersession-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,67 @@ describe('usersession Epics', () => {
testEpic(removeUserSessionEpicCreator(idSelector), 6, removeUserSession(), (actions) => {
expect(actions[0].type).toBe(USER_SESSION_LOADING);
expect(actions[1].type).toBe(USER_SESSION_REMOVED);
expect(actions[1].id).toBeFalsy();
expect(actions[1].session).toBeFalsy();
expect(actions[1].newSession).toBeTruthy();
}, initialState, done);
});

it("user Session Update on Partial Session Remove", (done) => {
const states = {
...initialState,
map: {
present: {
center: {
x: 118.91601562499996,
y: 42.617791432823395,
crs: 'EPSG:4326'
},
zoom: 16
}
},
layers: [{id: "layer1", group: 'background'}, {id: "layer2"}, {id: "layer3]"}],
toc: {test: false},
usersession: {
checkedSessionToClear: ['background_layers']
}
};

// remove background layers
testEpic(removeUserSessionEpicCreator(idSelector), 6, removeUserSession(), (actions) => {
// only background layers are removed
expect(actions[1].newSession.map.zoom).toBe(16);
expect(actions[1].newSession.map.center).toEqual({
x: 118.91601562499996,
y: 42.617791432823395,
crs: 'EPSG:4326'
});
expect(actions[1].newSession.map.layers.some(l=> l.group === 'background')).toBe(false);
}, states, done);


// remove annotation layers
testEpic(removeUserSessionEpicCreator(idSelector), 6, removeUserSession(), (actions) => {
expect(actions[1].newSession.map.layers.some(l=> l.id === 'annotations')).toBe(false);
}, {
...states,
usersession: {
checkedSessionToClear: ['annotations_layer']
}
}, done);


// remove map positions
testEpic(removeUserSessionEpicCreator(idSelector), 6, removeUserSession(), (actions) => {
expect(actions[1].newSession.map.zoom).toBeFalsy();
expect(actions[1].newSession.map.center).toBeFalsy();
}, {
...states,
usersession: {
checkedSessionToClear: ['map_pos']
}
}, done);

});

it('CLOSE_FEATURE_GRID and TEXT_SEARCH_RESET actions are triggered', (done) => {
testEpic(removeUserSessionEpicCreator(idSelector), 6, removeUserSession(), (actions) => {
expect(actions[2].type).toBe(CLOSE_FEATURE_GRID);
Expand Down
14 changes: 6 additions & 8 deletions web/client/epics/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import { Observable } from 'rxjs';
import axios from '../libs/ajax';
import { get, merge, isNaN, find, head } from 'lodash';
import { get, isNaN, find, head } from 'lodash';
import {
LOAD_NEW_MAP,
LOAD_MAP_CONFIG,
Expand Down Expand Up @@ -48,10 +48,12 @@ import {
import { getSupportedFormat } from '../api/WMS';
import { wrapStartStop } from '../observables/epics';
import { error } from '../actions/notifications';
import { applyOverrides } from '../utils/ConfigUtils';


const prepareMapConfiguration = (data, override, state) => {
const queryParamsMap = getRequestParameterValue('map', state);
let mapConfig = merge({}, data, override);
let mapConfig = applyOverrides(data, override);
mapConfig = {
...mapConfig,
...(queryParamsMap ?? {}),
Expand Down Expand Up @@ -104,7 +106,7 @@ const mapFlowWithOverride = (configName, mapId, config, mapInfo, state, override
const isNumberId = !isNaN(parseFloat(mapId));
return (
config ?
Observable.of({data: merge({}, config, overrideConfig), staticConfig: true}).delay(100) :
Observable.of({data: applyOverrides(config, overrideConfig ), staticConfig: true}).delay(100) :
Observable.defer(() => axios.get(configName)))
.switchMap(response => {
// added !config in order to avoid showing login modal when a new.json mapConfig is used in a public context
Expand Down Expand Up @@ -161,12 +163,8 @@ export const loadMapConfigAndConfigureMap = (action$, store) =>
const userName = userSelector(store.getState())?.name;
return Observable.of(loadUserSession(buildSessionName(null, mapId, userName))).merge(
action$.ofType(USER_SESSION_LOADED).switchMap(({session}) => {
const sessionData = {
...(session?.map && {map: session.map}),
...(session?.featureGrid && {featureGrid: session.featureGrid})
};
return Observable.merge(
mapFlowWithOverride(configName, mapId, config, mapInfo, store.getState(), sessionData),
mapFlowWithOverride(configName, mapId, config, mapInfo, store.getState(), session),
Observable.of(userSessionStartSaving())
);
})
Expand Down
5 changes: 2 additions & 3 deletions web/client/epics/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,10 @@ const createSessionFlow = (mapId, contextName, resourceCategory, action$, getSta
(mapId ? Observable.of(null) : getResourceDataByName(resourceCategory, contextName))
).flatMap(([id, data]) => {
const userName = userSelector(getState())?.name;
return Observable.of(loadUserSession(buildSessionName(id, mapId, userName))).merge(
return Observable.of(loadUserSession(buildSessionName(id, mapId, userName))).delay(2000).merge(
action$.ofType(USER_SESSION_LOADED).take(1).switchMap(({session}) => {
const sessionData = {
...(session?.map && {map: session.map}),
...(session?.featureGrid && {featureGrid: session.featureGrid})
...session
};
const contextSession = session?.context && {
...session.context
Expand Down
7 changes: 5 additions & 2 deletions web/client/epics/maptemplates.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ export const setAllowedTemplatesEpic = (action$, store) => action$
[attr.name]: attr.value
}), {});
};

// Since templates comes from api, override with session attributes
const sessionMapTemplates = store?.getState().usersession?.session?.mapTemplates;
return templates.length > 0
? Observable
.defer(() => Api.searchListByAttributes(makeFilter(), {params: { includeAttributes: true }}, '/resources/search/list'))
Expand All @@ -84,7 +85,9 @@ export const setAllowedTemplatesEpic = (action$, store) => action$
...pick(resource, 'id', 'name', 'description'),
...extractAttributes(resource),
dataLoaded: false,
loading: false
loading: false,
// override properties from userSession if any
...sessionMapTemplates?.find(template => template.id === resource.id)
}));
return Observable.of(setTemplates(newTemplates), setMapTemplatesLoaded(true));
})
Expand Down
60 changes: 52 additions & 8 deletions web/client/epics/usersession.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,17 @@ import {LOGOUT} from '../actions/security';
import {userSelector} from '../selectors/security';
import { wrapStartStop } from '../observables/epics';
import {originalConfigSelector, userSessionNameSelector, userSessionIdSelector,
userSessionSaveFrequencySelector, userSessionToSaveSelector, isAutoSaveEnabled} from "../selectors/usersession";
userSessionSaveFrequencySelector, userSessionToSaveSelector, isAutoSaveEnabled,
checkedSessionToClear} from "../selectors/usersession";
import { REDUCERS_LOADED } from '../actions/storemanager';
import { setSearchBookmarkConfig } from '../actions/searchbookmarkconfig';
import { onInitPlayback } from '../actions/playback';
import { setSearchConfigProp } from '../actions/searchconfig';
import { updateOverrideConfigToClean } from '../utils/ConfigUtils';
import { setTemplates } from '../actions/maptemplates';
import { getRegisterHandlers } from '../selectors/mapsave';

const {getSession, writeSession, removeSession} = UserSession;
const {getSession, writeSession} = UserSession;

const saveUserSessionErrorStatusToMessage = (status) => {
switch (status) {
Expand Down Expand Up @@ -133,14 +141,21 @@ export const loadUserSessionEpicCreator = (nameSelector = userSessionNameSelecto
* In order to clean up all plugins state as and where expected,
* closeFeatureGrid and resetSearch actions are included in the stream
*/
export const removeUserSessionEpicCreator = (idSelector = userSessionIdSelector) => (action$, store) =>
export const removeUserSessionEpicCreator = (idSelector = userSessionIdSelector, nameSelector = userSessionNameSelector) => (action$, store) =>
action$.ofType(REMOVE_USER_SESSION).switchMap(() => {
const state = store.getState();
const sessionId = idSelector(state);

return removeSession(sessionId).switchMap(() => Rx.Observable.of(userSessionRemoved(), closeFeatureGrid(), resetSearch(), success({
const checks = checkedSessionToClear(store.getState());
const id = idSelector(state);
const name = nameSelector(state);
const userName = userSelector(state)?.name;
const mapConfig = originalConfigSelector(store.getState());
// update new Session
const newSession = updateOverrideConfigToClean(userSessionToSaveSelector(state), checks, mapConfig, getRegisterHandlers());
// TODO: check whether to remove or update session on session serviceListOpenSelector(browser, server)
return writeSession(id, name, userName, newSession).switchMap(() => Rx.Observable.of(userSessionRemoved(newSession), closeFeatureGrid(), resetSearch(), success({
title: "success",
message: "userSession.successRemoved"
message: "userSession.successUpdated"
}))).let(wrapStartStop(
loading(true, 'userSessionRemoving'),
loading(false, 'userSessionRemoving'),
Expand All @@ -160,14 +175,43 @@ export const removeUserSessionEpicCreator = (idSelector = userSessionIdSelector)
* @param {object} store
*/
export const reloadOriginalConfigEpic = (action$, { getState = () => { } } = {}) =>
action$.ofType(USER_SESSION_REMOVED).switchMap(() => {
action$.ofType(USER_SESSION_REMOVED).switchMap(({newSession}) => {
const mapConfig = originalConfigSelector(getState());
const mapId = getState()?.mapInitialConfig?.mapId;
return Rx.Observable.of(loadMapConfig(null, mapId, mapConfig, undefined, {}), userSessionStartSaving());
return Rx.Observable.of(loadMapConfig(null, mapId, mapConfig, undefined, newSession || {}), userSessionStartSaving());
});

export const stopSaveSessionEpic = (action$) =>
// when auto save is activated
action$.ofType(USER_SESSION_START_SAVING).switchMap(() =>
action$.ofType(USER_SESSION_REMOVED, LOCATION_CHANGE, LOGOUT)
.switchMap(() => Rx.Observable.of(userSessionStopSaving())));

// some of the reducer are not ready when MAP_CONFIG_LOADED where merging of states takes place
// to handle initial update of states whose reducer are later initialized
// TODO: find better way to handle this, MAP_CONFIG_LOADED is loading before reducer initialization
export const setSessionToDynamicReducers = (action$, store) => {
return action$.ofType(REDUCERS_LOADED).switchMap(() => {
const state = store.getState();
let observables = [];

// only enabled in context map and has session
if (!state.context?.resource || !state.usersession?.session) return Rx.Observable.empty();

if (state.usersession?.session?.map?.bookmark_search_config) {
observables.push(Rx.Observable.of(setSearchBookmarkConfig('bookmarkSearchConfig', state.usersession?.session?.map?.bookmark_search_config)));
}
if (state.usersession?.session?.playback) {
observables.push(Rx.Observable.of(onInitPlayback({ ...state.usersession.session.playback })));
}
if (state.usersession?.session?.map?.text_search_config) {
observables.push(Rx.Observable.of(setSearchConfigProp('textSearchConfig', state.usersession?.session?.map?.text_search_config)));
}
// mapTemplates
if (state.usersession?.session?.mapTemplates) {
observables.push(Rx.Observable.of(setTemplates(state.usersession?.session.mapTemplates)));
}

return observables.length > 0 ? Rx.Observable.merge(...observables) : Rx.Observable.empty();
});
};
Loading

0 comments on commit 435be1b

Please sign in to comment.