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

geosolutions-it / MapStore2 / 14797665772

02 May 2025 02:55PM UTC coverage: 76.933% (-0.06%) from 76.991%
14797665772

Pull #11067

github

web-flow
Merge 99dcd7f92 into d28bf7035
Pull Request #11067: Fix #10966 basic auth for services

30858 of 48053 branches covered (64.22%)

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

3 existing lines in 3 files now uncovered.

38384 of 49893 relevant lines covered (76.93%)

35.93 hits per line

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

85.33
/web/client/components/catalog/RecordItem.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
import React from 'react';
9
import PropTypes from 'prop-types';
10
import { isObject, head, isArray, trim, isEmpty } from 'lodash';
11
import { Image, SplitButton as SplitButtonT, Glyphicon, MenuItem } from 'react-bootstrap';
12
import tooltip from '../../components/misc/enhancers/tooltip';
13
import {
14
    buildSRSMap,
15
    getRecordLinks
16
} from '../../utils/CatalogUtils';
17
import { isAllowedSRS, isSRSAllowed } from '../../utils/CoordinatesUtils';
18
import HtmlRenderer from '../misc/HtmlRenderer';
19
import {parseCustomTemplate} from '../../utils/TemplateUtils';
20
import {getMessageById} from '../../utils/LocaleUtils';
21
import Message from '../I18N/Message';
22
import SharingLinks from './SharingLinks';
23
import SideCard from '../misc/cardgrids/SideCard';
24
import Toolbar from '../misc/toolbar/Toolbar';
25
import API from '../../api/catalog';
26

27
import defaultThumb from './img/default.jpg';
28
import defaultBackgroundThumbs from '../../plugins/background/DefaultThumbs';
29
import unknown from "../../plugins/background/assets/img/dafault.jpg";
30
import { getResolutions } from "../../utils/MapUtils";
31
import Loader from '../misc/Loader';
32
const SplitButton = tooltip(SplitButtonT);
1✔
33

