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 () {