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

geosolutions-it / MapStore2 / 15000047957

13 May 2025 03:02PM UTC coverage: 76.901% (-0.09%) from 76.993%
15000047957

Pull #10515

github

web-flow
Merge f2dcb1e61 into d8d2cc134
Pull Request #10515: #10514 - FeatureEditor filter by geometric area

30975 of 48268 branches covered (64.17%)

27 of 42 new or added lines in 6 files covered. (64.29%)

532 existing lines in 55 files now uncovered.

38583 of 50172 relevant lines covered (76.9%)

35.98 hits per line

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

76.67
/web/client/plugins/MetadataExplorer.jsx
1
/*
2
 * Copyright 2017, 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

9
import './metadataexplorer/css/style.css';
10

11
import assign from 'object-assign';
12
import PropTypes from 'prop-types';
13
import React from 'react';
14
import { Glyphicon, Panel } from 'react-bootstrap';
15
import { connect } from 'react-redux';
16
import { branch, compose, defaultProps, renderComponent, withProps } from 'recompose';
17
import { createStructuredSelector } from 'reselect';
18

19
import { addBackgroundProperties, backgroundAdded, clearModalParameters } from '../actions/backgroundselector';
20
import {
21
    addLayer,
22
    addLayerError,
23
    addService,
24
    catalogClose,
25
    changeCatalogFormat,
26
    changeCatalogMode,
27
    changeMetadataTemplate,
28
    changeSelectedService,
29
    changeServiceFormat,
30
    changeServiceProperty,
31
    changeText,
32
    changeTitle,
33
    changeType,
34
    changeUrl,
35
    deleteService,
36
    focusServicesList,
37
    formatOptionsFetch,
38
    textSearch,
39
    toggleAdvancedSettings,
40
    toggleTemplate,
41
    toggleThumbnail,
42
    setNewServiceStatus,
43
    initPlugin
44
} from '../actions/catalog';
45
import { setControlProperty, toggleControl, setControlProperties } from '../actions/controls';
46
import { changeLayerProperties } from '../actions/layers';
47
import API from '../api/catalog';
48
import CatalogComp from '../components/catalog/Catalog';
49
import CatalogServiceEditor from '../components/catalog/CatalogServiceEditor';
50
import Message from '../components/I18N/Message';
51
import { metadataSourceSelector, modalParamsSelector } from '../selectors/backgroundselector';
52
import {
53
    isActiveSelector,
54
    authkeyParamNameSelector,
55
    groupSelector,
56
    layerErrorSelector,
57
    loadingErrorSelector,
58
    loadingSelector,
59
    modeSelector,
60
    newServiceSelector,
61
    newServiceTypeSelector,
62
    pageSizeSelector,
63
    resultSelector,
64
    savingSelector,
65
    searchOptionsSelector,
66
    searchTextSelector,
67
    selectedServiceLayerOptionsSelector,
68
    selectedServiceSelector,
69
    selectedServiceTypeSelector,
70
    serviceListOpenSelector,
71
    servicesSelector,
72
    servicesSelectorWithBackgrounds,
73
    tileSizeOptionsSelector,
74
    formatsLoadingSelector,
75
    getSupportedFormatsSelector,
76
    getSupportedGFIFormatsSelector,
77
    getNewServiceStatusSelector,
78
    showFormatErrorSelector,
79
    canEditServiceSelector
80
} from '../selectors/catalog';
81
import { layersSelector } from '../selectors/layers';
82
import { currentLocaleSelector, currentMessagesSelector } from '../selectors/locale';
83
import {burgerMenuSelector} from "../selectors/controls";
84
import { isLocalizedLayerStylesEnabledSelector } from '../selectors/localizedLayerStyles';
85
import { projectionSelector } from '../selectors/map';
86
import { mapLayoutValuesSelector } from '../selectors/maplayout';
87
import ResponsivePanel from "../components/misc/panels/ResponsivePanel";
88
import usePluginItems from '../hooks/usePluginItems';
89
import { setProtectedServices, setShowModalStatus } from '../actions/security';
90

91
export const DEFAULT_ALLOWED_PROVIDERS = ["OpenStreetMap", "OpenSeaMap", "Stamen"];
1✔
92

93
const metadataExplorerSelector = createStructuredSelector({
1✔
94
    searchOptions: searchOptionsSelector,
95
    showFormatError: showFormatErrorSelector,
96
    result: resultSelector,
97
    loadingError: loadingErrorSelector,
98
    selectedService: selectedServiceSelector,
99
    mode: modeSelector,
100
    services: servicesSelector,
101
    servicesWithBackgrounds: servicesSelectorWithBackgrounds,
102
    layerError: layerErrorSelector,
103
    active: isActiveSelector,
104
    dockStyle: state => mapLayoutValuesSelector(state, { height: true, right: true }, true),
6✔
105
    searchText: searchTextSelector,
106
    group: groupSelector,
107
    source: metadataSourceSelector,
108
    layers: layersSelector,
109
    modalParams: modalParamsSelector,
110
    authkeyParamNames: authkeyParamNameSelector,
111
    saving: savingSelector,
112
    openCatalogServiceList: serviceListOpenSelector,
113
    service: newServiceSelector,
114
    format: newServiceTypeSelector,
115
    selectedFormat: selectedServiceTypeSelector,
116
    options: searchOptionsSelector,
117
    layerOptions: selectedServiceLayerOptionsSelector,
118
    tileSizeOptions: tileSizeOptionsSelector,
119
    currentLocale: currentLocaleSelector,
120
    locales: currentMessagesSelector,
121
    pageSize: pageSizeSelector,
122
    loading: loadingSelector,
123
    crs: projectionSelector,
124
    isLocalizedLayerStylesEnabled: isLocalizedLayerStylesEnabledSelector,
125
    formatsLoading: formatsLoadingSelector,
126
    formatOptions: getSupportedFormatsSelector,
127
    infoFormatOptions: getSupportedGFIFormatsSelector,
128
    isNewServiceAdded: getNewServiceStatusSelector,
129
    canEdit: canEditServiceSelector
130
});
131

132

133
const Catalog = compose(
1✔
UNCOV
134
    withProps(({ result, selectedFormat, options, layerOptions, services, selectedService, locales}) => ({
×
135
        records: result && API[selectedFormat].getCatalogRecords(result, { ...options, layerOptions, service: services[selectedService] }, locales) || []
×
136
    })),
137
    defaultProps({
138
        buttonStyle: {
139
            marginBottom: "10px",
140
            marginRight: "5px"
141
        },
142
        formatOptions: [],
143
        advancedRasterStyles: {
144
            display: 'flex',
145
            alignItems: 'center',
146
            paddingTop: 15,
147
            borderTop: '1px solid #ddd'
148
        }
149
    }),
150
    branch(
UNCOV
151
        ({mode}) => mode === "edit",
×
152
        renderComponent(CatalogServiceEditor)
153
    )
154
)(CatalogComp);
155

156

157
class MetadataExplorerComponent extends React.Component {
158
    static propTypes = {
1✔
159
        id: PropTypes.string,
160
        source: PropTypes.string,
161
        active: PropTypes.bool,
162
        searchOnStartup: PropTypes.bool,
163
        serviceTypes: PropTypes.array,
164
        wrap: PropTypes.bool,
165
        wrapWithPanel: PropTypes.bool,
166
        panelStyle: PropTypes.object,
167
        panelClassName: PropTypes.string,
168
        closeCatalog: PropTypes.func,
169
        closeGlyph: PropTypes.string,
170
        buttonStyle: PropTypes.object,
171
        services: PropTypes.object,
172
        addonsItems: PropTypes.array,
173
        servicesWithBackgrounds: PropTypes.object,
174
        selectedService: PropTypes.string,
175
        style: PropTypes.object,
176
        dockProps: PropTypes.object,
177
        zoomToLayer: PropTypes.bool,
178
        isLocalizedLayerStylesEnabled: PropTypes.bool,
179

180
        // side panel properties
181
        width: PropTypes.number,
182
        dockStyle: PropTypes.object,
183
        group: PropTypes.string,
184
        onInitPlugin: PropTypes.func,
185
        editingAllowedRoles: PropTypes.array,
186
        editingAllowedGroups: PropTypes.array
187
    };
188

189
    static defaultProps = {
1✔
190
        id: "mapstore-metadata-explorer",
191
        serviceTypes: [{ name: "csw", label: "CSW" }, { name: "wms", label: "WMS" }, { name: "wmts", label: "WMTS" }, { name: "tms", label: "TMS", allowedProviders: DEFAULT_ALLOWED_PROVIDERS }, { name: "wfs", label: "WFS" }, { name: "3dtiles", label: "3D Tiles" }, {name: "model", label: "IFC Model"}, { name: "arcgis", label: "ArcGIS" }],
192
        active: false,
193
        wrap: false,
194
        modal: true,
195
        wrapWithPanel: false,
196
        panelStyle: {
197
            zIndex: 100,
198
            overflow: "hidden",
199
            height: "100%"
200
        },
201
        panelClassName: "catalog-panel",
202
        closeCatalog: () => {},
203
        onInitPlugin: () => {},
204
        closeGlyph: "1-close",
205
        zoomToLayer: true,
206

207
        // side panel properties
208
        width: 550,
209
        dockProps: {
210
            dimMode: "none",
211
            fluid: false,
212
            position: "right",
213
            zIndex: 1030
214
        },
215
        dockStyle: {},
216
        group: null,
217
        services: {},
218
        servicesWithBackgrounds: {},
219
        editingAllowedRoles: ["ALL"]
220
    };
221

222
    componentDidMount() {
223
        this.props.onInitPlugin({
3✔
224
            editingAllowedRoles: this.props.editingAllowedRoles,
225
            editingAllowedGroups: this.props.editingAllowedGroups
226
        });
227
    }
228

229
    componentWillUnmount() {
230
        this.props.closeCatalog();
3✔
231
    }
232
    render() {
233
        // TODO: separate catalog props from Container props (and handlers)
234
        const layerBaseConfig = {
6✔
235
            group: this.props.group || undefined
12✔
236
        };
237
        const panel = (
238
            <Catalog
6✔
239
                layerBaseConfig={layerBaseConfig}
240
                {...this.props}
241
                services={this.props.source === 'backgroundSelector' ? this.props.servicesWithBackgrounds : this.props.services}
6!
242
            />
243
        );
244
        return (
6✔
245
            <ResponsivePanel
246
                containerStyle={this.props.dockStyle}
247
                containerId="catalog-root"
248
                containerClassName={this.props.active ? 'catalog-active' : ''}
6!
249
                open={this.props.active}
250
                size={this.props.width}
251
                position="right"
252
                title={<Message msgId="catalog.title"/>}
UNCOV
253
                onClose={() => this.props.closeCatalog()}
×
254
                glyph="folder-open"
255
                style={this.props.dockStyle}
256
            >
257
                <Panel id={this.props.id} style={this.props.panelStyle} className={this.props.panelClassName}>
258
                    {panel}
259
                </Panel>
260
            </ResponsivePanel>
261
        );
262
    }
263
}
264

265
const MetadataExplorerComponentWrapper = (props, context) => {
1✔
266
    const { loadedPlugins } = context;
6✔
267
    const addonsItems = usePluginItems({ items: props.items, loadedPlugins }).filter(({ target }) => target === 'url-addon');
6✔
268
    return <MetadataExplorerComponent {...props} addonsItems={addonsItems}/>;
6✔
269
};
270

271

272
MetadataExplorerComponentWrapper.contextTypes = {
1✔
273
    loadedPlugins: PropTypes.object
274
};
275

276
const MetadataExplorerPlugin = connect(metadataExplorerSelector, {
1✔
277
    clearModal: clearModalParameters,
278
    onSearch: textSearch,
279
    onLayerAdd: addLayer,
280
    closeCatalog: catalogClose,
281
    onChangeFormat: changeCatalogFormat,
282
    onChangeServiceFormat: changeServiceFormat,
283
    onChangeUrl: changeUrl,
284
    onChangeType: changeType,
285
    onChangeTitle: changeTitle,
286
    onChangeMetadataTemplate: changeMetadataTemplate,
287
    onChangeText: changeText,
288
    onChangeServiceProperty: changeServiceProperty,
289
    onChangeSelectedService: changeSelectedService,
290
    onChangeCatalogMode: changeCatalogMode,
291
    onAddService: addService,
292
    onToggleAdvancedSettings: toggleAdvancedSettings,
293
    onToggleThumbnail: toggleThumbnail,
294
    onToggleTemplate: toggleTemplate,
295
    onDeleteService: deleteService,
296
    onError: addLayerError,
297
    // add layer action to pass to the layers
298
    onAddBackgroundProperties: addBackgroundProperties,
299
    onFocusServicesList: focusServicesList,
300
    onPropertiesChange: changeLayerProperties,
301
    onAddBackground: backgroundAdded,
302
    onFormatOptionsFetch: formatOptionsFetch,
303
    onToggle: toggleControl.bind(null, 'backgroundSelector', null),
304
    onLayerChange: setControlProperty.bind(null, 'backgroundSelector'),
305
    onStartChange: setControlProperty.bind(null, 'backgroundSelector', 'start'),
306
    setNewServiceStatus,
307
    onShowSecurityModal: setShowModalStatus,
308
    onSetProtectedServices: setProtectedServices,
309
    onInitPlugin: initPlugin
310
})(MetadataExplorerComponentWrapper);
311

312
const AddLayerButton = connect(() => ({}), {
3✔
313
    onClick: setControlProperties.bind(null, 'metadataexplorer', 'enabled', true, 'group')
314
})(({
315
    onClick,
316
    selectedNodes,
317
    status,
318
    itemComponent,
319
    statusTypes,
320
    config,
321
    ...props
322
}) => {
323
    const ItemComponent = itemComponent;
3✔
324

325
    // deprecated TOC configuration
326
    if (config.activateAddLayerButton === false) {
3!
UNCOV
327
        return null;
×
328
    }
329

330
    if ([statusTypes.DESELECT, statusTypes.GROUP].includes(status)) {
3!
331
        const group = selectedNodes?.[0]?.id;
3✔
332
        return (
3✔
333
            <ItemComponent
334
                {...props}
335
                glyph="add-layer"
336
                tooltipId={status === statusTypes.GROUP ? 'toc.addLayerToGroup' : 'toc.addLayer'}
3!
UNCOV
337
                onClick={() => onClick(group)}
×
338
            />
339
        );
340
    }
UNCOV
341
    return null;
×
342
});
343

344
/**
345
 * MetadataExplorer (Catalog) plugin. Shows the catalogs results (CSW, WMS, WMTS, TMS, WFS and COG).
346
 * Some useful flags in `localConfig.json`:
347
 * - `noCreditsFromCatalog`: avoid add credits (attribution) from catalog
348
 *
349
 * @class
350
 * @name MetadataExplorer
351
 * @memberof plugins
352
 * @prop {string} cfg.hideThumbnail shows/hides thumbnail
353
 * @prop {object[]} cfg.serviceTypes Service types available to add a new catalog. default: `[{ name: "csw", label: "CSW" }, { name: "wms", label: "WMS" }, { name: "wmts", label: "WMTS" }, { name: "tms", label: "TMS", allowedProviders },{ name: "wfs", label: "WFS" }]`.
354
 * `allowedProviders` is a whitelist of tileProviders from ConfigProvider.js. you can set a global variable allowedProviders in localConfig.json to set it up globally. You can configure it to "ALL" to get all the list (at your own risk, some services could change or not be available anymore)
355
 * @prop {object} cfg.hideIdentifier shows/hides identifier
356
 * @prop {boolean} cfg.hideExpand shows/hides full description button
357
 * @prop {number} cfg.zoomToLayer enable/disable zoom to layer when added
358
 * @prop {number} cfg.autoSetVisibilityLimits if true, allows fetching and setting visibility limits of the layer from capabilities on layer add (Note: The default configuration value is applied only on new catalog service (WMS/CSW))
359
 * @prop {number} [delayAutoSearch] time in ms passed after a search is triggered by filter changes, default 1000
360
 * @prop {object[]} items this property contains the items injected from the other plugins,
361
 * using the `url-addon` option in the plugin that want to inject the components.
362
 * You can select the position where to insert the components adding the `target` property.
363
 * The allowed targets are:
364
 * - `url-addon` target add an addon button in the url field of catalog form (in main viewer) in edit mode
365
 * ```javascript
366
 * const MyAddonComponent = connect(null,
367
 * {
368
 *     onSetShowModal: setShowModalStatus,
369
 *    }
370
 * )(({
371
 *    onSetShowModal, // opens a modal to enter credentials
372
 *    itemComponent // default component that provides a consistent UI (see UrlAddon in MainForm.jsx)
373
 *    }) => {
374
 *    const Component = itemComponent;
375
 *    return (<Component
376
 *        onClick={(value) => {
377
 *            onSetShowModal(true);
378
 *        }}
379
 *        btnClassName={condition ? "btn-success" : ""}
380
 *        glyph="glyph"
381
 *        tooltipId="path"
382
 *    />  );
383
 * });
384
 * createPlugin(
385
 *  'MyPlugin',
386
 *  {
387
 *      containers: {
388
 *          MetadataExplorer: {
389
 *              name: "TOOLNAME", // a name for the current tool.
390
 *              target: "url-addon", // the target where to insert the component
391
 *              Component: MyAddonComponent
392
 *          },
393
 * // ...
394
 * ```
395
 */
