• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 12808200637

16 Jan 2025 11:46AM UTC coverage: 77.139% (+0.009%) from 77.13%
12808200637

Pull #10712

github

web-flow
Merge c7134a75c into e4d081ec5
Pull Request #10712: Enhancing User Session #10657

30388 of 47184 branches covered (64.4%)

133 of 172 new or added lines in 15 files covered. (77.33%)

312 existing lines in 19 files now uncovered.

37769 of 48962 relevant lines covered (77.14%)

34.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.36
/web/client/utils/ConfigUtils.js
1
/**
2
 * Copyright 2015, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8
import Proj4js from 'proj4';
9
import PropTypes from 'prop-types';
10
import url from 'url';
11
import axios from 'axios';
12
import { castArray, isArray, isObject, endsWith, isNil, get, mergeWith } from 'lodash';
13
import assign from 'object-assign';
14
import { Promise } from 'es6-promise';
15
import isMobile from 'ismobilejs';
16
import {mergeConfigsPatch} from "@mapstore/patcher";
17

18
const epsg4326 = Proj4js ? new Proj4js.Proj('EPSG:4326') : null;
1!
19
const centerPropType = PropTypes.shape({
1✔
20
    x: PropTypes.number.isRequired,
21
    y: PropTypes.number.isRequired,
22
    crs: PropTypes.string
23
});
24

25
const urlQuery = url.parse(window.location.href, true).query;
1✔
26

27
let localConfigFile = 'configs/localConfig.json';
1✔
28

29
let defaultConfig = {
1✔
30
    // TODO: these should be changed tp relative paths, without /mapstore/ or / (to avoid the needing of overriding in default cases)
31
    proxyUrl: "/mapstore/proxy/?url=",
32
    geoStoreUrl: "/rest/geostore/",
33
    printUrl: "/mapstore/print/info.json",
34
    translationsPath: "translations",
35
    extensionsRegistry: "extensions/extensions.json",
36
    extensionsFolder: "extensions/",
37
    configurationFolder: "configs/",
38
    contextPluginsConfiguration: "configs/pluginsConfig.json",
39
    projectionDefs: [],
40
    themePrefix: "ms2",
41
    bingApiKey: null,
42
    mapquestApiKey: null,
43
    mapboxAccessToken: '',
44
    defaultSourceType: "gxp_wmssource",
45
    backgroundGroup: "background",
46
    userSessions: {
47
        enabled: false
48
    }
49
};
50

51
export const getConfigurationOptions = function(query, defaultName, extension, geoStoreBase) {
1✔
52
    const mapId = query.mapId;
22✔
53
    let configUrl;
54
    if (mapId) {
22✔
55
        configUrl = ( geoStoreBase || defaultConfig.geoStoreUrl ) + "data/" + mapId;
12✔
56
    } else {
57
        configUrl = defaultConfig.configurationFolder + (query.config || defaultName || 'config') + '.' + (extension || 'json');
10✔
58
    }
59
    return {
22✔
60
        configUrl: configUrl,
61
        legacy: !!mapId
62
    };
63
};
64
/**
65
 * WORKAROUND: it removes the extra ? when the authkey param is present
66
 * the url was like         http......?authkey=....?service=....&otherparam
67
 * that should become like  http......?authkey=....&service=....&otherparam
68
 *
69
 * The problem happens when you have this in the Record.properties
70
 * file of the csw folder in GeoServer, with csw plugin installed:
71
 * references.scheme='OGC:WMS'
72
 * references.value=strConcat('${url.wms}?service=WMS&request=GetMap&layers=', prefixedName)
73
 * That is ok when you are not using an authkey, but If you have the authkey
74
 * module installed and you get record with a proper authkey parameter the
75
 * ${url.wms} URL will have also ?authkey=... and so the final URL is something like:
76
 * http://domain.org/geoserver/?autkey=abcdefghijklmnopqrstuvz1234567890?service=WMS&request=GetMap&layers=LAYER_NAME
77
 * ${url.wms} is replaced with the wms URL after the execution of strConcat,
78
 * so the url can not be parsed in any way to solve the problem via configuration,
79
 * because you can not know if ${url.wms} contains ? or not.
80
*/
81
export const cleanDuplicatedQuestionMarks = (urlToNormalize) => {
1✔
82
    const urlParts = urlToNormalize.split("?");
813✔
83
    if (urlParts.length > 2) {
813✔
84
        let newUrlParts = urlParts.slice(1);
3✔
85
        return urlParts[0] + "?" + newUrlParts.join("&");
3✔
86
    }
87
    return urlToNormalize;
810✔
88
};
89
/**
90
 * it removes some params from the query string
91
 * return the shrinked url
92
*/
93
export const getUrlWithoutParameters = (urlToFilter, skip) => {
1✔
94
    const urlparts = cleanDuplicatedQuestionMarks(urlToFilter).split('?');
396✔
95
    let paramsFiltered = "";
396✔
96
    if (urlparts.length >= 2 && urlparts[1]) {
396✔
97
        const pars = urlparts[1].split(/[&;]/g).filter( p => !!p);
358✔
98
        pars.forEach((par, i) => {
59✔
99
            const param = par.split('=');
357✔
100
            if (skip.indexOf(param[0].toLowerCase()) === -1) {
357✔
101
                let addAnd = i === (pars.length - 1) ? "" : "&";
302✔
102
                paramsFiltered += param.join("=") + addAnd;
302✔
103
            }
104
        });
105
    }
106
    return !!paramsFiltered ? urlparts[0] + "?" + paramsFiltered : urlparts[0];
396✔
107
};
108

