From b4142d1594bb78b0c923ec72f8f937c007782cf0 Mon Sep 17 00:00:00 2001 From: meyerlor Date: Fri, 22 May 2026 09:19:12 +0200 Subject: [PATCH] add per-layer excludeFromSingleWMS option When 'Load layers as single WMS layer' is enabled at project level, layers marked with this new optional flag are dropped from the bundled GetMap request and rendered individually instead (direct to their WMS server when externalWmsToggle is set, otherwise via QGIS Server). This lets admins keep slow third-party WMS layers out of the bundle without disabling the bundle entirely. Refs https://github.com/3liz/lizmap-web-client/issues/6631 --- CHANGELOG-3.11.md | 1 + assets/src/modules/config/Layer.js | 11 +++++ assets/src/modules/map.js | 2 +- tests/end2end/playwright/singleWMS.spec.js | 47 ++++++++++++++++++++++ tests/js-units/node/config/layer.test.js | 29 +++++++++++++ 5 files changed, 89 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-3.11.md b/CHANGELOG-3.11.md index 1afcd717a8..13762005cc 100644 --- a/CHANGELOG-3.11.md +++ b/CHANGELOG-3.11.md @@ -15,6 +15,7 @@ with some extra keywords: backend, tests, test, translation, funders, important ### Added * Edition - Support QGIS dynamic default-value expressions in edit forms, including geometry-based (`$x`, `$y`, `$area`, `$length`, `$geometry`) and field-referencing expressions (e.g. `"firstname" || ' ' || "lastname"`). Defaults are re-evaluated when the geometry is drawn/edited and when a referenced field changes, honoring QGIS's `applyOnUpdate` flag. +* New per-layer option `excludeFromSingleWMS` to exclude a layer from the bundled "Load layers as a single WMS layer" request. The excluded layer is fetched individually (directly from its WMS server if `externalWmsToggle` is set, otherwise via QGIS Server). Useful for keeping slow third-party WMS layers out of the bundle. Requires the Lizmap plugin ≥ 5.x to expose the toggle in the UI. ([#6631](https://github.com/3liz/lizmap-web-client/issues/6631)) ### Changed diff --git a/assets/src/modules/config/Layer.js b/assets/src/modules/config/Layer.js index 583a3434a2..8a934dacac 100644 --- a/assets/src/modules/config/Layer.js +++ b/assets/src/modules/config/Layer.js @@ -47,6 +47,7 @@ const optionalProperties = { 'mutuallyExclusive': {type: 'boolean', default: false}, 'externalWmsToggle': {type: 'boolean', default: false}, 'externalAccess': {type: 'object'}, + 'excludeFromSingleWMS': {type: 'boolean', default: false}, }; /** @@ -91,6 +92,7 @@ export class LayerConfig extends BaseObjectConfig { * @param {boolean} [cfg.mutuallyExclusive] - the layer mutuallyExclusive (only group type) * @param {boolean} [cfg.externalWmsToggle] - the layer provides parameters for external access * @param {object} [cfg.externalAccess] - the layer external access + * @param {boolean} [cfg.excludeFromSingleWMS] - exclude this layer from the project-level single WMS request bundle (no effect when single WMS is not enabled at project level) */ constructor(cfg) { super(cfg, requiredProperties, optionalProperties) @@ -361,6 +363,15 @@ export class LayerConfig extends BaseObjectConfig { get externalAccess() { return this._externalAccess; } + + /** + * Exclude the layer from the project-level single WMS request bundle. + * No effect when the project-level "single WMS layer" option is disabled. + * @type {boolean} + */ + get excludeFromSingleWMS() { + return this._excludeFromSingleWMS; + } } /** diff --git a/assets/src/modules/map.js b/assets/src/modules/map.js index 9040a1af86..379f0d07d4 100644 --- a/assets/src/modules/map.js +++ b/assets/src/modules/map.js @@ -239,7 +239,7 @@ export default class map extends olMap { }); } } else { - if(mapState.singleWMSLayer){ + if(mapState.singleWMSLayer && !node.layerConfig.excludeFromSingleWMS){ this._statesSingleWMSLayers.set(node.name,node); node.singleWMSLayer = true; return diff --git a/tests/end2end/playwright/singleWMS.spec.js b/tests/end2end/playwright/singleWMS.spec.js index e1684c2523..ec5f8ba938 100644 --- a/tests/end2end/playwright/singleWMS.spec.js +++ b/tests/end2end/playwright/singleWMS.spec.js @@ -463,6 +463,53 @@ test.describe('Single WMS layer', () => { await requestSecondBaseLayer.response(); }); + test('Layer with excludeFromSingleWMS is removed from the bundle and requested individually', + { + tag:['@readonly'] + }, async ({ page }) => { + // Mark single_wms_lines as excluded from the single WMS request stack. + // It should then be requested as its own GetMap (still via QGIS Server, + // since no externalAccess is configured on it). + await page.route('**/service/getProjectConfig*', async route => { + const response = await route.fetch(); + const json = await response.json(); + json.layers['single_wms_lines']['excludeFromSingleWMS'] = true; + await route.fulfill({ response, json }); + }); + + const project = new ProjectPage(page, 'single_wms_image'); + const requestBundlePromise = project.waitForSingleWMSGetMapRequest(); + const requestExcludedPromise = project.waitForGetMapRequest('single_wms_lines'); + await project.open(); + + // Stop intercepting once the project is loaded. + await page.unroute('**/service/getProjectConfig*'); + + const requestBundle = await requestBundlePromise; + const expectedBundleParameters = { + 'SERVICE': 'WMS', + 'VERSION': '1.3.0', + 'REQUEST': 'GetMap', + 'FORMAT': 'image/png', + // single_wms_lines is dropped from the bundle, others kept in order + 'STYLES':'default,default,default,', + 'LAYERS':'single_wms_points,single_wms_points_group,single_wms_lines_group,GroupAsLayer', + } + requestExpect(requestBundle).toContainParametersInUrl(expectedBundleParameters); + await requestBundle.response(); + + const requestExcluded = await requestExcludedPromise; + const expectedExcludedParameters = { + 'SERVICE': 'WMS', + 'VERSION': '1.3.0', + 'REQUEST': 'GetMap', + 'FORMAT': 'image/png', + 'LAYERS': 'single_wms_lines', + } + requestExpect(requestExcluded).toContainParametersInUrl(expectedExcludedParameters); + await requestExcluded.response(); + }); + test('Edit a layer', { tag:['@write'] diff --git a/tests/js-units/node/config/layer.test.js b/tests/js-units/node/config/layer.test.js index 6b75529a3f..e76ac063c6 100644 --- a/tests/js-units/node/config/layer.test.js +++ b/tests/js-units/node/config/layer.test.js @@ -59,6 +59,7 @@ describe('LayerConfig', function () { expect(group.cached).to.be.false expect(group.clientCacheExpiration).to.be.eq(300) expect(group.mutuallyExclusive).to.be.true + expect(group.excludeFromSingleWMS).to.be.false const layer1 = new LayerConfig({ "id": "null_island20200414115730489", @@ -128,6 +129,7 @@ describe('LayerConfig', function () { expect(layer1.cached).to.be.false expect(layer1.clientCacheExpiration).to.be.eq(300) expect(layer1.mutuallyExclusive).to.be.false + expect(layer1.excludeFromSingleWMS).to.be.false const layer2 = new LayerConfig({ "abstract": "", @@ -196,6 +198,33 @@ describe('LayerConfig', function () { expect(layer2.cached).to.be.false expect(layer2.clientCacheExpiration).to.be.eq(300) expect(layer2.mutuallyExclusive).to.be.false + expect(layer2.excludeFromSingleWMS).to.be.false + + const excludedLayer = new LayerConfig({ + "id": "excluded_wms_layer", + "name": "excluded_wms_layer", + "type": "layer", + "title": "Excluded layer", + "abstract": "", + "link": "", + "minScale": 1, + "maxScale": 1000000000000, + "toggled": "True", + "popup": "False", + "popupSource": "auto", + "popupTemplate": "", + "popupMaxFeatures": 10, + "popupDisplayChildren": "False", + "groupAsLayer": "False", + "baseLayer": "False", + "displayInLegend": "True", + "singleTile": "True", + "imageFormat": "image/png", + "cached": "False", + "clientCacheExpiration": 300, + "excludeFromSingleWMS": true, + }); + expect(excludedLayer.excludeFromSingleWMS).to.be.true }) it('ValidationError', function () {