Skip to content
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
11 changes: 7 additions & 4 deletions docs/developer-guide/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| --- | --- | --- |
Expand All @@ -48,12 +48,13 @@ 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.
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": [
Expand All @@ -64,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.
Expand Down
10 changes: 10 additions & 0 deletions docs/developer-guide/mapstore-migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.01 to 2026.01.02

### 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
Expand Down
17 changes: 1 addition & 16 deletions web/client/configs/localConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
5 changes: 1 addition & 4 deletions web/client/selectors/__tests__/context-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import ConfigUtils from '../../utils/ConfigUtils';

import {
currentContextSelector,
contextMonitoredStateSelector,
isLoadingSelector,
pluginsSelector,
resourceSelector,
Expand All @@ -31,9 +30,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);
});
Expand Down
21 changes: 18 additions & 3 deletions web/client/utils/PluginsUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -76,7 +77,19 @@ const dynamicFederation = (scope, module) => {
})
};

const defaultMonitoredState = [{name: "mapType", path: 'maptype.mapType'}, {name: "user", path: 'security.user'}];
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 }), {}));
Expand Down Expand Up @@ -128,13 +141,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);
}, '');
});

Expand Down
27 changes: 27 additions & 0 deletions web/client/utils/__tests__/PluginsUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"]};
Expand Down
Loading