109
export const filterUrlParams = (urlToFilter, params = []) => {
1!
110
    if (isNil(urlToFilter) || urlToFilter === "") {
365✔
111
        return null;
1✔
112
    }
113
    return getUrlWithoutParameters(cleanDuplicatedQuestionMarks(urlToFilter), params);
364✔
114
};
115

116
export const getParsedUrl = (urlToParse, options, params = []) => {
1✔
117
    if (urlToParse) {
10✔
118
        const parsed = url.parse(filterUrlParams(urlToParse, params), true);
2✔
119
        let newPathname = null;
2✔
120
        if (endsWith(parsed.pathname, "wfs") || endsWith(parsed.pathname, "wms") || endsWith(parsed.pathname, "ows")) {
2✔
121
            newPathname = parsed.pathname.replace(/(wms|ows|wfs|wps)$/, "wps");
1✔
122
            return url.format(assign({}, parsed, {search: null, pathname: newPathname }, {
1✔
123
                query: assign({
124
                    service: "WPS",
125
                    ...options
126
                }, parsed.query)
127
            }));
128
        }
129
    }
130
    return null;
9✔
131
};
132
export const getDefaults = function() {
1✔
133
    return {...defaultConfig};
148✔
134
};
135
export const setLocalConfigurationFile = function(file) {
1✔
136
    localConfigFile = file;
115✔
137
};
138

139
export const loadConfiguration = function() {
1✔
140
    if (localConfigFile) {
28✔
141
        const configFiles = castArray(localConfigFile);
27✔
142
        return axios.all(configFiles.map(config =>
27✔
143
            axios.get(config)
28✔
144
                .then(response => response.data)
26✔
145
                .catch(() => null)
2✔
146
        )).then(configs => {
147
            const [main, ...patches] = configs;
27✔
148
            if (!main) throw new Error("local configuration file is broken");
27✔
149
            const merged = mergeConfigsPatch(main, patches.filter(c => c && typeof c === "object"));
25✔
150
            defaultConfig = merged ? {...defaultConfig, ...merged} : defaultConfig;
25!
151
            return {...defaultConfig};
25✔
152
        });
153
    }
154
    return new Promise((resolve) => {
1✔
155
        resolve({...defaultConfig});
1✔
156
    });
157
};
158

159
export const getCenter = function(center, projection) {
1✔
160
    const point = isArray(center) ? {x: center[0], y: center[1]} : center;
48✔
161
    const crs = center.crs || projection || 'EPSG:4326';
48✔
162
    const transformed = crs !== 'EPSG:4326' ? Proj4js.transform(new Proj4js.Proj(crs), epsg4326, point) : point;
48✔
163
    return assign({}, transformed, {crs: "EPSG:4326"});
48✔
164
};
165

