diff --git a/web/client/api/WFS.js b/web/client/api/WFS.js index 6370be114cf..8ca861526e1 100644 --- a/web/client/api/WFS.js +++ b/web/client/api/WFS.js @@ -159,8 +159,13 @@ export const describeFeatureType = function(url, typeName) { /** * Fetch the supported formats of the WFS service +<<<<<<< HEAD * @param url * @return {object} { infoFormats } +======= + * @param {string} url WFS endpoint + * @returns {Promise} resolves with { infoFormats } +>>>>>>> 58535023d (#12813: Layer settings - Trigger layer-specific capabilities request (#12816)) */ export const getSupportedFormat = (url) => { return getCapabilities(url) diff --git a/web/client/api/WMS.js b/web/client/api/WMS.js index faf4998ac89..3eb114ffeed 100644 --- a/web/client/api/WMS.js +++ b/web/client/api/WMS.js @@ -310,9 +310,9 @@ export const reset = () => { /** * Fetch the supported formats of the WMS service - * @param url - * @param includeGFIFormats - * @return {object|string} formats + * @param {string} url WMS endpoint + * @param {boolean} includeGFIFormats include GFI formats + * @returns {object|string} formats */ export const getSupportedFormat = (url, includeGFIFormats = false) => { return getCapabilities(url) diff --git a/web/client/api/__tests__/WFS-test.js b/web/client/api/__tests__/WFS-test.js index 9a78a036b0e..7a85a26ee31 100644 --- a/web/client/api/__tests__/WFS-test.js +++ b/web/client/api/__tests__/WFS-test.js @@ -197,6 +197,32 @@ describe('Test WFS ogc API functions', () => { }).catch(done); }); + it('getSupportedFormat with layer-specific URL', (done) => { + mockAxios.onGet().reply((config) => { + try { + expect(config.url).toBe('/geoserver/workspace/layer_name/wfs?version=1.1.0&service=WFS&request=GetCapabilities'); + } catch (e) { + done(e); + } + return [200, ` + + + + + application/json + text/html + + + + + `]; + }); + getSupportedFormat('/geoserver/workspace/layer_name/wfs').then((data) => { + expect(data).toEqual({ infoFormats: ['application/json', 'text/html'] }); + done(); + }).catch(done); + }); + it('toDescribeURL with URL array', () => { const _url = [ 'http://gs-stable.geosolutionsgroup.com:443/geoserver1', diff --git a/web/client/api/__tests__/WMS-test.js b/web/client/api/__tests__/WMS-test.js index dea877ed47b..d5ece7bf164 100644 --- a/web/client/api/__tests__/WMS-test.js +++ b/web/client/api/__tests__/WMS-test.js @@ -419,3 +419,77 @@ describe('Test get json wms graphic legend (mock axios)', () => { }); }); }); + +describe('Test getSupportedFormat WMS API (mock axios)', () => { + beforeEach((done) => { + mockAxios = new MockAdapter(axios); + setTimeout(done); + }); + + afterEach((done) => { + mockAxios.restore(); + setTimeout(done); + }); + + it('getSupportedFormat returns imageFormats and infoFormats for layer-specific request', (done) => { + mockAxios.onGet().reply((config) => { + try { + expect(config.url).toBe('http://localhost:8080/geoserver/workspace/layer_name/wms?service=WMS&version=1.3.0&request=GetCapabilities'); + } catch (e) { + done(e); + } + return [200, ` + + + + + image/png + image/jpeg + + + text/html + application/json + + + + + `]; + }); + + API.getSupportedFormat('http://localhost:8080/geoserver/workspace/layer_name/wms', true) + .then((formats) => { + try { + expect(formats.imageFormats).toEqual(['image/png', 'image/jpeg']); + expect(formats.infoFormats).toEqual(['text/html', 'application/json']); + done(); + } catch (e) { + done(e); + } + }).catch(done); + }); + + it('getSupportedFormat returns only imageFormats when includeGFIFormats is false', (done) => { + mockAxios.onGet().reply(200, ` + + + + + image/png + + + + + `); + + API.getSupportedFormat('http://localhost:8080/geoserver/wms') + .then((imageFormats) => { + try { + expect(imageFormats).toEqual(['image/png']); + done(); + } catch (e) { + done(e); + } + }).catch(done); + }); +}); + diff --git a/web/client/components/TOC/fragments/settings/Display.jsx b/web/client/components/TOC/fragments/settings/Display.jsx index dce79b55794..761d6911cac 100644 --- a/web/client/components/TOC/fragments/settings/Display.jsx +++ b/web/client/components/TOC/fragments/settings/Display.jsx @@ -21,7 +21,7 @@ import Message from '../../../I18N/Message'; import InfoPopover from '../../../widgets/widget/InfoPopover'; import Legend from '../../../../plugins/TOC/components/Legend'; import VisibilityLimitsForm from './VisibilityLimitsForm'; -import { ServerTypes } from '../../../../utils/LayersUtils'; +import { ServerTypes, getCapabilitiesUrl } from '../../../../utils/LayersUtils'; import {updateLayerLegendFilter} from '../../../../utils/FilterUtils'; import Select from 'react-select'; import { getSupportedFormat } from '../../../../api/WMS'; @@ -116,9 +116,9 @@ export default class extends React.Component { }); }; - onFormatOptionsFetch = (url) => { + onFormatOptionsFetch = (layer) => { this.setState({formatLoading: true}); - getSupportedFormat(url).then((imageFormats)=>{ + getSupportedFormat(getCapabilitiesUrl(layer)).then((imageFormats)=>{ this.props.onChange("imageFormats", imageFormats); this.setState({formatLoading: false}); }); @@ -165,7 +165,7 @@ export default class extends React.Component { onOpen={() => { if (!this.props.element?.imageFormats || this.props.element?.imageFormats?.length === 0) { - this.onFormatOptionsFetch(this.props.element?.url); + this.onFormatOptionsFetch(this.props.element); } }} onChange={({ value }) => { @@ -175,7 +175,7 @@ export default class extends React.Component { disabled={!!this.state.formatLoading} tooltipId="layerProperties.format.refresh" className="square-button no-border format-refresh" - onClick={() => {this.onFormatOptionsFetch(this.props.element?.url);}} + onClick={() => {this.onFormatOptionsFetch(this.props.element);}} key="format-refresh"> diff --git a/web/client/components/TOC/fragments/settings/FeatureInfo.jsx b/web/client/components/TOC/fragments/settings/FeatureInfo.jsx index 243dc8dfc00..e336a4077aa 100644 --- a/web/client/components/TOC/fragments/settings/FeatureInfo.jsx +++ b/web/client/components/TOC/fragments/settings/FeatureInfo.jsx @@ -17,7 +17,28 @@ import { Glyphicon } from 'react-bootstrap'; import Message from '../../../I18N/Message'; import includes from 'lodash/includes'; import isEmpty from 'lodash/isEmpty'; +<<<<<<< HEAD import { getDefaultInfoViewMode } from '../../../../utils/MapInfoUtils'; +======= +import { v1 as uuidv1 } from 'uuid'; +import { + getDefaultInfoViewMode, + getLayerFeatureInfoViews, + isLayerFeatureInfoDisabled +} from '../../../../utils/MapInfoUtils'; +import Message from '../../../I18N/Message'; +import { getCapabilitiesUrl } from '../../../../utils/LayersUtils'; +import FeatureInfoEditor from './FeatureInfoEditor'; +import localizedProps from '../../../misc/enhancers/localizedProps'; +import FeatureInfoRequestOptions from '../../../misc/FeatureInfoRequestOptions'; +import { isGeoServerLayer } from '../../../../utils/FeatureInfoRequestUtils'; +import ExternalDataEditor from './ExternalDataEditor'; +import PropertiesEditor from './PropertiesEditor'; +import { EXTERNAL_DATA, validateExternalDataConfiguration } from '../../../../utils/mapinfo/ExternalDataUtils'; + +const FormControl = localizedProps('placeholder')(FormControlRB); +const GlyphiconWithTitle = localizedProps('title')(Glyphicon); +>>>>>>> 58535023d (#12813: Layer settings - Trigger layer-specific capabilities request (#12816)) const supportedFormatRequests = { wms: getSupportedFormatWMS, @@ -58,7 +79,7 @@ export default class extends React.Component { // we dont know supported infoFormats yet if (getSupportedFormat && this.props.element.url && !this.props.element.infoFormats || this.props.element.infoFormats?.length === 0) { this.setState({ loading: true }); // eslint-disable-line -- TODO: need to be fixed - getSupportedFormat(this.props.element.url, true) + getSupportedFormat(getCapabilitiesUrl(this.props.element), true) .then(({ infoFormats }) => { this.props.onChange("infoFormats", infoFormats); this.setState({ loading: false }); // eslint-disable-line -- TODO: need to be fixed diff --git a/web/client/utils/LayersUtils.js b/web/client/utils/LayersUtils.js index 58df487e228..cf0caeb1828 100644 --- a/web/client/utils/LayersUtils.js +++ b/web/client/utils/LayersUtils.js @@ -814,8 +814,8 @@ export const getCapabilitiesUrl = (layer) => { if (!!matchedGeoServerName) { let urlParts = reqUrl.split(matchedGeoServerName); if (urlParts.length === 2) { - let layerParts = layer.name.split(":"); - if (layerParts.length === 2) { + let layerParts = layer?.name?.split(":"); + if (layerParts?.length === 2) { const [workspace, layerName] = layerParts; const rawTail = urlParts[1] || ''; const urlTail = rawTail.replace(/^\/+/, ''); diff --git a/web/client/utils/__tests__/LayersUtils-test.js b/web/client/utils/__tests__/LayersUtils-test.js index e4e94bfaff9..9a007f06e74 100644 --- a/web/client/utils/__tests__/LayersUtils-test.js +++ b/web/client/utils/__tests__/LayersUtils-test.js @@ -1609,6 +1609,17 @@ describe('LayersUtils', () => { expect(LayersUtils.getCapabilitiesUrl(layer)).toEqual('localhost:8080/geoserver/world/layer1/ows?token=value'); }); + it('test getCapabilitiesUrl with layer without name', () => { + const layer = { + url: 'localhost:8080/geoserver/wms', + params: { + token: 'value' + } + }; + expect(LayersUtils.getCapabilitiesUrl(layer)) + .toEqual('localhost:8080/geoserver/wms?token=value'); + }); + it('test getNestedGroupTitle', () => { const groups = [ diff --git a/web/client/utils/mapinfo/wfs.js b/web/client/utils/mapinfo/wfs.js index a864a7084d5..d4185f4eae2 100644 --- a/web/client/utils/mapinfo/wfs.js +++ b/web/client/utils/mapinfo/wfs.js @@ -9,7 +9,11 @@ import {Observable} from 'rxjs'; import {normalizeSRS} from '../CoordinatesUtils'; +<<<<<<< HEAD import { getLayerUrl } from '../LayersUtils'; +======= +import { getLayerUrl, getCapabilitiesUrl } from '../LayersUtils'; +>>>>>>> 58535023d (#12813: Layer settings - Trigger layer-specific capabilities request (#12816)) import { isObject } from 'lodash'; import { optionsToVendorParams } from '../VendorParamsUtils'; import { describeFeatureType, getFeature } from '../../api/WFS'; @@ -98,6 +102,23 @@ const getIdentifyGeometry = point => { export default { buildRequest, +<<<<<<< HEAD +======= + /** + * Detects the supported info formats, text/html is the only one a WFS service could be missing. + * @param {object} layer + * @param {string} infoFormat the info format needed by the identify request + * @return {Promise} resolves with the layer, with `infoFormats` when they could be detected + */ + resolveLayer: (layer, infoFormat) => { + if (infoFormat !== INFO_FORMATS.HTML || layer.infoFormats?.length || !layer.url) { + return Promise.resolve(layer); + } + return getSupportedFormat(getCapabilitiesUrl(layer)) + .then(({ infoFormats }) => ({ ...layer, infoFormats })) + .catch(() => layer); + }, +>>>>>>> 58535023d (#12813: Layer settings - Trigger layer-specific capabilities request (#12816)) getIdentifyFlow: (layer = {}, baseURL, defaultParams) => { const { point, features, ...baseParams } = defaultParams || {}; if (features) {