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

Enhancing User Session #10657 #10712

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
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
94 changes: 90 additions & 4 deletions web/client/epics/__tests__/usersession-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,102 @@ 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();
}, initialState, done);
expect(actions[1].newSession).toBeTruthy();
}, {...initialState,
map: {
present: {
center: {
x: -71.88845339541245,
y: 37.25911173702324,
crs: 'EPSG:4326'
},
maxExtent: [
-20037508.34,
-20037508.34,
20037508.34,
20037508.34
]
}
}
}, 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);
expect(actions[3].type).toBe(TEXT_SEARCH_RESET);
}, initialState, done);
}, {...initialState, map: {
present: {
center: {
x: -71.88845339541245,
y: 37.25911173702324,
crs: 'EPSG:4326'
},
maxExtent: [
-20037508.34,
-20037508.34,
20037508.34,
20037508.34
]
}
}}, done);
});

});
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
3 changes: 1 addition & 2 deletions web/client/epics/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ const createSessionFlow = (mapId, contextName, resourceCategory, action$, getSta
return Observable.of(loadUserSession(buildSessionName(id, mapId, userName))).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
Loading