166
export const setApiKeys = function(layer) {
1✔
167
    if (layer.type === 'bing') {
51✔
168
        layer.apiKey = defaultConfig.bingApiKey;
1✔
169
    }
170
    if (layer.type === 'mapquest') {
51✔
171
        layer.apiKey = defaultConfig.mapquestApiKey;
4✔
172
    }
173
    if (layer.type === 'tileprovider' && ['MapBoxStyle', 'MapBox'].includes(layer.provider)) {
51!
174
        // include an empty string if missing to avoid errors in the layer url template
175
        layer.accessToken = defaultConfig.mapboxAccessToken;
×
176
    }
177
    return layer;
51✔
178
};
179

180
export const setLayerId = function(layer, i) {
1✔
181
    if (!layer.id) {
51✔
182
        layer.id = layer.name + "__" + i;
47✔
183
    }
184
    return layer;
51✔
185
};
186

187
export const replacePlaceholders = function(inputUrl) {
1✔
188
    let currentUrl = inputUrl;
41✔
189
    (currentUrl.match(/\{.*?\}/g) || []).forEach((placeholder) => {
41✔
190
        const replacement = defaultConfig[placeholder.substring(1, placeholder.length - 1)];
7✔
191
        // replacement must exist, or the URL is intended as a real template for the URL (e.g REST URLs of WMTS)
192
        if (replacement !== undefined) {
7✔
193
            currentUrl = currentUrl.replace(placeholder, replacement || '');
6!
194
        }
195
    });
196
    return currentUrl;
41✔
197
};
198

199
export const setUrlPlaceholders = function(layer) {
1✔
200
    if (layer.url) {
54✔
201
        if (isArray(layer.url)) {
18✔
202
            layer.url = layer.url.map((currentUrl) => {
5✔
203
                return replacePlaceholders(currentUrl);
26✔
204
            });
205
        } else {
206
            layer.url = replacePlaceholders(layer.url);
13✔
207
        }
208
    }
209
    return layer;
54✔
210
};
211

212
export const normalizeConfig = function(config) {
1✔
213
    const {layers, groups, plugins, ...other} = config;
24✔
214
    other.center = getCenter(other.center);
24✔
215
    return {
24✔
216
        map: other,
217
        layers: layers.map(setApiKeys, config).map(setLayerId).map(setUrlPlaceholders),
218
        groups: groups,
219
        plugins: plugins
220
    };
221
};
222

223
export const getUserConfiguration = function(defaultName, extension, geoStoreBase) {
1✔
224
    return getConfigurationOptions(urlQuery, defaultName, extension, geoStoreBase);
1✔
225
};
226

227
export const getConfigUrl = ({mapId, config}) => {
1✔
228
    let id = mapId;
13✔
229
    let configUrl = config;
13✔
230
    // if mapId is a string, is the name of the config to load
231
    try {
13✔
232
        let mapIdNumber = parseInt(id, 10);
13✔
233
        if (isNaN(mapIdNumber)) {
13✔
234
            configUrl = mapId;
3✔
235
            id = null;
3✔
236
        }
237
    } catch (e) {
238
        configUrl = mapId;
×
239
        id = null;
×
240
    }
241
    return getConfigurationOptions({mapId: id, config: configUrl});
13✔
242
};
243

244
/**
245
 * set default wms source
246
 */
247
export const setupSources = function(sources, defaultSourceType) {
1✔
248
    var defType = defaultSourceType;
4✔
249
    var source;
250
    if (!defaultSourceType) {
4!
251
        defType = defaultConfig.defaultSourceType;
4✔
252
    }
253
    for (source in sources) {
4✔
254
        if (sources.hasOwnProperty(source)) {
28!
255
            if (!sources[source].ptype) {
28✔
256
                sources[source].ptype = defType;
8✔
257
            }
258
        }
259
    }
260
};
261

262
export const normalizeSourceUrl = function(sourceUrl) {
1✔
263
    if (sourceUrl && sourceUrl.indexOf('?') !== -1) {
24✔
264
        return sourceUrl.split('?')[0];
4✔
265
    }
266
    return sourceUrl;
20✔
267
};
268

269
/**
270
 * Copy important source options to layer options.
271
 */
