From 0382d389c86b72af6abb31fd76b9dc045909b751 Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Wed, 15 Jul 2026 10:26:59 +0800 Subject: [PATCH] fix: validate URL layer ids against live layer registry The invalid-layer check in layers-utils snapshotted LayerDefinitions keys once at mount, before OpenSPP collections are fetched and registered via registerDynamicLayer. Selecting a collection from the dynamic Collections browser then raised a spurious 'Invalid layer identifier(s)' notification even though the layer itself rendered correctly. Validate against the live LayerDefinitions map at validation time instead, via a getInvalidLayerIds helper next to registerDynamicLayer. --- frontend/src/config/dynamic-layers.test.ts | 44 ++++++++++++++++++++++ frontend/src/config/utils.ts | 9 +++++ frontend/src/utils/layers-utils.tsx | 11 +++--- 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 frontend/src/config/dynamic-layers.test.ts diff --git a/frontend/src/config/dynamic-layers.test.ts b/frontend/src/config/dynamic-layers.test.ts new file mode 100644 index 000000000..3839d3d2e --- /dev/null +++ b/frontend/src/config/dynamic-layers.test.ts @@ -0,0 +1,44 @@ +import { + LayerDefinitions, + getInvalidLayerIds, + registerDynamicLayer, +} from './utils'; + +describe('getInvalidLayerIds', () => { + const dynamicLayerId = 'openspp_TEST_DYNAMIC_adm0'; + + afterEach(() => { + delete (LayerDefinitions as Record)[dynamicLayerId]; + }); + + it('reports unknown layer ids as invalid', () => { + expect(getInvalidLayerIds([dynamicLayerId])).toEqual([dynamicLayerId]); + }); + + it('accepts statically configured layer ids', () => { + const staticId = Object.keys(LayerDefinitions)[0]; + expect(staticId).toBeDefined(); + expect(getInvalidLayerIds([staticId])).toEqual([]); + }); + + it('accepts layers registered at runtime via registerDynamicLayer', () => { + registerDynamicLayer(dynamicLayerId, { + id: dynamicLayerId, + type: 'openspp_report', + title: 'Test Dynamic Collection', + collectionId: 'TEST_DYNAMIC_adm0', + dataField: 'value', + opacity: 0.7, + legend: [], + legendText: 'Test Dynamic Collection', + } as any); + expect(getInvalidLayerIds([dynamicLayerId])).toEqual([]); + }); + + it('validates a mixed list, keeping only unknown ids', () => { + const staticId = Object.keys(LayerDefinitions)[0]; + expect(getInvalidLayerIds([staticId, dynamicLayerId])).toEqual([ + dynamicLayerId, + ]); + }); +}); diff --git a/frontend/src/config/utils.ts b/frontend/src/config/utils.ts index 9de992d06..b34de3d4e 100644 --- a/frontend/src/config/utils.ts +++ b/frontend/src/config/utils.ts @@ -272,6 +272,15 @@ export function registerDynamicLayer(key: string, layer: LayerType): void { (LayerDefinitions as Record)[key] = layer; } +/** + * Return the subset of the given layer ids that are not present in + * LayerDefinitions. Reads the live map at call time so layers added via + * registerDynamicLayer are recognized as valid. + */ +export function getInvalidLayerIds(layerIds: string[]): string[] { + return layerIds.filter(layerId => !(layerId in LayerDefinitions)); +} + export function getBoundaryLayers(): BoundaryLayerProps[] { return Object.values(LayerDefinitions) .filter((layer): layer is BoundaryLayerProps => layer.type === 'boundary') diff --git a/frontend/src/utils/layers-utils.tsx b/frontend/src/utils/layers-utils.tsx index ff1cca058..8ca7d7e99 100644 --- a/frontend/src/utils/layers-utils.tsx +++ b/frontend/src/utils/layers-utils.tsx @@ -15,6 +15,7 @@ import { AALayerIds, LayerDefinitions, getBoundaryLayerSingleton, + getInvalidLayerIds, isAnticipatoryActionLayer, isWindowedDates, } from 'config/utils'; @@ -341,12 +342,12 @@ const useLayers = () => { [serverAvailableDates], ); - const layerDefinitionIds = useMemo(() => Object.keys(LayerDefinitions), []); - - // Check for invalid layer ids. + // Check for invalid layer ids against the live LayerDefinitions map, so + // layers registered at runtime (e.g., discovered OpenSPP collections) are + // recognized as valid. const invalidLayersIds = useMemo( - () => urlLayerIds.filter(layerId => !layerDefinitionIds.includes(layerId)), - [layerDefinitionIds, urlLayerIds], + () => getInvalidLayerIds(urlLayerIds), + [urlLayerIds], ); // Adds missing layers to existing map instance