From d42870ed71cf2070bd9db058a0d90276502933f9 Mon Sep 17 00:00:00 2001 From: Suren Date: Thu, 7 Dec 2023 22:57:41 +0530 Subject: [PATCH] #9466: Handling GetFeatureInfo exceptions parameter format configuration (#9471) (#9802) Co-authored-by: mahmoud adel <58145645+mahmoudadel54@users.noreply.github.com> --- web/client/epics/__tests__/identify-test.js | 40 ----- web/client/epics/identify.js | 10 +- .../utils/mapinfo/__tests__/wms-test.js | 164 ++++++++++++++++++ web/client/utils/mapinfo/wms.js | 19 +- 4 files changed, 182 insertions(+), 51 deletions(-) create mode 100644 web/client/utils/mapinfo/__tests__/wms-test.js diff --git a/web/client/epics/__tests__/identify-test.js b/web/client/epics/__tests__/identify-test.js index c8b90379ef..4ffe34df9b 100644 --- a/web/client/epics/__tests__/identify-test.js +++ b/web/client/epics/__tests__/identify-test.js @@ -26,7 +26,6 @@ import { LOAD_FEATURE_INFO, NO_QUERYABLE_LAYERS, ERROR_FEATURE_INFO, - EXCEPTIONS_FEATURE_INFO, SHOW_MAPINFO_MARKER, HIDE_MAPINFO_MARKER, GET_VECTOR_INFO, @@ -401,45 +400,6 @@ describe('identify Epics', () => { } }, state); }); - it('getFeatureInfoOnFeatureInfoClick handle server exception', (done) => { - // remove previous hook - registerHook('RESOLUTION_HOOK', undefined); - const state = { - map: TEST_MAP_STATE, - mapInfo: { - clickPoint: { latlng: { lat: 36.95, lng: -79.84 } } - }, - layers: { - flat: [{ - id: "TEST", - "title": "TITLE", - type: "wms", - visibility: true, - url: 'base/web/client/test-resources/featureInfo-exception.json' - }] - } - }; - const sentActions = [featureInfoClick({ latlng: { lat: 36.95, lng: -79.84 } })]; - testEpic(getFeatureInfoOnFeatureInfoClick, 3, sentActions, ([a0, a1, a2]) => { - try { - expect(a0).toExist(); - expect(a0.type).toBe(PURGE_MAPINFO_RESULTS); - expect(a1).toExist(); - expect(a1.type).toBe(NEW_MAPINFO_REQUEST); - expect(a1.reqId).toExist(); - expect(a1.request).toExist(); - expect(a2).toExist(); - expect(a2.type).toBe(EXCEPTIONS_FEATURE_INFO); - expect(a2.exceptions).toExist(); - expect(a2.reqId).toExist(); - expect(a2.requestParams).toExist(); - expect(a2.layerMetadata.title).toBe(state.layers.flat[0].title); - done(); - } catch (ex) { - done(ex); - } - }, state); - }); it('Test local request, remote request and skip background layers', done => { const LAYERS = [{ id: 'OpenTopoMap__3', diff --git a/web/client/epics/identify.js b/web/client/epics/identify.js index 38612091f5..84c8a2f092 100644 --- a/web/client/epics/identify.js +++ b/web/client/epics/identify.js @@ -16,7 +16,7 @@ import { PURGE_MAPINFO_RESULTS, EDIT_LAYER_FEATURES, UPDATE_FEATURE_INFO_CLICK_POINT, featureInfoClick, updateCenterToMarker, purgeMapInfoResults, - exceptionsFeatureInfo, loadFeatureInfo, errorFeatureInfo, + loadFeatureInfo, errorFeatureInfo, noQueryableLayers, newMapInfoRequest, getVectorInfo, showMapinfoMarker, hideMapinfoMarker, setCurrentEditFeatureQuery, SET_MAP_TRIGGER, CLEAR_WARNING @@ -129,12 +129,8 @@ export const getFeatureInfoOnFeatureInfoClick = (action$, { getState = () => { } // this delay allows the panel to open and show the spinner for the first one // this delay mitigates the freezing of the app when there are a great amount of queried layers at the same time .delay(0) - .map((response) => - response.data.exceptions - ? exceptionsFeatureInfo(reqId, response.data.exceptions, requestParams, lMetaData) - : loadFeatureInfo(reqId, response.data, requestParams, { ...lMetaData, features: response.features, featuresCrs: response.featuresCrs }, layer) - ) - .catch((e) => Rx.Observable.of(errorFeatureInfo(reqId, e.data || e.statusText || e.status, requestParams, lMetaData))) + .map((response) =>loadFeatureInfo(reqId, response.data, requestParams, { ...lMetaData, features: response.features, featuresCrs: response.featuresCrs }, layer)) + .catch((e) => Rx.Observable.of(errorFeatureInfo(reqId, e, requestParams, lMetaData))) .concat(Rx.Observable.defer(() => { // update the layout only after the initial response // we don't need to trigger this for each query layer diff --git a/web/client/utils/mapinfo/__tests__/wms-test.js b/web/client/utils/mapinfo/__tests__/wms-test.js new file mode 100644 index 0000000000..f02664f274 --- /dev/null +++ b/web/client/utils/mapinfo/__tests__/wms-test.js @@ -0,0 +1,164 @@ +/* + * Copyright 2023, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import expect from 'expect'; + +import MockAdapter from "axios-mock-adapter"; + +import axios from '../../../libs/ajax'; +import {INFO_FORMATS} from "../../FeatureInfoUtils"; +import {getFeatureInfo} from "../../../api/identify"; +import wms from '../wms'; + +describe('mapinfo wms utils', () => { + let mockAxios; + beforeEach((done) => { + mockAxios = new MockAdapter(axios); + setTimeout(done); + }); + afterEach((done) => { + if (mockAxios) { + mockAxios.restore(); + } + mockAxios = null; + setTimeout(done); + }); + it('should return the response object from getIdentifyFlow in case of 200 with empty features,', (done) => { + const SAMPLE_LAYER = { + type: "wms", + name: "test_layer" + }; + mockAxios.onGet().reply(() => { + return [200, { + "type": "FeatureCollection", + "features": [], + "totalFeatures": "unknown", + "numberReturned": 0, + "timeStamp": "2023-09-22T08:50:30.808Z", + "crs": null + }]; + }); + getFeatureInfo( + "TEST_URL", { + info_format: INFO_FORMATS.PROPERTIES + }, SAMPLE_LAYER + ).subscribe( + n => { + expect(n.data.features.length).toEqual(0); + expect(n.features).toEqual([]); + done(); + }, + error => { + return done(error); + } + ); + }); + it('should return the response object from getIdentifyFlow in case of 200 with features,', (done) => { + const SAMPLE_LAYER = { + type: "wms", + name: "test_layer" + }; + mockAxios.onGet().reply(() => { + return [200, { + "type": "FeatureCollection", + "features": [{}, {}], + "totalFeatures": "unknown", + "numberReturned": 2, + "timeStamp": "2023-09-22T08:50:30.808Z", + "crs": null + }]; + }); + getFeatureInfo( + "TEST_URL", { + info_format: INFO_FORMATS.PROPERTIES + }, SAMPLE_LAYER + ).subscribe( + n => { + expect(n.data.features.length).toEqual(2); + expect(n.features.length).toEqual(2); + done(); + }, + error => { + return done(error); + } + ); + }); + it('should return the response object from getIdentifyFlow in case no data features as a text,', (done) => { + const SAMPLE_LAYER = { + type: "wms", + name: "test_layer" + }; + mockAxios.onGet().reply(() => { + return [200, 'no features were found']; + }); + getFeatureInfo( + "TEST_URL", { + info_format: INFO_FORMATS.TEXT + }, SAMPLE_LAYER + ).subscribe( + n => { + expect(n.data).toEqual('no features were found'); + done(); + }, + error => { + return done(error); + } + ); + }); + it('test intercept ogc error in wms if success', (done)=>{ + mockAxios.onGet().reply(() => { + return [200, { + "type": "FeatureCollection", + "features": [], + "totalFeatures": "unknown", + "numberReturned": 0, + "timeStamp": "2023-09-22T08:50:30.808Z", + "crs": null + }]; + }); + wms + .getIdentifyFlow(undefined, "/", { features: [] }) + .subscribe((response) => { + expect(response?.data?.features).toEqual([]); + done(); + }); + }); + it('test intercept ogc error in wms if exception', (done)=>{ + mockAxios.onGet().reply(() => { + return [200, ` + + + + java.lang.NumberFormatException: For input string: "asd" + For input string: "asd" + + `]; + }); + wms + .getIdentifyFlow(undefined, "/", { features: [] }) + .subscribe((response) => { + expect(response?.data?.exceptions).toExist(); + }, error=>{ + expect(error.name).toEqual('OGCError'); + done(); + }); + }); + it('test intercept ogc error in wms if failed', (done)=>{ + mockAxios.onGet().reply(() => { + return [404, {}]; + }); + wms + .getIdentifyFlow(undefined, "/", { features: [] }) + .subscribe((response) => { + expect(response?.data?.features).toEqual([]); + }, error => { + expect(error.status).toEqual(404); + done(); + }); + }); +}); diff --git a/web/client/utils/mapinfo/wms.js b/web/client/utils/mapinfo/wms.js index 79c8ae54de..b4f5825580 100644 --- a/web/client/utils/mapinfo/wms.js +++ b/web/client/utils/mapinfo/wms.js @@ -6,16 +6,19 @@ * LICENSE file in the root directory of this source tree. */ +import {Observable} from "rxjs"; import {getCurrentResolution} from '../MapUtils'; import {reproject, getProjectedBBox, normalizeSRS} from '../CoordinatesUtils'; import {getLayerUrl} from '../LayersUtils'; import {isObject, isNil} from 'lodash'; import { optionsToVendorParams } from '../VendorParamsUtils'; import { generateEnvString } from '../LayerLocalizationUtils'; - +import axios from "../../libs/ajax"; +// import {parseString} from "xml2js"; +// import {stripPrefix} from "xml2js/lib/processors"; import {addAuthenticationToSLD} from '../SecurityUtils'; import assign from 'object-assign'; - +import { interceptOGCError } from '../ObservableUtils'; export default { /** * Creates the request object and it's metadata for WMS GetFeatureInfo. @@ -59,7 +62,6 @@ export default { service: 'WMS', version: '1.1.1', request: 'GetFeatureInfo', - exceptions: 'application/json', id: layer.id, layers: layer.name, query_layers: queryLayers, @@ -86,5 +88,14 @@ export default { }, url: getLayerUrl(layer).replace(/[?].*$/g, '') }; - } + }, + /** + * Returns an Observable that emits the response when ready. + * @param {object} layer the layer + * @param {string} baseURL the URL for the request + * @param {object} params for the request + */ + getIdentifyFlow: (layer, basePath, params) => + Observable.defer(() => axios.get(basePath, { params })) + .let(interceptOGCError) };