272
export const copySourceOptions = function(layer, source) {
1✔
273
    layer.baseParams = source.baseParams;
24✔
274
    if (source.url) {
24✔
275
        let sourceParts = url.parse(source.url, true);
8✔
276
        for (let k in sourceParts.query) {
8✔
277
            if (k.toUpperCase() === "REQUEST" ) {
4!
278
                delete sourceParts.query[k];
×
279
            }
280
        }
281
        layer.baseParams = assign({}, layer.baseParams, sourceParts.query);
8✔
282
    }
283
    layer.url = normalizeSourceUrl(source.url);
24✔
284
};
285

286
/**
287
 * Setup the layer visibility for the background group.
288
 * if background layers are not visible, sets the last one
289
 */
290
export const setupLayers = function(layers, sources, supportedSourceTypes) {
1✔
291
    // setup background visibility
292
    var candidateVisible;
293
    var i; var layer; var source;
294
    for (i = 0; i < layers.length; i++) {
4✔
295
        layer = layers[i];
24✔
296
        source = sources[layer.source];
24✔
297
        if (source) {
24!
298
            copySourceOptions(layer, source);
24✔
299
        }
300

301
        let type = source.ptype;
24✔
302
        if (type) {
24!
303
            layer.type = type.replace(/^gxp_(.*)source$/i, "$1");
24✔
304
        } else {
305
            layer.type = 'unknown';
×
306
        }
307
        if (layer) {
24!
308
            if (supportedSourceTypes.indexOf(source && source.ptype) >= 0) {
24!
309
                if (layer.group === defaultConfig.backgroundGroup) {
24✔
310
                    // force to false if undefined
311
                    layer.visibility = layer.visibility || false;
16✔
312
                    if (candidateVisible && candidateVisible.visibility) {
16✔
313
                        /* if more than one layer is visible in the background group
314
                            shows only the last one hiding the previous.
315
                        */
316
                        if (layer.visibility) {
4!
317
                            candidateVisible.visibility = false;
4✔
318
                            candidateVisible = layer;
4✔
319
                        }
320
                    } else {
321
                        candidateVisible = layer;
12✔
322
                    }
323
                }
324
            } else {
325
                layer.visibility = false;
×
326
            }
327
        }
328
    }
329
    // set the candidate visible
330
    if (candidateVisible) {
4!
331
        candidateVisible.visibility = true;
4✔
332
    }
333
};
334

335
export const convertFromLegacy = function(config) {
1✔
336
    var mapConfig = config.map;
4✔
337
    var sources = config.gsSources || config.sources;
4!
338
    var layers = mapConfig.layers.filter(layer => sources[layer.source]);
24✔
339
    var latLng = getCenter(mapConfig.center, mapConfig.projection);
4✔
340
    var zoom = mapConfig.zoom;
4✔
341
    var maxExtent = mapConfig.maxExtent || mapConfig.extent;
4!
342

343
    // setup layers and sources with defaults
344
    setupSources(sources, config.defaultSourceType);
4✔
345
    setupLayers(layers, sources, ["gxp_osmsource", "gxp_wmssource", "gxp_googlesource", "gxp_bingsource", "gxp_mapquestsource", "gxp_olsource"]);
4✔
346
    return normalizeConfig({
4✔
347
        center: latLng,
348
        zoom: zoom,
349
        maxExtent: maxExtent, // TODO convert maxExtent
350
        layers: layers,
351
        projection: mapConfig.projection || 'EPSG:3857'
4!
352
    });
353
};
354

355
/**
356
 * Utility to merge different configs
357
 */
358
export const mergeConfigs = function(baseConfig, mapConfig) {
1✔
359
    baseConfig.map = mapConfig.map;
1✔
360
    baseConfig.gsSources = mapConfig.gsSources || mapConfig.sources;
1✔
361
    return baseConfig;
1✔
362
};
363
export const getProxyUrl = function(config) {
1✔
364
    return config?.proxyUrl ? config.proxyUrl : defaultConfig.proxyUrl;
541✔
365
};
366

