Skip to content

Commit 53bc9ab

Browse files
authored
Merge pull request #5792 from rldhont/fix-getlegendgraphic-get-method
Fix WMS GetLegendGraphic to perform request with get method with single layer
2 parents 95e830a + e3f0efd commit 53bc9ab

File tree

7 files changed

+312
-82
lines changed

7 files changed

+312
-82
lines changed

assets/src/modules/Errors.js

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,41 @@
66
* @license MPL-2.0
77
*/
88

9+
/**
10+
* Representing a request error with message and parameters before fetching
11+
* @class
12+
* @augments Error
13+
* @property {string} name - Error name: RequestError
14+
* @property {string} message - Error message
15+
* @property {object} parameters - The request parameters
16+
*/
17+
export class RequestError extends Error {
18+
19+
/**
20+
* Creating a request error with message and parameters before fetching
21+
* @param {string} message - Error message
22+
* @param {object} params - The request parameters
23+
*/
24+
constructor(message, params) {
25+
super(message);
26+
this.name = "RequestError";
27+
this.parameters = params;
28+
}
29+
}
30+
931
/**
1032
* Representing a network error with message, resource and options fetched
1133
* @class
1234
* @augments Error
35+
* @property {string} name - Error name: NetworkError
36+
* @property {string} message - Error message
37+
* @property {string} resource - The resource has been fetched
38+
* @property {string} options - The resource options
1339
*/
14-
class NetworkError extends Error {
40+
export class NetworkError extends Error {
1541

1642
/**
17-
* Creating an HTTP error with message and status code
43+
* Creating an HTTP error with message, resource and options fetched
1844
* @param {string} message - Error message
1945
* @param {string} resource - The resource has been fetched
2046
* @param {string} options - The resource options
@@ -30,9 +56,14 @@ class NetworkError extends Error {
3056
/**
3157
* Representing an HTTP error with message, status code, resource and options fetched
3258
* @class
33-
* @augments Error
59+
* @augments NetworkError
60+
* @property {string} name - Error name: HttpError
61+
* @property {string} message - Error message
62+
* @property {string} resource - The resource has been fetched
63+
* @property {string} options - The resource options
64+
* @property {number} statusCode - HTTP Error status code
3465
*/
35-
class HttpError extends NetworkError {
66+
export class HttpError extends NetworkError {
3667

3768
/**
3869
* Creating an HTTP error with message and status code
@@ -42,34 +73,36 @@ class HttpError extends NetworkError {
4273
* @param {string} options - The resource options
4374
*/
4475
constructor(message, statusCode, resource, options) {
45-
super(message);
76+
super(message, resource, options);
4677
this.name = "HttpError";
4778
this.statusCode = statusCode;
48-
this.resource = resource;
49-
this.options = options;
5079
}
5180
}
5281

5382
/**
54-
* Representing an HTTP error with message, response, resource and options fetched
83+
* Representing an response error with message, response, resource and options fetched
5584
* @class
56-
* @augments Error
85+
* @augments HttpError
86+
* @property {string} name - Error name: ResponseError
87+
* @property {string} message - Error message
88+
* @property {string} resource - The resource has been fetched
89+
* @property {string} options - The resource options
90+
* @property {number} statusCode - HTTP Error status code
91+
* @property {Response} response - Response object from fetch
5792
*/
58-
class ResponseError extends NetworkError {
93+
export class ResponseError extends HttpError {
5994

6095
/**
6196
* Creating an HTTP error with message and status code
6297
* @param {string} message - Error message
63-
* @param {Response} response - HTTP Error status code
98+
* @param {Response} response - Response object from fetch
6499
* @param {string} resource - The resource has been fetched
65100
* @param {string} options - The resource options
66101
*/
67102
constructor(message, response, resource, options) {
68-
super(message);
103+
super(message, response.status, resource, options);
69104
this.name = "ResponseError";
70105
this.response = response;
71-
this.resource = resource;
72-
this.options = options;
73106
}
74107
}
75108

@@ -78,7 +111,7 @@ class ResponseError extends NetworkError {
78111
* @class
79112
* @augments Error
80113
*/
81-
class ConversionError extends Error {
114+
export class ConversionError extends Error {
82115

83116
/**
84117
* Creating a conversion error
@@ -96,7 +129,7 @@ class ConversionError extends Error {
96129
* @class
97130
* @augments Error
98131
*/
99-
class ValidationError extends Error {
132+
export class ValidationError extends Error {
100133

101134
/**
102135
* Creating a validation error
@@ -114,7 +147,7 @@ class ValidationError extends Error {
114147
* @class
115148
* @augments ValidationError
116149
*/
117-
class PropertyRequiredError extends ValidationError {
150+
export class PropertyRequiredError extends ValidationError {
118151

119152
/**
120153
* Creating a property required error
@@ -126,5 +159,3 @@ class PropertyRequiredError extends ValidationError {
126159
}
127160

128161
}
129-
130-
export { NetworkError, HttpError, ResponseError, ConversionError, ValidationError, PropertyRequiredError };

assets/src/modules/WMS.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import { Utils } from './Utils.js';
9+
import { RequestError } from './Errors.js';
910

1011
/**
1112
* @class
@@ -54,15 +55,35 @@ export default class WMS {
5455
* Get legend graphic from WMS
5556
* @param {object} options - optional parameters which can override this._defaultGetLegendGraphicsParameters
5657
* @returns {Promise} Promise object represents data
58+
* @throws {HttpError} In case of not successful response (status not in the range 200 – 299)
59+
* @throws {NetworkError} In case of catch exceptions
5760
* @memberof WMS
5861
*/
5962
async getLegendGraphic(options) {
63+
const layers = options['LAYERS'] ?? options['LAYER'];
64+
// Check if layer is specified
65+
if (!layers) {
66+
return Promise.reject(
67+
new RequestError(
68+
'LAYERS or LAYER parameter is required for getLegendGraphic request',
69+
options,
70+
)
71+
);
72+
}
73+
const params = new URLSearchParams({
74+
...this._defaultGetLegendGraphicParameters,
75+
...options
76+
});
77+
// Check if multiple layers are specified
78+
if ((Array.isArray(layers) && layers.length == 1) ||
79+
(!Array.isArray(layers) && layers.split(',').length == 1)) {
80+
// Use GET request for single layer
81+
return Utils.fetchJSON(`${globalThis['lizUrls'].wms}?${params}`);
82+
}
83+
// Use POST request for multiple layers
6084
return Utils.fetchJSON(globalThis['lizUrls'].wms, {
6185
method: "POST",
62-
body: new URLSearchParams({
63-
...this._defaultGetLegendGraphicParameters,
64-
...options
65-
})
86+
body: params,
6687
});
6788
}
6889
}

lizmap/modules/lizmap/controllers/service.classic.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,12 +671,25 @@ protected function GetMap($wmsRequest)
671671
*/
672672
protected function GetLegendGraphics($wmsRequest)
673673
{
674-
$result = $wmsRequest->process();
675-
676674
/** @var jResponseBinary $rep */
677675
$rep = $this->getResponse('binary');
676+
// Etag header and cache control
677+
$etag = $this->buildEtag('GetLegendGraphic');
678+
$respCanBeCached = $this->canBeCached();
679+
if ($respCanBeCached && $rep->isValidCache(null, $etag)) {
680+
$this->setACAOHeader($rep);
681+
682+
return $rep;
683+
}
684+
685+
$result = $wmsRequest->process();
678686
$this->setupBinaryResponse($rep, $result, 'qgis_server_legend');
679687

688+
if ($respCanBeCached) {
689+
$this->setEtagCacheHeaders($rep, $etag);
690+
$this->setACAOHeader($rep);
691+
}
692+
680693
return $rep;
681694
}
682695

tests/end2end/playwright/globals.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import * as path from 'path';
1414
* @typedef {import('@playwright/test').APIResponse} APIResponse
1515
*/
1616

17+
/**
18+
* Playwright APIRequestContext
19+
* @typedef {import('@playwright/test').APIRequestContext} APIRequestContext
20+
*/
21+
1722
/**
1823
* Integer
1924
* @typedef {number} int
@@ -122,7 +127,7 @@ export async function gotoMap(url, page, mapMustLoad = true, layersInTreeView =
122127

123128
await expect(async () => {
124129
const response = await page.goto(url);
125-
expect(response.status()).toBeLessThan(400);
130+
expect(response?.status()).toBeLessThan(400);
126131
}).toPass({
127132
intervals: [1_000, 2_000, 10_000],
128133
timeout: 60_000
@@ -134,9 +139,14 @@ export async function gotoMap(url, page, mapMustLoad = true, layersInTreeView =
134139
if (waitForGetLegendGraphic) {
135140
// Wait for WMS GetLegendGraphic promise
136141
const getLegendGraphicPromise = page.waitForRequest(
137-
request => request.method() === 'POST' &&
142+
request => (
143+
request.method() === 'POST' &&
138144
request.postData() != null &&
139145
request.postData()?.includes('GetLegendGraphic') === true
146+
) || (
147+
request.method() === 'GET' &&
148+
request.url().includes('GetLegendGraphic') === true
149+
)
140150
);
141151
// Normal check about the map
142152
// Wait for WMS GetLegendGraphic
@@ -167,7 +177,7 @@ export async function reloadMap(page, check = true) {
167177

168178
await expect(async () => {
169179
const response = await page.reload();
170-
expect(response.status()).toBeLessThan(400);
180+
expect(response?.status()).toBeLessThan(400);
171181
}).toPass({
172182
intervals: [1_000, 2_000, 10_000],
173183
timeout: 60_000
@@ -178,10 +188,14 @@ export async function reloadMap(page, check = true) {
178188
if (check) {
179189
// Wait for WMS GetLegendGraphic promise
180190
const getLegendGraphicPromise = page.waitForRequest(
181-
request =>
191+
request => (
182192
request.method() === 'POST' &&
183193
request.postData() != null &&
184194
request.postData()?.includes('GetLegendGraphic') === true
195+
) || (
196+
request.method() === 'GET' &&
197+
request.url().includes('GetLegendGraphic') === true
198+
)
185199
);
186200
// Normal check about the map
187201
// Wait for WMS GetLegendGraphic
@@ -245,7 +259,7 @@ export async function expectToHaveLengthCompare(title, parameters, expectedLengt
245259

246260
/**
247261
* Get the JSON for the given project using the API
248-
* @param {import("playwright-core/types/types.js").APIRequestContext} request Request to use
262+
* @param {APIRequestContext} request Request to use
249263
* @param {string} project The project name
250264
* @param {string} repository The repository name, default to "testsrepository".
251265
* @returns {Promise<any>} The JSON response
@@ -259,9 +273,9 @@ export async function jsonFromProjectApi(request, project, repository = 'testsre
259273

260274
/**
261275
* Get the version of QGIS written in the project
262-
* @param {import("playwright-core/types/types.js").APIRequestContext} request Request to use
276+
* @param {APIRequestContext} request Request to use
263277
* @param {string} project The project name
264-
* @returns {int} The QGIS version, written as "34004" for QGIS 3.40.4, to be easily sortable.
278+
* @returns {Promise<int>} The QGIS version, written as "34004" for QGIS 3.40.4, to be easily sortable.
265279
*/
266280
export async function qgisVersionFromProjectApi(request, project) {
267281
const response = await jsonFromProjectApi(request, project);
@@ -284,12 +298,11 @@ export async function checkJson(response, status = 200) {
284298
return await response.json();
285299
}
286300

287-
/* eslint-disable jsdoc/check-types */
288301
/**
289302
* Check parameters against an object containing expected parameters
290303
* @param {string} title Check title, for testing and debug
291304
* @param {string} parameters List of parameters to check
292-
* @param {Object<string, string|RegExp>} expectedParameters List of expected parameters
305+
* @param {{[key: string]: string|RegExp}} expectedParameters List of expected parameters
293306
* @returns {Promise<URLSearchParams>} List of promise with parameters
294307
*/
295308
export async function expectParametersToContain(title, parameters, expectedParameters) {
@@ -332,9 +345,9 @@ const adminPassword = "Basic " + btoa("admin:admin");
332345

333346
/**
334347
* Create a GET request on a given URL with Basic authentication admin:admin
335-
* @param {import("playwright-core/types/types.js").APIRequestContext} request Request to use
348+
* @param {APIRequestContext} request Request to use
336349
* @param {string} url URL to do a GET request on
337-
* @returns {Promise<import("playwright-core/types/types.js").APIResponse>} Response
350+
* @returns {Promise<APIResponse>} Response
338351
*/
339352
export async function requestGETWithAdminBasicAuth(request, url) {
340353
return await request.get(url,
@@ -347,10 +360,10 @@ export async function requestGETWithAdminBasicAuth(request, url) {
347360

348361
/**
349362
* Create a POST request on a given URL with Basic authentication admin:admin
350-
* @param {import("playwright-core/types/types.js").APIRequestContext} request Request to use
363+
* @param {APIRequestContext} request Request to use
351364
* @param {string} url URL to do a POST request on
352365
* @param {object} data parameters for the request
353-
* @returns {Promise<import("playwright-core/types/types.js").APIResponse>} Response
366+
* @returns {Promise<APIResponse>} Response
354367
*/
355368
export async function requestPOSTWithAdminBasicAuth(request, url, data) {
356369
return await request.post(url,
@@ -364,10 +377,10 @@ export async function requestPOSTWithAdminBasicAuth(request, url, data) {
364377

365378
/**
366379
* Create a DELETE request on a given URL with Basic authentication admin:admin
367-
* @param {import("playwright-core/types/types.js").APIRequestContext} request Request to use
380+
* @param {APIRequestContext} request Request to use
368381
* @param {string} url URL to do a DELETE request on
369382
* @param {object} data parameters for the request
370-
* @returns {Promise<import("playwright-core/types/types.js").APIResponse>} Response
383+
* @returns {Promise<APIResponse>} Response
371384
*/
372385
export async function requestDELETEWithAdminBasicAuth(request, url, data) {
373386
return await request.delete(url,
@@ -378,4 +391,3 @@ export async function requestDELETEWithAdminBasicAuth(request, url, data) {
378391
data: data
379392
});
380393
}
381-
/* eslint-enable jsdoc/check-types */

0 commit comments

Comments
 (0)