From 18b17689c0e632c4078eef2f3f491f7a569cce34 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 14 Sep 2026 12:25:10 +0100 Subject: [PATCH 1/9] FCRM-7019 WIP - Started to add custom items to mapKey --- plugins/map-key/src/components/Key/MapKey.jsx | 9 ++- plugins/map-key/src/initialise/MapKeyInit.jsx | 24 ++++++- plugins/map-key/src/manifest.js | 2 + plugins/map-key/src/reducers/getAddedKeys.js | 53 +++++++++++++++ plugins/map-key/src/reducers/pluginState.js | 67 +++++++++++++++++++ 5 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 plugins/map-key/src/reducers/getAddedKeys.js create mode 100644 plugins/map-key/src/reducers/pluginState.js diff --git a/plugins/map-key/src/components/Key/MapKey.jsx b/plugins/map-key/src/components/Key/MapKey.jsx index d6cfc9ea3..fa2c7bb35 100755 --- a/plugins/map-key/src/components/Key/MapKey.jsx +++ b/plugins/map-key/src/components/Key/MapKey.jsx @@ -1,11 +1,12 @@ import React, { useState, useEffect } from 'react' import { getDatasetRegistry } from '../../registry/index.js' -import { mergeKeyGroupItems } from '../../utils/mergeKeyGroupItems.js' +// import { mergeKeyGroupItems } from '../../utils/mergeKeyGroupItems.js' +import { mergeKeyGroupItems } from '../../reducers/getAddedKeys.js' import { Key } from './Key.jsx' export function MapKey ({ mapState: { mapStyle }, - pluginConfig: { noKeyItemText, groups }, + pluginConfig: { noKeyItemText, groups: pluginConfigGroups }, services: { eventBus } }) { const [datasetRegistry, setDatasetRegistry] = useState(getDatasetRegistry()) @@ -18,7 +19,7 @@ export function MapKey ({ const getKeyItems = () => { const { items, hasGroups: _hasGroups } = datasetRegistry.keyItems() // Post Process the items - based on the map-key pluginConfig adding any groupConfigs - const groupItems = mergeKeyGroupItems(groups, items) + const groupItems = mergeKeyGroupItems(items) setKeyGroups(groupItems) setHasGroups(_hasGroups) } @@ -44,6 +45,8 @@ export function MapKey ({ } }, [datasetRegistry]) + console.log('keyGroups', keyGroups) + return ( { if (!mapState.isMapReady) { return } + console.log('attachPluginStateRef', pluginStateRef) + attachPluginStateRef(pluginStateRef) // Request a handle on the datasetsRegistry singleton eventBus.requestOnce('datasets:registry', setDatasetRegistry) }, [mapState.isMapReady]) + + useEffect(() => { + if (!pluginConfig.groups) { + return + } + const groups = Object.entries(pluginConfig.groups).map(([id, group]) => ({ ...group, id, label: group.groupLabel })) + console.log('dispatching', groups) + + dispatch({ type: 'ADD_KEY_GROUPS', payload: groups }) + }, [pluginConfig.groups]) + + console.log('groups', getAddedKeyGroups()) } diff --git a/plugins/map-key/src/manifest.js b/plugins/map-key/src/manifest.js index 1cd51e2e3..df0e73c5b 100755 --- a/plugins/map-key/src/manifest.js +++ b/plugins/map-key/src/manifest.js @@ -1,8 +1,10 @@ import { MapKey } from './components/Key/MapKey.jsx' import { MapKeyInit } from './initialise/MapKeyInit.jsx' +import { initialState, actions } from './reducers/pluginState.js' export const manifest = { InitComponent: MapKeyInit, + reducer: { initialState, actions }, panels: [ { id: 'mapKey', diff --git a/plugins/map-key/src/reducers/getAddedKeys.js b/plugins/map-key/src/reducers/getAddedKeys.js new file mode 100644 index 000000000..a63b0beb0 --- /dev/null +++ b/plugins/map-key/src/reducers/getAddedKeys.js @@ -0,0 +1,53 @@ +let _pluginStateRef = {} +export const attachPluginStateRef = (pluginStateRef) => { _pluginStateRef = pluginStateRef } + +let previousRef = null +let addedKeyGroups = [] + +export const getAddedKeyGroups = () => { + if (!_pluginStateRef?.current) return null + if (previousRef === _pluginStateRef.current) { + return addedKeyGroups + } + previousRef = _pluginStateRef.current + const { groups, keyDefinitions } = _pluginStateRef.current + + addedKeyGroups = groups.map(group => { + if (group.type === 'flat') { + return { + ...group, + keyDefinition: keyDefinitions.find(item => item.groupId === group.id) + } + } + + return { + ...group, + keyDefinitions: keyDefinitions.filter(item => item.groupId === group.id) + } + }) + return addedKeyGroups +} + +export const mergeKeyGroupItems = (datasetItems) => { + const pluginConfigGroups = [...getAddedKeyGroups()] + if (pluginConfigGroups?.length === 0) { + return datasetItems + } + + const mergedItems = datasetItems.map((item) => { + const groupIndex = pluginConfigGroups.findIndex((_group) => _group.id === item.id) + if (groupIndex === -1) { + return item + } + // Remove the matched group from the pluginConfigGroups array and merge it with the item + const group = pluginConfigGroups.splice(groupIndex, 1)[0] + return { ...item, ...group, keyDefinitions: [...group.keyDefinitions, ...(item.keyDefinitions || [])] } + }) + // Append any remaining pluginConfigGroups that were not matched with datasetItems + const finalMergedItems = [...mergedItems, ...pluginConfigGroups] + .filter(item => + ((item.type === 'flat' && item.keyDefinition) || + (item.type === 'group' && item.keyDefinitions?.length))) + console.log('finalMergedItems', finalMergedItems) + return finalMergedItems +} diff --git a/plugins/map-key/src/reducers/pluginState.js b/plugins/map-key/src/reducers/pluginState.js new file mode 100644 index 000000000..1bdf81106 --- /dev/null +++ b/plugins/map-key/src/reducers/pluginState.js @@ -0,0 +1,67 @@ +const initialState = { + keyDefinitions: [{ + id: 'test-key-item', + label: 'Test Key', + groupId: 'test-key-group', + type: 'manual', + hasSymbol: false, + hasPattern: false, + style: { + strokeWidth: 2, + fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, + stroke: { outdoor: '#1d70b8', dark: '#7fcdbb' } + }, + symbolDescription: 'A test key item' + }], + groups: [{ + id: 'test-key-group', + type: 'group', + groupLabel: 'Test Key Group' + }] +} + +const labelToId = (label) => label ? label.toLowerCase().replace(/\s+/g, '-') : null +const createNewGroup = (id, groupId, groupLabel) => { + return groupId + ? { id: groupId, type: 'group', label: groupLabel } + : { id, type: 'flat' } +} + +const addKeyItem = (state, keyDefinition) => { + const { id, groupLabel } = keyDefinition + const groupId = labelToId(groupLabel) + const existingGroup = groupId && state.groups.find(group => group.id === groupId) + const newGroup = existingGroup ? null : createNewGroup(id, groupId, groupLabel) + const groups = newGroup ? [...state.groups, newGroup] : state.groups + return { + ...state, + keyDefinitions: [...state.keyDefinitions, keyDefinition], + groups + } +} + +const addKeyGroups = (state, groups) => { + return { + ...state, + groups: [...state.groups, ...groups] + } +} + +const removeKeyItem = (state, keyDefinition) => { + const { id } = keyDefinition + return { + ...state, + keyDefinitions: state.keyDefinitions.filter(key => key.id !== id) + } +} + +const actions = { + ADD_KEY_ITEM: addKeyItem, + REMOVE_KEY_ITEM: removeKeyItem, + ADD_KEY_GROUPS: addKeyGroups +} + +export { + initialState, + actions +} From 744149a1981e896d076f735c93c80eb2c852da60 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 14 Sep 2026 15:25:57 +0100 Subject: [PATCH 2/9] FCRM-7019 Adding a key item at runtime is working --- demo/js/esri-datasets.js | 66 +++++++++++-------- demo/js/planning/siteBoundary.js | 3 + .../datasets/src/registry/datasetRegistry.js | 12 ++-- plugins/map-key/src/api/addKeyItem.js | 3 + plugins/map-key/src/api/index.js | 7 ++ plugins/map-key/src/api/removeKeyItem.js | 3 + plugins/map-key/src/components/Key/MapKey.jsx | 10 ++- plugins/map-key/src/initialise/MapKeyInit.jsx | 6 +- plugins/map-key/src/manifest.js | 4 +- plugins/map-key/src/reducers/getAddedKeys.js | 2 +- plugins/map-key/src/reducers/pluginState.js | 57 ++++++++-------- 11 files changed, 102 insertions(+), 71 deletions(-) create mode 100644 plugins/map-key/src/api/addKeyItem.js create mode 100644 plugins/map-key/src/api/index.js create mode 100644 plugins/map-key/src/api/removeKeyItem.js diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 4f868cf12..10b539a69 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -12,6 +12,7 @@ import scaleBarPlugin from '/plugins/beta/scale-bar/src/index.js' import searchPlugin from '/plugins/search/src/index.js' import { transformGeocodeRequest, transformVtsRequest3857, setupEsriConfig } from './auth.js' import createInteractPlugin from '/plugins/interact/src/index.js' +import { siteBoundary } from './planning/siteBoundary.js' const nonFloodZoneLight = '#2b8cbe' const nonFloodZoneDark = '#7fcdbb' @@ -301,10 +302,9 @@ const surfaceWaterDatasetGenerator = ({id, tileName, sourceLayer, timeframe, aep if (depthsKey) { return [extentsDataset, depthDataset] } - // We only really need one of these with visibleWhen: { menu: {dataset: ['surfacewater'], depth: ['depthAll'] } }, + // We only need one depthsKey, so we only return it here if it isn't already defined depthsKey = { id: 'depths-key', - label: 'Surface water', groupId: 'surface-water-depth-in-millimetres', showInKey: true, visibleWhen: { menu: { dataset: ['surfacewater'], depth: ['depthAll'] } }, @@ -316,17 +316,7 @@ const surfaceWaterDatasetGenerator = ({id, tileName, sourceLayer, timeframe, aep } }) } - const extraDepthKeys = [] - for(let i = 3; i <= depthsKey.sublayers.length; i++) { - extraDepthKeys.push({ - ...depthsKey, - groupLabel: `${depthsKey.groupLabel} [${i}]`, - id: `${depthsKey.id}-${i}`, - sublayers: depthsKey.sublayers.slice(0, i).map((sublayer) => ({ ...sublayer, label: `${i}.${sublayer.label}`.replaceAll('0', '') })), - }) - } return [depthsKey, extentsDataset, depthDataset] - // return [...extraDepthKeys, extentsDataset, depthDataset] } const surfaceWaterExtentsKey = { @@ -616,6 +606,23 @@ const interactPlugin = createInteractPlugin({ interactionModes: ['placeMarker'], }) +const mapKeyPlugin = createMapKeyPlugin({ + groups: { + 'surface-water-depth-in-millimetres': { + groupLabel: 'Surface water depth in millimetres', + groupStyle: 'horizontal-ramp' + } + }, + manifest: { + panels: [{ + id: 'mapKey', + mobile: { slot: 'drawer', modal: false }, + tablet: { slot: 'left-top', width: '360px' }, + desktop: { slot: 'left-top', width: '360px' }, + }] + }, +}) + const interactiveMap = new InteractiveMap('map', { behaviour: 'mapOnly', mapProvider: esriProvider({ setupConfig: setupEsriConfig }), @@ -644,22 +651,7 @@ const interactiveMap = new InteractiveMap('map', { scaleBarPlugin({ units: 'metric' }), drawPlugin, framePlugin, - createMapKeyPlugin({ - groups: { - 'surface-water-depth-in-millimetres': { - groupLabel: 'Surface water depth in millimetres', - groupStyle: 'horizontal-ramp' - } - }, - manifest: { - panels: [{ - id: 'mapKey', - mobile: { slot: 'drawer', modal: false }, - tablet: { slot: 'left-top', width: '360px' }, - desktop: { slot: 'left-top', width: '360px' }, - }] - }, - }), + mapKeyPlugin, createMenuPlugin({ manifest: { panels: [{ @@ -703,6 +695,24 @@ interactiveMap.on('interact:markerchange', function (e) { }) }) +const siteBoundaryKeyDefinition = { + id: 'site-boundary', + label: 'Location boundary', + // groupLabel: 'Other features', + style: { + strokeWidth: 2, + fill: 'none', + stroke: { outdoor: '#D4351D', dark: '#ffffff' } + }, +} + +siteBoundary.onSetFeature = (feature) => { + if (feature) { + mapKeyPlugin.addKeyItem(siteBoundaryKeyDefinition) + } else { + mapKeyPlugin.removeKeyItem(siteBoundaryKeyDefinition) + } +} const onEditPolygon = (isEditing) => { // toggleKeyWhenEditing(isEditing) diff --git a/demo/js/planning/siteBoundary.js b/demo/js/planning/siteBoundary.js index 5e6c46505..25226488f 100644 --- a/demo/js/planning/siteBoundary.js +++ b/demo/js/planning/siteBoundary.js @@ -7,6 +7,7 @@ const FRAME_MAX_ZOOM = 22 export class SiteBoundary { constructor (id = 'boundary') { + this.onSetFeature = () => {} this._feature = null this._id = id this._state = SiteBoundary.EMPTY @@ -66,6 +67,7 @@ export class SiteBoundary { if (!feature?.geometry?.coordinates) { this._feature = null this.state = SiteBoundary.EMPTY + this.onSetFeature(null) return } // round the coordinates to 2 decimal places @@ -78,6 +80,7 @@ export class SiteBoundary { // set the feature and update the state this._feature = { ...feature, id, properties } this.state = SiteBoundary.COMPLETE + this.onSetFeature(this._feature) } diff --git a/plugins/datasets/src/registry/datasetRegistry.js b/plugins/datasets/src/registry/datasetRegistry.js index e543a37e0..d9d9de158 100644 --- a/plugins/datasets/src/registry/datasetRegistry.js +++ b/plugins/datasets/src/registry/datasetRegistry.js @@ -101,16 +101,16 @@ const datasetRegistry = { const groups = new Map() const getOrCreateGroup = (groupLabel, groupId) => { - if (groups.has(groupLabel)) { - return groups.get(groupLabel) + if (groups.has(groupId)) { + return groups.get(groupId) } const groupObject = { type: 'group', - id: groupId, // groupLabel.toLowerCase().replaceAll(/\s+/g, '-'), + id: groupId, groupLabel, keyDefinitions: [] } - groups.set(groupLabel, groupObject) + groups.set(groupId, groupObject) _items.push(groupObject) return groupObject } @@ -119,14 +119,14 @@ const datasetRegistry = { if (!dataset.keyVisibility) { return } - const isGroup = dataset.hasSublayers || dataset.groupLabel + const isGroup = dataset.hasSublayers || dataset.groupId || dataset.groupLabel if (!isGroup) { _items.push({ type: 'flat', id: dataset.id, keyDefinition: dataset.keyDefinition }) return } const groupId = dataset.groupId - const groupLabel = dataset.groupLabel || dataset.label + const groupLabel = dataset.groupLabel || groupId || dataset.label const groupObject = getOrCreateGroup(groupLabel, groupId) if (!dataset.hasSublayers) { groupObject.keyDefinitions.push(dataset.keyDefinition) diff --git a/plugins/map-key/src/api/addKeyItem.js b/plugins/map-key/src/api/addKeyItem.js new file mode 100644 index 000000000..e581899ae --- /dev/null +++ b/plugins/map-key/src/api/addKeyItem.js @@ -0,0 +1,3 @@ +export const addKeyItem = ({ pluginState: { dispatch } }, keyDefinition) => { + dispatch({ type: 'ADD_KEY_ITEM', payload: keyDefinition }) +} diff --git a/plugins/map-key/src/api/index.js b/plugins/map-key/src/api/index.js new file mode 100644 index 000000000..3e7b9aaa9 --- /dev/null +++ b/plugins/map-key/src/api/index.js @@ -0,0 +1,7 @@ +import { addKeyItem } from './addKeyItem.js' +import { removeKeyItem } from './removeKeyItem.js' + +export const api = { + addKeyItem, + removeKeyItem +} diff --git a/plugins/map-key/src/api/removeKeyItem.js b/plugins/map-key/src/api/removeKeyItem.js new file mode 100644 index 000000000..60a01d272 --- /dev/null +++ b/plugins/map-key/src/api/removeKeyItem.js @@ -0,0 +1,3 @@ +export const removeKeyItem = ({ pluginState: { dispatch } }, keyDefinition) => { + dispatch({ type: 'REMOVE_KEY_ITEM', payload: keyDefinition }) +} diff --git a/plugins/map-key/src/components/Key/MapKey.jsx b/plugins/map-key/src/components/Key/MapKey.jsx index fa2c7bb35..81c059f65 100755 --- a/plugins/map-key/src/components/Key/MapKey.jsx +++ b/plugins/map-key/src/components/Key/MapKey.jsx @@ -6,7 +6,8 @@ import { Key } from './Key.jsx' export function MapKey ({ mapState: { mapStyle }, - pluginConfig: { noKeyItemText, groups: pluginConfigGroups }, + pluginConfig: { noKeyItemText }, + pluginState, services: { eventBus } }) { const [datasetRegistry, setDatasetRegistry] = useState(getDatasetRegistry()) @@ -45,7 +46,12 @@ export function MapKey ({ } }, [datasetRegistry]) - console.log('keyGroups', keyGroups) + useEffect(() => { + if (!datasetRegistry) { + return + } + getKeyItems() + }, [pluginState]) return ( ({ ...group, id, label: group.groupLabel })) - console.log('dispatching', groups) dispatch({ type: 'ADD_KEY_GROUPS', payload: groups }) }, [pluginConfig.groups]) - - console.log('groups', getAddedKeyGroups()) } diff --git a/plugins/map-key/src/manifest.js b/plugins/map-key/src/manifest.js index df0e73c5b..b804e806b 100755 --- a/plugins/map-key/src/manifest.js +++ b/plugins/map-key/src/manifest.js @@ -1,6 +1,7 @@ import { MapKey } from './components/Key/MapKey.jsx' import { MapKeyInit } from './initialise/MapKeyInit.jsx' import { initialState, actions } from './reducers/pluginState.js' +import { api } from './api/index.js' export const manifest = { InitComponent: MapKeyInit, @@ -28,5 +29,6 @@ export const manifest = { icons: [{ id: 'key', svgContent: '' - }] + }], + api } diff --git a/plugins/map-key/src/reducers/getAddedKeys.js b/plugins/map-key/src/reducers/getAddedKeys.js index a63b0beb0..311bfe68c 100644 --- a/plugins/map-key/src/reducers/getAddedKeys.js +++ b/plugins/map-key/src/reducers/getAddedKeys.js @@ -16,7 +16,7 @@ export const getAddedKeyGroups = () => { if (group.type === 'flat') { return { ...group, - keyDefinition: keyDefinitions.find(item => item.groupId === group.id) + keyDefinition: keyDefinitions.find(item => item.id === group.id) } } diff --git a/plugins/map-key/src/reducers/pluginState.js b/plugins/map-key/src/reducers/pluginState.js index 1bdf81106..153f0130c 100644 --- a/plugins/map-key/src/reducers/pluginState.js +++ b/plugins/map-key/src/reducers/pluginState.js @@ -1,57 +1,58 @@ const initialState = { - keyDefinitions: [{ - id: 'test-key-item', - label: 'Test Key', - groupId: 'test-key-group', - type: 'manual', - hasSymbol: false, - hasPattern: false, - style: { - strokeWidth: 2, - fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, - stroke: { outdoor: '#1d70b8', dark: '#7fcdbb' } - }, - symbolDescription: 'A test key item' - }], - groups: [{ - id: 'test-key-group', - type: 'group', - groupLabel: 'Test Key Group' - }] + keyDefinitions: [], + groups: [] } const labelToId = (label) => label ? label.toLowerCase().replace(/\s+/g, '-') : null const createNewGroup = (id, groupId, groupLabel) => { return groupId - ? { id: groupId, type: 'group', label: groupLabel } + ? { id: groupId, type: 'group', groupLabel } : { id, type: 'flat' } } +const findGroup = (state, id, groupId) => { + return state.groups.find(group => { + return (group.type === 'group' && group.id === groupId) || + (group.type === 'flat' && group.id === id) + }) +} + const addKeyItem = (state, keyDefinition) => { const { id, groupLabel } = keyDefinition - const groupId = labelToId(groupLabel) - const existingGroup = groupId && state.groups.find(group => group.id === groupId) + const groupId = keyDefinition.groupId || labelToId(groupLabel) + // Check for an existingGroup + const existingGroup = findGroup(state, id, groupId) + // If no existing group is found, create a new one const newGroup = existingGroup ? null : createNewGroup(id, groupId, groupLabel) + // Add the new group to the state if it was created const groups = newGroup ? [...state.groups, newGroup] : state.groups + // Add the key definition to the state + const keyDefinitions = [...state.keyDefinitions, { + ...keyDefinition, + type: 'manual', + ...(groupId ? { groupId } : {}) + }] + + // Return the updated state with the new key definitions and groups return { ...state, - keyDefinitions: [...state.keyDefinitions, keyDefinition], + keyDefinitions, groups } } -const addKeyGroups = (state, groups) => { +const removeKeyItem = (state, keyDefinition) => { + const { id } = keyDefinition return { ...state, - groups: [...state.groups, ...groups] + keyDefinitions: state.keyDefinitions.filter(key => key.id !== id) } } -const removeKeyItem = (state, keyDefinition) => { - const { id } = keyDefinition +const addKeyGroups = (state, groups) => { return { ...state, - keyDefinitions: state.keyDefinitions.filter(key => key.id !== id) + groups: [...state.groups, ...groups] } } From 6b5b49b6a746ccd86f5fe5d1aa070b0fddb54057 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 14 Sep 2026 16:14:02 +0100 Subject: [PATCH 3/9] FCRM-7019 Fixed MapKeyInit.test.jsx --- plugins/map-key/src/components/Key/MapKey.jsx | 3 +- plugins/map-key/src/initialise/MapKeyInit.jsx | 2 +- .../src/initialise/MapKeyInit.test.jsx | 50 ++++++++++++-- plugins/map-key/src/reducers/getAddedKeys.js | 53 --------------- .../src/reducers/mergeKeyGroupItems.js | 66 +++++++++++++++++++ .../map-key/src/utils/mergeKeyGroupItems.js | 12 ---- .../src/utils/mergeKeyGroupItems.test.js | 53 --------------- 7 files changed, 111 insertions(+), 128 deletions(-) delete mode 100644 plugins/map-key/src/reducers/getAddedKeys.js create mode 100644 plugins/map-key/src/reducers/mergeKeyGroupItems.js delete mode 100644 plugins/map-key/src/utils/mergeKeyGroupItems.js delete mode 100644 plugins/map-key/src/utils/mergeKeyGroupItems.test.js diff --git a/plugins/map-key/src/components/Key/MapKey.jsx b/plugins/map-key/src/components/Key/MapKey.jsx index 81c059f65..4b16fac9b 100755 --- a/plugins/map-key/src/components/Key/MapKey.jsx +++ b/plugins/map-key/src/components/Key/MapKey.jsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react' import { getDatasetRegistry } from '../../registry/index.js' -// import { mergeKeyGroupItems } from '../../utils/mergeKeyGroupItems.js' -import { mergeKeyGroupItems } from '../../reducers/getAddedKeys.js' +import { mergeKeyGroupItems } from '../../reducers/mergeKeyGroupItems.js' import { Key } from './Key.jsx' export function MapKey ({ diff --git a/plugins/map-key/src/initialise/MapKeyInit.jsx b/plugins/map-key/src/initialise/MapKeyInit.jsx index 74008656d..1ac19178e 100644 --- a/plugins/map-key/src/initialise/MapKeyInit.jsx +++ b/plugins/map-key/src/initialise/MapKeyInit.jsx @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react' import { setDatasetRegistry } from '../registry/getDatasetRegistry.js' -import { attachPluginStateRef } from '../reducers/getAddedKeys.js' +import { attachPluginStateRef } from '../reducers/mergeKeyGroupItems.js' // additional possible params here are: pluginConfig, appState, mapProvider, export function MapKeyInit ({ pluginConfig, pluginState, mapState, services }) { diff --git a/plugins/map-key/src/initialise/MapKeyInit.test.jsx b/plugins/map-key/src/initialise/MapKeyInit.test.jsx index 4a359479f..ed1547f97 100644 --- a/plugins/map-key/src/initialise/MapKeyInit.test.jsx +++ b/plugins/map-key/src/initialise/MapKeyInit.test.jsx @@ -7,33 +7,69 @@ jest.mock('../registry/getDatasetRegistry.js', () => ({ })) const eventBus = { requestOnce: jest.fn() } -const services = { eventBus } +const pluginState = { dispatch: jest.fn() } +const pluginConfig = { + groups: { + alpha: { groupLabel: 'Alpha' } + } +} beforeEach(() => { jest.clearAllMocks() }) describe('MapKeyInit', () => { + const renderInit = ({ isMapReady = false, config = {} } = {}) => render( + + ) + it('does not call requestOnce when map is not ready', () => { - render() + renderInit({ isMapReady: false }) expect(eventBus.requestOnce).not.toHaveBeenCalled() }) it('calls requestOnce for datasets:registry when map is ready', () => { - render() + renderInit({ isMapReady: true }) expect(eventBus.requestOnce).toHaveBeenCalledWith('datasets:registry', setDatasetRegistry) }) + it('dispatches configured groups when pluginConfig.groups is provided', () => { + renderInit({ isMapReady: true, config: pluginConfig }) + expect(pluginState.dispatch).toHaveBeenCalledWith({ + type: 'ADD_KEY_GROUPS', + payload: [{ id: 'alpha', groupLabel: 'Alpha', label: 'Alpha' }] + }) + }) + it('calls requestOnce when isMapReady changes to true', () => { - const { rerender } = render() + const { rerender } = renderInit({ isMapReady: false }) expect(eventBus.requestOnce).not.toHaveBeenCalled() - rerender() + rerender( + + ) expect(eventBus.requestOnce).toHaveBeenCalledTimes(1) }) it('does not call requestOnce again on re-render when isMapReady stays true', () => { - const { rerender } = render() - rerender() + const { rerender } = renderInit({ isMapReady: true }) + rerender( + + ) expect(eventBus.requestOnce).toHaveBeenCalledTimes(1) }) }) diff --git a/plugins/map-key/src/reducers/getAddedKeys.js b/plugins/map-key/src/reducers/getAddedKeys.js deleted file mode 100644 index 311bfe68c..000000000 --- a/plugins/map-key/src/reducers/getAddedKeys.js +++ /dev/null @@ -1,53 +0,0 @@ -let _pluginStateRef = {} -export const attachPluginStateRef = (pluginStateRef) => { _pluginStateRef = pluginStateRef } - -let previousRef = null -let addedKeyGroups = [] - -export const getAddedKeyGroups = () => { - if (!_pluginStateRef?.current) return null - if (previousRef === _pluginStateRef.current) { - return addedKeyGroups - } - previousRef = _pluginStateRef.current - const { groups, keyDefinitions } = _pluginStateRef.current - - addedKeyGroups = groups.map(group => { - if (group.type === 'flat') { - return { - ...group, - keyDefinition: keyDefinitions.find(item => item.id === group.id) - } - } - - return { - ...group, - keyDefinitions: keyDefinitions.filter(item => item.groupId === group.id) - } - }) - return addedKeyGroups -} - -export const mergeKeyGroupItems = (datasetItems) => { - const pluginConfigGroups = [...getAddedKeyGroups()] - if (pluginConfigGroups?.length === 0) { - return datasetItems - } - - const mergedItems = datasetItems.map((item) => { - const groupIndex = pluginConfigGroups.findIndex((_group) => _group.id === item.id) - if (groupIndex === -1) { - return item - } - // Remove the matched group from the pluginConfigGroups array and merge it with the item - const group = pluginConfigGroups.splice(groupIndex, 1)[0] - return { ...item, ...group, keyDefinitions: [...group.keyDefinitions, ...(item.keyDefinitions || [])] } - }) - // Append any remaining pluginConfigGroups that were not matched with datasetItems - const finalMergedItems = [...mergedItems, ...pluginConfigGroups] - .filter(item => - ((item.type === 'flat' && item.keyDefinition) || - (item.type === 'group' && item.keyDefinitions?.length))) - console.log('finalMergedItems', finalMergedItems) - return finalMergedItems -} diff --git a/plugins/map-key/src/reducers/mergeKeyGroupItems.js b/plugins/map-key/src/reducers/mergeKeyGroupItems.js new file mode 100644 index 000000000..056ee9419 --- /dev/null +++ b/plugins/map-key/src/reducers/mergeKeyGroupItems.js @@ -0,0 +1,66 @@ +let _pluginStateRef = {} +export const attachPluginStateRef = (pluginStateRef) => { _pluginStateRef = pluginStateRef } + +let previousRef = null +let addedKeyGroups = [] + +const getStateKeyGroups = () => { + if (!_pluginStateRef?.current) return null + if (previousRef === _pluginStateRef.current) { + return addedKeyGroups + } + previousRef = _pluginStateRef.current + const { groups, keyDefinitions } = _pluginStateRef.current + + addedKeyGroups = groups.map(group => { + if (group.type === 'flat') { + return { + ...group, + keyDefinition: keyDefinitions.find(item => item.id === group.id) + } + } + + return { + ...group, + keyDefinitions: keyDefinitions.filter(item => item.groupId === group.id) + } + }) + return addedKeyGroups +} + +// Merges the state key group items with items provided by another plugin, +// specifically the datasets plugin +export const mergeKeyGroupItems = (keyGroupItemsToMerge) => { + // Clone the current state key groups from the plugin state reference + // to avoid mutating the original array + const stateKeyGroups = [...getStateKeyGroups()] + if (stateKeyGroups?.length === 0) { + // if there is nothing in the state key groups, just return the items that were passed in + return keyGroupItemsToMerge + } + + const mergedItems = keyGroupItemsToMerge.map((keyGroupItem) => { + const groupIndex = stateKeyGroups.findIndex((_group) => _group.id === keyGroupItem.id) + if (groupIndex === -1) { + return keyGroupItem + } + // Splice the matched group from the stateKeyGroups and merge it with the item + const group = stateKeyGroups.splice(groupIndex, 1)[0] + return { + ...keyGroupItem, + ...group, + keyDefinitions: [ + ...group.keyDefinitions, + ...(keyGroupItem.keyDefinitions || []) + ] + } + }) + + // Append any remaining pluginConfigGroups that were not matched with datasetItems + // and filter out any groups that have no key definitions + const finalMergedItems = [...mergedItems, ...stateKeyGroups] + .filter(item => + ((item.type === 'flat' && item.keyDefinition) || + (item.type === 'group' && item.keyDefinitions?.length))) + return finalMergedItems +} diff --git a/plugins/map-key/src/utils/mergeKeyGroupItems.js b/plugins/map-key/src/utils/mergeKeyGroupItems.js deleted file mode 100644 index b547a2fcf..000000000 --- a/plugins/map-key/src/utils/mergeKeyGroupItems.js +++ /dev/null @@ -1,12 +0,0 @@ -export const mergeKeyGroupItems = (groups, items) => { - if (!groups) { - return items - } - return items.map((item) => { - const { id } = item - if (groups[id]) { - return { ...item, ...groups[id] } - } - return item - }) -} diff --git a/plugins/map-key/src/utils/mergeKeyGroupItems.test.js b/plugins/map-key/src/utils/mergeKeyGroupItems.test.js deleted file mode 100644 index 0fcdf6d2a..000000000 --- a/plugins/map-key/src/utils/mergeKeyGroupItems.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { mergeKeyGroupItems } from './mergeKeyGroupItems' - -describe('mergeKeyGroupItems', () => { - it('returns items unchanged when groups is not provided', () => { - const items = [{ id: 'a', label: 'A' }, { id: 'b', label: 'B' }] - - expect(mergeKeyGroupItems(undefined, items)).toBe(items) - }) - - it('returns items unchanged when groups is null', () => { - const items = [{ id: 'a', label: 'A' }] - - expect(mergeKeyGroupItems(null, items)).toBe(items) - }) - - it('merges matching group data into an item', () => { - const items = [{ id: 'a', label: 'A' }] - const groups = { a: { group: 'Group 1' } } - - expect(mergeKeyGroupItems(groups, items)).toEqual([ - { id: 'a', label: 'A', group: 'Group 1' } - ]) - }) - - it('overrides item properties with matching group properties', () => { - const items = [{ id: 'a', label: 'A' }] - const groups = { a: { label: 'Overridden' } } - - expect(mergeKeyGroupItems(groups, items)).toEqual([ - { id: 'a', label: 'Overridden' } - ]) - }) - - it('leaves items without a matching group unchanged', () => { - const items = [{ id: 'a', label: 'A' }, { id: 'b', label: 'B' }] - const groups = { a: { group: 'Group 1' } } - - expect(mergeKeyGroupItems(groups, items)).toEqual([ - { id: 'a', label: 'A', group: 'Group 1' }, - { id: 'b', label: 'B' } - ]) - }) - - it('returns an empty array when items is empty', () => { - expect(mergeKeyGroupItems({ a: { group: 'Group 1' } }, [])).toEqual([]) - }) - - it('handles an empty groups object as no matches', () => { - const items = [{ id: 'a', label: 'A' }] - - expect(mergeKeyGroupItems({}, items)).toEqual(items) - }) -}) From 128d0f6e1ace2f69f8a5807b873b5843b8e45d12 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 14 Sep 2026 16:21:45 +0100 Subject: [PATCH 4/9] FCRM-7019 fixed MapKey.test.jsx --- plugins/map-key/src/reducers/mergeKeyGroupItems.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/map-key/src/reducers/mergeKeyGroupItems.js b/plugins/map-key/src/reducers/mergeKeyGroupItems.js index 056ee9419..52bf9331d 100644 --- a/plugins/map-key/src/reducers/mergeKeyGroupItems.js +++ b/plugins/map-key/src/reducers/mergeKeyGroupItems.js @@ -5,12 +5,12 @@ let previousRef = null let addedKeyGroups = [] const getStateKeyGroups = () => { - if (!_pluginStateRef?.current) return null + if (!_pluginStateRef?.current) return [] if (previousRef === _pluginStateRef.current) { return addedKeyGroups } previousRef = _pluginStateRef.current - const { groups, keyDefinitions } = _pluginStateRef.current + const { groups = [], keyDefinitions = [] } = _pluginStateRef.current addedKeyGroups = groups.map(group => { if (group.type === 'flat') { @@ -30,11 +30,11 @@ const getStateKeyGroups = () => { // Merges the state key group items with items provided by another plugin, // specifically the datasets plugin -export const mergeKeyGroupItems = (keyGroupItemsToMerge) => { +export const mergeKeyGroupItems = (keyGroupItemsToMerge = []) => { // Clone the current state key groups from the plugin state reference // to avoid mutating the original array const stateKeyGroups = [...getStateKeyGroups()] - if (stateKeyGroups?.length === 0) { + if (stateKeyGroups.length === 0) { // if there is nothing in the state key groups, just return the items that were passed in return keyGroupItemsToMerge } From 63afc90a296ac3ce0240f03415cb2eee02d35dfb Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 14 Sep 2026 16:31:15 +0100 Subject: [PATCH 5/9] FCRM-7019 added test to almost fully cover map-key --- plugins/map-key/src/api/addKeyItem.test.js | 14 ++++ plugins/map-key/src/api/index.test.js | 13 +++ plugins/map-key/src/api/removeKeyItem.test.js | 14 ++++ .../src/reducers/mergeKeyGroupItems.js | 2 +- .../src/reducers/mergeKeyGroupItems.test.js | 70 ++++++++++++++++ .../map-key/src/reducers/pluginState.test.js | 82 +++++++++++++++++++ 6 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 plugins/map-key/src/api/addKeyItem.test.js create mode 100644 plugins/map-key/src/api/index.test.js create mode 100644 plugins/map-key/src/api/removeKeyItem.test.js create mode 100644 plugins/map-key/src/reducers/mergeKeyGroupItems.test.js create mode 100644 plugins/map-key/src/reducers/pluginState.test.js diff --git a/plugins/map-key/src/api/addKeyItem.test.js b/plugins/map-key/src/api/addKeyItem.test.js new file mode 100644 index 000000000..60f27a779 --- /dev/null +++ b/plugins/map-key/src/api/addKeyItem.test.js @@ -0,0 +1,14 @@ +import { addKeyItem } from './addKeyItem.js' + +describe('addKeyItem', () => { + it('dispatches an ADD_KEY_ITEM action with the key definition', () => { + const dispatch = jest.fn() + + addKeyItem({ pluginState: { dispatch } }, { id: 'map-key', groupLabel: 'Map key' }) + + expect(dispatch).toHaveBeenCalledWith({ + type: 'ADD_KEY_ITEM', + payload: { id: 'map-key', groupLabel: 'Map key' } + }) + }) +}) diff --git a/plugins/map-key/src/api/index.test.js b/plugins/map-key/src/api/index.test.js new file mode 100644 index 000000000..8f2889d78 --- /dev/null +++ b/plugins/map-key/src/api/index.test.js @@ -0,0 +1,13 @@ +import { api } from './index.js' +import { addKeyItem } from './addKeyItem.js' +import { removeKeyItem } from './removeKeyItem.js' + +describe('api/index', () => { + it('exports the addKeyItem API', () => { + expect(api.addKeyItem).toBe(addKeyItem) + }) + + it('exports the removeKeyItem API', () => { + expect(api.removeKeyItem).toBe(removeKeyItem) + }) +}) diff --git a/plugins/map-key/src/api/removeKeyItem.test.js b/plugins/map-key/src/api/removeKeyItem.test.js new file mode 100644 index 000000000..3c31e9cdb --- /dev/null +++ b/plugins/map-key/src/api/removeKeyItem.test.js @@ -0,0 +1,14 @@ +import { removeKeyItem } from './removeKeyItem.js' + +describe('removeKeyItem', () => { + it('dispatches a REMOVE_KEY_ITEM action with the key definition', () => { + const dispatch = jest.fn() + + removeKeyItem({ pluginState: { dispatch } }, { id: 'map-key' }) + + expect(dispatch).toHaveBeenCalledWith({ + type: 'REMOVE_KEY_ITEM', + payload: { id: 'map-key' } + }) + }) +}) diff --git a/plugins/map-key/src/reducers/mergeKeyGroupItems.js b/plugins/map-key/src/reducers/mergeKeyGroupItems.js index 52bf9331d..3fe4e7f7a 100644 --- a/plugins/map-key/src/reducers/mergeKeyGroupItems.js +++ b/plugins/map-key/src/reducers/mergeKeyGroupItems.js @@ -50,7 +50,7 @@ export const mergeKeyGroupItems = (keyGroupItemsToMerge = []) => { ...keyGroupItem, ...group, keyDefinitions: [ - ...group.keyDefinitions, + ...(group.keyDefinitions || []), ...(keyGroupItem.keyDefinitions || []) ] } diff --git a/plugins/map-key/src/reducers/mergeKeyGroupItems.test.js b/plugins/map-key/src/reducers/mergeKeyGroupItems.test.js new file mode 100644 index 000000000..862a1e946 --- /dev/null +++ b/plugins/map-key/src/reducers/mergeKeyGroupItems.test.js @@ -0,0 +1,70 @@ +import { attachPluginStateRef, mergeKeyGroupItems } from './mergeKeyGroupItems.js' + +describe('mergeKeyGroupItems', () => { + afterEach(() => { + attachPluginStateRef({ current: null }) + }) + + it('returns the incoming items unchanged when no plugin state is attached', () => { + attachPluginStateRef({ current: null }) + const items = [{ id: 'dataset-1', keyDefinitions: [{ id: 'k1' }] }] + + expect(mergeKeyGroupItems(items)).toBe(items) + }) + + it('returns dataset items unchanged when plugin state groups are empty', () => { + attachPluginStateRef({ current: { groups: [], keyDefinitions: [] } }) + const items = [{ id: 'dataset-1', keyDefinitions: [{ id: 'k1' }] }] + + expect(mergeKeyGroupItems(items)).toEqual(items) + }) + + it('reuses the cached plugin-state groups when the ref object is unchanged', () => { + const ref = { current: { groups: [{ id: 'group-1', type: 'group', groupLabel: 'Group 1' }], keyDefinitions: [{ id: 'k1', groupId: 'group-1' }] } } + attachPluginStateRef(ref) + + const first = mergeKeyGroupItems([{ id: 'dataset-1', keyDefinitions: [{ id: 'd1' }] }]) + const second = mergeKeyGroupItems([{ id: 'dataset-1', keyDefinitions: [{ id: 'd2' }] }]) + + expect(first).toEqual([{ id: 'group-1', type: 'group', groupLabel: 'Group 1', keyDefinitions: [{ id: 'k1', groupId: 'group-1' }] }]) + expect(second).toEqual(first) + }) + + it('merges a matching flat group into the dataset item when the key definition matches the same id', () => { + attachPluginStateRef({ + current: { + groups: [{ id: 'dataset-1', type: 'flat' }], + keyDefinitions: [{ id: 'dataset-1' }] + } + }) + + const items = [{ id: 'dataset-1', keyDefinitions: [{ id: 'dataset-1' }] }] + + expect(mergeKeyGroupItems(items)).toEqual([ + { + id: 'dataset-1', + type: 'flat', + keyDefinition: { id: 'dataset-1' }, + keyDefinitions: [{ id: 'dataset-1' }] + } + ]) + }) + + it('keeps only unmatched state groups that still have key definitions', () => { + attachPluginStateRef({ + current: { + groups: [ + { id: 'group-1', type: 'group', groupLabel: 'Group 1' }, + { id: 'group-2', type: 'group', groupLabel: 'Group 2' } + ], + keyDefinitions: [{ id: 'k1', groupId: 'group-1' }] + } + }) + + const items = [{ id: 'dataset-1', keyDefinitions: [{ id: 'd1' }] }] + + expect(mergeKeyGroupItems(items)).toEqual([ + { id: 'group-1', type: 'group', groupLabel: 'Group 1', keyDefinitions: [{ id: 'k1', groupId: 'group-1' }] } + ]) + }) +}) diff --git a/plugins/map-key/src/reducers/pluginState.test.js b/plugins/map-key/src/reducers/pluginState.test.js new file mode 100644 index 000000000..e6c3d062e --- /dev/null +++ b/plugins/map-key/src/reducers/pluginState.test.js @@ -0,0 +1,82 @@ +import { actions, initialState } from './pluginState.js' + +describe('pluginState reducer helpers', () => { + it('returns the initial state', () => { + expect(initialState).toEqual({ keyDefinitions: [], groups: [] }) + }) + + it('adds a flat key item and creates a flat group when needed', () => { + const nextState = actions.ADD_KEY_ITEM(initialState, { id: 'flat-key', label: 'Flat key' }) + + expect(nextState).toEqual({ + keyDefinitions: [{ id: 'flat-key', label: 'Flat key', type: 'manual' }], + groups: [{ id: 'flat-key', type: 'flat' }] + }) + }) + + it('adds a grouped key item when the label can be converted to a group id', () => { + const nextState = actions.ADD_KEY_ITEM(initialState, { id: 'group-key', groupLabel: 'Group Label', label: 'Group key' }) + + expect(nextState).toEqual({ + keyDefinitions: [{ + id: 'group-key', + groupId: 'group-label', + groupLabel: 'Group Label', + label: 'Group key', + type: 'manual' + }], + groups: [{ id: 'group-label', type: 'group', groupLabel: 'Group Label' }] + }) + }) + + it('reuses an existing group when the key item matches a flat group already in state', () => { + const state = { + keyDefinitions: [{ id: 'existing-item' }], + groups: [{ id: 'existing-item', type: 'flat' }] + } + + expect(actions.ADD_KEY_ITEM(state, { id: 'existing-item', label: 'Existing item' })).toEqual({ + keyDefinitions: [ + { id: 'existing-item' }, + { id: 'existing-item', label: 'Existing item', type: 'manual' } + ], + groups: [{ id: 'existing-item', type: 'flat' }] + }) + }) + + it('reuses an existing grouped definition when the same group id is already present', () => { + const state = { + keyDefinitions: [{ id: 'existing-group-item', groupId: 'group-1' }], + groups: [{ id: 'group-1', type: 'group', groupLabel: 'Group 1' }] + } + + expect(actions.ADD_KEY_ITEM(state, { id: 'new-item', groupId: 'group-1', groupLabel: 'Group 1', label: 'New item' })).toEqual({ + keyDefinitions: [ + { id: 'existing-group-item', groupId: 'group-1' }, + { id: 'new-item', groupId: 'group-1', groupLabel: 'Group 1', label: 'New item', type: 'manual' } + ], + groups: [{ id: 'group-1', type: 'group', groupLabel: 'Group 1' }] + }) + }) + + it('removes a key definition by id', () => { + const state = { + keyDefinitions: [{ id: 'a' }, { id: 'b' }], + groups: [{ id: 'a', type: 'flat' }] + } + + expect(actions.REMOVE_KEY_ITEM(state, { id: 'a' })).toEqual({ + keyDefinitions: [{ id: 'b' }], + groups: [{ id: 'a', type: 'flat' }] + }) + }) + + it('adds configured groups to state without mutating the source array', () => { + const groups = [{ id: 'g1', type: 'group', groupLabel: 'G1' }] + + expect(actions.ADD_KEY_GROUPS(initialState, groups)).toEqual({ + keyDefinitions: [], + groups: [{ id: 'g1', type: 'group', groupLabel: 'G1' }] + }) + }) +}) From d2ea1dcbd2ec0e9bfafd66c49b58426f26bedb98 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 14 Sep 2026 16:34:41 +0100 Subject: [PATCH 6/9] FCRM-7019 full coverage --- .../src/reducers/mergeKeyGroupItems.test.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/map-key/src/reducers/mergeKeyGroupItems.test.js b/plugins/map-key/src/reducers/mergeKeyGroupItems.test.js index 862a1e946..362c80283 100644 --- a/plugins/map-key/src/reducers/mergeKeyGroupItems.test.js +++ b/plugins/map-key/src/reducers/mergeKeyGroupItems.test.js @@ -19,6 +19,27 @@ describe('mergeKeyGroupItems', () => { expect(mergeKeyGroupItems(items)).toEqual(items) }) + it('uses default empty state values and empty arguments when no groups or items are supplied', () => { + attachPluginStateRef({ current: {} }) + + expect(mergeKeyGroupItems()).toEqual([]) + }) + + it('merges a matched group even when the state group has no keyDefinitions array', () => { + attachPluginStateRef({ + current: { + groups: [{ id: 'dataset-1', type: 'group', groupLabel: 'Dataset 1' }], + keyDefinitions: [] + } + }) + + const items = [{ id: 'dataset-1', keyDefinitions: [{ id: 'd1' }] }] + + expect(mergeKeyGroupItems(items)).toEqual([ + { id: 'dataset-1', type: 'group', groupLabel: 'Dataset 1', keyDefinitions: [{ id: 'd1' }] } + ]) + }) + it('reuses the cached plugin-state groups when the ref object is unchanged', () => { const ref = { current: { groups: [{ id: 'group-1', type: 'group', groupLabel: 'Group 1' }], keyDefinitions: [{ id: 'k1', groupId: 'group-1' }] } } attachPluginStateRef(ref) From b3b42ba6b5c819af9ad59fa01dc324f149e37d4b Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 14 Sep 2026 16:47:42 +0100 Subject: [PATCH 7/9] FCRM-7019 SONAR fix --- plugins/map-key/src/reducers/mergeKeyGroupItems.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/map-key/src/reducers/mergeKeyGroupItems.js b/plugins/map-key/src/reducers/mergeKeyGroupItems.js index 3fe4e7f7a..0ba2a4104 100644 --- a/plugins/map-key/src/reducers/mergeKeyGroupItems.js +++ b/plugins/map-key/src/reducers/mergeKeyGroupItems.js @@ -5,7 +5,9 @@ let previousRef = null let addedKeyGroups = [] const getStateKeyGroups = () => { - if (!_pluginStateRef?.current) return [] + if (!_pluginStateRef?.current) { + return [] + } if (previousRef === _pluginStateRef.current) { return addedKeyGroups } From a79920bff681a73b71c082665c1d86abfaca3dbe Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 14 Sep 2026 17:43:49 +0100 Subject: [PATCH 8/9] FCRM-7019 map-key:ready is emitted --- demo/js/esri-datasets.js | 5 +++++ plugins/map-key/src/initialise/MapKeyInit.jsx | 1 + plugins/map-key/src/initialise/MapKeyInit.test.jsx | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 10b539a69..9e26ecf9d 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -714,6 +714,11 @@ siteBoundary.onSetFeature = (feature) => { } } + interactiveMap.on('map-key:ready', function () { + siteBoundary.onSetFeature(true) + }) + + const onEditPolygon = (isEditing) => { // toggleKeyWhenEditing(isEditing) if (isEditing) { diff --git a/plugins/map-key/src/initialise/MapKeyInit.jsx b/plugins/map-key/src/initialise/MapKeyInit.jsx index 1ac19178e..ed73993fa 100644 --- a/plugins/map-key/src/initialise/MapKeyInit.jsx +++ b/plugins/map-key/src/initialise/MapKeyInit.jsx @@ -16,6 +16,7 @@ export function MapKeyInit ({ pluginConfig, pluginState, mapState, services }) { attachPluginStateRef(pluginStateRef) // Request a handle on the datasetsRegistry singleton eventBus.requestOnce('datasets:registry', setDatasetRegistry) + eventBus.emit('map-key:ready') }, [mapState.isMapReady]) useEffect(() => { diff --git a/plugins/map-key/src/initialise/MapKeyInit.test.jsx b/plugins/map-key/src/initialise/MapKeyInit.test.jsx index ed1547f97..db8c33983 100644 --- a/plugins/map-key/src/initialise/MapKeyInit.test.jsx +++ b/plugins/map-key/src/initialise/MapKeyInit.test.jsx @@ -6,7 +6,7 @@ jest.mock('../registry/getDatasetRegistry.js', () => ({ setDatasetRegistry: jest.fn() })) -const eventBus = { requestOnce: jest.fn() } +const eventBus = { requestOnce: jest.fn(), emit: jest.fn() } const pluginState = { dispatch: jest.fn() } const pluginConfig = { groups: { From d56794dee2debfbb2e13b658346b0ecdaebfbaff Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Tue, 15 Sep 2026 10:18:11 +0100 Subject: [PATCH 9/9] FCRM-7019 renamed add/removeKeyItem as add/removeSymbol --- demo/js/esri-datasets.js | 4 ++-- plugins/map-key/src/api/addKeyItem.js | 3 --- plugins/map-key/src/api/addKeyItem.test.js | 14 -------------- plugins/map-key/src/api/addSymbol.js | 3 +++ plugins/map-key/src/api/addSymbol.test.js | 14 ++++++++++++++ plugins/map-key/src/api/index.js | 8 ++++---- plugins/map-key/src/api/index.test.js | 12 ++++++------ plugins/map-key/src/api/removeKeyItem.js | 3 --- plugins/map-key/src/api/removeKeyItem.test.js | 14 -------------- plugins/map-key/src/api/removeSymbol.js | 3 +++ plugins/map-key/src/api/removeSymbol.test.js | 14 ++++++++++++++ plugins/map-key/src/reducers/pluginState.js | 8 ++++---- plugins/map-key/src/reducers/pluginState.test.js | 10 +++++----- 13 files changed, 55 insertions(+), 55 deletions(-) delete mode 100644 plugins/map-key/src/api/addKeyItem.js delete mode 100644 plugins/map-key/src/api/addKeyItem.test.js create mode 100644 plugins/map-key/src/api/addSymbol.js create mode 100644 plugins/map-key/src/api/addSymbol.test.js delete mode 100644 plugins/map-key/src/api/removeKeyItem.js delete mode 100644 plugins/map-key/src/api/removeKeyItem.test.js create mode 100644 plugins/map-key/src/api/removeSymbol.js create mode 100644 plugins/map-key/src/api/removeSymbol.test.js diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 9e26ecf9d..6fd69fe33 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -708,9 +708,9 @@ const siteBoundaryKeyDefinition = { siteBoundary.onSetFeature = (feature) => { if (feature) { - mapKeyPlugin.addKeyItem(siteBoundaryKeyDefinition) + mapKeyPlugin.addSymbol(siteBoundaryKeyDefinition) } else { - mapKeyPlugin.removeKeyItem(siteBoundaryKeyDefinition) + mapKeyPlugin.removeSymbol(siteBoundaryKeyDefinition) } } diff --git a/plugins/map-key/src/api/addKeyItem.js b/plugins/map-key/src/api/addKeyItem.js deleted file mode 100644 index e581899ae..000000000 --- a/plugins/map-key/src/api/addKeyItem.js +++ /dev/null @@ -1,3 +0,0 @@ -export const addKeyItem = ({ pluginState: { dispatch } }, keyDefinition) => { - dispatch({ type: 'ADD_KEY_ITEM', payload: keyDefinition }) -} diff --git a/plugins/map-key/src/api/addKeyItem.test.js b/plugins/map-key/src/api/addKeyItem.test.js deleted file mode 100644 index 60f27a779..000000000 --- a/plugins/map-key/src/api/addKeyItem.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import { addKeyItem } from './addKeyItem.js' - -describe('addKeyItem', () => { - it('dispatches an ADD_KEY_ITEM action with the key definition', () => { - const dispatch = jest.fn() - - addKeyItem({ pluginState: { dispatch } }, { id: 'map-key', groupLabel: 'Map key' }) - - expect(dispatch).toHaveBeenCalledWith({ - type: 'ADD_KEY_ITEM', - payload: { id: 'map-key', groupLabel: 'Map key' } - }) - }) -}) diff --git a/plugins/map-key/src/api/addSymbol.js b/plugins/map-key/src/api/addSymbol.js new file mode 100644 index 000000000..c4a6749c4 --- /dev/null +++ b/plugins/map-key/src/api/addSymbol.js @@ -0,0 +1,3 @@ +export const addSymbol = ({ pluginState: { dispatch } }, keyDefinition) => { + dispatch({ type: 'ADD_KEY_SYMBOL', payload: keyDefinition }) +} diff --git a/plugins/map-key/src/api/addSymbol.test.js b/plugins/map-key/src/api/addSymbol.test.js new file mode 100644 index 000000000..3a618b71a --- /dev/null +++ b/plugins/map-key/src/api/addSymbol.test.js @@ -0,0 +1,14 @@ +import { addSymbol } from './addSymbol.js' + +describe('addSymbol', () => { + it('dispatches an ADD_KEY_SYMBOL action with the key definition', () => { + const dispatch = jest.fn() + + addSymbol({ pluginState: { dispatch } }, { id: 'map-key', groupLabel: 'Map key' }) + + expect(dispatch).toHaveBeenCalledWith({ + type: 'ADD_KEY_SYMBOL', + payload: { id: 'map-key', groupLabel: 'Map key' } + }) + }) +}) diff --git a/plugins/map-key/src/api/index.js b/plugins/map-key/src/api/index.js index 3e7b9aaa9..96180320a 100644 --- a/plugins/map-key/src/api/index.js +++ b/plugins/map-key/src/api/index.js @@ -1,7 +1,7 @@ -import { addKeyItem } from './addKeyItem.js' -import { removeKeyItem } from './removeKeyItem.js' +import { addSymbol } from './addSymbol.js' +import { removeSymbol } from './removeSymbol.js' export const api = { - addKeyItem, - removeKeyItem + addSymbol, + removeSymbol } diff --git a/plugins/map-key/src/api/index.test.js b/plugins/map-key/src/api/index.test.js index 8f2889d78..db9cd7ce8 100644 --- a/plugins/map-key/src/api/index.test.js +++ b/plugins/map-key/src/api/index.test.js @@ -1,13 +1,13 @@ import { api } from './index.js' -import { addKeyItem } from './addKeyItem.js' -import { removeKeyItem } from './removeKeyItem.js' +import { addSymbol } from './addSymbol.js' +import { removeSymbol } from './removeSymbol.js' describe('api/index', () => { - it('exports the addKeyItem API', () => { - expect(api.addKeyItem).toBe(addKeyItem) + it('exports the addSymbol API', () => { + expect(api.addSymbol).toBe(addSymbol) }) - it('exports the removeKeyItem API', () => { - expect(api.removeKeyItem).toBe(removeKeyItem) + it('exports the removeSymbol API', () => { + expect(api.removeSymbol).toBe(removeSymbol) }) }) diff --git a/plugins/map-key/src/api/removeKeyItem.js b/plugins/map-key/src/api/removeKeyItem.js deleted file mode 100644 index 60a01d272..000000000 --- a/plugins/map-key/src/api/removeKeyItem.js +++ /dev/null @@ -1,3 +0,0 @@ -export const removeKeyItem = ({ pluginState: { dispatch } }, keyDefinition) => { - dispatch({ type: 'REMOVE_KEY_ITEM', payload: keyDefinition }) -} diff --git a/plugins/map-key/src/api/removeKeyItem.test.js b/plugins/map-key/src/api/removeKeyItem.test.js deleted file mode 100644 index 3c31e9cdb..000000000 --- a/plugins/map-key/src/api/removeKeyItem.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import { removeKeyItem } from './removeKeyItem.js' - -describe('removeKeyItem', () => { - it('dispatches a REMOVE_KEY_ITEM action with the key definition', () => { - const dispatch = jest.fn() - - removeKeyItem({ pluginState: { dispatch } }, { id: 'map-key' }) - - expect(dispatch).toHaveBeenCalledWith({ - type: 'REMOVE_KEY_ITEM', - payload: { id: 'map-key' } - }) - }) -}) diff --git a/plugins/map-key/src/api/removeSymbol.js b/plugins/map-key/src/api/removeSymbol.js new file mode 100644 index 000000000..7c4fe284e --- /dev/null +++ b/plugins/map-key/src/api/removeSymbol.js @@ -0,0 +1,3 @@ +export const removeSymbol = ({ pluginState: { dispatch } }, keyDefinition) => { + dispatch({ type: 'REMOVE_KEY_SYMBOL', payload: keyDefinition }) +} diff --git a/plugins/map-key/src/api/removeSymbol.test.js b/plugins/map-key/src/api/removeSymbol.test.js new file mode 100644 index 000000000..2601ebb1e --- /dev/null +++ b/plugins/map-key/src/api/removeSymbol.test.js @@ -0,0 +1,14 @@ +import { removeSymbol } from './removeSymbol.js' + +describe('removeSymbol', () => { + it('dispatches a REMOVE_KEY_SYMBOL action with the key definition', () => { + const dispatch = jest.fn() + + removeSymbol({ pluginState: { dispatch } }, { id: 'map-key' }) + + expect(dispatch).toHaveBeenCalledWith({ + type: 'REMOVE_KEY_SYMBOL', + payload: { id: 'map-key' } + }) + }) +}) diff --git a/plugins/map-key/src/reducers/pluginState.js b/plugins/map-key/src/reducers/pluginState.js index 153f0130c..316819094 100644 --- a/plugins/map-key/src/reducers/pluginState.js +++ b/plugins/map-key/src/reducers/pluginState.js @@ -17,7 +17,7 @@ const findGroup = (state, id, groupId) => { }) } -const addKeyItem = (state, keyDefinition) => { +const addSymbol = (state, keyDefinition) => { const { id, groupLabel } = keyDefinition const groupId = keyDefinition.groupId || labelToId(groupLabel) // Check for an existingGroup @@ -41,7 +41,7 @@ const addKeyItem = (state, keyDefinition) => { } } -const removeKeyItem = (state, keyDefinition) => { +const removeSymbol = (state, keyDefinition) => { const { id } = keyDefinition return { ...state, @@ -57,8 +57,8 @@ const addKeyGroups = (state, groups) => { } const actions = { - ADD_KEY_ITEM: addKeyItem, - REMOVE_KEY_ITEM: removeKeyItem, + ADD_KEY_SYMBOL: addSymbol, + REMOVE_KEY_SYMBOL: removeSymbol, ADD_KEY_GROUPS: addKeyGroups } diff --git a/plugins/map-key/src/reducers/pluginState.test.js b/plugins/map-key/src/reducers/pluginState.test.js index e6c3d062e..9c031c806 100644 --- a/plugins/map-key/src/reducers/pluginState.test.js +++ b/plugins/map-key/src/reducers/pluginState.test.js @@ -6,7 +6,7 @@ describe('pluginState reducer helpers', () => { }) it('adds a flat key item and creates a flat group when needed', () => { - const nextState = actions.ADD_KEY_ITEM(initialState, { id: 'flat-key', label: 'Flat key' }) + const nextState = actions.ADD_KEY_SYMBOL(initialState, { id: 'flat-key', label: 'Flat key' }) expect(nextState).toEqual({ keyDefinitions: [{ id: 'flat-key', label: 'Flat key', type: 'manual' }], @@ -15,7 +15,7 @@ describe('pluginState reducer helpers', () => { }) it('adds a grouped key item when the label can be converted to a group id', () => { - const nextState = actions.ADD_KEY_ITEM(initialState, { id: 'group-key', groupLabel: 'Group Label', label: 'Group key' }) + const nextState = actions.ADD_KEY_SYMBOL(initialState, { id: 'group-key', groupLabel: 'Group Label', label: 'Group key' }) expect(nextState).toEqual({ keyDefinitions: [{ @@ -35,7 +35,7 @@ describe('pluginState reducer helpers', () => { groups: [{ id: 'existing-item', type: 'flat' }] } - expect(actions.ADD_KEY_ITEM(state, { id: 'existing-item', label: 'Existing item' })).toEqual({ + expect(actions.ADD_KEY_SYMBOL(state, { id: 'existing-item', label: 'Existing item' })).toEqual({ keyDefinitions: [ { id: 'existing-item' }, { id: 'existing-item', label: 'Existing item', type: 'manual' } @@ -50,7 +50,7 @@ describe('pluginState reducer helpers', () => { groups: [{ id: 'group-1', type: 'group', groupLabel: 'Group 1' }] } - expect(actions.ADD_KEY_ITEM(state, { id: 'new-item', groupId: 'group-1', groupLabel: 'Group 1', label: 'New item' })).toEqual({ + expect(actions.ADD_KEY_SYMBOL(state, { id: 'new-item', groupId: 'group-1', groupLabel: 'Group 1', label: 'New item' })).toEqual({ keyDefinitions: [ { id: 'existing-group-item', groupId: 'group-1' }, { id: 'new-item', groupId: 'group-1', groupLabel: 'Group 1', label: 'New item', type: 'manual' } @@ -65,7 +65,7 @@ describe('pluginState reducer helpers', () => { groups: [{ id: 'a', type: 'flat' }] } - expect(actions.REMOVE_KEY_ITEM(state, { id: 'a' })).toEqual({ + expect(actions.REMOVE_KEY_SYMBOL(state, { id: 'a' })).toEqual({ keyDefinitions: [{ id: 'b' }], groups: [{ id: 'a', type: 'flat' }] })