34
class RecordItem extends React.Component {
35
    static propTypes = {
1✔
36
        addAuthentication: PropTypes.bool,
37
        authkeyParamNames: PropTypes.array,
38
        buttonSize: PropTypes.string,
39
        catalogURL: PropTypes.string,
40
        catalogType: PropTypes.string,
41
        crs: PropTypes.string,
42
        currentLocale: PropTypes.string,
43
        hideThumbnail: PropTypes.bool,
44
        hideExpand: PropTypes.bool,
45
        hideIdentifier: PropTypes.bool,
46
        layerBaseConfig: PropTypes.object,
47
        onCopy: PropTypes.func,
48
        onError: PropTypes.func,
49
        onLayerAdd: PropTypes.func,
50
        record: PropTypes.object,
51
        showGetCapLinks: PropTypes.bool,
52
        zoomToLayer: PropTypes.bool,
53
        onPropertiesChange: PropTypes.func,
54
        onLayerChange: PropTypes.func,
55
        layers: PropTypes.array,
56
        onAdd: PropTypes.func,
57
        source: PropTypes.string,
58
        onAddBackgroundProperties: PropTypes.func,
59
        deletedId: PropTypes.string,
60
        clearModal: PropTypes.func,
61
        service: PropTypes.object,
62
        selectedService: PropTypes.string,
63
        showTemplate: PropTypes.bool,
64
        defaultFormat: PropTypes.string
65
    };
66

67
    static defaultProps = {
1✔
68
        buttonSize: "small",
69
        crs: "EPSG:3857",
70
        currentLocale: 'en-US',
71
        onAddBackgroundProperties: () => {},
72
        hideThumbnail: false,
73
        hideIdentifier: false,
74
        hideExpand: false,
75
        layerBaseConfig: {},
76
        onCopy: () => {},
77
        onError: () => {},
78
        onLayerAdd: () => {},
79
        onPropertiesChange: () => {},
80
        onLayerChange: () => {},
81
        clearModal: () => {},
82
        style: {},
83
        showGetCapLinks: false,
84
        zoomToLayer: true,
85
        layers: [],
86
        onAdd: () => {},
87
        source: 'metadataExplorer',
88
        showTemplate: false,
89
        changeLayerProperties: () => {},
90
        defaultFormat: "image/png"
91
    };
92
    state = {
43✔
93
        visibleExpand: false,
94
        loading: false
95
    }
96

97
    static contextTypes = {
1✔
98
        messages: PropTypes.object
99
    };
100

101
    componentDidMount() {
102
        const notAvailable = getMessageById(this.context.messages, "catalog.notAvailable");
43✔
103
        const record = this.props.record;
43✔
104
        this.setState({visibleExpand: !this.props.hideExpand &&  // eslint-disable-line -- TODO: need to be fixed
43✔
105
            (
106
                this.displayExpand() ||
107
                // show expand if the template is not empty
108
                !!(this.props.showTemplate && record && record.metadataTemplate && parseCustomTemplate(record.metadataTemplate, record.metadata, (attribute) => `${trim(attribute.substring(2, attribute.length - 1))} ${notAvailable}`))
3✔
109
            )
110
        });
111
    }
112
    UNSAFE_componentWillMount() {
113
        document.addEventListener('click', this.handleClick, false);
43✔
114
    }
115

116
    componentWillUnmount() {
117
        document.removeEventListener('click', this.handleClick, false);
43✔
118
    }
119

120
    getTitle = (title) => {
43✔
121
        return isObject(title) ? title[this.props.currentLocale] || title.default : title || '';
256✔
122
    };
123

124
    renderThumb = (thumbURL, record) => {
43✔
125
        let thumbSrc = thumbURL || defaultThumb;
126✔
126

127
        return (<Image className="preview" src={thumbSrc} alt={record && this.getTitle(record.title)}/>);
126✔
128

129
    };
130

131
    isSRSNotAllowed = (record) => {
43✔
132
        if (record.serviceType !== 'cog') {
23✔
133
            const ogcReferences = record.ogcReferences || { SRS: [] };
22✔
134
            const allowedSRS = ogcReferences?.SRS?.length > 0 && buildSRSMap(ogcReferences.SRS);
22✔
135
            return allowedSRS && !isAllowedSRS(this.props.crs, allowedSRS);
22✔
136
        }
137
        const crs = record?.sourceMetadata?.crs;
1✔
138
        return crs && !isSRSAllowed(crs);
1✔
139
    }
140

141
    onAddToMap = (record, serviceType = record.serviceType) => {
43✔
142
        if (this.isSRSNotAllowed(record)) {
23✔
143
            return this.props.onError('catalog.srs_not_allowed');
3✔
144
        }
145
        this.setState({ loading: true });
20✔
146
        return API[serviceType].getLayerFromRecord(record, {
20✔
147
            fetchCapabilities: !!record.fetchCapabilities,
148
            service: {
149
                ...this.props.service,
150
                format: this.props?.service?.format ?? this.props.defaultFormat
39✔
151
            },
152
            layerBaseConfig: this.props.layerBaseConfig,
153
            removeParams: this.props.authkeyParamNames,
154
            catalogURL: this.props.catalogType === "csw" && this.props.catalogURL
43✔
155
                ? this.props.catalogURL +
156
                "?request=GetRecordById&service=CSW&version=2.0.2&elementSetName=full&id=" +
157
                record.identifier
158
                : null,
159
            map: {
160
                projection: this.props.crs,
161
                resolutions: getResolutions()
162
            }
163
        }, true)
164
            .then((layer) => {
165
                if (layer) {
20!
166
                    let layerOpts = layer;
20✔
167
                    if (this.props.service?.protectedId && this.props.selectedService) {
20!
NEW
168
                        layerOpts = {
×
169
                            ...layerOpts,
170
                            security: {
171
                                type: "basic",
172
                                sourceId: this.props.service.protectedId
173
                            }
174
                        };
175
                    }
176
                    this.addLayer(layerOpts, record);
20✔
177
                }
178
            }).finally(()=> {
179
                this.setState({ loading: false });
20✔
180
            });
181
    }
182

183
    getButtons = (record) => {
43✔
184
        const links = this.props.showGetCapLinks ? getRecordLinks(record) : [];
130✔
185
        const showServices = !isEmpty(record.additionalOGCServices);
130✔
186
        return [
130✔
187
            ...(showServices ? [{
130✔
188
                Element: () => (
189
                    <SplitButton
4✔
190
                        id="add-layer-button"
191
                        tooltipId="catalog.addToMap"
192
                        className="square-button-md"
193
                        bsStyle="primary"
194
                        title={this.state.loading ? <Loader className={'ms-loader ms-loader-primary'}/> : <Glyphicon glyph="plus" />}
4✔
195
                        onClick={() => this.onAddToMap(record)}
×
196
                        pullRight="left"
197
                        disabled={this.state.loading}
198
                    >
199
                        <MenuItem onClick={() => this.onAddToMap(record)}>
×
200
                            <Message msgId="catalog.additionalOGCServices.wms"/>
201
                        </MenuItem>
202
                        {
203
                            Object.keys(record.additionalOGCServices ?? {})
4!
204
                                .map(serviceType => (
205
                                    <MenuItem id={`ogc-${serviceType}`}
4✔
206
                                        onClick={() => this.onAddToMap(record.additionalOGCServices[serviceType], serviceType)}>
1✔
207
                                        <Message msgId={`catalog.additionalOGCServices.${serviceType}`}/>
208
                                    </MenuItem>)
209
                                )
210
                        }
211
                    </SplitButton>
212
                )
213
            }] : [{
214
                tooltipId: 'catalog.addToMap',
215
                className: 'square-button-md',
216
                bsStyle: 'primary',
217
                disabled: this.state.loading,
218
                loading: this.state.loading,
219
                glyph: 'plus',
220
                onClick: () => this.onAddToMap(record)
22✔
221
            }]),
222
            ...(links.length > 0
130✔
223
                ? [{
224
                    Element: () => (
225
                        <SharingLinks
2✔
226
                            key="sharing-links"
227
                            popoverContainer={this}
228
                            links={links}
229
                            onCopy={this.props.onCopy}
230
                            buttonSize={this.props.buttonSize}
231
                            addAuthentication={this.props.addAuthentication}
232
                        />
233
                    )
234
                }]
235
                : [])
236
        ];
237
    };
238

239
    renderDescription = (record) => {
43✔
240
        if (!record) {
258!
241
            return null;
×
242
        }
243
        const notAvailable = getMessageById(this.context.messages, "catalog.notAvailable");
258✔
244
        return this.state.fullText && record.metadataTemplate
258✔
245
            ? (<div className="catalog-metadata ql-editor">
246
                <HtmlRenderer html={parseCustomTemplate(record.metadataTemplate, record.metadata, (attribute) => `${trim(attribute.substring(2, attribute.length - 1))} ${notAvailable}`)}/>
2✔
247
            </div>)
248
            : record.metadataTemplate ? '' : (isArray(record.description) ? record.description.join(", ") : record.description);
496!
249
    };
250

251
    render() {
252
        const record = this.props.record;
132✔
253
        const background = record && record.background;
132✔
254
        const disabled = background && head((this.props.layers || []).filter(layer => layer.id === background.name ||
132!
255
            layer.type === background.type && layer.source === background.source && layer.name === background.name));
256
        // the preview and toolbar width depends on the values defined in the theme (variable.less)
257
        // IMPORTANT: if those values are changed then these defaults also have to change
258
        return record ? (<div>
132✔
259
            <SideCard
260
                style={{transform: "none", opacity: disabled ? 0.4 : 1}}
130!
261
                fullText={this.state.fullText}
262
                preview={!this.props.hideThumbnail &&
256✔
263
                    this.renderThumb(record && record.thumbnail ||
264!
264
                        background && ((background.name || background.source) ? defaultBackgroundThumbs[background.source][background.name] : unknown), record)}
×
265
                title={record && this.getTitle(record.title)}
260✔
266
                description={<span><div className ref={sideCardDesc => {
267
                    this.sideCardDesc = sideCardDesc;
260✔
268
                }}>{this.renderDescription(record)}</div></span>}
269
                caption={
270
                    <div>
271
                        {!this.props.hideIdentifier && <div className="identifier">{record && record.identifier}</div>}
514✔
272
                        <div>{!record.isValid && <small className="text-danger"><Message msgId="catalog.missingReference"/></small>}</div>
144✔
273
                        {!this.props.hideExpand &&
258✔
274
                                <div
275
                                    className="ms-ruler"
276
                                    style={{visibility: 'hidden', height: 0, whiteSpace: 'nowrap', position: 'absolute' }}
277
                                    ref={ruler => { this.descriptionRuler = ruler; }}>{this.renderDescription(record)}
256✔
278
                                </div>
279
                        }
280
                    </div>
281
                }
282
                tools={
283
                    <Toolbar
284
                        btnDefaultProps={{
285
                            className: 'square-button-md',
286
                            bsStyle: 'primary'
287
                        }}
288
                        btnGroupProps={{
289
                            style: {
290
                                margin: 10
291
                            }
292
                        }}
293
                        buttons={[
294
                            {
295
                                visible: !!disabled,
296
                                Element: () => <Message msgId="catalog.backgroundAlreadyAdded"/>
×
297
                            },
298
                            ...(record?.references && !disabled
390!
299
                                ? this.getButtons(record)
300
                                : []),
301
                            {
302
                                glyph: this.state.fullText ? 'chevron-down' : 'chevron-left',
130✔
303
                                visible: this.state.visibleExpand,
304
                                tooltipId: this.state.fullText ? 'collapse' : 'expand',
130✔
305
                                onClick: () => this.setState({ fullText: !this.state.fullText })
2✔
306
                            }
307
                        ]}/>
308
                }/>
309
        </div>) : null;
310
    }
311

312
    isLinkCopied = (key) => {
43✔
313
        return this.state[key];
×
314
    };
315

316
    setLinkCopiedStatus = (key, status) => {
43✔
317
        this.setState({[key]: status});
×
318
    };
319

320
    addLayer = (layer, {background} = {}) => {
43!
321
        // TODO: extenralize this switch
322
        if (this.props.source === 'backgroundSelector') {
20!
323
            if (background) {
×
324
                // background
325
                this.props.onLayerAdd({...layer, group: 'background'}, { source: this.props.source });
×
326
                this.props.onAddBackground(layer.id);
×
327
            } else {
328
                this.props.onAddBackgroundProperties({
×
329
                    editing: false,
330
                    layer
331
                }, true);
332
            }
333
        } else {
334
            const zoomToLayer = this.props.zoomToLayer;
20✔
335
            this.props.onLayerAdd(layer, { zoomToLayer });
20✔
336
        }
337
    }
338
    /**
339
     * it manages visibility of expand button.
340
     * it checks if the width of descriptionRuler is higher than the width of the desc-section
341
     * @return {boolean} the value of the check
342
    */
343
    displayExpand = () => {
43✔
344
        const descriptionRulerWidth = this.descriptionRuler ? this.descriptionRuler.clientWidth : 0;
42✔
345
        const descriptionWidth = this.sideCardDesc ? this.sideCardDesc.clientWidth : 0;
42✔
346
        return descriptionRulerWidth > descriptionWidth;
42✔
347
    };
348
}
349

350
export default RecordItem;
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

© 2025 Coveralls, Inc