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

geosolutions-it / MapStore2 / 14836692220

05 May 2025 12:42PM UTC coverage: 76.933% (-0.06%) from 76.991%
14836692220

Pull #11067

github

web-flow
Merge a0fc46865 into d65a30efd
Pull Request #11067: Fix #10966 basic auth for services

30859 of 48053 branches covered (64.22%)

104 of 172 new or added lines in 23 files covered. (60.47%)

4 existing lines in 4 files now uncovered.

38384 of 49893 relevant lines covered (76.93%)

35.96 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

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

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

131

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

155

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

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

188
    static defaultProps = {
1✔
189
        id: "mapstore-metadata-explorer",
190
        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" }],
191
        active: false,
192
        wrap: false,
193
        modal: true,
194
        wrapWithPanel: false,
195
        panelStyle: {
196
            zIndex: 100,
197
            overflow: "hidden",
198
            height: "100%"
199
        },
200
        panelClassName: "catalog-panel",
201
        closeCatalog: () => {},
202
        onInitPlugin: () => {},
203
        closeGlyph: "1-close",
204
        zoomToLayer: true,
205

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

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

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

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

270

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

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

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

322
    // deprecated TOC configuration
323
    if (config.activateAddLayerButton === false) {
3!
324
        return null;
×
325
    }
326

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

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