367
export const getProxiedUrl = function(uri, config = {}) {
1✔
368
    let sameOrigin = !(uri.indexOf("http") === 0);
4✔
369
    let urlParts = !sameOrigin && uri.match(/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/);
4✔
370
    // ajax.addAuthenticationToAxios(config);
371
    if (urlParts) {
4!
372
        let location = window.location;
4✔
373
        sameOrigin =
4✔
374
            urlParts[1] === location.protocol &&
8✔
375
            urlParts[3] === location.hostname;
376
        let uPort = urlParts[4];
4✔
377
        let lPort = location.port;
4✔
378
        let defaultPort = location.protocol.indexOf("https") === 0 ? 443 : 80;
4!
379
        uPort = uPort === "" ? defaultPort + "" : uPort + "";
4!
380
        lPort = lPort === "" ? defaultPort + "" : lPort + "";
4!
381
        sameOrigin = sameOrigin && uPort === lPort;
4!
382
    }
383
    if (!sameOrigin) {
4!
384
        let proxyUrl = getProxyUrl(config);
4✔
385
        if (proxyUrl) {
4!
386
            let useCORS = [];
4✔
387
            if (isObject(proxyUrl)) {
4✔
388
                useCORS = proxyUrl.useCORS || [];
1!
389
                proxyUrl = proxyUrl.url;
1✔
390
            }
391
            const isCORS = useCORS.reduce((found, current) => found || uri.indexOf(current) === 0, false);
4✔
392
            if (!isCORS) {
4✔
393
                return proxyUrl + encodeURIComponent(uri);
3✔
394
            }
395
        }
396
    }
397
    return uri;
1✔
398
};
399
/**
400
* Utility to detect browser properties.
401
* Code from leaflet-src.js
402
*/
403
export const getBrowserProperties = function() {
1✔
404

405
    let ie = 'ActiveXObject' in window;
671✔
406
    let ielt9 = ie && !document.addEventListener;
671!
407
    let ie11 = ie && (window.location.hash === !!window.MSInputMethodContext && !!document.documentMode);
671!
408

409
    // terrible browser detection to work around Safari / iOS / Android browser bugs
410
    let ua = navigator.userAgent.toLowerCase();
671✔
411
    let webkit = ua.indexOf('webkit') !== -1;
671✔
412
    let chrome = ua.indexOf('chrome') !== -1;
671✔
413
    let safari = ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1;
671✔
414
    let phantomjs = ua.indexOf('phantom') !== -1;
671✔
415
    let android = ua.indexOf('android') !== -1;
671✔
416
    let android23 = ua.search('android [23]') !== -1;
671✔
417
    let gecko = ua.indexOf('gecko') !== -1;
671✔
418

419
    let mobile = isMobile.any; // typeof window.orientation !== undefined + '';
671✔
420
    let msPointer = !window.PointerEvent && window.MSPointerEvent;
671!
421
    let pointer = window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints ||
671!
422
                msPointer;
423
    let retina = 'devicePixelRatio' in window && window.devicePixelRatio > 1 ||
671✔
424
                'matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
425
                window.matchMedia('(min-resolution:144dpi)').matches;
426

427
    let doc = document.documentElement;
671✔
428
    let ie3d = ie && 'transition' in doc.style;
671!
429
    let webkit3d = 'WebKitCSSMatrix' in window && 'm11' in new window.WebKitCSSMatrix() && !android23;
671✔
430
    let gecko3d = 'MozPerspective' in doc.style;
671✔
431
    let opera3d = 'OTransition' in doc.style;
671✔
432
    let any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
671!
433

434
    let touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window ||
671!
435
    window.DocumentTouch && document instanceof window.DocumentTouch);
436

437
    return {
671✔
438
        ie: ie,
439
        ie11: ie11,
440
        ielt9: ielt9,
441
        webkit: webkit,
442
        gecko: gecko && !webkit && !window.opera && !ie,
1,342!
443

444
        android: android,
445
        android23: android23,
446

447
        chrome: chrome,
448

449
        safari,
450

451
        ie3d: ie3d,
452
        webkit3d: webkit3d,
453
        gecko3d: gecko3d,
454
        opera3d: opera3d,
455
        any3d: any3d,
456

457
        mobile: mobile,
458
        mobileWebkit: mobile && webkit,
671!
459
        mobileWebkit3d: mobile && webkit3d,
671!
460
        mobileOpera: mobile && window.opera,
671!
461

462
        touch: touch,
463
        msPointer: msPointer,
464
        pointer: pointer,
465

466
        retina: retina
467
    };
468
};
469

