From 382f1c5a730006e565290cdfc51406a46529fa80 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Fri, 24 Apr 2026 17:55:54 +0200 Subject: [PATCH 1/4] Fix 12309 - Add usergroups to default monitoredState --- docs/developer-guide/expressions.md | 1 + web/client/utils/PluginsUtils.js | 9 ++++--- .../utils/__tests__/PluginsUtils-test.js | 27 +++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/developer-guide/expressions.md b/docs/developer-guide/expressions.md index ce63b168af4..f356c4e6922 100644 --- a/docs/developer-guide/expressions.md +++ b/docs/developer-guide/expressions.md @@ -48,6 +48,7 @@ The following aliases are usually available out-of-the-box in the standard versi | `userrole` | `security.user.role` | Current user's role | | `printEnabled` | `print.capabilities` | Printing availability | | `resourceCanEdit` | `resources.initialSelectedResource.canEdit` | Permission on current resource | +| `usergroups` | (internal selector) | User's groups (`groupName`) array (only enabled ones) | They are configured by default in the `monitoredState` section of the standard `localConfig.json`, allowing them to be customized. diff --git a/web/client/utils/PluginsUtils.js b/web/client/utils/PluginsUtils.js index 31e18181e06..aef14b5f7cf 100644 --- a/web/client/utils/PluginsUtils.js +++ b/web/client/utils/PluginsUtils.js @@ -16,6 +16,7 @@ import {combineReducers as originalCombineReducers} from 'redux'; import {wrapEpics} from "./EpicsUtils"; import { randomInt } from './RandomUtils'; import { pluginUtilsExpressionEvaluation } from './ExpressionUtils'; +import { userGroupsEnabledSelector } from '../selectors/security'; /** * Loads a script inside the current page. @@ -76,7 +77,7 @@ const dynamicFederation = (scope, module) => { }) }; -const defaultMonitoredState = [{name: "mapType", path: 'maptype.mapType'}, {name: "user", path: 'security.user'}]; +const defaultMonitoredState = [{name: "mapType", path: 'maptype.mapType'}, {name: "user", path: 'security.user'}, {name: "usergroups", selector: userGroupsEnabledSelector}]; export const getFromPlugins = curry((selector, plugins) => Object.keys(plugins).map((name) => plugins[name][selector]) .reduce((previous, current) => ({ ...previous, ...current }), {})); @@ -128,13 +129,15 @@ export const combineEpics = (plugins, epics = {}, epicWrapper) => { */ export const filterState = memoize((state, monitor) => { return monitor.reduce((previous, current) => { + const value = current.selector ? current.selector(state) : get(state, current.path); return Object.assign(previous, { - [current.name]: get(state, current.path) + [current.name]: value }); }, {}); }, (state, monitor) => { return monitor.reduce((previous, current) => { - return previous + JSON.stringify(get(state, current.path)); + const value = current.selector ? current.selector(state) : get(state, current.path); + return previous + JSON.stringify(value); }, ''); }); diff --git a/web/client/utils/__tests__/PluginsUtils-test.js b/web/client/utils/__tests__/PluginsUtils-test.js index e05763e3f82..1da236b2a49 100644 --- a/web/client/utils/__tests__/PluginsUtils-test.js +++ b/web/client/utils/__tests__/PluginsUtils-test.js @@ -116,6 +116,33 @@ describe('PluginsUtils', () => { it('getMonitoredState', () => { expect(PluginsUtils.getMonitoredState({maptype: {mapType: "leaflet"}}).mapType).toBe("leaflet"); }); + it('getMonitoredState includes usergroups from security state', () => { + const state = { + security: { + user: { + groups: { + group: [ + {groupName: "everyone", enabled: true}, + {groupName: "editors", enabled: true}, + {groupName: "disabled-group", enabled: false} + ] + } + } + } + }; + const monitored = PluginsUtils.getMonitoredState(state); + expect(monitored.usergroups).toExist(); + expect(monitored.usergroups.length).toBe(2); + expect(monitored.usergroups).toInclude("everyone"); + expect(monitored.usergroups).toInclude("editors"); + expect(monitored.usergroups).toExclude("disabled-group"); + }); + it('getMonitoredState returns empty usergroups when user has no groups', () => { + const state = { security: { user: {} } }; + const monitored = PluginsUtils.getMonitoredState(state); + expect(monitored.usergroups).toExist(); + expect(monitored.usergroups.length).toBe(0); + }); it('handleExpression in case there is a chaining within the expression that needs to access available state', () => { const state = {groups: ["ADMIN", "NORMAL_USER"]}; From a0a710c012d75b7ec8fe76b6d83ec24e49cec8ae Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Fri, 24 Apr 2026 18:02:07 +0200 Subject: [PATCH 2/4] Fixed old test --- web/client/selectors/__tests__/context-test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/client/selectors/__tests__/context-test.js b/web/client/selectors/__tests__/context-test.js index f7b704d3586..cd2a91e9baf 100644 --- a/web/client/selectors/__tests__/context-test.js +++ b/web/client/selectors/__tests__/context-test.js @@ -31,9 +31,7 @@ describe('context selectors', () => { it('currentContextSelector', () => { expect(currentContextSelector(stateMocker(setContext(CONTEXT_DATA)))).toEqual(CONTEXT_DATA); }); - it('contextMonitoredStateSelector', () => { - expect(contextMonitoredStateSelector(stateMocker())).toBe('{}'); - }); + it('isLoadingSelector', () => { expect(isLoadingSelector(stateMocker(loading(true)))).toBe(true); }); From a44c754ffa9e2871b86412eeea06776baa7ddab9 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Fri, 24 Apr 2026 18:07:42 +0200 Subject: [PATCH 3/4] Fixed old test --- web/client/selectors/__tests__/context-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/web/client/selectors/__tests__/context-test.js b/web/client/selectors/__tests__/context-test.js index cd2a91e9baf..4fe62c38974 100644 --- a/web/client/selectors/__tests__/context-test.js +++ b/web/client/selectors/__tests__/context-test.js @@ -10,7 +10,6 @@ import ConfigUtils from '../../utils/ConfigUtils'; import { currentContextSelector, - contextMonitoredStateSelector, isLoadingSelector, pluginsSelector, resourceSelector, From 474fd91b7557575a06d0553f27f853052b0f8394 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Mon, 11 May 2026 15:24:40 +0200 Subject: [PATCH 4/4] Moved monitored state. add doc --- docs/developer-guide/expressions.md | 10 ++++++---- .../developer-guide/mapstore-migration-guide.md | 10 ++++++++++ web/client/configs/localConfig.json | 17 +---------------- web/client/utils/PluginsUtils.js | 14 +++++++++++++- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/docs/developer-guide/expressions.md b/docs/developer-guide/expressions.md index f356c4e6922..52270803c9f 100644 --- a/docs/developer-guide/expressions.md +++ b/docs/developer-guide/expressions.md @@ -33,11 +33,11 @@ The expression syntax can potentially be applied to every configuration. However ## State Access and `monitorState` -The `state('name')` function allows you to access a "slice" of the Redux store. However, for performance and security reasons, only specific parts of the state are exposed. These are defined in the `monitorState` section of your `localConfig.json`. +The `state('name')` function allows you to access a "slice" of the Redux store. However, for performance and security reasons, only specific parts of the state are exposed. ### Default Monitored States -The following aliases are usually available out-of-the-box in the standard version of MapStore: +The following aliases are available out-of-the-box in the standard version of MapStore: | Alias | Path in Redux Store | Purpose | | --- | --- | --- | @@ -50,11 +50,11 @@ The following aliases are usually available out-of-the-box in the standard versi | `resourceCanEdit` | `resources.initialSelectedResource.canEdit` | Permission on current resource | | `usergroups` | (internal selector) | User's groups (`groupName`) array (only enabled ones) | -They are configured by default in the `monitoredState` section of the standard `localConfig.json`, allowing them to be customized. +They are available by default and they do not require any additional configuration. You can use them directly in your expressions, for example: `{state('userrole') == 'admin'}`. ### Customizing Monitored State -You can extend this list in `localConfig.json` to expose any part of the MapStore state to your expressions: +You can extend the set of monitored states by adding entries in `localConfig.json` `monitorState` property. Each entry should specify a `name` (the alias used in expressions) and a `path` (the path in the Redux store). For example: ```json "monitorState": [ @@ -65,6 +65,8 @@ You can extend this list in `localConfig.json` to expose any part of the MapStor *Usage:* `{state('myCustomValue') == 'active'}` +Redefining an existing alias (e.g., `userrole`) will override the default path, allowing you to point it to a different part of the state if needed. + ## Core Syntax & Operators The [engine](https://www.npmjs.com/package/filtrex) supports standard mathematical operations, string concatenation, and logic. diff --git a/docs/developer-guide/mapstore-migration-guide.md b/docs/developer-guide/mapstore-migration-guide.md index 258b2ff5c66..954b1111b38 100644 --- a/docs/developer-guide/mapstore-migration-guide.md +++ b/docs/developer-guide/mapstore-migration-guide.md @@ -20,6 +20,16 @@ This is a list of things to check if you want to update from a previous version - Optionally check also accessory files like `.eslinrc`, if you want to keep aligned with lint standards. - Follow the instructions below, in order, from your version to the one you want to update to. +## Migration from 2026.01.00 to 2026.02.00 + +### Monitored state available by default + +Several monitored state entries have been added to the default configuration of MapStore and they are now available by default without the need to add them in the `monitorState` section of `localConfig.json`. + +The entries you have configured will still work by overriding the default ones, anyway, in order to reduce the configuration, you should remove the entries if they are already available by default. + +The entries that you can remove because are available by default are documented in the [State Access and monitorState](./expressions.md#state-access-and-monitorstate) guide. + ## Migration from 2025.02.02 to 2026.01.00 ### Database update diff --git a/web/client/configs/localConfig.json b/web/client/configs/localConfig.json index 0cbac63e5f1..6d3658eee4e 100644 --- a/web/client/configs/localConfig.json +++ b/web/client/configs/localConfig.json @@ -66,22 +66,7 @@ } } ], - "monitorState": [ - { "name": "router", "path": "router.location.pathname" }, - { "name": "browser", "path": "browser" }, - { "name": "geostorymode", "path": "geostory.mode" }, - { "name": "featuregridmode", "path": "featuregrid.mode" }, - { "name": "userrole", "path": "security.user.role" }, - { "name": "printEnabled", "path": "print.capabilities" }, - { - "name": "resourceCanEdit", - "path": "resources.initialSelectedResource.canEdit" - }, - { - "name": "resourceDetails", - "path": "resources.initialSelectedResource.attributes.details" - } - ], + "monitorState": [], "userSessions": { "enabled": true }, diff --git a/web/client/utils/PluginsUtils.js b/web/client/utils/PluginsUtils.js index aef14b5f7cf..8fec01c2d27 100644 --- a/web/client/utils/PluginsUtils.js +++ b/web/client/utils/PluginsUtils.js @@ -77,7 +77,19 @@ const dynamicFederation = (scope, module) => { }) }; -const defaultMonitoredState = [{name: "mapType", path: 'maptype.mapType'}, {name: "user", path: 'security.user'}, {name: "usergroups", selector: userGroupsEnabledSelector}]; +const defaultMonitoredState = [ + { name: "browser", path: "browser" }, + { name: "router", path: "router.location.pathname" }, + { name: "user", path: 'security.user'}, + { name: "userrole", path: "security.user.role" }, + { name: "usergroups", selector: userGroupsEnabledSelector }, + { name: "resourceCanEdit", path: "resources.initialSelectedResource.canEdit" }, + { name: "resourceDetails", path: "resources.initialSelectedResource.attributes.details" }, + { name: "printEnabled", path: "print.capabilities" }, + { name: "geostorymode", path: "geostory.mode" }, + { name: "featuregridmode", path: "featuregrid.mode" }, + {name: "mapType", path: 'maptype.mapType'} +]; export const getFromPlugins = curry((selector, plugins) => Object.keys(plugins).map((name) => plugins[name][selector]) .reduce((previous, current) => ({ ...previous, ...current }), {}));