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

geosolutions-it / MapStore2 / 13203217720

07 Feb 2025 03:46PM UTC coverage: 76.855% (+0.01%) from 76.842%
13203217720

Pull #10712

github

web-flow
Merge 42e82e1b8 into ec8726a47
Pull Request #10712: Enhancing User Session #10657

31182 of 48727 branches covered (63.99%)

145 of 185 new or added lines in 15 files covered. (78.38%)

1 existing line in 1 file now uncovered.

38767 of 50442 relevant lines covered (76.85%)

34.39 hits per line

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

97.38
/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};
152✔
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;
531✔
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,728✔
472
};
473
export const setConfigProp = function(prop, value) {
1✔
474
    defaultConfig[prop] = value;
893✔
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);
163!
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
 * After the introduction of individual reset of the session, override Config is updated here. Previously it was clearing all session.
514
 * This function handles to update the overrideConfig(from session) to clean specific settings
515
 *
516
 * Layers are handled differently as they are partially updating properties(annotations, measurements, backgrounds, others) and merging function from loadash has problem in merging arrays of objects(it merges all properties of object from both arrays) which is used in `applyOverrides` function in this same file below
517
 * So for layers merge of original Config and overrideConfig  happens here. And applyOverrides use the value of overrideConfig
518
 * No Problem for arrays that gets entirely reset
519
 *
520
 * @param {object} override - current overrideConfig
521
 * @param {Array} thingsToClear - IDs of settings to be cleared
522
 * @param {object} originalConfig - original config
523
 * @param {object} customHandlers - session Saved By registerCustomSaveHandler
524
 * @returns {object} updated overrideConfig
525
*/
526
export const updateOverrideConfig = (override = {}, thingsToClear = [], originalConfig = {}, customHandlers = []) => {
1!
527
    let overrideConfig = JSON.parse(JSON.stringify(override));
19✔
528

529
    if (thingsToClear?.includes(SESSION_IDS.EVERYTHING)) {
19✔
530
        overrideConfig = {};
1✔
531
        return overrideConfig;
1✔
532
    }
533

534
    // zoom and center
535
    if (thingsToClear.includes(SESSION_IDS.MAP_POS)) {
18✔
536
        delete overrideConfig.map.zoom;
2✔
537
        delete overrideConfig.map.center;
2✔
538
    }
539
    // visualization mode
540
    if (thingsToClear.includes(SESSION_IDS.VISUALIZATION_MODE)) {
18✔
541
        delete overrideConfig.map.visualizationMode;
1✔
542
    }
543

544
    // 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)
545
    // 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.
546

547
    // annotation layers
548
    if (thingsToClear?.includes(SESSION_IDS.ANNOTATIONS_LAYER)) {
18✔
549
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l.id?.includes('annotations')), ...originalConfig?.map?.layers.filter((l)=>l.id?.includes('annotations'))];
7✔
550
    }
551
    // measurements layers
552
    if (thingsToClear?.includes(SESSION_IDS.MEASUREMENTS_LAYER)) {
18✔
553
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l?.name?.includes('measurements')), ...originalConfig?.map?.layers.filter(l=> l?.name?.includes('measurements'))];
4✔
554
    }
555
    // background layers
556
    if (thingsToClear?.includes(SESSION_IDS.BACKGROUND_LAYERS)) {
18✔
557
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l?.group?.includes('background')), ...originalConfig?.map?.layers.filter(l=> l?.group?.includes('background'))];
10✔
558
    }
559
    // other layers
560
    if (thingsToClear?.includes(SESSION_IDS.OTHER_LAYERS)) {
18!
NEW
561
        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"))];
×
562
    }
563

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

577
    // search services
578
    if (thingsToClear?.includes(SESSION_IDS.TEXT_SEARCH_SERVICES)) {
18✔
579
        delete overrideConfig.map.text_search_config;
1✔
580
    }
581

582
    // bookmarks
583
    if (thingsToClear?.includes(SESSION_IDS.BOOKMARKS)) {
18✔
584
        delete overrideConfig.map.bookmark_search_config;
1✔
585
    }
586

587
    // feature grid
588
    if (thingsToClear?.includes(SESSION_IDS.FEATURE_GRID)) {
18✔
589
        // each properties are updated dynamically in featureGrid reducer(MAP_CONFIG_LOADED), so each attribute of featureGrid should be reset
590
        Object.keys(overrideConfig?.featureGrid ?? {}).forEach((fg)=>{
1!
591
            overrideConfig.featureGrid[fg] = {}; // all properties's value are in object
1✔
592
        });
593
    }
594

595
    if (thingsToClear?.includes(SESSION_IDS.USER_PLUGINS)) {
18✔
596
        delete overrideConfig.context.userPlugins;
1✔
597
    }
598

599
    // handle config from registerCustomSaveConfig
600
    customHandlers?.forEach((k) => {
18✔
601
        if (thingsToClear?.includes(k)) {
19✔
602
            delete overrideConfig[k];
1✔
603
        }
604
    });
605

606

607
    return overrideConfig;
18✔
608

609
};
610
/**
611
* Merge two configurations
612
* While overriding, overrideConfig properties and original config has arrays then overrideConfig gets more priority
613
* merge from loadash has problem while merging arrays of objects(it merges properties of objects from both), So merge logic has been changed
614
 * @param {object} config the configuration to override
615
 * @param {object} override the data to use for override
616
 * @returns {object}
617
 */
618
export const applyOverrides = (config, override) => {
1✔
619
    const merged = mergeWith({}, config, override, (objValue, srcValue) => {
27✔
620
        // Till now layers is the only case that get partially reset and has case where two array with objects tries to merge, so merging with original config happens in updateOverrideConfig(above in the this file) while reset
621
        if (Array.isArray(objValue) && Array.isArray(srcValue)) {
429✔
622
            // Give priority if there are some elements in override
623
            if (srcValue.length > 0) {
8✔
624
                return [...srcValue];
6✔
625
            }
626
            return [...objValue];
2✔
627

628
        }
629
        // default merge rules for other cases
630
        // eslint-disable-next-line consistent-return
631
        return undefined;
421✔
632
    });
633
    return merged;
27✔
634
};
635
const ConfigUtils = {
1✔
636
    PropTypes: {
637
        center: centerPropType,
638
        config: PropTypes.shape({
639
            center: centerPropType,
640
            zoom: PropTypes.number.isRequired
641
        }),
642
        mapStateSource: PropTypes.string
643
    },
644
    getParsedUrl,
645
    getDefaults,
646
    setLocalConfigurationFile,
647
    loadConfiguration,
648
    getCenter,
649
    normalizeConfig,
650
    getUserConfiguration,
651
    getConfigurationOptions,
652
    getConfigUrl,
653
    convertFromLegacy,
654
    setupSources,
655
    normalizeSourceUrl,
656
    copySourceOptions,
657
    setupLayers,
658
    mergeConfigs,
659
    getProxyUrl,
660
    cleanDuplicatedQuestionMarks,
661
    getUrlWithoutParameters,
662
    filterUrlParams,
663
    getProxiedUrl,
664
    getBrowserProperties,
665
    setApiKeys,
666
    setUrlPlaceholders,
667
    replacePlaceholders,
668
    setLayerId,
669
    getConfigProp,
670
    setConfigProp,
671
    removeConfigProp,
672
    getMiscSetting
673
};
674

675
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