470
export const getConfigProp = function(prop) {
1✔
471
    return defaultConfig[prop];
11,615✔
472
};
473
export const setConfigProp = function(prop, value) {
1✔
474
    defaultConfig[prop] = value;
855✔
475
};
476
export const removeConfigProp = function(prop) {
1✔
477
    delete defaultConfig[prop];
6✔
478
};
479

480
/**
481
 * Get misc settings from localConfig
482
 * @param {string} name of the prop to find
483
 * @param {any} defaultVal
484
 * @returns {any} value configured in misc settings
485
 */
486
export const getMiscSetting = (name, defaultVal) => {
1✔
487
    return get(getConfigProp('miscSettings') ?? {}, name, defaultVal);
108!
488
};
489

490
// IDs for userSession
491
export const SESSION_IDS = {
1✔
492
    EVERYTHING: "everything",
493
    MAP: "map",
494
    MAP_POS: "map_pos",
495
    VISUALIZATION_MODE: "visualization_mode",
496
    LAYERS: "layers",
497
    ANNOTATIONS_LAYER: "annotations_layer",
498
    MEASUREMENTS_LAYER: "measurements_layer",
499
    BACKGROUND_LAYERS: "background_layers",
500
    OTHER_LAYERS: "other_layers",
501
    CATALOG_SERVICES: "catalog_services",
502
    WIDGETS: "widgets",
503
    SEARCH: "search",
504
    TEXT_SEARCH_SERVICES: "text_search_services",
505
    BOOKMARKS: "bookmarks",
506
    FEATURE_GRID: "feature_grid",
507
    OTHER: "other",
508
    USER_PLUGINS: "userPlugins"
509
};
510

511
/*
512
 * Function to update overrideConfig to clean specific settings
513
 * @param {object} override - current overrideConfig
514
 * @param {Array} thingsToClear - IDs of settings to be cleared
515
 * @param {object} originalConfig - original config
516
 * @param {object} customHandlers - session Saved By registerCustomSaveHandler
517
 * @returns {object} updated overrideConfig
518
*/
519
export const updateOverrideConfig = (override = {}, thingsToClear = [], originalConfig = {}, customHandlers = []) => {
1!
520
    let overrideConfig = JSON.parse(JSON.stringify(override));
19✔
521

522
    if (thingsToClear?.includes(SESSION_IDS.EVERYTHING)) {
19✔
523
        overrideConfig = {};
1✔
524
        return overrideConfig;
1✔
525
    }
526

527
    // zoom and center
528
    if (thingsToClear.includes(SESSION_IDS.MAP_POS)) {
18✔
529
        delete overrideConfig.map.zoom;
2✔
530
        delete overrideConfig.map.center;
2✔
531
    }
532
    // visualization mode
533
    if (thingsToClear.includes(SESSION_IDS.VISUALIZATION_MODE)) {
18✔
534
        delete overrideConfig.map.visualizationMode;
1✔
535
    }
536

537
    // layers is the only case that partially gets reset, and there is problem to merge arrays with different index(it merges properties from two array on same index)
538
    // so merging original layers here. And while merging override config with original config: Override config is given priority. Check applyOverrides function below in this file.
539

540
    // annotation layers
541
    if (thingsToClear?.includes(SESSION_IDS.ANNOTATIONS_LAYER)) {
18✔
542
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l.id?.includes('annotations')), ...originalConfig?.map?.layers.filter((l)=>l.id?.includes('annotations'))];
7✔
543
    }
544
    // measurements layers
545
    if (thingsToClear?.includes(SESSION_IDS.MEASUREMENTS_LAYER)) {
18✔
546
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l?.name?.includes('measurements')), ...originalConfig?.map?.layers.filter(l=> l?.name?.includes('measurements'))];
4✔
547
    }
548
    // background layers
549
    if (thingsToClear?.includes(SESSION_IDS.BACKGROUND_LAYERS)) {
18✔
550
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l?.group?.includes('background')), ...originalConfig?.map?.layers.filter(l=> l?.group?.includes('background'))];
10✔
551
    }
552
    // other layers
553
    if (thingsToClear?.includes(SESSION_IDS.OTHER_LAYERS)) {
18!
NEW
554
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter(l=> l?.id?.includes('annotations') || l?.name?.includes('measurements') || l.group?.includes("background")), ...originalConfig?.map?.layers?.filter(l=> !l?.id?.includes('annotations') && !l?.name?.includes('measurements') && !l.group?.includes("background"))];
×
555
    }
