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

geosolutions-it / MapStore2 / 19710972030

26 Nov 2025 03:38PM UTC coverage: 76.665% (-0.3%) from 76.929%
19710972030

Pull #11119

github

web-flow
Fix maven publish (#11739)
Pull Request #11119: Layer Selection Plugin on ArcGIS, WFS & WMS layers

32272 of 50209 branches covered (64.28%)

3 of 3 new or added lines in 2 files covered. (100.0%)

3018 existing lines in 249 files now uncovered.

40157 of 52380 relevant lines covered (76.66%)

37.9 hits per line

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

27.59
/web/client/components/catalog/CompactCatalog.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 { isNil, isObject, isEmpty } from 'lodash';
10
import React from 'react';
11
import { compose, mapPropsStream, withPropsOnChange } from 'recompose';
12
import Rx from 'rxjs';
13
import uuid from 'uuid';
14
import API from '../../api/catalog';
15
import Message from '../I18N/Message';
16
import BorderLayout from '../layout/BorderLayout';
17
import SideGridComp from '../misc/cardgrids/SideGrid';
18
import emptyState from '../misc/enhancers/emptyState';
19
import withVirtualScroll from '../misc/enhancers/infiniteScroll/withInfiniteScroll';
20
import loadingState from '../misc/enhancers/loadingState';
21
import withControllableState from '../misc/enhancers/withControllableState';
22
import Icon from '../misc/FitIcon';
23
import LoadingSpinner from '../misc/LoadingSpinner';
24
import CatalogForm from './CatalogForm';
25

26
const defaultPreview = <Icon glyph="geoserver" padding={20}/>;
1✔
27
const SideGrid = compose(
1✔
28
    loadingState(({loading, items = []} ) => items.length === 0 && loading),
×
29
    emptyState(
30
        ({loading, error} ) => !loading && error,
×
31
        {
32
            title: <Message msgId="catalog.error" />,
33
            style: { transform: "translateY(50%)"}
34
        }),
35
    emptyState(
36
        ({loading, items = []} ) => items.length === 0 && !loading,
×
37
        {
38
            title: <Message msgId="catalog.noRecordsMatched" />,
39
            style: { transform: "translateY(50%)"}
40
        })
41

42
)(SideGridComp);
43

44
/*
45
 * assigns an identifier to a record
46
 */
47
/*
48
 * assigns an identifier to a record. The ID is required for local selection.
49
 * TODO: improve identifier generation.
50
 */
51
const getIdentifier = (r) =>
1✔
52
    r.identifier ? r.identifier
×
53
        :  r.provider
×
54
            ? r.provider + (r.variant ?? "") // existing tileprovider
×
55
            : (r.tileMapUrl // TMS 1.0.0
×
56
        || r.url + uuid()); //  default
57
/*
58
 * converts record item into a item for SideGrid
59
 */
60
const resToProps = ({records, result = {}, catalog = {}}) => ({
1!
61
    items: (records || []).map((record = {}) => ({
×
62
        title: record.title && isObject(record.title) && record.title.default || record.title,
×
63
        caption: getIdentifier(record),
64
        description: record.description,
65
        preview: !catalog.hideThumbnail ? record.thumbnail ? <img src={record.thumbnail} /> : defaultPreview : null,
×
66
        record: {
67
            ...record, identifier: getIdentifier(record)
68
        }
69
    })),
70
    total: result && result.numberOfRecordsMatched
×
71
});
72
const PAGE_SIZE = 10;
1✔
73
/*
74
 * retrieves data from a catalog service and converts to props
75
 */
76
const loadPage = ({text, catalog = {}}, page = 0) => {
1!
77
    const type = catalog.type;
×
78
    let options = {options: {service: catalog}};
×
79
    return Rx.Observable
×
80
        .fromPromise(API[type].textSearch(catalog.url, page * PAGE_SIZE + (type === "csw" ? 1 : 0), PAGE_SIZE, text, options))
×
UNCOV
81
        .map((result) => ({ result, records: API[type].getCatalogRecords(result || [], { url: catalog && catalog.url, service: catalog })}))
×
82
        .map(({records, result}) => resToProps({records, result, catalog}));
×
83
};
84
const scrollSpyOptions = {querySelector: ".ms2-border-layout-body .ms2-border-layout-content", pageSize: PAGE_SIZE};
1✔
85
const getCatalogItems = (items = [], selected = {}) => items.map(i =>
1!
UNCOV
86
    (i === selected || selected && i && i.record && selected.identifier === i.record?.identifier)
×
87
        ? {...i, selected: true}
88
        : i
89
);
90
/**
91
 * Compat catalog : Reusable catalog component, with infinite scroll.
92
 * You can simply pass the catalog to browse and the handler onRecordSelected.
93
 * @example
94
 * <CompactCatalog catalog={type: "csw", url: "..."} onSelected={selected => console.log(selected)} />
95
 * @name CompactCatalog
96
 * @memberof components.catalog
97
 * @prop {object} catalog the definition of the selected catalog as `{type: "wms"|"wmts"|"csw", url: "..."}`
98
 * @prop {object} selected the record selected. Passing this will show it as selected (highlighted) in the list. It will compare record's `identifier` property to guess the selected record in the list
99
 * @prop {function} onRecordSelected
100
 * @prop {boolean} showCatalogSelector if true shows the catalog selector - TODO
101
 * @prop {array} services TODO allow selection of catalog from a list
102
 * @prop {string} [searchText] the search text (if you want to control it)
103
 * @prop {function} [setSearchText] handler to get search text changes (if not defined, the component will control the text by it's own)
104
 */
105
export default compose(
106
    withControllableState('searchText', "setSearchText", ""),
107
    withVirtualScroll({loadPage, scrollSpyOptions}),
108
    mapPropsStream( props$ =>
UNCOV
109
        props$.merge(props$.take(1).switchMap(({loadFirst = () => {}, services }) =>
×
UNCOV
110
            props$
×
111
                .debounceTime(500)
112
                .startWith({searchText: ""})
113
                .distinctUntilKeyChanged('searchText')
UNCOV
114
                .do(({searchText, selectedService: nextSelectedService} = {}) => !isEmpty(services[nextSelectedService]) && loadFirst({text: searchText, catalog: services[nextSelectedService] }))
×
115
                .ignoreElements() // don't want to emit props
116
        ))),
117
    withPropsOnChange(['selectedService'], props => {
UNCOV
118
        const service = props.services?.[props.selectedService];
×
UNCOV
119
        if (!isEmpty(service)) {
×
UNCOV
120
            props.loadFirst({text: props.searchText || "", catalog: service});
×
121
        }
122
    })
123
)(({ setSearchText = () => { }, selected, onRecordSelected, loading, searchText, items = [], total, catalog, services = {}, title, showCatalogSelector = true, error,
×
124
    onChangeSelectedService = () => {},
×
125
    selectedService, onChangeCatalogMode = () => {},
×
UNCOV
126
    getItems = (_items) => getCatalogItems(_items, selected),
×
UNCOV
127
    onItemClick = ({record} = {}) => onRecordSelected(record, catalog),
×
128
    canEditService
129
}) => {
130
    return (<BorderLayout
×
131
        className="compat-catalog"
132
        header={<CatalogForm onChangeCatalogMode={onChangeCatalogMode} onChangeSelectedService={onChangeSelectedService}
133
            services={Object.keys(services).map(key =>({ label: services[key]?.title, value: {...services[key], key}}))}
×
134
            selectedService={services[selectedService]} showCatalogSelector={showCatalogSelector}
135
            title={title}
136
            searchText={searchText}
137
            onSearchTextChange={setSearchText}
138
            canEditService={canEditService}/>}
139
        footer={<div className="catalog-footer">
140
            {loading ? <LoadingSpinner /> : null}
×
141
            {!isNil(total) ? <span className="res-info"><Message msgId="catalog.pageInfoInfinite" msgParams={{loaded: items.length, total}}/></span> : null}
×
142
        </div>}>
143
        <SideGrid
144
            items={getItems(items)}
145
            loading={loading}
146
            error={error}
147
            onItemClick={onItemClick}/>
148
    </BorderLayout>);
149
});
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