396
export default {
397
    MetadataExplorerPlugin: assign(MetadataExplorerPlugin, {
398
        BurgerMenu: {
399
            name: 'metadataexplorer',
400
            position: 5,
401
            text: <Message msgId="catalog.title"/>,
402
            tooltip: "catalog.tooltip",
403
            icon: <Glyphicon glyph="folder-open"/>,
404
            action: setControlProperty.bind(null, "metadataexplorer", "enabled", true, true),
405
            doNotHide: true,
406
            priority: 1
407
        },
408
        BackgroundSelector: {
409
            name: 'MetadataExplorer',
410
            doNotHide: true,
411
            priority: 1
412
        },
413
        TOC: {
414
            name: 'MetadataExplorer',
415
            doNotHide: true,
416
            priority: 1,
417
            target: 'toolbar',
418
            Component: AddLayerButton,
419
            position: 2
420
        },
421
        SidebarMenu: {
422
            name: 'metadataexplorer',
423
            position: 5,
424
            text: <Message msgId="catalog.title"/>,
425
            tooltip: "catalog.tooltip",
426
            icon: <Glyphicon glyph="folder-open"/>,
427
            action: setControlProperty.bind(null, "metadataexplorer", "enabled", true, true),
UNCOV
428
            selector: (state) => ({
×
429
                style: { display: burgerMenuSelector(state) ? 'none' : null }
×
430
            }),
431
            toggle: true,
432
            doNotHide: true,
433
            priority: 1
434
        }
435
    }),
436
    reducers: {catalog: require('../reducers/catalog').default},
437
    epics: require("../epics/catalog").default(API)
438
};
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