556

557
    // reorder layers based on existing order of layers: since layers are modified in different orders, so sorting is important
558
    overrideConfig.map.layers = overrideConfig.map.layers.sort((a, b) =>
18✔
559
        override?.map?.layers.findIndex(item => item.id === a.id) - override?.map?.layers?.findIndex(item => item.id === b.id)
156✔
560
    );
561
    // catalog services
562
    if (thingsToClear?.includes(SESSION_IDS.CATALOG_SERVICES)) {
18✔
563
        delete overrideConfig.catalogServices;
1✔
564
    }
565
    // widgets
566
    if (thingsToClear?.includes(SESSION_IDS.WIDGETS)) {
18✔
567
        delete overrideConfig.widgetsConfig;
1✔
568
    }
569

570
    // search services
571
    if (thingsToClear?.includes(SESSION_IDS.TEXT_SEARCH_SERVICES)) {
18✔
572
        delete overrideConfig.map.text_search_config;
1✔
573
    }
574

575
    // bookmarks
576
    if (thingsToClear?.includes(SESSION_IDS.BOOKMARKS)) {
18✔
577
        delete overrideConfig.map.bookmark_search_config;
1✔
578
    }
579

580
    // feature grid
581
    if (thingsToClear?.includes(SESSION_IDS.FEATURE_GRID)) {
18✔
582
        // each properties are updated dynamically in featureGrid reducer(MAP_CONFIG_LOADED), so each attribute of featureGrid should be reset
583
        Object.keys(overrideConfig?.featureGrid ?? {}).forEach((fg)=>{
1!
584
            overrideConfig.featureGrid[fg] = {}; // all properties's value are in object
1✔
585
        });
586
    }
587

588
    if (thingsToClear?.includes(SESSION_IDS.USER_PLUGINS)) {
18✔
589
        delete overrideConfig.context.userPlugins;
1✔
590
    }
591

592
    // handle config from registerCustomSaveConfig
593
    customHandlers?.forEach((k) => {
18✔
594
        if (thingsToClear?.includes(k)) {
19✔
595
            delete overrideConfig[k];
1✔
596
        }
597
    });
598

599

600
    return overrideConfig;
18✔
601

602
};
603
/**
604
    Merge two configurations
605
 * @param {object} config the configuration to override
606
 * @param {object} override the data to use for override
607
 * @returns {object}
608
 */
609
export const applyOverrides = (config, override) => {
1✔
610
    const merged = mergeWith({}, config, override, (objValue, srcValue) => {
20✔
611
        // Till now layers is the only case that get partially reset, so merging with original config happens in updateOverrideConfig(above in the this file) while reset
612
        if (Array.isArray(objValue) && Array.isArray(srcValue)) {
397✔
613
            return [...srcValue];
4✔
614
        }
615
        // default merge rules for other cases
616
        // eslint-disable-next-line consistent-return
617
        return undefined;
393✔
618
    });
619
    return merged;
20✔
620
};
621
const ConfigUtils = {
1✔
622
    PropTypes: {
623
        center: centerPropType,
624
        config: PropTypes.shape({
625
            center: centerPropType,
626
            zoom: PropTypes.number.isRequired
627
        }),
628
        mapStateSource: PropTypes.string
629
    },
630
    getParsedUrl,
631
    getDefaults,
632
    setLocalConfigurationFile,
633
    loadConfiguration,
634
    getCenter,
635
    normalizeConfig,
636
    getUserConfiguration,
637
    getConfigurationOptions,
638
    getConfigUrl,
639
    convertFromLegacy,
640
    setupSources,
641
    normalizeSourceUrl,
642
    copySourceOptions,
643
    setupLayers,
644
    mergeConfigs,
645
    getProxyUrl,
646
    cleanDuplicatedQuestionMarks,
647
    getUrlWithoutParameters,
648
    filterUrlParams,
649
    getProxiedUrl,
650
    getBrowserProperties,
651
    setApiKeys,
652
    setUrlPlaceholders,
653
    replacePlaceholders,
654
    setLayerId,
655
    getConfigProp,
656
    setConfigProp,
657
    removeConfigProp,
658
    getMiscSetting
659
};
660

661
export default ConfigUtils;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc