diff --git a/web/client/api/CSW.js b/web/client/api/CSW.js index 613576e78a..a9572c696b 100644 --- a/web/client/api/CSW.js +++ b/web/client/api/CSW.js @@ -110,12 +110,28 @@ const extractWMSParamsFromURL = wms => { return false; }; +// Extract the relevant information from the wfs URL for (RNDT / INSPIRE) +const extractWFSParamsFromURL = wfs => { + const lowerCaseParams = new Map(Array.from(new URLSearchParams(wfs.value)).map(([key, value]) => [key.toLowerCase(), value])); + const layerName = lowerCaseParams.get('typename'); + if (layerName) { + return { + ...wfs, + protocol: 'OGC:WFS', + name: layerName, + value: `${wfs.value.match(/[^\?]+[\?]+/g)}service=WFS` + }; + } + return false; +}; + const toReference = (layerType, data, options) => { if (!data.name) { return null; } switch (layerType) { case 'wms': + case 'wfs': const urlValue = !(data.value.indexOf("http") === 0) ? (options && options.catalogURL || "") + "/" + data.value : data.value; @@ -142,39 +158,69 @@ const toReference = (layerType, data, options) => { }; const REGEX_WMS_EXPLICIT = [/^OGC:WMS-(.*)-http-get-map/g, /^OGC:WMS/g]; +const REGEX_WFS_EXPLICIT = [/^OGC:WFS-(.*)-http-get-(capabilities|feature)/g, /^OGC:WFS/g]; const REGEX_WMS_EXTRACT = /serviceType\/ogc\/wms/g; +const REGEX_WFS_EXTRACT = /serviceType\/ogc\/wfs/g; const REGEX_WMS_ALL = REGEX_WMS_EXPLICIT.concat(REGEX_WMS_EXTRACT); export const getLayerReferenceFromDc = (dc, options, checkEsri = true) => { const URI = dc?.URI && castArray(dc.URI); + const isWMS = isNil(options?.type) || options?.type === "wms"; + const isWFS = options?.type === "wfs"; // look in URI objects for wms and thumbnail if (URI) { - const wms = head(URI.map(uri => { - if (uri.protocol) { - if (REGEX_WMS_EXPLICIT.some(regex => uri.protocol.match(regex))) { + if (isWMS) { + const wms = head(URI.map(uri => { + if (uri.protocol) { + if (REGEX_WMS_EXPLICIT.some(regex => uri.protocol.match(regex))) { /** wms protocol params are explicitly defined as attributes (INSPIRE)*/ - return uri; - } - if (uri.protocol.match(REGEX_WMS_EXTRACT)) { + return uri; + } + if (uri.protocol.match(REGEX_WMS_EXTRACT)) { /** wms protocol params must be extracted from the element text (RNDT / INSPIRE) */ - return extractWMSParamsFromURL(uri); + return extractWMSParamsFromURL(uri); + } } + return false; + }).filter(item => item)); + if (wms) { + return toReference('wms', wms, options); + } + } + if (isWFS) { + const wfs = head(URI.map(uri => { + if (uri.protocol) { + if (REGEX_WFS_EXPLICIT.some(regex => uri.protocol.match(regex))) { + /** wfs protocol params are explicitly defined as attributes (INSPIRE)*/ + return uri; + } + if (uri.protocol.match(REGEX_WFS_EXTRACT)) { + /** wfs protocol params must be extracted from the element text (RNDT / INSPIRE) */ + return extractWFSParamsFromURL(uri); + } + } + return false; + }).filter(item => item)); + if (wfs) { + return toReference('wfs', wfs, options); } - return false; - }).filter(item => item)); - if (wms) { - return toReference('wms', wms, options); } } // look in references objects if (dc?.references?.length) { const refs = castArray(dc.references); const wms = head(refs.filter((ref) => { return ref.scheme && REGEX_WMS_EXPLICIT.some(regex => ref.scheme.match(regex)); })); - if (wms) { + const wfs = head(refs.filter((ref) => { return ref.scheme && REGEX_WFS_EXPLICIT.some(regex => ref.scheme.match(regex)); })); + if (isWMS && wms) { let urlObj = urlUtil.parse(getDefaultUrl(wms.value), true); let layerName = urlObj.query && urlObj.query.layers || dc.alternative; return toReference('wms', { ...wms, value: urlUtil.format(urlObj), name: layerName }, options); } + if (isWFS && wfs) { + let urlObj = urlUtil.parse(getDefaultUrl(wfs.value), true); + let layerName = urlObj.query && urlObj.query.layers || dc.alternative; + return toReference('wfs', { ...wfs, value: urlUtil.format(urlObj), name: layerName }, options); + } if (checkEsri) { // checks for esri arcgis in geonode csw const esri = head(refs.filter((ref) => { diff --git a/web/client/api/__tests__/CSW-test.js b/web/client/api/__tests__/CSW-test.js index 45069874e2..5856bea050 100644 --- a/web/client/api/__tests__/CSW-test.js +++ b/web/client/api/__tests__/CSW-test.js @@ -440,6 +440,34 @@ describe("getLayerReferenceFromDc", () => { expect(layerRef.type).toBe('OGC:WMS'); expect(layerRef.url).toBe('catalog_url/wmsurl?SERVICE=WMS&VERSION=1.3.0'); }); + it("test layer reference with dc.references of scheme OGC:WFS", () => { + const dc = {references: [{value: "http://wmsurl", scheme: 'OGC:WMS'}, {value: "http://wfsurl", scheme: 'OGC:WFS'}], alternative: "some_layer"}; + const layerRef = getLayerReferenceFromDc(dc, {type: "wfs"}); + expect(layerRef.params.name).toBe('some_layer'); + expect(layerRef.type).toBe('OGC:WFS'); + expect(layerRef.url).toBe('http://wfsurl/'); + }); + it("test layer reference with dc.references of scheme OGC:WMS-http-get-capabilities", () => { + const dc = {references: [{value: "http://wmsurl", scheme: 'OGC:WMS-http-get-map'}, {value: "http://wfsurl", scheme: 'OGC:WFS-http-get-capabilities'}], alternative: "some_layer"}; + const layerRef = getLayerReferenceFromDc(dc, {type: "wfs"}); + expect(layerRef.params.name).toBe('some_layer'); + expect(layerRef.type).toBe('OGC:WFS-http-get-capabilities'); + expect(layerRef.url).toBe('http://wfsurl/'); + }); + it("test layer reference with dc.references of scheme OGC:WMS-http-get-feature", () => { + const dc = {references: [{value: "http://wmsurl", scheme: 'OGC:WMS-http-get-map'}, {value: "http://wfsurl", scheme: 'OGC:WFS-http-get-feature'}], alternative: "some_layer"}; + const layerRef = getLayerReferenceFromDc(dc, {type: "wfs"}); + expect(layerRef.params.name).toBe('some_layer'); + expect(layerRef.type).toBe('OGC:WFS-http-get-feature'); + expect(layerRef.url).toBe('http://wfsurl/'); + }); + it("test layer reference with dc.URI of scheme serviceType/ogc/wfs and options", () => { + const dc = {URI: [{value: "wfsurl?service=wfs&typename=some_layer&version=1.3.0", protocol: 'serviceType/ogc/wfs'}, {value: "wmsurl", protocol: 'OGC:WMS'}]}; + const layerRef = getLayerReferenceFromDc(dc, {type: "wfs", catalogURL: "catalog_url"}); + expect(layerRef.params.name).toBe('some_layer'); + expect(layerRef.type).toBe('OGC:WFS'); + expect(layerRef.url).toBe('catalog_url/wfsurl?service=WFS'); + }); it("test layer reference with multiple dc.URI of scheme OGC:WMS", () => { const dc = {URI: [{value: "http://wmsurl", protocol: 'OGC:WMS', name: 'some_layer'}, {value: "wfsurl", protocol: 'OGC:WFS'}]}; const layerRef = getLayerReferenceFromDc(dc); @@ -480,6 +508,15 @@ describe("getLayerReferenceFromDc", () => { expect(getLayerReferenceFromDc(dc).url).toBe(_url[0]); }); + it('getLayerReferenceFromDc for service type WFS', () => { + const _url = [ + 'http://gs-stable.geosolutionsgroup.com:443/geoserver1', + 'http://gs-stable.geosolutionsgroup.com:443/geoserver2', + 'http://gs-stable.geosolutionsgroup.com:443/geoserver3' + ]; + const dc = {references: [{value: 'wmsurl', scheme: 'OGC:WMS'}, {value: _url, scheme: 'OGC:WFS'}], alternative: "some_layer"}; + expect(getLayerReferenceFromDc(dc, {type: "wfs"}).url).toBe(_url[0]); + }); }); diff --git a/web/client/api/catalog/CSW.js b/web/client/api/catalog/CSW.js index 9e00062de2..abc788a567 100644 --- a/web/client/api/catalog/CSW.js +++ b/web/client/api/catalog/CSW.js @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -import { head, isString, includes, castArray, sortBy, uniq } from 'lodash'; +import { head, isString, includes, castArray, sortBy, uniq, isEmpty } from 'lodash'; import { getLayerFromRecord as getLayerFromWMSRecord } from './WMS'; import { getMessageById } from '../../utils/LocaleUtils'; import { transformExtentToObj} from '../../utils/CoordinatesUtils'; @@ -104,12 +104,22 @@ function getThumbnailFromDc(dc, options) { } return thumbURL; } + +/** + * Extract bounding box object from the record + * @param {Object} record from OGC service + */ +function getBoundingBox(record) { + if (isEmpty(record.boundingBox?.crs) || isEmpty(record.boundingBox?.extent)) { + return null; + } + return { + crs: record.boundingBox?.crs, + bounds: transformExtentToObj(record.boundingBox?.extent) + }; +} function getCatalogRecord3DTiles(record, metadata) { const dc = record.dc; - let bbox = { - crs: record.boundingBox.crs, - bounds: transformExtentToObj(record.boundingBox.extent) - }; return { serviceType: '3dtiles', isValid: true, @@ -118,7 +128,7 @@ function getCatalogRecord3DTiles(record, metadata) { identifier: dc && isString(dc.identifier) && dc.identifier || '', url: dc?.URI?.value || "", thumbnail: null, - bbox, + bbox: getBoundingBox(record), format: dc && dc.format || "", references: [], catalogType: 'csw', @@ -136,6 +146,28 @@ const recordToLayer = (record, options) => { return null; } }; +const ADDITIONAL_OGC_SERVICES = ['wfs']; // Add services when support is provided +const getAdditionalOGCService = (record, references, parsedReferences = {}) => { + const hasAdditionalService = ADDITIONAL_OGC_SERVICES.some(serviceType => !isEmpty(parsedReferences[serviceType])); + if (hasAdditionalService) { + return { + additionalOGCServices: { + ...ADDITIONAL_OGC_SERVICES + .map(serviceType => { + const ogcReferences = parsedReferences[serviceType] ?? {}; + const {url, params: {name} = {}} = ogcReferences; + return {[serviceType]: { + url, name, references, ogcReferences, fetchCapabilities: true, + boundingBox: getBoundingBox(record) + }}; + }) + .flat() + .reduce((a, c) => ({...c, ...a}), {}) + } + }; + } + return null; +}; export const preprocess = commonPreprocess; export const validate = commonValidate; @@ -168,9 +200,11 @@ export const getCatalogRecords = (records, options, locales) => { }); } - const layerReference = getLayerReferenceFromDc(dc, options); - if (layerReference) { - references.push(layerReference); + const layerReferences = ['wms', ...ADDITIONAL_OGC_SERVICES].map(serviceType => { + return getLayerReferenceFromDc(dc, {...options, type: serviceType}); + }).filter(ref => ref); + if (!isEmpty(layerReferences)) { + references = references.concat(layerReferences); } // create the references array (now only wms is supported) @@ -229,16 +263,16 @@ export const getCatalogRecords = (records, options, locales) => { ...extractEsriReferences({ references }) }; - const layerType = Object.keys(parsedReferences).find(key => parsedReferences[key]); - const ogcReferences = layerType && layerType !== 'esri' - ? parsedReferences[layerType] - : undefined; let catRecord; if (dc && dc.format === THREE_D_TILES) { catRecord = getCatalogRecord3DTiles(record, metadata); } else if (dc && dc.format === MODEL) { // todo: handle get catalog record for ifc } else { + const layerType = Object.keys(parsedReferences).filter(key => !ADDITIONAL_OGC_SERVICES.includes(key)).find(key => parsedReferences[key]); + const ogcReferences = layerType && layerType !== 'esri' + ? parsedReferences[layerType] + : undefined; catRecord = { serviceType: 'csw', layerType, @@ -253,7 +287,8 @@ export const getCatalogRecords = (records, options, locales) => { tags: dc && dc.tags || '', metadata, capabilities: record.capabilities, - ogcReferences + ogcReferences, + ...getAdditionalOGCService(record, references, parsedReferences) }; } return catRecord; diff --git a/web/client/api/catalog/WFS.js b/web/client/api/catalog/WFS.js index 6aa69deb20..d3942e5af6 100644 --- a/web/client/api/catalog/WFS.js +++ b/web/client/api/catalog/WFS.js @@ -17,7 +17,7 @@ import { testService as commonTestService, preprocess as commonPreprocess } from './common'; -import { get, castArray } from 'lodash'; +import { get, castArray, isEmpty } from 'lodash'; const capabilitiesCache = {}; @@ -142,7 +142,25 @@ export const getCatalogRecords = ({records} = {}) => { return null; }; +/** + * Formulate WFS layer data from record + * and fetch capabilities if needed to add capibilities specific data + * @param {Object} record data obtained from catalog service + * @param {Object} options props specific to wfs + * @returns {Promise} promise that resolves to formulated layer data + */ +const getLayerData = (record, options) => { + const layer = recordToLayer(record, options); + return getRecords(record.url, 1, 1, record.name).then((result)=> { + const [newRecord] = result?.records ?? []; + return isEmpty(newRecord) ? layer : recordToLayer(newRecord, options); + }).catch(() => layer); +}; + export const getLayerFromRecord = (record, options, asPromise) => { + if (options.fetchCapabilities && asPromise) { + return getLayerData(record, options); + } const layer = recordToLayer(record, options); return asPromise ? Promise.resolve(layer) : layer; }; diff --git a/web/client/api/catalog/__tests__/CSW-test.js b/web/client/api/catalog/__tests__/CSW-test.js index 04e7f6482a..0a846d313e 100644 --- a/web/client/api/catalog/__tests__/CSW-test.js +++ b/web/client/api/catalog/__tests__/CSW-test.js @@ -172,6 +172,137 @@ describe('Test correctness of the CSW catalog APIs', () => { expect(records[0].metadata.uri).toEqual(['']); }); + it('csw with additional OGC services', () => { + const records = getCatalogRecords({ + records: [{ + boundingBox: { + extent: [43.718, 11.348, 43.84, 11.145], + crs: 'EPSG:3003' + }, + dc: { + references: [], + identifier: 'c_d612:sha-identifier', + title: 'title', + type: 'dataset', + subject: [ + 'web', + 'world', + 'sport', + 'transportation' + ], + format: [ + 'ESRI Shapefile', + 'KML' + ], + contributor: 'contributor', + rights: [ + 'otherRestrictions', + 'otherRestrictions' + ], + source: 'source', + relation: { + TYPE_NAME: 'DC_1_1.SimpleLiteral' + }, + URI: [{ + TYPE_NAME: "DC_1_1.URI", + description: "layer1 description", + name: "layer1", + protocol: "OGC:WMS", + value: "https://geoserver/wms?SERVICE=WMS&REQUEST=GetCapabilities" + }, + { + TYPE_NAME: "DC_1_1.URI", + description: "layer2 description", + name: "layer2", + protocol: "OGC:WFS", + value: "https://geoserver/wfs?SERVICE=WFS&REQUEST=GetCapabilities" + }] + } + }] + }, {}); + expect(records.length).toEqual(1); + expect(records[0].boundingBox).toEqual({ extent: [43.718, 11.348, 43.84, 11.145], crs: 'EPSG:3003' }); + expect(records[0].description).toEqual(""); + expect(records[0].identifier).toEqual("c_d612:sha-identifier"); + expect(records[0].thumbnail).toEqual(null); + expect(records[0].title).toEqual('title'); + expect(records[0].tags).toEqual(''); + expect(records[0].metadata.boundingBox).toEqual(['43.718,11.348,43.84,11.145']); + expect(records[0].metadata.contributor).toEqual(['contributor']); + expect(records[0].metadata.format).toEqual(['ESRI Shapefile', 'KML']); + expect(records[0].metadata.identifier).toEqual(['c_d612:sha-identifier']); + expect(records[0].metadata.relation).toEqual([{ TYPE_NAME: 'DC_1_1.SimpleLiteral' }]); + expect(records[0].metadata.rights).toEqual(['otherRestrictions']); + expect(records[0].metadata.references).toEqual(['']); + expect(records[0].metadata.source).toEqual(['source']); + expect(records[0].metadata.subject).toEqual([""]); + expect(records[0].metadata.title).toEqual(['title']); + expect(records[0].metadata.type).toEqual(['dataset']); + expect(records[0].metadata.uri).toEqual(['']); + + expect(records[0].references).toEqual([{ + type: 'OGC:WMS', + url: 'https://geoserver/wms?SERVICE=WMS&REQUEST=GetCapabilities', + SRS: [], + params: { + name: 'layer1' + } + }, { + type: 'OGC:WFS', + url: 'https://geoserver/wfs?SERVICE=WFS&REQUEST=GetCapabilities', + SRS: [], + params: { + name: 'layer2' + } + }]); + expect(records[0].ogcReferences).toEqual({ + type: 'OGC:WMS', + url: 'https://geoserver/wms?SERVICE=WMS&REQUEST=GetCapabilities', + SRS: [], + params: { + name: 'layer1' + } + }); + expect(records[0].additionalOGCServices).toEqual( + { + wfs: { + url: 'https://geoserver/wfs?SERVICE=WFS&REQUEST=GetCapabilities', + name: 'layer2', + references: [ + { + type: 'OGC:WMS', + url: 'https://geoserver/wms?SERVICE=WMS&REQUEST=GetCapabilities', + SRS: [], + params: { name: 'layer1'} + }, + { + type: 'OGC:WFS', + url: 'https://geoserver/wfs?SERVICE=WFS&REQUEST=GetCapabilities', + SRS: [], + params: {name: 'layer2'} + } + ], + ogcReferences: { + type: 'OGC:WFS', + url: 'https://geoserver/wfs?SERVICE=WFS&REQUEST=GetCapabilities', + SRS: [], + params: {name: 'layer2'} + }, + fetchCapabilities: true, + boundingBox: { + crs: 'EPSG:3003', + bounds: { + minx: 43.718, + miny: 11.348, + maxx: 43.84, + maxy: 11.145 + } + } + } + } + ); + }); + it('csw dct:temporal metadata YYYY-MM-DD', () => { let records = getCatalogRecords({ records: [{ diff --git a/web/client/components/catalog/RecordItem.jsx b/web/client/components/catalog/RecordItem.jsx index b290d35047..0d2d7eab03 100644 --- a/web/client/components/catalog/RecordItem.jsx +++ b/web/client/components/catalog/RecordItem.jsx @@ -7,9 +7,9 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import {isObject, head, isArray, trim } from 'lodash'; -import { Image } from 'react-bootstrap'; - +import { isObject, head, isArray, trim, isEmpty } from 'lodash'; +import { Image, SplitButton as SplitButtonT, Glyphicon, MenuItem } from 'react-bootstrap'; +import tooltip from '../../components/misc/enhancers/tooltip'; import { buildSRSMap, getRecordLinks @@ -28,6 +28,8 @@ import defaultThumb from './img/default.jpg'; import defaultBackgroundThumbs from '../../plugins/background/DefaultThumbs'; import unknown from "../../plugins/background/assets/img/dafault.jpg"; import { getResolutions } from "../../utils/MapUtils"; +import Loader from '../misc/Loader'; +const SplitButton = tooltip(SplitButtonT); class RecordItem extends React.Component { static propTypes = { @@ -135,47 +137,77 @@ class RecordItem extends React.Component { return crs && !isSRSAllowed(crs); } + onAddToMap = (record, serviceType = record.serviceType) => { + if (this.isSRSNotAllowed(record)) { + return this.props.onError('catalog.srs_not_allowed'); + } + this.setState({ loading: true }); + return API[serviceType].getLayerFromRecord(record, { + fetchCapabilities: !!record.fetchCapabilities, + service: { + ...this.props.service, + format: this.props?.service?.format ?? this.props.defaultFormat + }, + layerBaseConfig: this.props.layerBaseConfig, + removeParams: this.props.authkeyParamNames, + catalogURL: this.props.catalogType === "csw" && this.props.catalogURL + ? this.props.catalogURL + + "?request=GetRecordById&service=CSW&version=2.0.2&elementSetName=full&id=" + + record.identifier + : null, + map: { + projection: this.props.crs, + resolutions: getResolutions() + } + }, true) + .then((layer) => { + if (layer) { + this.addLayer(layer, record); + } + }).finally(()=> { + this.setState({ loading: false }); + }); + } + getButtons = (record) => { const links = this.props.showGetCapLinks ? getRecordLinks(record) : []; + const showServices = !isEmpty(record.additionalOGCServices); return [ - { + ...(showServices ? [{ + Element: () => ( + : } + onClick={() => this.onAddToMap(record)} + pullRight="left" + disabled={this.state.loading} + > + this.onAddToMap(record)}> + + + { + Object.keys(record.additionalOGCServices ?? {}) + .map(serviceType => ( + this.onAddToMap(record.additionalOGCServices[serviceType], serviceType)}> + + ) + ) + } + + ) + }] : [{ tooltipId: 'catalog.addToMap', className: 'square-button-md', bsStyle: 'primary', disabled: this.state.loading, loading: this.state.loading, glyph: 'plus', - onClick: () => { - if (this.isSRSNotAllowed(record)) { - return this.props.onError('catalog.srs_not_allowed'); - } - this.setState({ loading: true }); - return API[record.serviceType].getLayerFromRecord(record, { - service: { - ...this.props.service, - format: this.props?.service?.format ?? this.props.defaultFormat - }, - layerBaseConfig: this.props.layerBaseConfig, - removeParams: this.props.authkeyParamNames, - catalogURL: this.props.catalogType === "csw" && this.props.catalogURL - ? this.props.catalogURL + - "?request=GetRecordById&service=CSW&version=2.0.2&elementSetName=full&id=" + - record.identifier - : null, - map: { - projection: this.props.crs, - resolutions: getResolutions() - } - }, true) - .then((layer) => { - if (layer) { - this.addLayer(layer, record); - } - this.setState({ loading: false }); - }); - - } - }, + onClick: () => this.onAddToMap(record) + }]), ...(links.length > 0 ? [{ Element: () => ( diff --git a/web/client/components/catalog/__tests__/RecordItem-test.jsx b/web/client/components/catalog/__tests__/RecordItem-test.jsx index 16b8bbd2c0..aec1eabd04 100644 --- a/web/client/components/catalog/__tests__/RecordItem-test.jsx +++ b/web/client/components/catalog/__tests__/RecordItem-test.jsx @@ -502,6 +502,84 @@ describe('This test for RecordItem', () => { expect(button).toBeTruthy(); button.click(); }); + it('check additional OGC service', (done) => { + const record = { + serviceType: 'csw', + layerType: "wms", + isValid: true, + identifier: "test-identifier", + title: "sample title", + tags: ["subject1", "subject2"], + description: "sample abstract", + thumbnail: SAMPLE_IMAGE, + boundingBox: { + extent: [10.686, + 44.931, + 46.693, + 12.54], + crs: "EPSG:4326" + }, + references: [{ + type: "OGC:WMS", + url: "http://wms.sample.service:80/geoserver/wms?SERVICE=WMS&ms2-authkey=TEST&requiredParam=REQUIRED", + SRS: [], + params: { name: "workspace:layername1" } + }], + ogcReferences: { + type: "OGC:WMS", + url: "http://wms.sample.service:80/geoserver/wms?SERVICE=WMS&ms2-authkey=TEST&requiredParam=REQUIRED", + SRS: [], + params: { name: "workspace:layername2" } + }, + additionalOGCServices: { + "wfs": { + url: "http://wfs.sample.service:80/geoserver/wfs?SERVICE=WFS", + name: "workspace:layername2", + fetchCapabilities: true, + references: [{ + type: "OGC:WMS", + url: "http://wms.sample.service:80/geoserver/wms?SERVICE=WMS&ms2-authkey=TEST&requiredParam=REQUIRED", + SRS: [], + params: { name: "workspace:layername1" } + }, { + type: "OGC:WFS", + url: "http://wfs.sample.service:80/geoserver/wfs?SERVICE=WFS", + SRS: [], + params: { name: "workspace:layername2" } + }] + } + } + }; + let actions = { + onLayerAdd: (layer, options) => { + const url = "http://wfs.sample.service:80/geoserver/wfs?SERVICE=WFS"; + expect(layer.type).toBe("wfs"); + expect(layer.search).toBeTruthy(); + expect(layer.search).toEqual({url, type: "wfs"}); + expect(layer.url).toBe(url); + expect(layer.name).toBe("workspace:layername2"); + expect(options.zoomToLayer).toBeTruthy(); + done(); + } + }; + const item = ReactDOM.render(, document.getElementById("container")); + expect(item).toBeTruthy(); + + const itemDom = ReactDOM.findDOMNode(item); + expect(itemDom).toBeTruthy(); + const splitButton = document.querySelector('#add-layer-button'); + expect(splitButton).toBeTruthy(); + TestUtils.Simulate.click(splitButton); + const menuWFS = document.querySelector('#ogc-wfs'); + expect(menuWFS).toBeTruthy(); + TestUtils.Simulate.click(menuWFS); + }); it('check event handlers with layerBaseConfig and csw service', (done) => { let actions = { diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json index 170108af60..d2e8914c4e 100644 --- a/web/client/translations/data.de-DE.json +++ b/web/client/translations/data.de-DE.json @@ -1614,6 +1614,10 @@ "noRecordsMatched": "Kein passender Eintrag", "wmsGetCapLink": "WMS", "wfsGetCapLink": "WFS", + "additionalOGCServices": { + "wfs": "AUS WFS", + "wms": "AUS WMS" + }, "share": "Teilen", "copyToClipboard": "In die Zwischenablage kopieren", "copied": "Kopiert!", diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json index 052412a569..6b3291e82c 100644 --- a/web/client/translations/data.en-US.json +++ b/web/client/translations/data.en-US.json @@ -1575,6 +1575,10 @@ "noRecordsMatched": "No record matched", "wmsGetCapLink": "WMS", "wfsGetCapLink": "WFS", + "additionalOGCServices": { + "wfs": "FROM WFS", + "wms": "FROM WMS" + }, "share": "Share", "copyToClipboard": "Copy to clipboard", "copied": "Copied!", diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json index b2b0e6fc80..db2200b3a1 100644 --- a/web/client/translations/data.es-ES.json +++ b/web/client/translations/data.es-ES.json @@ -1576,6 +1576,10 @@ "noRecordsMatched": "Ningún resultado encontrado", "wmsGetCapLink": "WMS", "wfsGetCapLink": "WFS", + "additionalOGCServices": { + "wfs": "DE WFS", + "wms": "DE WMS" + }, "share": "Compartir", "copyToClipboard": "Copiar en el portapapeles", "copied": "¡Copiado!", diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json index e0d4108af8..5a21e96ac1 100644 --- a/web/client/translations/data.fr-FR.json +++ b/web/client/translations/data.fr-FR.json @@ -1576,6 +1576,10 @@ "noRecordsMatched": "Aucun enregistrement correspondant", "wmsGetCapLink": "WMS", "wfsGetCapLink": "WFS", + "additionalOGCServices": { + "wfs": "DEPUIS WFS", + "wms": "DEPUIS WMS" + }, "share": "Partager", "copyToClipboard": "Copier vers le presse-papier", "copied": "Copié !", diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json index 402e49753d..1cc6898ef7 100644 --- a/web/client/translations/data.it-IT.json +++ b/web/client/translations/data.it-IT.json @@ -1575,6 +1575,10 @@ "noRecordsMatched": "Nessun risultato trovato", "wmsGetCapLink": "WMS", "wfsGetCapLink": "WFS", + "additionalOGCServices": { + "wfs": "DA WFS", + "wms": "DA WMS" + }, "share": "Condividi", "copyToClipboard": "Copia negli appunti", "copied": "Copiato!",