diff --git a/web/client/actions/__tests__/mapInfo-test.js b/web/client/actions/__tests__/mapInfo-test.js index cb444f3fa5..e8dd1a585a 100644 --- a/web/client/actions/__tests__/mapInfo-test.js +++ b/web/client/actions/__tests__/mapInfo-test.js @@ -8,9 +8,6 @@ var expect = require('expect'); var { - ERROR_FEATURE_INFO, - EXCEPTIONS_FEATURE_INFO, - LOAD_FEATURE_INFO, CHANGE_MAPINFO_STATE, NEW_MAPINFO_REQUEST, PURGE_MAPINFO_RESULTS, @@ -20,7 +17,6 @@ var { GET_VECTOR_INFO, TOGGLE_MAPINFO_STATE, UPDATE_CENTER_TO_MARKER, - getFeatureInfo, changeMapInfoState, newMapInfoRequest, purgeMapInfoResults, @@ -36,108 +32,6 @@ var { describe('Test correctness of the map actions', () => { - it('get feature info data', (done) => { - /*eslint-disable */ - let reqId; - /*eslint-enable */ - getFeatureInfo('base/web/client/test-resources/featureInfo-response.json', {p: "p"}, "meta")((e) => { - if (e.type === NEW_MAPINFO_REQUEST) { - reqId = e.reqId; - try { - expect(e).toExist(); - expect(e.type).toBe(NEW_MAPINFO_REQUEST); - expect(e.reqId).toExist(); - expect(e.request).toExist(); - expect(e.request.p).toBe("p"); - } catch (ex) { - done(ex); - } - } else if (e.type === LOAD_FEATURE_INFO) { - try { - expect(e).toExist(); - expect(e.type).toBe(LOAD_FEATURE_INFO); - expect(e.data).toExist(); - expect(e.requestParams).toExist(); - expect(e.reqId).toExist(); - expect(e.reqId).toBe(reqId); - expect(e.requestParams.p).toBe("p"); - expect(e.layerMetadata).toBe("meta"); - done(); - } catch (ex) { - done(ex); - } - } - }); - }); - - it('get feature info exception', (done) => { - /*eslint-disable */ - let reqId; - /*eslint-enable */ - getFeatureInfo('base/web/client/test-resources/featureInfo-exception.json', {p: "p"}, "meta")((e) => { - if (e.type === NEW_MAPINFO_REQUEST) { - reqId = e.reqId; - try { - expect(e).toExist(); - expect(e.type).toBe(NEW_MAPINFO_REQUEST); - expect(e.reqId).toExist(); - expect(e.request).toExist(); - expect(e.request.p).toBe("p"); - } catch (ex) { - done(ex); - } - } else if (e.type === EXCEPTIONS_FEATURE_INFO) { - try { - expect(e).toExist(); - expect(e.type).toBe(EXCEPTIONS_FEATURE_INFO); - expect(e.exceptions).toExist(); - expect(e.reqId).toExist(); - expect(e.reqId).toBe(reqId); - expect(e.requestParams).toExist(); - expect(e.requestParams.p).toBe("p"); - expect(e.layerMetadata).toBe("meta"); - done(); - } catch (ex) { - done(ex); - } - } - }); - }); - - it('get feature info error', (done) => { - /*eslint-disable */ - let reqId; - /*eslint-enable */ - getFeatureInfo('requestError.json', {p: "p"}, "meta")((e) => { - if (e.type === NEW_MAPINFO_REQUEST) { - reqId = e.reqId; - try { - expect(e).toExist(); - expect(e.type).toBe(NEW_MAPINFO_REQUEST); - expect(e.reqId).toExist(); - expect(e.request).toExist(); - expect(e.request.p).toBe("p"); - } catch (ex) { - done(ex); - } - } else if (e.type === ERROR_FEATURE_INFO) { - try { - expect(e).toExist(); - expect(e.type).toBe(ERROR_FEATURE_INFO); - expect(e.error).toExist(); - expect(e.reqId).toExist(); - expect(e.reqId).toBe(reqId); - expect(e.requestParams).toExist(); - expect(e.requestParams.p).toBe("p"); - expect(e.layerMetadata).toBe("meta"); - done(); - } catch (ex) { - done(ex); - } - } - }); - }); - it('gets vector info', () => { const retval = getVectorInfo('layer', 'request', 'metadata'); diff --git a/web/client/actions/mapInfo.js b/web/client/actions/mapInfo.js index 6102781082..544e793e8e 100644 --- a/web/client/actions/mapInfo.js +++ b/web/client/actions/mapInfo.js @@ -5,10 +5,6 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ - -const assign = require('object-assign'); -const axios = require('axios'); -const uuid = require('uuid'); const GeoCodingApi = require('../api/Nominatim'); const LOAD_FEATURE_INFO = 'LOAD_FEATURE_INFO'; @@ -48,7 +44,7 @@ function loadFeatureInfo(reqId, data, rParams, lMetaData) { /** * Private - * @return a ERROR_FEATURE_INFO action with the error occured + * @return a ERROR_FEATURE_INFO action with the error occurred */ function errorFeatureInfo(reqId, e, rParams, lMetaData) { return { @@ -62,7 +58,7 @@ function errorFeatureInfo(reqId, e, rParams, lMetaData) { /** * Private - * @return a EXCEPTIONS_FEATURE_INFO action with the wms exception occured + * @return a EXCEPTIONS_FEATURE_INFO action with the wms exception occurred * during a GetFeatureInfo request. */ function exceptionsFeatureInfo(reqId, exceptions, rParams, lMetaData) { @@ -104,31 +100,6 @@ function getVectorInfo(layer, request, metadata) { }; } - -/** - * Sends a GetFeatureInfo request and dispatches the right action - * in case of success, error or exceptions. - * - * @param basePath {string} base path to the service - * @param requestParams {object} map of params for a getfeatureinfo request. - */ -function getFeatureInfo(basePath, requestParams, lMetaData, options = {}) { - const param = assign({}, options, requestParams); - const reqId = uuid.v1(); - return (dispatch) => { - dispatch(newMapInfoRequest(reqId, param)); - axios.get(basePath, {params: param}).then((response) => { - if (response.data.exceptions) { - dispatch(exceptionsFeatureInfo(reqId, response.data.exceptions, requestParams, lMetaData)); - } else { - dispatch(loadFeatureInfo(reqId, response.data, requestParams, lMetaData)); - } - }).catch((e) => { - dispatch(errorFeatureInfo(reqId, e.data || e.statusText || e.status, requestParams, lMetaData)); - }); - }; -} - function changeMapInfoState(enabled) { return { type: CHANGE_MAPINFO_STATE, @@ -263,7 +234,7 @@ module.exports = { TOGGLE_SHOW_COORD_EDITOR, toggleShowCoordinateEditor, CHANGE_FORMAT, changeFormat, closeIdentify, - getFeatureInfo, + exceptionsFeatureInfo, changeMapInfoState, newMapInfoRequest, purgeMapInfoResults, diff --git a/web/client/components/data/identify/Identify.jsx b/web/client/components/data/identify/Identify.jsx deleted file mode 100644 index efd6f8bb3e..0000000000 --- a/web/client/components/data/identify/Identify.jsx +++ /dev/null @@ -1,319 +0,0 @@ -const PropTypes = require('prop-types'); -/** - * Copyright 2016, 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. - */ - -const React = require('react'); -const {Panel, Glyphicon, Modal} = require('react-bootstrap'); -const {findIndex, isArray} = require('lodash'); - -require('./css/identify.css'); - -const Draggable = require('react-draggable'); - -const MapInfoUtils = require('../../../utils/MapInfoUtils'); -const Spinner = require('../../misc/spinners/BasicSpinner/BasicSpinner'); -const Message = require('../../I18N/Message'); -const DefaultViewer = require('./DefaultViewer'); -const GeocodeViewer = require('./GeocodeViewer'); -const Dialog = require('../../misc/Dialog'); - -class Identify extends React.Component { - static propTypes = { - enabled: PropTypes.bool, - draggable: PropTypes.bool, - collapsible: PropTypes.bool, - style: PropTypes.object, - point: PropTypes.object, - layer: PropTypes.string, - format: PropTypes.string, - map: PropTypes.object, - layers: PropTypes.array, - buffer: PropTypes.number, - requests: PropTypes.array, - responses: PropTypes.array, - viewerOptions: PropTypes.object, - viewer: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - purgeResults: PropTypes.func, - noQueryableLayers: PropTypes.func, - clearWarning: PropTypes.func, - queryableLayersFilter: PropTypes.func, - buildRequest: PropTypes.func, - sendRequest: PropTypes.func, - localRequest: PropTypes.func, - showMarker: PropTypes.func, - hideMarker: PropTypes.func, - changeMousePointer: PropTypes.func, - maxItems: PropTypes.number, - excludeParams: PropTypes.array, - includeOptions: PropTypes.array, - showRevGeocode: PropTypes.func, - hideRevGeocode: PropTypes.func, - showModalReverse: PropTypes.bool, - reverseGeocodeData: PropTypes.object, - enableRevGeocode: PropTypes.bool, - wrapRevGeocode: PropTypes.bool, - panelClassName: PropTypes.string, - headerClassName: PropTypes.string, - bodyClassName: PropTypes.string, - asPanel: PropTypes.bool, - headerGlyph: PropTypes.string, - closeGlyph: PropTypes.string, - allowMultiselection: PropTypes.bool, - warning: PropTypes.string, - currentLocale: PropTypes.string, - fullscreen: PropTypes.bool - }; - - static defaultProps = { - enabled: false, - draggable: true, - collapsible: false, - format: MapInfoUtils.getDefaultInfoFormatValue(), - requests: [], - responses: [], - buffer: 2, - viewerOptions: {}, - viewer: DefaultViewer, - purgeResults: () => {}, - buildRequest: MapInfoUtils.buildIdentifyRequest, - localRequest: () => {}, - sendRequest: () => {}, - showMarker: () => {}, - hideMarker: () => {}, - noQueryableLayers: () => {}, - clearWarning: () => {}, - changeMousePointer: () => {}, - showRevGeocode: () => {}, - hideRevGeocode: () => {}, - containerProps: { - continuous: false - }, - showModalReverse: false, - reverseGeocodeData: {}, - enableRevGeocode: true, - wrapRevGeocode: false, - queryableLayersFilter: MapInfoUtils.defaultQueryableFilter, - style: {}, - point: {}, - layer: null, - map: {}, - layers: [], - maxItems: 10, - excludeParams: ["SLD_BODY"], - includeOptions: [ - "buffer", - "cql_filter", - "filter", - "propertyName" - ], - panelClassName: "modal-dialog info-panel modal-content", - headerClassName: "modal-header", - bodyClassName: "modal-body info-wrap", - asPanel: false, - headerGlyph: "", - closeGlyph: "1-close", - className: "square-button", - allowMultiselection: false, - currentLocale: 'en-US', - fullscreen: false - }; - - state = { - fullClass: '' - }; - componentDidMount() { - if (this.props.enabled) { - this.props.changeMousePointer('pointer'); - } - } - componentWillReceiveProps(newProps) { - if (this.needsRefresh(newProps)) { - if (!newProps.point.modifiers || newProps.point.modifiers.ctrl !== true || !newProps.allowMultiselection) { - this.props.purgeResults(); - } - const queryableLayers = isArray(newProps.layers) && (newProps.queryableLayersFilter && newProps.layers - .filter(newProps.queryableLayersFilter) || newProps.layers) || []; - /* - * .filter(newProps.layer ? l => l.id === newProps.layer : () => true); - * this line was filtering too much, i.e. see issue #3344 - */ - queryableLayers.forEach((layer) => { - const {url, request, metadata} = this.props.buildRequest(layer, newProps); - if (url) { - this.props.sendRequest(url, request, metadata, this.filterRequestParams(layer)); - } else { - this.props.localRequest(layer, request, metadata); - } - - }); - if (queryableLayers.length === 0) { - this.props.noQueryableLayers(); - } else { - if (!newProps.layer) { - this.props.showMarker(); - } else { - this.props.hideMarker(); - } - } - - } - - if (newProps.enabled && !this.props.enabled) { - this.props.changeMousePointer('pointer'); - } else if (!newProps.enabled && this.props.enabled) { - this.props.changeMousePointer('auto'); - this.props.hideMarker(); - this.props.purgeResults(); - } - } - - onModalHiding = () => { - this.props.hideMarker(); - this.props.purgeResults(); - }; - - renderHeader = (missing) => { - return ( -
- { missing !== 0 ? : null } - {this.props.fullscreen ? { this.setFullscreen(); }} glyph={this.state.fullscreen ? 'chevron-down' : 'chevron-up'} /> : null}  - {this.props.headerGlyph ? : null}  - -
- ); - }; - - renderResults = (missingResponses) => { - const Viewer = this.props.viewer; - return ; - }; - - renderReverseGeocode = (latlng) => { - if (this.props.enableRevGeocode) { - let reverseGeocodeData = this.props.reverseGeocodeData; - const Viewer = (} - revGeocodeDisplayName={reverseGeocodeData.error ? : this.props.reverseGeocodeData.display_name} - hideRevGeocode={this.props.hideRevGeocode} - identifyRevGeocodeSubmitText={} - identifyRevGeocodeCloseText={} - modalOptions={{bsClass: 'mapstore-identify-modal modal'}} />); - return this.props.wrapRevGeocode ? -  }> - {Viewer} - - :
{Viewer}
; - } - return null; - }; - - renderContent = () => { - let missingResponses = this.props.requests.length - this.props.responses.length; - let latlng = this.props.point.latlng; - return this.props.asPanel ? - -
- {this.renderHeader(missingResponses)} -
- {this.renderReverseGeocode(latlng)} - {this.renderResults(missingResponses)} -
- : - - {this.renderHeader(missingResponses)} -
- {this.renderReverseGeocode(latlng)} - {this.renderResults(missingResponses)} -
-
- ; - }; - - render() { - if (this.props.enabled && this.props.requests.length !== 0) { - return this.props.draggable && this.props.asPanel ? - - {this.renderContent()} - - : this.renderContent(); - } - if (this.props.warning) { - return ( { - this.props.clearWarning(); - }}> - - - - -
-
- - -
); - } - return null; - } - - needsRefresh = (props) => { - if (props.enabled && props.point && props.point.pixel) { - if (!this.props.point || !this.props.point.pixel || - this.props.point.pixel.x !== props.point.pixel.x || - this.props.point.pixel.y !== props.point.pixel.y ) { - return true; - } - if (!this.props.point || !this.props.point.pixel || props.point.pixel && this.props.format !== props.format) { - return true; - } - } - return false; - }; - - filterRequestParams = (layer) => { - let includeOpt = this.props.includeOptions || []; - let excludeList = this.props.excludeParams || []; - let options = Object.keys(layer).reduce((op, next) => { - if (next !== "params" && includeOpt.indexOf(next) !== -1) { - op[next] = layer[next]; - } else if (next === "params" && excludeList.length > 0) { - let params = layer[next]; - Object.keys(params).forEach((n) => { - if (findIndex(excludeList, (el) => {return el === n; }) === -1) { - op[n] = params[n]; - } - }, {}); - } - return op; - }, {}); - return options; - }; - - setFullscreen = () => { - const fullscreen = !this.state.fullscreen; - this.setState({ - fullscreen, - fullClass: fullscreen ? ' fullscreen' : '' - }); - }; -} - -module.exports = Identify; diff --git a/web/client/components/data/identify/__tests__/Identify-test.jsx b/web/client/components/data/identify/__tests__/Identify-test.jsx deleted file mode 100644 index 0ac01a2511..0000000000 --- a/web/client/components/data/identify/__tests__/Identify-test.jsx +++ /dev/null @@ -1,471 +0,0 @@ -/** - * Copyright 2016, 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. - */ -const React = require('react'); -const ReactDOM = require('react-dom'); - -const Identify = require('../Identify.jsx'); -const TestUtils = require('react-dom/test-utils'); -const expect = require('expect'); - -describe('Identify', () => { - - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - it('creates the Identify component with defaults', () => { - const identify = ReactDOM.render( - , - document.getElementById("container") - ); - - expect(identify).toExist(); - }); - - it('creates the Identify component with available requests', () => { - const identify = ReactDOM.render( - , - document.getElementById("container") - ); - - expect(identify).toExist(); - const dom = ReactDOM.findDOMNode(identify); - expect(dom.parentNode.getElementsByClassName('info-panel').length).toBe(1); - }); - - it('creates the Identify component with missing responses', () => { - const identify = ReactDOM.render( - , - document.getElementById("container") - ); - - expect(identify).toExist(); - const dom = ReactDOM.findDOMNode(identify); - expect(dom.getElementsByClassName('spinner').length).toBe(1); - }); - - it('creates the Identify component with no missing responses', () => { - const identify = ReactDOM.render( - , - document.getElementById("container") - ); - - expect(identify).toExist(); - const dom = ReactDOM.findDOMNode(identify); - expect(dom.getElementsByClassName('spinner').length).toBe(0); - }); - - it('creates the Identify component changes mousepointer on enable / disable', () => { - - const testHandlers = { - changeMousePointer: () => {} - }; - - const spyMousePointer = expect.spyOn(testHandlers, 'changeMousePointer'); - - ReactDOM.render( - , - document.getElementById("container") - ); - ReactDOM.render( - , - document.getElementById("container") - ); - expect(spyMousePointer.calls.length).toEqual(1); - ReactDOM.render( - , - document.getElementById("container") - ); - expect(spyMousePointer.calls.length).toEqual(2); - }); - - it('creates the Identify component sends requests on point', () => { - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({url: "myurl"})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({url: "myurl"})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(2); - }); - - it('creates the Identify component which sends requests on point and 2 other layers (toppstates and a background)', () => { - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - ({url: "myurl"})} - />, - document.getElementById("container") - ); - const layers = [ - { - id: 'OpenTopoMap__3', - group: 'background', - source: 'OpenTopoMap', - name: 'OpenTopoMap', - title: 'OpenTopoMap', - type: 'tileprovider', - visibility: false, - handleClickOnLayer: false, - hidden: false - }, - { - id: 'topp:states__4', - name: 'topp:states', - title: 'USA Population', - type: 'wms', - url: 'https://demo.geo-solutions.it:443/geoserver/wms', - visibility: true, - handleClickOnLayer: false, - hidden: false - }, - { - id: 'annotations', - features: [], - name: 'Annotations', - type: 'vector', - visibility: true, - handleClickOnLayer: true, - hidden: false - } - ]; - ReactDOM.render( - ({url: "myurl"})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(2); - }); - - it('creates the Identify component sends local requess on point if no url is specified', () => { - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} localRequest={testHandlers.sendRequest} buildRequest={() => ({url: ""})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} localRequest={testHandlers.sendRequest} buildRequest={() => ({url: ""})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(2); - }); - - it('creates the Identify component does not send requests on point if disabled', () => { - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - true} - enabled={false} layers={[{}, {}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled={false} layers={[{}, {}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(0); - }); - - it('creates the Identify component filters layers', () => { - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - layer.type === "wms"} - enabled layers={[{type: "wms"}, {type: "osm"}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({url: "myurl"})} - />, - document.getElementById("container") - ); - ReactDOM.render( - layer.type === "wms"} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{type: "wms"}, {type: "osm"}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({url: "myurl"})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(1); - }); - - it('creates the Identify component shows marker on point', () => { - const testHandlers = { - showMarker: () => {}, - hideMarker: () => {} - }; - - const spyShowMarker = expect.spyOn(testHandlers, 'showMarker'); - const spyHideMarker = expect.spyOn(testHandlers, 'hideMarker'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyShowMarker.calls.length).toEqual(1); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled={false} layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyHideMarker.calls.length).toEqual(1); - }); - - it('creates the Identify component no queryable layer', () => { - const testHandlers = { - noQueryableLayers: () => {} - }; - - const spyNoQueryableLayers = expect.spyOn(testHandlers, 'noQueryableLayers'); - - ReactDOM.render( - false} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - ReactDOM.render( - false} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyNoQueryableLayers.calls.length).toEqual(1); - }); - - it('creates the Identify component purge results on point', () => { - const testHandlers = { - purgeResults: () => {} - }; - - const spyPurgeResults = expect.spyOn(testHandlers, 'purgeResults'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(1); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled={false} layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(2); - }); - - it('creates the Identify component does not purge if multiselection enabled', () => { - const testHandlers = { - purgeResults: () => {} - }; - - const spyPurgeResults = expect.spyOn(testHandlers, 'purgeResults'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - multiSelection - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - modifiers={{ctrl: false}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - multiSelection - />, - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(1); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - modifiers={{ctrl: true}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - multiSelection - />, - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(1); - }); - - it('creates the Identify component uses custom viewer', () => { - const Viewer = (props) => {props.responses.length}; - const identify = ReactDOM.render( - true} - viewer={Viewer} - requests={[{}]} - enabled layers={[{}, {}]} responses={[{}, {}]} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - const dom = ReactDOM.findDOMNode(identify); - const viewer = dom.getElementsByClassName("myviewer"); - expect(viewer.length).toBe(1); - expect(viewer[0].innerHTML).toBe('2'); - }); - - it('test options and parameters filtering', () => { - const Viewer = (props) => {props.responses.length}; - const layer = { - INTERNAL_OPTION: true, - WMS_OPTION: true, - params: { - ONLY_GETMAP: true, - WMS_PARAMETER_TO_SHARE: true - }}; - const identify = ReactDOM.render( - true} - point={{latlng: {lat: 40, lng: 10}}} - viewer={Viewer} - enabled - layers={[layer]} - sendRequest={[{}, {}]} - buildRequest={() => ({})} - requests={[{}]} - reverseGeocodeData={{display_name: "test"}} />, - document.getElementById("container") - ); - expect(identify).toExist(); - let params = identify.filterRequestParams(layer); - expect(params).toExist(); - expect(params.ONLY_GETMAP).toNotExist(); - expect(params.INTERNAL_OPTION).toNotExist(); - expect(params.WMS_PARAMETER_TO_SHARE).toBe(true); - expect(params.WMS_OPTION).toBe(true); - - }); - - it('test need refresh with null point', () => { - const identify = ReactDOM.render( - , - document.getElementById("container") - ); - expect(identify).toExist(); - expect(identify.needsRefresh({ enabled: true, point: { pixel: {x: 0, y: 0}}})).toBe(true); - }); - - it('test click/touch on header fullscreen false', () => { - const identify = ReactDOM.render( - , - document.getElementById("container") - ); - expect(identify).toExist(); - const arrow = document.getElementsByClassName('m-fullscreen-btn'); - expect(arrow.length).toBe(0); - }); - - it('test click/touch on header fullscreen true', () => { - const identify = ReactDOM.render( - , - document.getElementById("container") - ); - expect(identify).toExist(); - const arrow = document.getElementsByClassName('m-fullscreen-btn'); - expect(arrow.length).toBe(1); - TestUtils.Simulate.click(arrow[0]); - expect(document.getElementsByClassName('fullscreen').length).toBe(1); - TestUtils.Simulate.click(arrow[0]); - expect(document.getElementsByClassName('fullscreen').length).toBe(0); - }); -}); diff --git a/web/client/components/data/identify/enhancers/__tests__/identify-test.jsx b/web/client/components/data/identify/enhancers/__tests__/identify-test.jsx index 5ac8ce1031..af877eaf63 100644 --- a/web/client/components/data/identify/enhancers/__tests__/identify-test.jsx +++ b/web/client/components/data/identify/enhancers/__tests__/identify-test.jsx @@ -70,347 +70,6 @@ describe("test identify enhancers", () => { expect(spyMousePointer.calls.length).toEqual(2); }); - it('test switchControlledIdentify component sends requests on point', () => { - const Component = identifyLifecycle(() =>
); - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({url: "myurl"})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({url: "myurl"})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(2); - }); - - it('creates the Identify component which sends requests on point and another layer', () => { - const Component = identifyLifecycle(() =>
); - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - ({url: "myurl"})} - />, - document.getElementById("container") - ); - const layers = [ - { - id: 'OpenTopoMap__3', - group: 'background', - source: 'OpenTopoMap', - name: 'OpenTopoMap', - title: 'OpenTopoMap', - type: 'tileprovider', - visibility: false, - handleClickOnLayer: false, - hidden: false - }, - { - id: 'topp:states__4', - name: 'topp:states', - title: 'USA Population', - type: 'wms', - url: 'https://demo.geo-solutions.it:443/geoserver/wms', - visibility: true, - handleClickOnLayer: false, - hidden: false - }, - { - id: 'annotations', - features: [], - name: 'Annotations', - type: 'vector', - visibility: true, - handleClickOnLayer: true, - hidden: false - } - ]; - ReactDOM.render( - ({url: "myurl"})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(3); - }); - it('test switchControlledIdentify component sends local requess on point if no url is specified', () => { - const Component = identifyLifecycle(() =>
); - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} localRequest={testHandlers.sendRequest} buildRequest={() => ({url: ""})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} localRequest={testHandlers.sendRequest} buildRequest={() => ({url: ""})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(2); - }); - - it('test switchControlledIdentify component does not send requests on point if disabled', () => { - const Component = identifyLifecycle(() =>
); - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - true} - enabled={false} layers={[{}, {}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled={false} layers={[{}, {}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(0); - }); - - it('test switchControlledIdentify component filters layers', () => { - const Component = identifyLifecycle(() =>
); - const testHandlers = { - sendRequest: () => {} - }; - - const spySendRequest = expect.spyOn(testHandlers, 'sendRequest'); - - ReactDOM.render( - layer.type === "wms"} - enabled layers={[{type: "wms"}, {type: "osm"}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({url: "myurl"})} - />, - document.getElementById("container") - ); - ReactDOM.render( - layer.type === "wms"} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{type: "wms"}, {type: "osm"}]} sendRequest={testHandlers.sendRequest} buildRequest={() => ({url: "myurl"})} - />, - document.getElementById("container") - ); - expect(spySendRequest.calls.length).toEqual(1); - }); - - it('test switchControlledIdentify component shows marker on point', () => { - const Component = identifyLifecycle(() =>
); - const testHandlers = { - showMarker: () => {}, - hideMarker: () => {} - }; - - const spyShowMarker = expect.spyOn(testHandlers, 'showMarker'); - const spyHideMarker = expect.spyOn(testHandlers, 'hideMarker'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyShowMarker.calls.length).toEqual(1); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled={false} layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyHideMarker.calls.length).toEqual(1); - }); - - it('test switchControlledIdentify component no queryable layer', () => { - - const Component = identifyLifecycle(() =>
); - const testHandlers = { - noQueryableLayers: () => {} - }; - - const spyNoQueryableLayers = expect.spyOn(testHandlers, 'noQueryableLayers'); - - ReactDOM.render( - false} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - ReactDOM.render( - false} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyNoQueryableLayers.calls.length).toEqual(1); - }); - - it('test switchControlledIdentify component purge results on point', () => { - - const Component = identifyLifecycle(() =>
); - - const testHandlers = { - purgeResults: () => {} - }; - - const spyPurgeResults = expect.spyOn(testHandlers, 'purgeResults'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(1); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - enabled={false} layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - />, - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(2); - }); - - it('test switchControlledIdentify component does not purge if multiselection enabled', () => { - - const Component = identifyLifecycle(() =>
); - - const testHandlers = { - purgeResults: () => {} - }; - - const spyPurgeResults = expect.spyOn(testHandlers, 'purgeResults'); - - ReactDOM.render( - true} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - multiSelection - />, - document.getElementById("container") - ); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - modifiers={{ctrl: false}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - multiSelection - />, - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(1); - ReactDOM.render( - true} - point={{pixel: {x: 1, y: 1}}} - modifiers={{ctrl: true}} - enabled layers={[{}, {}]} {...testHandlers} buildRequest={() => ({})} - multiSelection - />, - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(1); - }); - - it('test switchControlledIdentify component need refresh with null point', () => { - - const Component = identifyLifecycle(() =>
); - const testHandlers = { - purgeResults: () => {} - }; - const spyPurgeResults = expect.spyOn(testHandlers, 'purgeResults'); - ReactDOM.render( - , - document.getElementById("container") - ); - ReactDOM.render( - , - document.getElementById("container") - ); - expect(spyPurgeResults.calls.length).toEqual(1); - }); - - it('test switchControlledIdentify component need reset current index on new request', () => { - const Component = identifyLifecycle(() =>
); - const testHandlers = { - setIndex: () => {} - }; - const spySetIndex = expect.spyOn(testHandlers, 'setIndex'); - ReactDOM.render( - , - document.getElementById("container") - ); - ReactDOM.render( - , - document.getElementById("container") - ); - expect(spySetIndex.calls.length).toEqual(1); - }); - it("test switchControlledIdentify component doesn't need reset current index when requests are the same", () => { const Component = identifyLifecycle(() =>
); const testHandlers = { diff --git a/web/client/components/data/identify/enhancers/identify.js b/web/client/components/data/identify/enhancers/identify.js index ba1cd1c049..c8e853ab58 100644 --- a/web/client/components/data/identify/enhancers/identify.js +++ b/web/client/components/data/identify/enhancers/identify.js @@ -6,10 +6,9 @@ * LICENSE file in the root directory of this source tree. */ -const {lifecycle, withHandlers, branch, withState, compose, defaultProps} = require('recompose'); -const MapInfoUtils = require('../../../../utils/MapInfoUtils'); +const {lifecycle, withHandlers, branch, withState, compose} = require('recompose'); const {set} = require('../../../../utils/ImmutableUtils'); -const {isEqual, isArray, isNil} = require('lodash'); +const {isEqual, isNil} = require('lodash'); /** * Enhancer to enable set index only if Component has not header in viewerOptions props @@ -67,20 +66,16 @@ const identifyHandlers = withHandlers({ }); /** - * Basic identify lificycle used in Identify plugin with IdentifyContainer component + * Basic identify lifecycle used in Identify plugin with IdentifyContainer component * - componentDidMount: show cursor on map as pointer if enabled props is true * - componentWillReceiveProps: - * - sends new request only if needsRefresh returns true - * - changes pointer enbale true/false - * - set index to 0 to when responses are changed to avoid empty view if index is greather than current responses lenght - * @memberof enhancers.identifyLifecycle - * @class + * - changes pointer enable true/false - TODO: move it in an epic + * - set index to 0 to when responses are changed to avoid empty view if index is greater than current responses length + * @memberof components.data.identify.enhancers.identify + * @name identifyLifecycle */ const identifyLifecycle = compose( identifyHandlers, - defaultProps({ - queryableLayersFilter: () => true - }), lifecycle({ componentDidMount() { const { @@ -105,48 +100,8 @@ const identifyLifecycle = compose( changeMousePointer = () => {}, setIndex, enabled, - responses, - showMarker = () => {}, - needsRefresh = () => false, - buildRequest = () => {}, - sendRequest = () => {}, - localRequest = () => {}, - noQueryableLayers = () => {}, - includeOptions = [], - excludeParams = [] + responses } = this.props; - - if (needsRefresh(this.props, newProps)) { - if (!newProps.point.modifiers || newProps.point.modifiers.ctrl !== true || !newProps.allowMultiselection) { - purgeResults(); - } - const queryableLayers = isArray(newProps.layers) && (newProps.queryableLayersFilter && newProps.layers - .filter(newProps.queryableLayersFilter) || newProps.layers); - /* - * .filter(newProps.layer ? l => l.id === newProps.layer : () => true); - * this line was filtering too much, i.e. see issue #3344 - */ - if (queryableLayers) { - queryableLayers.forEach((layer) => { - const {url, request, metadata} = buildRequest(layer, newProps); - if (url) { - sendRequest(url, request, metadata, MapInfoUtils.filterRequestParams(layer, includeOptions, excludeParams)); - } else { - localRequest(layer, request, metadata); - } - }); - } - if (queryableLayers && queryableLayers.length === 0) { - noQueryableLayers(); - } else { - if (!newProps.layer) { - showMarker(); - } else { - hideMarker(); - } - } - } - if (newProps.enabled && !enabled) { changeMousePointer('pointer'); } else if (!newProps.enabled && enabled) { diff --git a/web/client/epics/__tests__/identify-test.js b/web/client/epics/__tests__/identify-test.js index 30dca91e24..3f8068abf7 100644 --- a/web/client/epics/__tests__/identify-test.js +++ b/web/client/epics/__tests__/identify-test.js @@ -8,17 +8,291 @@ const expect = require('expect'); -const {ZOOM_TO_POINT, clickOnMap} = require('../../actions/map'); -const { FEATURE_INFO_CLICK, UPDATE_CENTER_TO_MARKER, PURGE_MAPINFO_RESULTS, loadFeatureInfo, featureInfoClick, closeIdentify} = require('../../actions/mapInfo'); -const { zoomToVisibleAreaEpic, onMapClick, closeFeatureAndAnnotationEditing} = require('../identify'); +const { ZOOM_TO_POINT, clickOnMap } = require('../../actions/map'); +const { FEATURE_INFO_CLICK, UPDATE_CENTER_TO_MARKER, PURGE_MAPINFO_RESULTS, NEW_MAPINFO_REQUEST, LOAD_FEATURE_INFO, NO_QUERYABLE_LAYERS, ERROR_FEATURE_INFO, EXCEPTIONS_FEATURE_INFO, SHOW_MAPINFO_MARKER, HIDE_MAPINFO_MARKER, GET_VECTOR_INFO, loadFeatureInfo, featureInfoClick, closeIdentify } = require('../../actions/mapInfo'); +const { getFeatureInfoOnFeatureInfoClick, zoomToVisibleAreaEpic, onMapClick, closeFeatureAndAnnotationEditing, handleMapInfoMarker } = require('../identify'); const { CLOSE_ANNOTATIONS } = require('../../actions/annotations'); -const {testEpic, TEST_TIMEOUT, addTimeoutEpic} = require('./epicTestUtils'); -const {registerHook} = require('../../utils/MapUtils'); +const { testEpic, TEST_TIMEOUT, addTimeoutEpic } = require('./epicTestUtils'); +const { registerHook } = require('../../utils/MapUtils'); + +const TEST_MAP_STATE = { + present: { + size: { + width: 1581, + height: 946 + }, + zoom: 4, + projection: 'EPSG:3857', + bbox: { + bounds: { + maxx: -5732165, + maxy: 5722381, + minx: -9599267, + miny: 3408479 + }, + crs: 'EPSG:3857' + } + } +}; describe('identify Epics', () => { + it('getFeatureInfoOnFeatureInfoClick, no queriable layers', (done) => { + const state = { + layers: { + flat: [{ + id: "TEST", + "title": "TITLE", + type: "wms", + url: 'base/web/client/test-resources/featureInfo-response.json' + }] + } + }; + const sentActions = [featureInfoClick({ latlng: { lat: 36.95, lng: -79.84 } })]; + testEpic(getFeatureInfoOnFeatureInfoClick, 2, sentActions, ([a0, a1]) => { + expect(a0.type).toBe(PURGE_MAPINFO_RESULTS); + expect(a1.type).toBe(NO_QUERYABLE_LAYERS); - it('test center to visible area', (done) => { + done(); + }, state); + }); + it('getFeatureInfoOnFeatureInfoClick WMS', (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-response.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(LOAD_FEATURE_INFO); + expect(a2.data).toExist(); + expect(a2.requestParams).toExist(); + expect(a2.reqId).toExist(); + expect(a2.layerMetadata.title).toBe(state.layers.flat[0].title); + done(); + } catch (ex) { + done(ex); + } + }, state); + }); + it('getFeatureInfoOnFeatureInfoClick with multiSelection', (done) => { + // remove previous hook + registerHook('RESOLUTION_HOOK', undefined); + const CLICK_POINT = { + latlng: { lat: 36.95, lng: -79.84}, + modifiers: { + alt: false, + ctrl: true, + shift: false + }, + // TODO: this should be moved in the application state to be configurable + // now is supported this way, but the application do not manage it + multiSelection: true + }; + const state = { + map: TEST_MAP_STATE, + mapInfo: { + clickPoint: CLICK_POINT + }, + layers: { + flat: [{ + id: "TEST", + "title": "TITLE", + type: "wms", + visibility: true, + url: 'base/web/client/test-resources/featureInfo-response.json' + }] + } + }; + const sentActions = [featureInfoClick(CLICK_POINT)]; + testEpic(getFeatureInfoOnFeatureInfoClick, 2, sentActions, ([a1, a2]) => { + try { + // no purge + 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(LOAD_FEATURE_INFO); + expect(a2.data).toExist(); + expect(a2.requestParams).toExist(); + expect(a2.reqId).toExist(); + expect(a2.layerMetadata.title).toBe(state.layers.flat[0].title); + done(); + } catch (ex) { + done(ex); + } + }, state); + }); + it('getFeatureInfoOnFeatureInfoClick triggers ERROR_FEATURE_INFO on load error', (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: 'requestError.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(ERROR_FEATURE_INFO); + expect(a2).toExist(); + expect(a2.type).toBe(ERROR_FEATURE_INFO); + expect(a2.error).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('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', + group: 'background', + source: 'OpenTopoMap', + name: 'OpenTopoMap', + title: 'OpenTopoMap', + type: 'tileprovider', + visibility: false, + handleClickOnLayer: false, + hidden: false + }, + { + id: 'topp:states__4', + name: 'topp:states', + title: 'USA Population', + type: 'wms', + url: 'base/web/client/test-resources/featureInfo-response.json', + visibility: true, + handleClickOnLayer: false, + hidden: false + }, + { + id: 'annotations', + features: [], + name: 'Annotations', + type: 'vector', + visibility: true, + handleClickOnLayer: true, + hidden: false + }]; + const state = { + map: TEST_MAP_STATE, + mapInfo: { + clickPoint: { latlng: { lat: 36.95, lng: -79.84 } } + }, + layers: LAYERS + }; + const sentActions = [featureInfoClick({ latlng: { lat: 36.95, lng: -79.84 } })]; + testEpic(getFeatureInfoOnFeatureInfoClick, 4, sentActions, ([a0, a1, a2, a3]) => { + 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.type).toBe(GET_VECTOR_INFO); + expect(a3).toExist(); + expect(a3.type).toBe(LOAD_FEATURE_INFO); + done(); + } catch (ex) { + done(ex); + } + }, state); + }); + it('handleMapInfoMarker show', done => { + testEpic(handleMapInfoMarker, 1, featureInfoClick({}), ([ a ]) => { + expect(a.type).toBe(SHOW_MAPINFO_MARKER); + done(); + }, {}); + }); + it('handleMapInfoMarker hide when layer is present', done => { + testEpic(handleMapInfoMarker, 1, featureInfoClick("POINT", "LAYER"), ([ a ]) => { + expect(a.type).toBe(HIDE_MAPINFO_MARKER); + done(); + }, {}); + }); + it('test center to visible area', (done) => { // remove previous hook registerHook('RESOLUTION_HOOK', undefined); @@ -26,25 +300,7 @@ describe('identify Epics', () => { mapInfo: { centerToMarker: true }, - map: { - present: { - size: { - width: 1581, - height: 946 - }, - zoom: 4, - projection: 'EPSG:3857', - bbox: { - bounds: { - maxx: -5732165, - maxy: 5722381, - minx: -9599267, - miny: 3408479 - }, - crs: 'EPSG:3857' - } - } - }, + map: TEST_MAP_STATE, maplayout: { boundingMapRect: { left: 500, @@ -53,22 +309,22 @@ describe('identify Epics', () => { } }; - const sentActions = [featureInfoClick({latlng: {lat: 36.95, lng: -79.84}}), loadFeatureInfo()]; + const sentActions = [featureInfoClick({ latlng: { lat: 36.95, lng: -79.84 } }), loadFeatureInfo()]; const expectedAction = actions => { expect(actions.length).toBe(2); actions.map((action) => { switch (action.type) { - case ZOOM_TO_POINT: - expect(action.zoom).toBe(4); - expect({x: parseFloat(action.pos.x.toFixed(2)), y: parseFloat(action.pos.y.toFixed(2))}).toEqual({x: -101.81, y: 27.68}); - expect(action.crs).toBe('EPSG:4326'); - break; - case UPDATE_CENTER_TO_MARKER: - expect(action.status).toBe('enabled'); - break; - default: - expect(true).toBe(false); + case ZOOM_TO_POINT: + expect(action.zoom).toBe(4); + expect({ x: parseFloat(action.pos.x.toFixed(2)), y: parseFloat(action.pos.y.toFixed(2)) }).toEqual({ x: -101.81, y: 27.68 }); + expect(action.crs).toBe('EPSG:4326'); + break; + case UPDATE_CENTER_TO_MARKER: + expect(action.status).toBe('enabled'); + break; + default: + expect(true).toBe(false); } }); done(); @@ -86,25 +342,7 @@ describe('identify Epics', () => { mapInfo: { centerToMarker: true }, - map: { - present: { - size: { - width: 1581, - height: 946 - }, - zoom: 4, - projection: 'EPSG:3857', - bbox: { - bounds: { - maxx: -5732165, - maxy: 5722381, - minx: -9599267, - miny: 3408479 - }, - crs: 'EPSG:3857' - } - } - }, + map: TEST_MAP_STATE, maplayout: { boundingMapRect: { left: 0, @@ -113,17 +351,17 @@ describe('identify Epics', () => { } }; - const sentActions = [featureInfoClick({latlng: {lat: 36.95, lng: -79.84}}), loadFeatureInfo()]; + const sentActions = [featureInfoClick({ latlng: { lat: 36.95, lng: -79.84 } }), loadFeatureInfo()]; const expectedAction = actions => { expect(actions.length).toBe(1); actions.map((action) => { switch (action.type) { - case UPDATE_CENTER_TO_MARKER: - expect(action.status).toBe('disabled'); - break; - default: - expect(true).toBe(false); + case UPDATE_CENTER_TO_MARKER: + expect(action.status).toBe('disabled'); + break; + default: + expect(true).toBe(false); } }); done(); @@ -136,15 +374,15 @@ describe('identify Epics', () => { expect(action.type === FEATURE_INFO_CLICK); done(); }, { - mapInfo: { - enabled: true, - disableAlwaysOn: false - } - }); + mapInfo: { + enabled: true, + disableAlwaysOn: false + } + }); }); it('onMapClick do not trigger when mapinfo is not elabled', done => { testEpic(addTimeoutEpic(onMapClick, 10), 1, [clickOnMap()], ([action]) => { - if (action.type === TEST_TIMEOUT ) { + if (action.type === TEST_TIMEOUT) { done(); } }, { diff --git a/web/client/epics/identify.js b/web/client/epics/identify.js index a952c958dd..5ed4a0ff34 100644 --- a/web/client/epics/identify.js +++ b/web/client/epics/identify.js @@ -6,34 +6,100 @@ * LICENSE file in the root directory of this source tree. */ const Rx = require('rxjs'); -const {get} = require('lodash'); +const { get } = require('lodash'); +const axios = require('../libs/ajax'); +const uuid = require('uuid'); +const { LOAD_FEATURE_INFO, ERROR_FEATURE_INFO, GET_VECTOR_INFO, FEATURE_INFO_CLICK, CLOSE_IDENTIFY, featureInfoClick, updateCenterToMarker, purgeMapInfoResults, + exceptionsFeatureInfo, loadFeatureInfo, errorFeatureInfo, noQueryableLayers, newMapInfoRequest, getVectorInfo, showMapinfoMarker, hideMapinfoMarker } = require('../actions/mapInfo'); -const { LOAD_FEATURE_INFO, ERROR_FEATURE_INFO, GET_VECTOR_INFO, FEATURE_INFO_CLICK, CLOSE_IDENTIFY, featureInfoClick, updateCenterToMarker, purgeMapInfoResults} = require('../actions/mapInfo'); - -const {closeFeatureGrid} = require('../actions/featuregrid'); -const {CHANGE_MOUSE_POINTER, CLICK_ON_MAP, zoomToPoint} = require('../actions/map'); +const { closeFeatureGrid } = require('../actions/featuregrid'); +const { CHANGE_MOUSE_POINTER, CLICK_ON_MAP, zoomToPoint } = require('../actions/map'); const { closeAnnotations } = require('../actions/annotations'); -const {MAP_CONFIG_LOADED} = require('../actions/config'); -const {stopGetFeatureInfoSelector} = require('../selectors/mapinfo'); -const {centerToMarkerSelector} = require('../selectors/layers'); -const {mapSelector} = require('../selectors/map'); -const {boundingMapRectSelector} = require('../selectors/maplayout'); -const {centerToVisibleArea, isInsideVisibleArea} = require('../utils/CoordinatesUtils'); -const {getCurrentResolution, parseLayoutValue} = require('../utils/MapUtils'); +const { MAP_CONFIG_LOADED } = require('../actions/config'); +const { stopGetFeatureInfoSelector, queryableLayersSelector, identifyOptionsSelector } = require('../selectors/mapinfo'); +const { centerToMarkerSelector } = require('../selectors/layers'); +const { mapSelector } = require('../selectors/map'); +const { boundingMapRectSelector } = require('../selectors/maplayout'); +const { centerToVisibleArea, isInsideVisibleArea } = require('../utils/CoordinatesUtils'); +const { getCurrentResolution, parseLayoutValue } = require('../utils/MapUtils'); +const MapInfoUtils = require('../utils/MapInfoUtils'); + +/** + * Sends a GetFeatureInfo request and dispatches the right action + * in case of success, error or exceptions. + * + * @param basePath {string} base path to the service + * @param requestParams {object} map of params for a getfeatureinfo request. + */ +const getFeatureInfo = (basePath, requestParams, lMetaData, options = {}) => { + const param = { ...options, ...requestParams }; + const reqId = uuid.v1(); + return Rx.Observable.defer(() => axios.get(basePath, { params: param })) + .map((response) => + response.data.exceptions + ? exceptionsFeatureInfo(reqId, response.data.exceptions, requestParams, lMetaData) + : loadFeatureInfo(reqId, response.data, requestParams, lMetaData) + ) + .catch((e) => Rx.Observable.of(errorFeatureInfo(reqId, e.data || e.statusText || e.status, requestParams, lMetaData))) + .startWith(newMapInfoRequest(reqId, param)); +}; + /** * Epics for Identify and map info * @name epics.identify * @type {Object} */ - module.exports = { + /** + * Triggers data load on FEATURE_INFO_CLICK events + */ + getFeatureInfoOnFeatureInfoClick: (action$, { getState = () => { } }) => + action$.ofType(FEATURE_INFO_CLICK).switchMap(({ point }) => { + const queryableLayers = queryableLayersSelector(getState()); + if (queryableLayers.length === 0) { + return Rx.Observable.of(purgeMapInfoResults(), noQueryableLayers()); + } + // TODO: make it in the application state + const excludeParams = ["SLD_BODY"]; + const includeOptions = [ + "buffer", + "cql_filter", + "filter", + "propertyName" + ]; + const out$ = Rx.Observable.from((queryableLayers)) + .mergeMap(layer => { + const { url, request, metadata } = MapInfoUtils.buildIdentifyRequest(layer, identifyOptionsSelector(getState())); + if (url) { + return getFeatureInfo(url, request, metadata, MapInfoUtils.filterRequestParams(layer, includeOptions, excludeParams)); + } + return Rx.Observable.of(getVectorInfo(layer, request, metadata)); + }); + // NOTE: multiSelection is inside the event + // TODO: move this flag in the application state + if (point && point.modifiers && point.modifiers.ctrl === true && point.multiSelection) { + return out$; + } + return out$.startWith(purgeMapInfoResults()); + + }), + /** + * if `clickLayer` is present, this means that `handleClickOnLayer` is true for the clicked layer, so the marker have to be hidden, because + * it's managed by the layer itself (e.g. annotations). So the marker have to be hidden. + */ + handleMapInfoMarker: (action$) => + action$.ofType(FEATURE_INFO_CLICK) + .map(({ layer }) => layer + ? hideMapinfoMarker() + : showMapinfoMarker() + ), closeFeatureGridFromIdentifyEpic: (action$) => action$.ofType(LOAD_FEATURE_INFO, GET_VECTOR_INFO) - .switchMap(() => { - return Rx.Observable.of(closeFeatureGrid()); - }), + .switchMap(() => { + return Rx.Observable.of(closeFeatureGrid()); + }), /** * Check if something is editing in feature info. * If so, as to the proper tool to close (annotations) diff --git a/web/client/plugins/Identify.jsx b/web/client/plugins/Identify.jsx index 35e498248e..55aba2a072 100644 --- a/web/client/plugins/Identify.jsx +++ b/web/client/plugins/Identify.jsx @@ -14,10 +14,10 @@ const {createSelector} = require('reselect'); const {mapSelector} = require('../selectors/map'); const {layersSelector} = require('../selectors/layers'); +const { generalInfoFormatSelector, clickPointSelector } = require('../selectors/mapinfo'); -const {getFeatureInfo, getVectorInfo, showMapinfoMarker, hideMapinfoMarker, showMapinfoRevGeocode, hideMapinfoRevGeocode, noQueryableLayers, clearWarning, toggleMapInfoState, changeMapInfoFormat, updateCenterToMarker, closeIdentify, purgeMapInfoResults, featureInfoClick, changeFormat, - toggleShowCoordinateEditor -} = require('../actions/mapInfo'); + +const {hideMapinfoMarker, showMapinfoRevGeocode, hideMapinfoRevGeocode, clearWarning, toggleMapInfoState, changeMapInfoFormat, updateCenterToMarker, closeIdentify, purgeMapInfoResults, featureInfoClick, changeFormat, toggleShowCoordinateEditor} = require('../actions/mapInfo'); const {changeMousePointer} = require('../actions/map'); const {currentLocaleSelector} = require('../selectors/locale'); @@ -38,11 +38,10 @@ const selector = createSelector([ (state) => state.mapInfo && state.mapInfo.enabled || state.controls && state.controls.info && state.controls.info.enabled || false, (state) => state.mapInfo && state.mapInfo.responses || [], (state) => state.mapInfo && state.mapInfo.requests || [], - (state) => state.mapInfo && state.mapInfo.infoFormat, + generalInfoFormatSelector, mapSelector, layersSelector, - (state) => state.mapInfo && state.mapInfo.clickPoint, - (state) => state.mapInfo && state.mapInfo.clickLayer, + clickPointSelector, (state) => state.mapInfo && state.mapInfo.showModalReverse, (state) => state.mapInfo && state.mapInfo.reverseGeocodeData, (state) => state.mapInfo && state.mapInfo.warning, @@ -50,8 +49,8 @@ const selector = createSelector([ state => mapLayoutValuesSelector(state, {height: true}), (state) => state.mapInfo && state.mapInfo.formatCoord, (state) => state.mapInfo && state.mapInfo.showCoordinateEditor -], (enabled, responses, requests, format, map, layers, point, layer, showModalReverse, reverseGeocodeData, warning, currentLocale, dockStyle, formatCoord, showCoordinateEditor) => ({ - enabled, responses, requests, format, map, layers, point, layer, showModalReverse, reverseGeocodeData, warning, currentLocale, dockStyle, formatCoord, showCoordinateEditor +], (enabled, responses, requests, format, map, layers, point, showModalReverse, reverseGeocodeData, warning, currentLocale, dockStyle, formatCoord, showCoordinateEditor) => ({ + enabled, responses, requests, format, map, layers, point, showModalReverse, reverseGeocodeData, warning, currentLocale, dockStyle, formatCoord, showCoordinateEditor })); // result panel @@ -71,16 +70,10 @@ const identifyDefaultProps = defaultProps({ format: MapInfoUtils.getDefaultInfoFormatValue(), requests: [], responses: [], - buffer: 2, viewerOptions: {}, viewer: DefaultViewer, purgeResults: () => {}, - buildRequest: MapInfoUtils.buildIdentifyRequest, - localRequest: () => {}, - sendRequest: () => {}, - showMarker: () => {}, hideMarker: () => {}, - noQueryableLayers: () => {}, clearWarning: () => {}, changeMousePointer: () => {}, showRevGeocode: () => {}, @@ -94,20 +87,11 @@ const identifyDefaultProps = defaultProps({ reverseGeocodeData: {}, enableRevGeocode: true, wrapRevGeocode: false, - queryableLayersFilter: MapInfoUtils.defaultQueryableFilter, style: {}, point: {}, layer: null, map: {}, layers: [], - maxItems: 10, - excludeParams: ["SLD_BODY"], - includeOptions: [ - "buffer", - "cql_filter", - "filter", - "propertyName" - ], panelClassName: "modal-dialog info-panel modal-content", headerClassName: "modal-header", bodyClassName: "modal-body info-wrap", @@ -115,7 +99,6 @@ const identifyDefaultProps = defaultProps({ headerGlyph: "", closeGlyph: "1-close", className: "square-button", - allowMultiselection: false, currentLocale: 'en-US', fullscreen: false, showTabs: true, @@ -169,16 +152,12 @@ const identifyDefaultProps = defaultProps({ const IdentifyPlugin = compose( connect(selector, { - sendRequest: getFeatureInfo, - localRequest: getVectorInfo, purgeResults: purgeMapInfoResults, closeIdentify, onChangeClickPoint: featureInfoClick, onToggleShowCoordinateEditor: toggleShowCoordinateEditor, onChangeFormat: changeFormat, changeMousePointer, - showMarker: showMapinfoMarker, - noQueryableLayers, clearWarning, hideMarker: hideMapinfoMarker, showRevGeocode: showMapinfoRevGeocode, diff --git a/web/client/selectors/mapinfo.js b/web/client/selectors/mapinfo.js index 660522ff5d..a6e6fd3ede 100644 --- a/web/client/selectors/mapinfo.js +++ b/web/client/selectors/mapinfo.js @@ -8,8 +8,14 @@ const {get} = require('lodash'); -const {createSelector} = require('reselect'); +const { createSelector, createStructuredSelector } = require('reselect'); const {modeSelector} = require('./featuregrid'); +const {mapSelector} = require('./map'); +const { currentLocaleSelector } = require('./locale'); + +const {layersSelector} = require('./layers'); +const {defaultQueryableFilter} = require('../utils/MapInfoUtils'); + const {queryPanelSelector} = require('./controls'); /** @@ -36,6 +42,12 @@ const mapInfoRequestsSelector = state => get(state, "mapInfo.requests") || []; */ const generalInfoFormatSelector = (state) => get(state, "mapInfo.infoFormat", "text/plain"); +/** + * Clicked point of mapInfo + * @param {object} state the state + */ +const clickPointSelector = state => state && state.mapInfo && state.mapInfo.clickPoint; + const measureActiveSelector = (state) => get(state, "measurement.lineMeasureEnabled") || get(state, "measurement.areaMeasureEnabled") || get(state, "measurement.bearingMeasureEnabled"); const drawSupportActiveSelector = (state) => { const drawStatus = get(state, "draw.drawStatus", false); @@ -44,6 +56,12 @@ const drawSupportActiveSelector = (state) => { const gridEditingSelector = createSelector(modeSelector, (mode) => mode === 'EDIT'); const annotationsEditingSelector = (state) => get(state, "annotations.editing"); const mapInfoDisabledSelector = (state) => !get(state, "mapInfo.enabled", false); +/** + * Select queriable layers + * @param {object} state the state + * @return the queriable layers + */ +const queryableLayersSelector = state => layersSelector(state).filter(defaultQueryableFilter); /** * selects stopGetFeatureInfo from state @@ -68,8 +86,18 @@ const stopGetFeatureInfoSelector = createSelector( || !!isQueryPanelActive ); +const identifyOptionsSelector = createStructuredSelector({ + format: generalInfoFormatSelector, + map: mapSelector, + point: clickPointSelector, + currentLocale: currentLocaleSelector + }); + module.exports = { + identifyOptionsSelector, + clickPointSelector, generalInfoFormatSelector, + queryableLayersSelector, mapInfoRequestsSelector, stopGetFeatureInfoSelector }; diff --git a/web/client/utils/MapInfoUtils.js b/web/client/utils/MapInfoUtils.js index 3cf77cd9fc..f97bfff2aa 100644 --- a/web/client/utils/MapInfoUtils.js +++ b/web/client/utils/MapInfoUtils.js @@ -66,7 +66,7 @@ const MapInfoUtils = { && layer.featureInfo.format && INFO_FORMATS[layer.featureInfo.format] || props.format - || 'application/json', + || MapInfoUtils.getDefaultInfoFormatValue(), getLayerFeatureInfoViewer(layer) { if (layer.featureInfo && layer.featureInfo.viewer) { @@ -119,12 +119,20 @@ const MapInfoUtils = { ...otherParams }; }, - buildIdentifyRequest(layer, props) { + /** + * + * @param {object} layer the layer object + * @param {object} options the options for the request + * @param {string} options.format the format to use + * @param {string} options.map the map object, with projection and + * @param {object} options.point + */ + buildIdentifyRequest(layer, options) { if (MapInfoUtils.services[layer.type]) { - let infoFormat = MapInfoUtils.getDefaultInfoFormatValueFromLayer(layer, props); + let infoFormat = MapInfoUtils.getDefaultInfoFormatValueFromLayer(layer, options); let viewer = MapInfoUtils.getLayerFeatureInfoViewer(layer); const featureInfo = MapInfoUtils.getLayerFeatureInfo(layer); - return MapInfoUtils.services[layer.type].buildRequest(layer, props, infoFormat, viewer, featureInfo); + return MapInfoUtils.services[layer.type].buildRequest(layer, options, infoFormat, viewer, featureInfo); } return {}; }, diff --git a/web/client/utils/mapinfo/vector.js b/web/client/utils/mapinfo/vector.js index a82bcac8ac..9d960491a7 100644 --- a/web/client/utils/mapinfo/vector.js +++ b/web/client/utils/mapinfo/vector.js @@ -19,7 +19,7 @@ module.exports = { fields: layer.features && layer.features.length && Object.keys(layer.features[0].properties) || [], title: layer.name, resolution: props.map && props.map && props.map.zoom && MapUtils.getCurrentResolution(props.map.zoom, 0, 21, 96), - buffer: props.buffer, + buffer: props.buffer || 2, units: props.map && props.map.units, rowViewer: layer.rowViewer }, diff --git a/web/client/utils/mapinfo/wms.js b/web/client/utils/mapinfo/wms.js index 05e89b386f..937a554250 100644 --- a/web/client/utils/mapinfo/wms.js +++ b/web/client/utils/mapinfo/wms.js @@ -15,32 +15,40 @@ const SecurityUtils = require('../SecurityUtils'); const assign = require('object-assign'); module.exports = { - buildRequest: (layer, props, infoFormat, viewer, featureInfo) => { + /** + * Creates the request object and it's metadata for WMS GetFeatureInfo. + * @param {object} layer + * @param {object} options + * @param {string} infoFormat + * @param {string} viewer + * @return {object} an object with `request`, containing request paarams, `metadata` with some info about the layer and the request, and `url` to send the request to. + */ + buildRequest: (layer, { sizeBBox, map = {}, point, currentLocale, params: defaultParams, maxItems = 10} = {}, infoFormat, viewer, featureInfo) => { /* In order to create a valid feature info request * we create a bbox of 101x101 pixel that wrap the point. * center point is re-projected then is built a box of 101x101pixel around it */ - const heightBBox = props && props.sizeBBox && props.sizeBBox.height || 101; - const widthBBox = props && props.sizeBBox && props.sizeBBox.width || 101; + const heightBBox = sizeBBox && sizeBBox.height || 101; + const widthBBox = sizeBBox && sizeBBox.width || 101; const size = [heightBBox, widthBBox]; const rotation = 0; - const resolution = MapUtils.getCurrentResolution(Math.ceil(props.map.zoom), 0, 21, 96); - let wrongLng = props.point.latlng.lng; + const resolution = MapUtils.getCurrentResolution(Math.ceil(map.zoom), 0, 21, 96); + let wrongLng = point.latlng.lng; // longitude restricted to the [-180°,+180°] range let lngCorrected = wrongLng - 360 * Math.floor(wrongLng / 360 + 0.5); - const center = {x: lngCorrected, y: props.point.latlng.lat}; - let centerProjected = CoordinatesUtils.reproject(center, 'EPSG:4326', props.map.projection); + const center = {x: lngCorrected, y: point.latlng.lat}; + let centerProjected = CoordinatesUtils.reproject(center, 'EPSG:4326', map.projection); let bounds = CoordinatesUtils.getProjectedBBox(centerProjected, resolution, rotation, size, null); let queryLayers = layer.name; if (layer.queryLayers) { queryLayers = layer.queryLayers.join(","); } - const locale = props.currentLocale ? head(props.currentLocale.split('-')) : null; + const locale = currentLocale ? head(currentLocale.split('-')) : null; const ENV = locale ? 'locale:' + locale : ''; const params = optionsToVendorParams({ filterObj: layer.filterObj, - params: assign({}, layer.baseParams, layer.params, props.params) + params: assign({}, layer.baseParams, layer.params, defaultParams) }); return { request: SecurityUtils.addAuthenticationToSLD({ @@ -56,18 +64,18 @@ module.exports = { y: widthBBox % 2 === 1 ? Math.ceil(widthBBox / 2) : widthBBox / 2, height: heightBBox, width: widthBBox, - srs: CoordinatesUtils.normalizeSRS(props.map.projection) || 'EPSG:4326', + srs: CoordinatesUtils.normalizeSRS(map.projection) || 'EPSG:4326', bbox: bounds.minx + "," + bounds.miny + "," + bounds.maxx + "," + bounds.maxy, - feature_count: props.maxItems, + feature_count: maxItems, info_format: infoFormat, ENV, ...assign({}, params) }, layer), metadata: { - title: isObject(layer.title) ? layer.title[props.currentLocale] || layer.title.default : layer.title, + title: isObject(layer.title) ? layer.title[currentLocale] || layer.title.default : layer.title, regex: layer.featureInfoRegex, viewer, featureInfo