From 43647c89bdc57ec8de1de89a33937530b183517c Mon Sep 17 00:00:00 2001 From: jansule Date: Thu, 1 Dec 2022 17:28:48 +0100 Subject: [PATCH 01/16] tmp - REBASE ME --- src/util/ArcGISRest.js | 22 ++++ src/view/form/AddArcGISRest.js | 49 +++++--- src/view/tree/ArcGISRestServiceTree.js | 149 +++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 18 deletions(-) create mode 100644 src/view/tree/ArcGISRestServiceTree.js diff --git a/src/util/ArcGISRest.js b/src/util/ArcGISRest.js index 9c82bf308..8766311f0 100644 --- a/src/util/ArcGISRest.js +++ b/src/util/ArcGISRest.js @@ -85,7 +85,29 @@ Ext.define('BasiGX.util.ArcGISRest', { } return url; + }, + + createMapServerUrl: function(serviceUrl, serverName, format) { + // TODO refactor with createFeatureServerUrl if code works + if (!BasiGX.util.ArcGISRest.isArcGISRestUrl(serviceUrl)) { + return; + } + var urlObj = new URL(serviceUrl); + var parts = urlObj.pathname.split('/'); + if (parts[parts.length - 1] === '') { + parts.pop(); + } + parts.pop(); + parts.push(serverName); + parts.push('MapServer'); + var path = parts.join('/'); + + var url = urlObj.origin + path; + if (format) { + url = BasiGX.util.Url.setQueryParam(url, 'f', format); + } + return url; }, /** diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index e45c94d4e..5da552ba0 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -29,11 +29,16 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { 'Ext.form.FieldSet', 'Ext.form.field.ComboBox', 'Ext.form.CheckboxGroup', + 'Ext.tree.Panel', 'Ext.Promise', + 'Ext.data.TreeStore', + 'Ext.data.Model', + 'Ext.data.proxy.Ajax', 'BasiGX.util.Map', 'BasiGX.util.MsgBox', 'BasiGX.util.Url', - 'BasiGX.util.ArcGISRest' + 'BasiGX.util.ArcGISRest', + 'BasiGX.view.tree.ArcGISRestServiceTree' ], viewModel: { @@ -67,7 +72,8 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { msgInvalidUrl: 'Die angegebene URL ist keine valide ArcGISRest URL', documentation: '

ArcGISRest Layer hinzufügen

• In ' + 'diesem Dialog können Sie mit Hilfe einer ArcGISRest-URL ' + - 'einen beliebigen Kartendienst der Karte hinzufügen.' + 'einen beliebigen Kartendienst der Karte hinzufügen.', + serviceLayersVisibility: {} } }, @@ -360,16 +366,16 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { return layerConfig.service.type === 'FeatureServer'; } ); + var nonFeatureServers = Ext.Array.filter( + layerConfigs, function(layerConfig) { + return layerConfig.service.type !== 'FeatureServer'; + } + ); this.loadLayersOfFeatureServers(featureServers) .then(function(featureServerConfigs) { - layerConfigs = Ext.Array.filter( - layerConfigs, function(layerConfig) { - return layerConfig.service.type !== 'FeatureServer'; - } - ); - layerConfigs = Ext.Array.merge( - layerConfigs, featureServerConfigs); - this.fillAvailableLayersFieldset(layerConfigs); + var mergedConfigs = Ext.Array.merge( + nonFeatureServers, featureServerConfigs); + this.fillAvailableLayersFieldset(mergedConfigs); this.updateControlToolbarState(); this.setLoading(false); }.bind(this)); @@ -609,16 +615,23 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { var checkBoxes = []; var candidatesInitiallyChecked = me.getCandidatesInitiallyChecked(); Ext.each(layers, function(layer) { - var boxLabel = layer.service.name; - if (layer.service.type === 'FeatureServer') { - boxLabel += '/' + layer.layer.name; - } + // var boxLabel = layer.service.name; + // if (layer.service.type === 'FeatureServer') { + // boxLabel += '/' + layer.layer.name; + // } + checkBoxes.push({ - xtype: 'checkbox', - boxLabel: boxLabel, - checked: candidatesInitiallyChecked, - arcGISLayerConfig: layer + xtype: 'basigx-tree-arcgisrestservicetree', + arcGISLayerConfig: layer, + checked: candidatesInitiallyChecked }); + + // checkBoxes.push({ + // xtype: 'checkbox', + // boxLabel: boxLabel, + // checked: candidatesInitiallyChecked, + // arcGISLayerConfig: layer + // }); }); cbGroup.add(checkBoxes); diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js new file mode 100644 index 000000000..32bee49d7 --- /dev/null +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -0,0 +1,149 @@ +/* Copyright (c) 2022-present terrestris GmbH & Co. KG + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** + * Used to add an ArcGIS REST layer to the map + * + * @class BasiGX.view.tree.ArcGISRestServiceTree + */ +Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { + extend: 'Ext.tree.Panel', + xtype: 'basigx-tree-arcgisrestservicetree', + + requires: [ + 'GeoExt.data.model.ArcGISRestServiceLayer', + 'BasiGX.util.ArcGISRest', + 'Ext.tree.Column' + ], + + config: { + /** + * Whether we will send the `X-Requested-With` header when fetching the + * document from the URL. The `X-Requested-With` header is + * usually added for XHR, but adding it should lead to a preflight + * request (see https://goo.gl/6JzdUI), which some servers fail. + * + * @type {Boolean} + */ + useDefaultXhrHeader: false + }, + + arcGISLayerConfig: null, + + checked: null, + + columns: { + header: false, + items: [{ + xtype: 'treecolumn', + renderer: function(v, metaData, record) { + if (!record.isRoot()) { + var eyeGlyph = 'xf06e@FontAwesome'; + var eyeSlashGlyph = 'xf070@FontAwesome'; + if (record.get('visibility')) { + metaData.glyph = eyeGlyph; + } else { + metaData.glyph = eyeSlashGlyph; + } + } + return record.get('name'); + } + }], + defaults: { + flex: 1 + } + }, + + initComponent: function() { + var me = this; + me.callParent(); + + var rootLabel = me.arcGISLayerConfig.service.name; + if (me.arcGISLayerConfig.service.type === 'FeatureServer') { + rootLabel += '/' + me.arcGISLayerConfig.layer.name; + } + + me.setStore({ + model: 'GeoExt.data.model.ArcGISRestServiceLayer', + root: { + name: rootLabel, + children: [] + }, + listeners: { + 'nodeexpand': me.onNodeExpand.bind(me) + } + }); + }, + + onNodeExpand: function(expandedNode) { + var me = this; + if (expandedNode.hasChildNodes()) { + return; + } + var serviceUrl = BasiGX.util.ArcGISRest.createMapServerUrl( + this.arcGISLayerConfig.url, + this.arcGISLayerConfig.service.name, + 'json' + ); + // TODO requesting service and populating store should + // be done by parent component. We should only fire an event + this.requestService(serviceUrl) + .then( + function(response) { + return me.onRequestServiceSuccess(response, expandedNode); + }, + this.onRequestServiceFailure.bind(this) + ); + }, + + requestService: function(serviceUrl) { + var me = this; + return new Ext.Promise(function (resolve, reject) { + Ext.Ajax.request({ + url: serviceUrl, + method: 'GET', + useDefaultXhrHeader: me.getUseDefaultXhrHeader(), + success: function (response) { + var respJson = Ext.decode(response.responseText); + resolve(respJson); + }, + failure: function (response) { + reject(response.status); + } + }); + }); + }, + + onRequestServiceSuccess: function(response, expandedNode) { + var layers = Ext.Array.map(response.layers, function(layer) { + return Ext.create('GeoExt.data.model.ArcGISRestServiceLayer',{ + layerId: layer.id, + name: layer.name, + // TODO remove this line as soon as we use our custom leaf item + text: layer.name + layer.defaultVisibility, + defaultVisibility: layer.defaultVisibility, + visibility: layer.defaultVisibility, + leaf: true + }); + }); + expandedNode.appendChild(layers); + }, + + onRequestServiceFailure: function(status) { + // TODO give feedback + console.log('failed to request service'); + } + +}); From e189c64965411be1f637f76999b851e7b83d6e27 Mon Sep 17 00:00:00 2001 From: Jakob Miksch Date: Wed, 4 Jan 2023 10:39:39 +0100 Subject: [PATCH 02/16] Toggle visibility of sublayer --- src/view/tree/ArcGISRestServiceTree.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js index 32bee49d7..1acd1d89f 100644 --- a/src/view/tree/ArcGISRestServiceTree.js +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -44,6 +44,14 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { checked: null, + listeners: { + itemclick: function(view, record){ + // toggle visibility of sublayer + var currentVisibility = record.get('visibility'); + record.set('visibility', !currentVisibility); + } + }, + columns: { header: false, items: [{ From 3b9b67cf7f1e60671ac06e06e88d0d555dc667c0 Mon Sep 17 00:00:00 2001 From: Jakob Miksch Date: Wed, 4 Jan 2023 11:52:01 +0100 Subject: [PATCH 03/16] Basic display of sublayers --- src/util/ArcGISRest.js | 18 +++++++++++++++++- src/view/form/AddArcGISRest.js | 15 ++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/util/ArcGISRest.js b/src/util/ArcGISRest.js index 8766311f0..7cdad34ae 100644 --- a/src/util/ArcGISRest.js +++ b/src/util/ArcGISRest.js @@ -160,6 +160,9 @@ Ext.define('BasiGX.util.ArcGISRest', { * a FeatureServer layer. Mandatory for layers of type Feature Server. * @param {number} layerConfig.layer.id The id of a FeatureServer layer. * @param {string} layerConfig.layer.name The name of a FeatureServer + * + * TODO: add sublayers + * * layer. * @param {boolean} useDefaultHeader Whether to use the default * Xhr header. @@ -174,6 +177,15 @@ Ext.define('BasiGX.util.ArcGISRest', { Ext.log.warn('Provided URL is not a valid ArcGISRest URL'); return Ext.Promise.reject(); } + + // collect all sublayer indexes the user has marked as visible + var visibleLayerIndexes = []; + layerConfig.subLayers.each(function(sublayer, layerIndex){ + if (sublayer.get('visibility')){ + visibleLayerIndexes.push(layerIndex); + } + }); + var serviceUrl = [rootUrl, service.name, service.type].join('/'); var onReject = function() { return Ext.Promise.reject(); @@ -194,7 +206,11 @@ Ext.define('BasiGX.util.ArcGISRest', { source = new ol.source.TileArcGISRest({ url: serviceUrl, projection: 'EPSG:' + - serviceInfo.spatialReference.wkid + serviceInfo.spatialReference.wkid, + params: { + 'LAYERS': 'show:' + visibleLayerIndexes.join(',') + } + }); layer = new ol.layer.Tile({ source: source, diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index 5da552ba0..2adabc48e 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -111,7 +111,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { /** * Default url for the textfield or combobox. */ - defaultUrl: 'https://gis.epa.ie/arcgis/rest/services', + defaultUrl: 'https://gis.epa.ie/arcgis/rest/services/Copernicus', /** * Whether we will send the `X-Requested-With` header when fetching the @@ -700,11 +700,16 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { addCheckedLayers: function() { var me = this; var fs = me.down('[name=fs-available-layers]'); - var checkboxes = fs.query('checkbox[checked=true][disabled=false]'); + // var checkboxes = fs.query('checkbox[checked=true][disabled=false]'); + // TODO: find cleaner approach with error handling + var layerItems = fs.items.items[0].items.items; var map = BasiGX.util.Map.getMapComponent().getMap(); var useDefaultHeader = me.getUseDefaultXhrHeader(); - Ext.each(checkboxes, function(checkbox) { - var config = checkbox.arcGISLayerConfig; + Ext.each(layerItems, function(layerItem) { + var config = layerItem.arcGISLayerConfig; + + // TODO: rethink provided argument + config.subLayers = layerItem.getStore(); BasiGX.util.ArcGISRest.createOlLayerFromArcGISRest( config, useDefaultHeader ) @@ -715,7 +720,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { me.fireEvent('beforearcgisrestadd', layer); map.addLayer(layer); me.fireEvent('arcgisrestadd', layer); - checkbox.setDisabled(true); + layerItem.setDisabled(true); me.updateControlToolbarState(); }); }); From 34bc77892db2e578edda485a93672a41026c7d01 Mon Sep 17 00:00:00 2001 From: Jakob Miksch Date: Wed, 4 Jan 2023 15:30:29 +0100 Subject: [PATCH 04/16] Cleanup layer adding --- src/util/ArcGISRest.js | 20 ++++++++--------- src/view/form/AddArcGISRest.js | 31 +++++++++++++++++++++----- src/view/tree/ArcGISRestServiceTree.js | 2 +- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/util/ArcGISRest.js b/src/util/ArcGISRest.js index 7cdad34ae..ddb1b9648 100644 --- a/src/util/ArcGISRest.js +++ b/src/util/ArcGISRest.js @@ -159,13 +159,9 @@ Ext.define('BasiGX.util.ArcGISRest', { * @param {object} layerConfig.layer (optional) The layer config of * a FeatureServer layer. Mandatory for layers of type Feature Server. * @param {number} layerConfig.layer.id The id of a FeatureServer layer. - * @param {string} layerConfig.layer.name The name of a FeatureServer - * - * TODO: add sublayers - * - * layer. - * @param {boolean} useDefaultHeader Whether to use the default - * Xhr header. + * @param {string} layerConfig.layer.name The name of a FeatureServer layer. + * @param {Ext.data.TreeStore} layerConfig.subLayerStore The tree store containing the sublayers. + * @param {boolean} useDefaultHeader Whether to use the default Xhr header. * @return {Ext.Promise} A promise containing the olLayer. */ createOlLayerFromArcGISRest: function(layerConfig, useDefaultHeader) { @@ -180,9 +176,13 @@ Ext.define('BasiGX.util.ArcGISRest', { // collect all sublayer indexes the user has marked as visible var visibleLayerIndexes = []; - layerConfig.subLayers.each(function(sublayer, layerIndex){ - if (sublayer.get('visibility')){ - visibleLayerIndexes.push(layerIndex); + layerConfig.subLayerStore.each(function(sublayer, layerIndex){ + var visibility = sublayer.get('visibility'); + var layerId = sublayer.get('layerId'); + var layerIdValid = Ext.isNumeric(layerId) && layerId >= 0; + + if (visibility && layerIdValid){ + visibleLayerIndexes.push(layerId); } }); diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index 2adabc48e..b205a83de 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -700,16 +700,37 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { addCheckedLayers: function() { var me = this; var fs = me.down('[name=fs-available-layers]'); - // var checkboxes = fs.query('checkbox[checked=true][disabled=false]'); - // TODO: find cleaner approach with error handling - var layerItems = fs.items.items[0].items.items; + + // collect checked layers from form + var layerItems = []; + var layerConfigTrees = fs.query('basigx-tree-arcgisrestservicetree'); + Ext.each(layerConfigTrees, function(layerConfig){ + var store = layerConfig.getStore(); + if (!store) { + return; + } + + var root = store.getRoot(); + if (!root) { + return; + } + + if (root.get('checked')) { + layerItems.push(layerConfig) + } + }); + var map = BasiGX.util.Map.getMapComponent().getMap(); var useDefaultHeader = me.getUseDefaultXhrHeader(); Ext.each(layerItems, function(layerItem) { var config = layerItem.arcGISLayerConfig; - // TODO: rethink provided argument - config.subLayers = layerItem.getStore(); + var subLayerStore = layerItem.getStore(); + if (!subLayerStore){ + return; + } + + config.subLayerStore = subLayerStore; BasiGX.util.ArcGISRest.createOlLayerFromArcGISRest( config, useDefaultHeader ) diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js index 1acd1d89f..f4de34257 100644 --- a/src/view/tree/ArcGISRestServiceTree.js +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -42,7 +42,6 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { arcGISLayerConfig: null, - checked: null, listeners: { itemclick: function(view, record){ @@ -87,6 +86,7 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { model: 'GeoExt.data.model.ArcGISRestServiceLayer', root: { name: rootLabel, + checked: true, children: [] }, listeners: { From 83233b193132b7a8244f7da3e214688c78b956ba Mon Sep 17 00:00:00 2001 From: Jakob Miksch Date: Wed, 4 Jan 2023 16:20:30 +0100 Subject: [PATCH 05/16] Cleanup --- src/util/ArcGISRest.js | 6 ++++-- src/view/tree/ArcGISRestServiceTree.js | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/ArcGISRest.js b/src/util/ArcGISRest.js index ddb1b9648..15ce3b91a 100644 --- a/src/util/ArcGISRest.js +++ b/src/util/ArcGISRest.js @@ -159,9 +159,11 @@ Ext.define('BasiGX.util.ArcGISRest', { * @param {object} layerConfig.layer (optional) The layer config of * a FeatureServer layer. Mandatory for layers of type Feature Server. * @param {number} layerConfig.layer.id The id of a FeatureServer layer. - * @param {string} layerConfig.layer.name The name of a FeatureServer layer. + * @param {string} layerConfig.layer.name The name of a FeatureServer + * layer. * @param {Ext.data.TreeStore} layerConfig.subLayerStore The tree store containing the sublayers. - * @param {boolean} useDefaultHeader Whether to use the default Xhr header. + * @param {boolean} useDefaultHeader Whether to use the default Xhr + * header. * @return {Ext.Promise} A promise containing the olLayer. */ createOlLayerFromArcGISRest: function(layerConfig, useDefaultHeader) { diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js index f4de34257..749ca0999 100644 --- a/src/view/tree/ArcGISRestServiceTree.js +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -139,8 +139,6 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { return Ext.create('GeoExt.data.model.ArcGISRestServiceLayer',{ layerId: layer.id, name: layer.name, - // TODO remove this line as soon as we use our custom leaf item - text: layer.name + layer.defaultVisibility, defaultVisibility: layer.defaultVisibility, visibility: layer.defaultVisibility, leaf: true From 6302b131551f002df79001f69125e44e06cf00d1 Mon Sep 17 00:00:00 2001 From: Jakob Miksch Date: Wed, 4 Jan 2023 17:19:40 +0100 Subject: [PATCH 06/16] Cleanup --- src/util/ArcGISRest.js | 2 +- src/view/form/AddArcGISRest.js | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/util/ArcGISRest.js b/src/util/ArcGISRest.js index 15ce3b91a..8b6c253f8 100644 --- a/src/util/ArcGISRest.js +++ b/src/util/ArcGISRest.js @@ -176,7 +176,7 @@ Ext.define('BasiGX.util.ArcGISRest', { return Ext.Promise.reject(); } - // collect all sublayer indexes the user has marked as visible + // collect all sublayer indexes that the user has marked as visible var visibleLayerIndexes = []; layerConfig.subLayerStore.each(function(sublayer, layerIndex){ var visibility = sublayer.get('visibility'); diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index b205a83de..4993e5d6f 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -615,23 +615,11 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { var checkBoxes = []; var candidatesInitiallyChecked = me.getCandidatesInitiallyChecked(); Ext.each(layers, function(layer) { - // var boxLabel = layer.service.name; - // if (layer.service.type === 'FeatureServer') { - // boxLabel += '/' + layer.layer.name; - // } - checkBoxes.push({ xtype: 'basigx-tree-arcgisrestservicetree', arcGISLayerConfig: layer, checked: candidatesInitiallyChecked }); - - // checkBoxes.push({ - // xtype: 'checkbox', - // boxLabel: boxLabel, - // checked: candidatesInitiallyChecked, - // arcGISLayerConfig: layer - // }); }); cbGroup.add(checkBoxes); From b45f6b34f0ea9047bd1d41cdf554bb0970a42d31 Mon Sep 17 00:00:00 2001 From: Jakob Miksch Date: Wed, 4 Jan 2023 17:35:14 +0100 Subject: [PATCH 07/16] Display error --- src/view/tree/ArcGISRestServiceTree.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js index 749ca0999..c08a4e865 100644 --- a/src/view/tree/ArcGISRestServiceTree.js +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -147,9 +147,12 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { expandedNode.appendChild(layers); }, - onRequestServiceFailure: function(status) { - // TODO give feedback - console.log('failed to request service'); + onRequestServiceFailure: function (status) { + // TODO: read text from viewmodel or config + var errorText = 'Requesting sublayers failed.' + Ext.Logger.error(errorText); + Ext.Logger.error(status); + BasiGX.warn(errorText); } }); From 5052f34f22b5b3455a78d56a336d198f09cd8e7d Mon Sep 17 00:00:00 2001 From: Jakob Miksch Date: Wed, 4 Jan 2023 17:58:39 +0100 Subject: [PATCH 08/16] Comment code --- src/util/ArcGISRest.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/util/ArcGISRest.js b/src/util/ArcGISRest.js index 8b6c253f8..fe8788b11 100644 --- a/src/util/ArcGISRest.js +++ b/src/util/ArcGISRest.js @@ -87,6 +87,17 @@ Ext.define('BasiGX.util.ArcGISRest', { return url; }, + /** + * Creates the URL for a MapServer request. + * + * TODO: does not work for this URL yet: + * https://gis.epa.ie/arcgis/rest/services + * + * @param {string} serviceUrl The URL of the service. + * @param {string} serverName The name of the MapServer. + * @param {string} format The output format. + * @return {string} The URL to the MapServer. + */ createMapServerUrl: function(serviceUrl, serverName, format) { // TODO refactor with createFeatureServerUrl if code works if (!BasiGX.util.ArcGISRest.isArcGISRestUrl(serviceUrl)) { From 79beeee1eac2a91ab784c605c63501670ec69cd2 Mon Sep 17 00:00:00 2001 From: Jakob Miksch Date: Wed, 4 Jan 2023 18:29:49 +0100 Subject: [PATCH 09/16] Connection of checked and expanded state of layer --- src/view/tree/ArcGISRestServiceTree.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js index c08a4e865..94254ce32 100644 --- a/src/view/tree/ArcGISRestServiceTree.js +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -45,9 +45,15 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { listeners: { itemclick: function(view, record){ - // toggle visibility of sublayer - var currentVisibility = record.get('visibility'); - record.set('visibility', !currentVisibility); + // toggle visibility of sublayer + var currentVisibility = record.get('visibility'); + record.set('visibility', !currentVisibility); + }, + beforecheckchange: function(node, checked){ + // when layer is not checked anymore it will be collapsed + if (checked) { + node.collapse(); + } } }, @@ -97,6 +103,10 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { onNodeExpand: function(expandedNode) { var me = this; + + // ensure expanded layer is always checked + expandedNode.set('checked', true); + if (expandedNode.hasChildNodes()) { return; } From 932831ca2a9df428630df006d26ec91186f8f6e6 Mon Sep 17 00:00:00 2001 From: fergaldoyle Date: Thu, 2 Feb 2023 12:44:34 +0000 Subject: [PATCH 10/16] Make AddArcGISRest form height automattic, allow parent to configure maxHeight of availableLayers --- src/view/form/AddArcGISRest.js | 43 ++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index 4993e5d6f..481f45cce 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -73,7 +73,8 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { documentation: '

ArcGISRest Layer hinzufügen

• In ' + 'diesem Dialog können Sie mit Hilfe einer ArcGISRest-URL ' + 'einen beliebigen Kartendienst der Karte hinzufügen.', - serviceLayersVisibility: {} + serviceLayersVisibility: {}, + availableLayersFieldSetMaxHeight: null } }, @@ -121,7 +122,12 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * * @type {Boolean} */ - useDefaultXhrHeader: false + useDefaultXhrHeader: false, + + /** + * Allow parent configure available layers fieldset maxHeight + */ + availableLayersFieldSetMaxHeight: null }, /** @@ -133,18 +139,18 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { defaultButton: 'requestLayersBtn', + layout: 'vbox', + items: [ { xtype: 'fieldset', - layout: 'anchor', - defaults: { - anchor: '100%' - }, + width: '100%', bind: { title: '{queryParamsFieldSetTitle}' }, items: [{ xtype: 'textfield', + width: '100%', bind: { fieldLabel: '{arcGISUrlTextFieldLabel}' }, @@ -194,14 +200,13 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { { xtype: 'fieldset', name: 'fs-available-layers', - layout: 'anchor', + flex: 1, + width: '100%', scrollable: 'y', - maxHeight: 200, - defaults: { - anchor: '100%' - }, + hidden: true, bind: { - title: '{availableLayersFieldSetTitle}' + title: '{availableLayersFieldSetTitle}', + maxHeight: '{availableLayersFieldSetMaxHeight}' }, items: { xtype: 'checkboxgroup', @@ -614,6 +619,9 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { var cbGroup = fs.down('checkboxgroup'); var checkBoxes = []; var candidatesInitiallyChecked = me.getCandidatesInitiallyChecked(); + + fs.setHidden(false); + Ext.each(layers, function(layer) { checkBoxes.push({ xtype: 'basigx-tree-arcgisrestservicetree', @@ -663,6 +671,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { me.add({ xtype: 'toolbar', name: 'interact-w-available-layers', + width: '100%', items: tbItems }); }, @@ -755,5 +764,15 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { Ext.each(checkboxes, function(checkbox) { checkbox.setValue(false); }); + }, + + /** + * Set the viewModel property availableLayersFieldSetMaxHeight when the component + * config property availableLayersFieldSetMaxHeight changes to it can be used in a binding + */ + updateAvailableLayersFieldSetMaxHeight: function(newValue) { + var me = this; + var vm = me.getViewModel(); + vm.set('availableLayersFieldSetMaxHeight', newValue); } }); From fed1e27e85e4ae22ee6053280291418d100fe0f0 Mon Sep 17 00:00:00 2001 From: fergaldoyle Date: Tue, 7 Feb 2023 10:54:38 +0000 Subject: [PATCH 11/16] Re-arrange buttons in AddArcGISRest form --- src/view/form/AddArcGISRest.js | 85 ++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index 481f45cce..62d7d17bb 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -195,6 +195,51 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { } } } + }, { + xtype: 'container', + layout: { + type: 'hbox', + pack: 'end' + }, + items: [{ + xtype: 'button', + name: 'resetFormBtn', + bind: { + text: '{resetBtnText}' + }, + handler: function(btn) { + var view = btn.up('basigx-form-addarcgisrest'); + view.getForm().reset(); + view.removeAddLayersComponents(); + view.resetState(); + var defaultValue = view.defaultUrl; + var combo = view.down('combobox[name=urlCombo]'); + combo.setValue(defaultValue); + var textfield = view.down('textfield[name=url]'); + textfield.setValue(defaultValue); + var fs = view.down('[name=fs-available-layers]'); + fs.setHidden(true); + } + }, { + xtype: 'button', + bind: { + text: '{requestLayersBtnText}' + }, + margin: '0 0 0 5', + name: 'requestLayersBtn', + reference: 'requestLayersBtn', + formBind: true, // only enabled once the form is valid + disabled: true, + handler: function(btn) { + var view = btn.up('basigx-form-addarcgisrest'); + view.resetState(); + view.requestLayers() + .then( + view.onGetServicesSuccess.bind(view), + view.onGetServicesFailure.bind(view) + ); + } + }] }] }, { @@ -225,46 +270,6 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { } ], - // Reset and Submit buttons - buttons: [ - { - name: 'resetFormBtn', - bind: { - text: '{resetBtnText}' - }, - handler: function(btn) { - var view = btn.up('basigx-form-addarcgisrest'); - view.getForm().reset(); - view.removeAddLayersComponents(); - view.resetState(); - var defaultValue = view.defaultUrl; - var combo = view.down('combobox[name=urlCombo]'); - combo.setValue(defaultValue); - var textfield = view.down('textfield[name=url]'); - textfield.setValue(defaultValue); - } - }, - '->', - { - bind: { - text: '{requestLayersBtnText}' - }, - name: 'requestLayersBtn', - reference: 'requestLayersBtn', - formBind: true, // only enabled once the form is valid - disabled: true, - handler: function(btn) { - var view = btn.up('basigx-form-addarcgisrest'); - view.resetState(); - view.requestLayers() - .then( - view.onGetServicesSuccess.bind(view), - view.onGetServicesFailure.bind(view) - ); - } - } - ], - /** * Initializes the form and sets up the parser instance. */ From efc50cf26650a3aa415eca0410c9102c2af9f9f7 Mon Sep 17 00:00:00 2001 From: fergaldoyle Date: Wed, 8 Feb 2023 13:05:45 +0000 Subject: [PATCH 12/16] Refactor createFeatureServerUrl and createMapServerUrl to use shared function and support root url, linting, tests --- src/util/ArcGISRest.js | 75 ++++++++++++++------------ src/view/form/AddArcGISRest.js | 13 +++-- src/view/tree/ArcGISRestServiceTree.js | 24 ++++----- test/spec/util/ArcGISRest.test.js | 31 +++++++++++ 4 files changed, 92 insertions(+), 51 deletions(-) diff --git a/src/util/ArcGISRest.js b/src/util/ArcGISRest.js index fe8788b11..a03229011 100644 --- a/src/util/ArcGISRest.js +++ b/src/util/ArcGISRest.js @@ -62,22 +62,27 @@ Ext.define('BasiGX.util.ArcGISRest', { }, /** - * Creates the URL for a FeatureServer request. + * Creates the URL for a FeatureServer/MapServer/GPServer. * * @param {string} serviceUrl The URL of the service. - * @param {string} serverName The name of the FeatureServer. + * @param {string} serverName The name of the Server. * @param {string} format The output format. - * @return {string} The URL to the FeatureServer. + * @param {string} serverType FeatureServer/MapServer/GPServer. + * @return {string} The URL to the FeatureServer/MapServer/GPServer. */ - createFeatureServerUrl: function(serviceUrl, serverName, format) { + createServerUrl: function(serviceUrl, serverName, format, serverType) { if (!BasiGX.util.ArcGISRest.isArcGISRestUrl(serviceUrl)) { return; } - var urlObj = new URL(serviceUrl); + + // Working with the root Url ensures that this code works + // with both root and folder level services + var rootUrl = BasiGX.util.ArcGISRest.getArcGISRestRootUrl( + serviceUrl + ); + var urlObj = new URL(rootUrl); var parts = urlObj.pathname.split('/'); - parts.push(serverName); - parts.push('FeatureServer'); - var path = parts.join('/'); + var path = parts.concat([serverName, serverType]).join('/'); var url = urlObj.origin + path; if (format) { @@ -88,10 +93,24 @@ Ext.define('BasiGX.util.ArcGISRest', { }, /** - * Creates the URL for a MapServer request. + * Creates the URL for a FeatureServer request. * - * TODO: does not work for this URL yet: - * https://gis.epa.ie/arcgis/rest/services + * @param {string} serviceUrl The URL of the service. + * @param {string} serverName The name of the FeatureServer. + * @param {string} format The output format. + * @return {string} The URL to the FeatureServer. + */ + createFeatureServerUrl: function(serviceUrl, serverName, format) { + return BasiGX.util.ArcGISRest.createServerUrl( + serviceUrl, + serverName, + format, + 'FeatureServer' + ); + }, + + /** + * Creates the URL for a MapServer request. * * @param {string} serviceUrl The URL of the service. * @param {string} serverName The name of the MapServer. @@ -99,26 +118,12 @@ Ext.define('BasiGX.util.ArcGISRest', { * @return {string} The URL to the MapServer. */ createMapServerUrl: function(serviceUrl, serverName, format) { - // TODO refactor with createFeatureServerUrl if code works - if (!BasiGX.util.ArcGISRest.isArcGISRestUrl(serviceUrl)) { - return; - } - var urlObj = new URL(serviceUrl); - var parts = urlObj.pathname.split('/'); - if (parts[parts.length - 1] === '') { - parts.pop(); - } - parts.pop(); - parts.push(serverName); - parts.push('MapServer'); - var path = parts.join('/'); - - var url = urlObj.origin + path; - if (format) { - url = BasiGX.util.Url.setQueryParam(url, 'f', format); - } - - return url; + return BasiGX.util.ArcGISRest.createServerUrl( + serviceUrl, + serverName, + format, + 'MapServer' + ); }, /** @@ -172,7 +177,8 @@ Ext.define('BasiGX.util.ArcGISRest', { * @param {number} layerConfig.layer.id The id of a FeatureServer layer. * @param {string} layerConfig.layer.name The name of a FeatureServer * layer. - * @param {Ext.data.TreeStore} layerConfig.subLayerStore The tree store containing the sublayers. + * @param {Ext.data.TreeStore} layerConfig.subLayerStore The tree + * store containing the sublayers. * @param {boolean} useDefaultHeader Whether to use the default Xhr * header. * @return {Ext.Promise} A promise containing the olLayer. @@ -189,7 +195,7 @@ Ext.define('BasiGX.util.ArcGISRest', { // collect all sublayer indexes that the user has marked as visible var visibleLayerIndexes = []; - layerConfig.subLayerStore.each(function(sublayer, layerIndex){ + layerConfig.subLayerStore.each(function(sublayer){ var visibility = sublayer.get('visibility'); var layerId = sublayer.get('layerId'); var layerIdValid = Ext.isNumeric(layerId) && layerId >= 0; @@ -221,7 +227,8 @@ Ext.define('BasiGX.util.ArcGISRest', { projection: 'EPSG:' + serviceInfo.spatialReference.wkid, params: { - 'LAYERS': 'show:' + visibleLayerIndexes.join(',') + 'LAYERS': 'show:' + + visibleLayerIndexes.join(',') } }); diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index 62d7d17bb..0c8dc8a2e 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -709,16 +709,16 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { Ext.each(layerConfigTrees, function(layerConfig){ var store = layerConfig.getStore(); if (!store) { - return; + return; } var root = store.getRoot(); if (!root) { - return; + return; } if (root.get('checked')) { - layerItems.push(layerConfig) + layerItems.push(layerConfig); } }); @@ -772,8 +772,11 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { }, /** - * Set the viewModel property availableLayersFieldSetMaxHeight when the component - * config property availableLayersFieldSetMaxHeight changes to it can be used in a binding + * Set the viewModel property availableLayersFieldSetMaxHeight + * when the component config property availableLayersFieldSetMaxHeight + * changes to it can be used in a binding + * + * @param {Number} newValue Value to set */ updateAvailableLayersFieldSetMaxHeight: function(newValue) { var me = this; diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js index 94254ce32..d66b4c6cf 100644 --- a/src/view/tree/ArcGISRestServiceTree.js +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -44,17 +44,17 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { listeners: { - itemclick: function(view, record){ - // toggle visibility of sublayer - var currentVisibility = record.get('visibility'); - record.set('visibility', !currentVisibility); - }, - beforecheckchange: function(node, checked){ - // when layer is not checked anymore it will be collapsed - if (checked) { - node.collapse(); - } - } + itemclick: function(view, record){ + // toggle visibility of sublayer + var currentVisibility = record.get('visibility'); + record.set('visibility', !currentVisibility); + }, + beforecheckchange: function(node, checked){ + // when layer is not checked anymore it will be collapsed + if (checked) { + node.collapse(); + } + } }, columns: { @@ -159,7 +159,7 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { onRequestServiceFailure: function (status) { // TODO: read text from viewmodel or config - var errorText = 'Requesting sublayers failed.' + var errorText = 'Requesting sublayers failed.'; Ext.Logger.error(errorText); Ext.Logger.error(status); BasiGX.warn(errorText); diff --git a/test/spec/util/ArcGISRest.test.js b/test/spec/util/ArcGISRest.test.js index 9b062f02f..b834168ff 100644 --- a/test/spec/util/ArcGISRest.test.js +++ b/test/spec/util/ArcGISRest.test.js @@ -100,4 +100,35 @@ describe('BasiGX.util.ArcGISRest', function() { }); }); + + describe('#createMapServerUrl', function() { + + it('returns the MapServer url when the service in on the root', function() { + var serviceUrl = 'http://example.com/services'; + var mapServerName = 'foo'; + var url = serviceUrl + '/' + mapServerName + '/MapServer'; + var result = BasiGX.util.ArcGISRest.createMapServerUrl( + serviceUrl, mapServerName); + expect(result).to.equal(url); + }); + + it('returns the MapServer url when the service is in a folder', function() { + var serviceUrl = 'http://example.com/services/folder'; + var mapServerName = 'folder/servername'; + var expectedUrl = 'http://example.com/services/folder/servername/MapServer'; + var result = BasiGX.util.ArcGISRest.createMapServerUrl( + serviceUrl, mapServerName); + expect(result).to.equal(expectedUrl); + }); + + it('returns the MapServer url with a specified format', function() { + var serviceUrl = 'http://example.com/services'; + var featureServerName = 'foo'; + var format = 'bar'; + var url = serviceUrl + '/' + featureServerName + '/MapServer?f=' + format; + var result = BasiGX.util.ArcGISRest.createMapServerUrl( + serviceUrl, featureServerName, format); + expect(result).to.equal(url); + }); + }); }); From d270a905ee51b16e784d1f14eb42dad15cbfdaec Mon Sep 17 00:00:00 2001 From: fergaldoyle Date: Wed, 8 Feb 2023 16:00:58 +0000 Subject: [PATCH 13/16] Fix check / uncheck all --- src/view/form/AddArcGISRest.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index 0c8dc8a2e..616413fb8 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -753,10 +753,10 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * Checks all checkboxes in the available layers fieldset. */ checkAllLayers: function() { - var sel = '[name=fs-available-layers] checkbox[disabled=false]'; - var checkboxes = this.query(sel); - Ext.each(checkboxes, function(checkbox) { - checkbox.setValue(true); + var sel = '[name=fs-available-layers] basigx-tree-arcgisrestservicetree'; + var trees = this.query(sel); + Ext.each(trees, function(tree) { + tree.getStore().getAt(0).set('checked', true); }); }, @@ -764,10 +764,10 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * Unchecks all checkboxes in the available layers fieldset. */ uncheckAllLayers: function() { - var sel = '[name=fs-available-layers] checkbox[disabled=false]'; - var checkboxes = this.query(sel); - Ext.each(checkboxes, function(checkbox) { - checkbox.setValue(false); + var sel = '[name=fs-available-layers] basigx-tree-arcgisrestservicetree'; + var trees = this.query(sel); + Ext.each(trees, function(tree) { + tree.getStore().getAt(0).set('checked', false); }); }, From 6f8fae781985bc69fdb0005cd2eadf0128eae6c0 Mon Sep 17 00:00:00 2001 From: fergaldoyle Date: Wed, 8 Feb 2023 16:31:49 +0000 Subject: [PATCH 14/16] Add tests for checkAll uncheckAll --- test/spec/view/form/AddArcGISRest.test.js | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/spec/view/form/AddArcGISRest.test.js b/test/spec/view/form/AddArcGISRest.test.js index 76634edef..6b4a1cac5 100644 --- a/test/spec/view/form/AddArcGISRest.test.js +++ b/test/spec/view/form/AddArcGISRest.test.js @@ -94,6 +94,19 @@ describe('BasiGX.view.form.AddArcGISRest', function() { describe('Methods', function() { var form = null; + var layers = [{ + service: { + name: 'Copernicus/HighResolutionLayers', + type: 'MapServer' + }, + url: 'https://gis.epa.ie/arcgis/rest/services/Copernicus?f=json' + },{ + service: { + name: 'Copernicus/HotSpotMonitoring', + type: 'MapServer' + }, + url: 'https://gis.epa.ie/arcgis/rest/services/Copernicus?f=json' + }]; beforeEach(function() { form = Ext.create('BasiGX.view.form.AddArcGISRest', { renderTo: Ext.getBody() @@ -185,5 +198,24 @@ describe('BasiGX.view.form.AddArcGISRest', function() { }); }); }); + describe('checkAllLayers / uncheckAllLayers', function() { + it('checks and unchecks all layers', function() { + function isAllChecked(trees) { + return Ext.Array.every(trees, function (tree) { + return tree.getStore().getAt(0).get('checked'); + }); + } + + form.fillAvailableLayersFieldset(layers); + var trees = form.query('[name=fs-available-layers] basigx-tree-arcgisrestservicetree'); + expect(isAllChecked(trees)).to.be(true); + + form.uncheckAllLayers(); + expect(isAllChecked(trees)).to.be(false); + + form.checkAllLayers(); + expect(isAllChecked(trees)).to.be(true); + }); + }); }); }); From c1fc63a9102dca583fa614c6850fbb66e09f5c78 Mon Sep 17 00:00:00 2001 From: fergaldoyle Date: Fri, 25 Aug 2023 16:26:46 +0100 Subject: [PATCH 15/16] Move sub layer service call to AddArcGISRest, get error message from viewmodel --- src/view/form/AddArcGISRest.js | 69 ++++++++++++++++++++++++-- src/view/tree/ArcGISRestServiceTree.js | 53 +------------------- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index 616413fb8..fc0430791 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -631,7 +631,20 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { checkBoxes.push({ xtype: 'basigx-tree-arcgisrestservicetree', arcGISLayerConfig: layer, - checked: candidatesInitiallyChecked + checked: candidatesInitiallyChecked, + listeners: { + arcgisrestservicetreenodeexpand: function (expandedNode) { + me.requestLayer(layer).then( + function (response) { + me.onRequestLayerSuccess( + response, + expandedNode + ); + }, + me.onGetServicesFailure.bind(me) + ); + } + } }); }); cbGroup.add(checkBoxes); @@ -753,7 +766,8 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * Checks all checkboxes in the available layers fieldset. */ checkAllLayers: function() { - var sel = '[name=fs-available-layers] basigx-tree-arcgisrestservicetree'; + var sel = '[name=fs-available-layers]' + + ' basigx-tree-arcgisrestservicetree'; var trees = this.query(sel); Ext.each(trees, function(tree) { tree.getStore().getAt(0).set('checked', true); @@ -764,7 +778,8 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * Unchecks all checkboxes in the available layers fieldset. */ uncheckAllLayers: function() { - var sel = '[name=fs-available-layers] basigx-tree-arcgisrestservicetree'; + var sel = '[name=fs-available-layers]' + + ' basigx-tree-arcgisrestservicetree'; var trees = this.query(sel); Ext.each(trees, function(tree) { tree.getStore().getAt(0).set('checked', false); @@ -782,5 +797,53 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { var me = this; var vm = me.getViewModel(); vm.set('availableLayersFieldSetMaxHeight', newValue); + }, + + /** + * Request layer to get information about sub layers within the layer + * + * @param {Object} config Layer ArcGIS layer config + */ + requestLayer: function(config) { + var me = this; + var serviceUrl = BasiGX.util.ArcGISRest.createMapServerUrl( + config.url, + config.service.name, + 'json' + ); + return new Ext.Promise(function (resolve, reject) { + Ext.Ajax.request({ + url: serviceUrl, + method: 'GET', + useDefaultXhrHeader: me.getUseDefaultXhrHeader(), + success: function (response) { + var respJson = Ext.decode(response.responseText); + resolve(respJson); + }, + failure: function (response) { + reject(response.status); + } + }); + }); + }, + + /** + * Request layer to get information about sub layers within the layer + * + * @param {Object} response ArcGIS Rest response + * @param {GeoExt.data.model.ArcGISRestServiceLayer} expandedNode Layer ArcGIS layer config + */ + onRequestLayerSuccess: function(response, expandedNode) { + console.log(expandedNode); + var layers = Ext.Array.map(response.layers, function(layer) { + return Ext.create('GeoExt.data.model.ArcGISRestServiceLayer',{ + layerId: layer.id, + name: layer.name, + defaultVisibility: layer.defaultVisibility, + visibility: layer.defaultVisibility, + leaf: true + }); + }); + expandedNode.appendChild(layers); } }); diff --git a/src/view/tree/ArcGISRestServiceTree.js b/src/view/tree/ArcGISRestServiceTree.js index d66b4c6cf..a3cf02b45 100644 --- a/src/view/tree/ArcGISRestServiceTree.js +++ b/src/view/tree/ArcGISRestServiceTree.js @@ -110,59 +110,8 @@ Ext.define('BasiGX.view.tree.ArcGISRestServiceTree', { if (expandedNode.hasChildNodes()) { return; } - var serviceUrl = BasiGX.util.ArcGISRest.createMapServerUrl( - this.arcGISLayerConfig.url, - this.arcGISLayerConfig.service.name, - 'json' - ); - // TODO requesting service and populating store should - // be done by parent component. We should only fire an event - this.requestService(serviceUrl) - .then( - function(response) { - return me.onRequestServiceSuccess(response, expandedNode); - }, - this.onRequestServiceFailure.bind(this) - ); - }, - - requestService: function(serviceUrl) { - var me = this; - return new Ext.Promise(function (resolve, reject) { - Ext.Ajax.request({ - url: serviceUrl, - method: 'GET', - useDefaultXhrHeader: me.getUseDefaultXhrHeader(), - success: function (response) { - var respJson = Ext.decode(response.responseText); - resolve(respJson); - }, - failure: function (response) { - reject(response.status); - } - }); - }); - }, - - onRequestServiceSuccess: function(response, expandedNode) { - var layers = Ext.Array.map(response.layers, function(layer) { - return Ext.create('GeoExt.data.model.ArcGISRestServiceLayer',{ - layerId: layer.id, - name: layer.name, - defaultVisibility: layer.defaultVisibility, - visibility: layer.defaultVisibility, - leaf: true - }); - }); - expandedNode.appendChild(layers); - }, - onRequestServiceFailure: function (status) { - // TODO: read text from viewmodel or config - var errorText = 'Requesting sublayers failed.'; - Ext.Logger.error(errorText); - Ext.Logger.error(status); - BasiGX.warn(errorText); + me.fireEvent('arcgisrestservicetreenodeexpand', expandedNode); } }); From 6ed669b9d04e80c0df610da4b47bbc74896c5433 Mon Sep 17 00:00:00 2001 From: fergaldoyle Date: Tue, 29 Aug 2023 12:44:43 +0100 Subject: [PATCH 16/16] Add more ARCGIS REST tests --- src/view/form/AddArcGISRest.js | 5 +- ...opernicus-hotspotmonitoring-mapServer.json | 197 ++++++++++++++++++ test/resources/arcgis/copernicus.json | 14 ++ test/resources/arcgis/services.json | 16 ++ test/spec/view/form/AddArcGISRest.test.js | 149 +++++++++++++ 5 files changed, 378 insertions(+), 3 deletions(-) create mode 100644 test/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json create mode 100644 test/resources/arcgis/copernicus.json create mode 100644 test/resources/arcgis/services.json diff --git a/src/view/form/AddArcGISRest.js b/src/view/form/AddArcGISRest.js index fc0430791..f2fde697c 100644 --- a/src/view/form/AddArcGISRest.js +++ b/src/view/form/AddArcGISRest.js @@ -388,7 +388,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { this.fillAvailableLayersFieldset(mergedConfigs); this.updateControlToolbarState(); this.setLoading(false); - }.bind(this)); + }.bind(this)); }, /** @@ -423,7 +423,7 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { */ loadLayersOfFeatureServers: function(featureServers) { var me = this; - var mappedPromises = Ext.Array.map(featureServers, function(server) { + var mappedPromises = Ext.Array.map(featureServers, function(server) { return me.requestFeatureServer.call(me, server) .then(function(res) { var config = me.getFeatureServerConfigs( @@ -834,7 +834,6 @@ Ext.define('BasiGX.view.form.AddArcGISRest', { * @param {GeoExt.data.model.ArcGISRestServiceLayer} expandedNode Layer ArcGIS layer config */ onRequestLayerSuccess: function(response, expandedNode) { - console.log(expandedNode); var layers = Ext.Array.map(response.layers, function(layer) { return Ext.create('GeoExt.data.model.ArcGISRestServiceLayer',{ layerId: layer.id, diff --git a/test/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json b/test/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json new file mode 100644 index 000000000..3db821f34 --- /dev/null +++ b/test/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json @@ -0,0 +1,197 @@ +{ + "currentVersion": 10.41, + "serviceDescription": "", + "mapName": "Layers", + "description": "", + "copyrightText": "", + "supportsDynamicLayers": false, + "layers": [ + { + "id": 0, + "name": "Urban Atlas", + "parentLayerId": -1, + "defaultVisibility": false, + "subLayerIds": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "minScale": 0, + "maxScale": 0 + }, + { + "id": 1, + "name": "Street Trees - Dublin", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 2, + "name": "Street Trees - Galway", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 3, + "name": "Street Trees - Waterford", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 4, + "name": "Land Cover - Cork", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 5, + "name": "Land Cover - Dublin", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 6, + "name": "Land Cover - Galway", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 7, + "name": "Land Cover - Limerick", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 8, + "name": "Land Cover - Waterford", + "parentLayerId": 0, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 9, + "name": "Riparian Zones", + "parentLayerId": -1, + "defaultVisibility": false, + "subLayerIds": [ + 10, + 11 + ], + "minScale": 0, + "maxScale": 0 + }, + { + "id": 10, + "name": "Green Linear Elements", + "parentLayerId": 9, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 11, + "name": "Land Cover", + "parentLayerId": 9, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + }, + { + "id": 12, + "name": "Natura 2000", + "parentLayerId": -1, + "defaultVisibility": false, + "subLayerIds": [ + 13 + ], + "minScale": 0, + "maxScale": 0 + }, + { + "id": 13, + "name": "Natura 2000", + "parentLayerId": 12, + "defaultVisibility": false, + "subLayerIds": null, + "minScale": 0, + "maxScale": 0 + } + ], + "tables": [], + "spatialReference": { + "wkid": 2157, + "latestWkid": 2157 + }, + "singleFusedMapCache": false, + "initialExtent": { + "xmin": -2192431.987076646, + "ymin": -1622206.551353204, + "xmax": 5343982.999664094, + "ymax": 3891074.1323311795, + "spatialReference": { + "wkid": 2157, + "latestWkid": 2157 + } + }, + "fullExtent": { + "xmin": 448851.36479999963, + "ymin": 533041.3618999999, + "xmax": 754159.6705, + "ymax": 950054.5759999994, + "spatialReference": { + "wkid": 2157, + "latestWkid": 2157 + } + }, + "minScale": 0, + "maxScale": 0, + "units": "esriMeters", + "supportedImageFormatTypes": "PNG32,PNG24,PNG,JPG,DIB,TIFF,EMF,PS,PDF,GIF,SVG,SVGZ,BMP", + "documentInfo": { + "Title": "", + "Author": "", + "Comments": "", + "Subject": "The European Environment Agency, as lead authority under the Copernicus Land Monitoring Service, co-ordinate the development of high resolution \u201clocal component\u201d or \u201chot spot monitoring\u201d datasets. Comparable data is produced across Europe using earth", + "Category": "", + "AntialiasingMode": "None", + "TextAntialiasingMode": "Force", + "Keywords": "Copernicus,Land Cover,Land Use,Riparian Zones,Natura,Urban Atlas,Hot Spot Monitoring" + }, + "capabilities": "Map,Query,Data", + "supportedQueryFormats": "JSON, AMF, geoJSON", + "exportTilesAllowed": false, + "maxRecordCount": 1000, + "maxImageHeight": 4096, + "maxImageWidth": 4096, + "supportedExtensions": "KmlServer" +} diff --git a/test/resources/arcgis/copernicus.json b/test/resources/arcgis/copernicus.json new file mode 100644 index 000000000..e80cf988a --- /dev/null +++ b/test/resources/arcgis/copernicus.json @@ -0,0 +1,14 @@ +{ + "currentVersion": 10.41, + "folders": [], + "services": [ + { + "name": "Copernicus/HighResolutionLayers", + "type": "MapServer" + }, + { + "name": "Copernicus/HotSpotMonitoring", + "type": "MapServer" + } + ] +} diff --git a/test/resources/arcgis/services.json b/test/resources/arcgis/services.json new file mode 100644 index 000000000..68a533b8e --- /dev/null +++ b/test/resources/arcgis/services.json @@ -0,0 +1,16 @@ +{ + "currentVersion": 10.41, + "folders": [ + "BW", + "Copernicus", + "EPAMapServices", + "TimpeallAnTi", + "Utilities" + ], + "services": [ + { + "name": "BasemapHW", + "type": "MapServer" + } + ] +} diff --git a/test/spec/view/form/AddArcGISRest.test.js b/test/spec/view/form/AddArcGISRest.test.js index 6b4a1cac5..8ef4eff83 100644 --- a/test/spec/view/form/AddArcGISRest.test.js +++ b/test/spec/view/form/AddArcGISRest.test.js @@ -218,4 +218,153 @@ describe('BasiGX.view.form.AddArcGISRest', function() { }); }); }); + + describe('Behaviour', function() { + var form = null; + var xhr; + var requests; + beforeEach(function() { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = function(xhr) { + requests.push(xhr); + }; + form = Ext.create('BasiGX.view.form.AddArcGISRest', { + renderTo: Ext.getBody() + }); + }); + afterEach(function() { + xhr.restore(); + //requests = []; + if (form) { + form.destroy(); + form = null; + } + }); + + function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + async function fakeXhrResponse(path) { + const res = await fetch(path); + const json = await res.text(); + requests[requests.length - 1].respond(200, { 'Content-Type': 'application/json' }, json); + // allow time for Extjs to render UI items + await sleep(10); + } + + it('fetches root services and creates checkboxgroups', async function() { + var spy = sinon.spy(form, 'requestLayers'); + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/services.json'); + + var checkboxes = form.query('basigx-tree-arcgisrestservicetree'); + expect(checkboxes.length).to.be(1); + expect(spy.called).to.be(true); + }); + + it('fetches services and creates checkboxgroups', async function() { + var spy = sinon.spy(form, 'requestLayers'); + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/copernicus.json'); + + var checkboxes = form.query('basigx-tree-arcgisrestservicetree'); + expect(checkboxes.length).to.be(2); + expect(spy.called).to.be(true); + }); + + it('fetches layers and populates checkboxgroups when a checkboxgroup is expanded', async function() { + var spy = sinon.spy(form, 'requestLayer'); + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/copernicus.json'); + + var tree = form.down('basigx-tree-arcgisrestservicetree'); + tree.expandNode(tree.getStore().getAt(0)); + await fakeXhrResponse('/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json'); + + expect(tree.getStore().getAt(0).childNodes.length).to.be(14); + expect(spy.called).to.be(true); + }); + + it('adds checked layers', async function() { + var spy = sinon.spy(); + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/copernicus.json'); + + var tree = form.down('basigx-tree-arcgisrestservicetree'); + tree.expandNode(tree.getStore().getAt(0)); + await fakeXhrResponse('/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json'); + + form.down('[name=add-checked-layers]').click(); + + // monitor arcgisrestadd events + form.on('arcgisrestadd', spy); + + // respond again with layer info since createOlLayerFromArcGISRest calls the service url again + // two layers are checked so two xhr requests are created in quick succession + // requests are created + const res = await fetch('/resources/arcgis/copernicus-hotspotmonitoring-mapServer.json'); + const json = await res.text(); + requests[requests.length - 2].respond(200, { 'Content-Type': 'application/json' }, json); + requests[requests.length - 1].respond(200, { 'Content-Type': 'application/json' }, json); + await sleep(10); + + // two layers added, means two arcgisrestadd events fired + expect(spy.callCount).to.be(2); + }); + + it('handles service call errors', async function() { + var spy = sinon.spy(form, 'onGetServicesFailure'); + var codes = [0, 400, 401, 403, 404, 429, 500, 503, 504, 505]; + var count = 0; + for (var code of codes) { + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + requests[requests.length - 1].respond(code); + count++; + await sleep(10); + expect(spy.callCount).to.be(count); + } + }); + + it('handles arcgis rest errors', async function() { + // doesn't seem to be an easy way to check if the warn dialog is present + // instead check the getErrorMessage is called along with onGetServicesSuccess + // this means the response is 200 but body has an arcgis rest error + var spy1 = sinon.spy(form, 'onGetServicesSuccess'); + var spy2 = sinon.spy(form, 'getErrorMessage'); + + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + requests[requests.length - 1].respond( + 200, + { 'Content-Type': 'application/json' }, + '{"error":{"code":404,"message":"Folder not found","details":[]}}'); + await sleep(10); + + expect(spy1.callCount).to.be(1); + expect(spy2.callCount).to.be(1); + }); + + it('resets ui and state on reset button click', async function() { + // populate the ui + form.down('[name="url"]').setValue('https://test.com/services/service'); + form.down('[name="requestLayersBtn"]').click(); + await fakeXhrResponse('/resources/arcgis/services.json'); + var fs = form.down('[name=fs-available-layers]'); + + // verify available-layers is show, then click reset + expect(fs.hidden).to.be(false); + form.down('[name="resetFormBtn"]').click(); + + // verify available-layers is hidden and url textbox has been reset + expect(form.down('[name="url"]').getValue()).to.not.be('https://test.com/services/service'); + expect(fs.hidden).to.be(true); + }); + }); });