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

geosolutions-it / MapStore2 / 18371528919

09 Oct 2025 09:16AM UTC coverage: 76.738% (-0.05%) from 76.789%
18371528919

Pull #11572

github

web-flow
Merge 62e9c9670 into 2686c544e
Pull Request #11572: Feat: #11527 Add the tabbed view for the dashboard

31855 of 49574 branches covered (64.26%)

94 of 155 new or added lines in 10 files covered. (60.65%)

3 existing lines in 2 files now uncovered.

39633 of 51647 relevant lines covered (76.74%)

37.71 hits per line

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

85.45
/web/client/observables/autocomplete.js
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
 * Observables used for autocomplete in the feature editor
10
 * @name observables.autocomplete
11
 * @type {Object}
12
 *
13
*/
14

15
import url from 'url';
16

17
import { endsWith, head, isNil } from 'lodash';
18
import Rx from 'rxjs';
19

20
import {API} from '../api/searchText';
21
import axios from '../libs/ajax';
22
import { getWpsPayload } from '../utils/ogc/WPS/autocomplete';
23

24
export const singleAttributeFilter = ({searchText = "", queriableAttributes = [], predicate = "ILIKE"} ) => {
1✔
25
    const attribute = head(queriableAttributes);
4✔
26
    const text = searchText.toLowerCase();
4✔
27
    let filter = `strToLowerCase(${attribute}) ${predicate} '%${text}%'`;
4✔
28
    if (isNil(attribute)) {
4✔
29
        return "";
1✔
30
    }
31
    return "(" + filter + ")";
3✔
32
};
33

34
/**
35
 * creates a stream for fetching data via WPS
36
 * @param  {external:Observable} props
37
 * @memberof observables.autocomplete
38
 * @return {external:Observable} the stream used for fetching data for the autocomplete editor
39
*/
40
export const createPagedUniqueAutompleteStream = (props$) => props$
13✔
41
    .distinctUntilChanged( ({value, currentPage, attribute}, newProps = {}) =>
×
42
        !(newProps.value !== value || newProps.currentPage !== currentPage || newProps.attribute !== attribute))
×
43
    .throttle(props => Rx.Observable.timer(props.delayDebounce || 0))
7✔
44
    .merge(props$.debounce(props => Rx.Observable.timer(props.delayDebounce || 0))).distinctUntilChanged()
7✔
45
    .switchMap((p) => {
46
        if (p.performFetch) {
7✔
47
            const data = getWpsPayload({
2✔
48
                attribute: p.attribute,
49
                layerName: p.typeName,
50
                maxFeatures: p.maxFeatures,
51
                startIndex: (p.currentPage - 1) * p.maxFeatures,
52
                value: p.value
53
            });
54
            return Rx.Observable.fromPromise(
2✔
55
                axios.post(p.url, data, {
56
                    timeout: 60000,
57
                    headers: {'Accept': 'application/json', 'Content-Type': 'application/xml'}
58
                }).then(response => { return {fetchedData: response.data, busy: false}; }))
1✔
59
                .catch(() => {
60
                    return Rx.Observable.of({fetchedData: {values: [], size: 0}, busy: false});
1✔
61
                }).startWith({busy: true});
62
        }
63
        return Rx.Observable.of({fetchedData: {values: [], size: 0}, busy: false});
5✔
64
    }).startWith({});
65
/**
66
 * creates a stream for fetching data via WPS based on the provided `filterProps`
67
 * configuration to retrieve unique attribute values from a specified source layer
68
 * @param  {external:Observable} props
69
 * @memberof observables.autocomplete
70
 * @return {external:Observable} the stream used for fetching data for the autocomplete editor
71
*/
72
export const createCustomPagedUniqueAutompleteStream = (props$) => props$
5✔
73
    .distinctUntilChanged( ({value, currentPage, filterProps = {}}, newProps = {}) =>{
×
74
    // Extract filterProps safely
75
        const newFilterProps = newProps.filterProps || {};
×
76
        // Compare relevant properties to avoid unnecessary fetches
77
        return !(
×
78
            newProps.value !== value ||
×
79
                newProps.currentPage !== currentPage ||
80
                newFilterProps.typeName !== filterProps.typeName ||
81
                // Deep compare queriableAttributes array using JSON.stringify for simplicity
82
                JSON.stringify(newFilterProps.queriableAttributes) !== JSON.stringify(filterProps.queriableAttributes) ||
83
                newFilterProps.maxFeatures !== filterProps.maxFeatures ||
84
                newFilterProps.predicate !== filterProps.predicate
85
        );
86
    })
87
    .throttle(props => Rx.Observable.timer(props.delayDebounce || 0))
3✔
88
    .merge(props$.debounce(props => Rx.Observable.timer(props.delayDebounce || 0))).distinctUntilChanged()
3✔
89
    .switchMap((p) => {
90
        const { filterProps = {}, value, currentPage } = p;
3✔
91
        const {
92
            typeName: sourceTypeName,
93
            queriableAttributes,
94
            maxFeatures: configuredMaxFeatures,
95
            blacklist,
96
            performFetch = true,
3✔
97
            predicate
98
        } = filterProps;
3✔
99
        if (performFetch && p.url && sourceTypeName && queriableAttributes && queriableAttributes.length > 0) {
3✔
100
            const startIndex = (currentPage - 1) * configuredMaxFeatures;
2✔
101
            const targetAttribute = queriableAttributes[0]; // ** Note: Currently, only the 'first' attribute in this array (`queriableAttributes[0]`) is used
2✔
102

103
            const data = getWpsPayload({
2✔
104
                attribute: targetAttribute,
105
                layerName: sourceTypeName,
106
                maxFeatures: configuredMaxFeatures,
107
                startIndex: startIndex,
108
                value: value,
109
                predicate
110
            });
111
            if (!data) {
2!
112
                return Rx.Observable.of({ fetchedData: { values: [], size: 0 }, busy: false });
×
113
            }
114
            return Rx.Observable.fromPromise(
2✔
115
                axios.post(p.url, data, {
116
                    timeout: 60000,
117
                    headers: {'Accept': 'application/json', 'Content-Type': 'application/xml'}
118
                }).then(response => {
119
                    let fetchedValues = response.data?.values || [];
1!
120
                    const totalSize = response.data?.size || 0;
1!
121
                    // filter blacklist
122
                    if (blacklist && Array.isArray(blacklist) && blacklist.length > 0) {
1!
123
                        fetchedValues = fetchedValues.filter(val => !blacklist.includes(val));
×
124
                    }
125

126
                    return { fetchedData: { values: fetchedValues, size: totalSize }, busy: false };
1✔
127
                }))
128
                .catch(() => {
129
                    return Rx.Observable.of({fetchedData: {values: [], size: 0}, busy: false});
1✔
130
                }).startWith({busy: true});
131
        }
132
        return Rx.Observable.of({fetchedData: {values: [], size: 0}, busy: false});
1✔
133
    }).startWith({});
134
export const createWFSFetchStream = (props$) =>
1✔
135
    Rx.Observable.merge(
4✔
136
        props$.distinctUntilChanged(({value} = {}, {value: nextValue} = {}) => value === nextValue ).debounce(props => Rx.Observable.timer(props.delayDebounce || 0)),
4!
UNCOV
137
        props$.distinctUntilChanged( ({filterProps, currentPage} = {}, {filterProps: nextFilterProps, currentPage: nextCurrentPage} ) => filterProps === nextFilterProps && currentPage === nextCurrentPage)
×
138
    )
139
        .switchMap((p) => {
140
            if (p.performFetch) {
8✔
141
                let parsed = url.parse(p.url, true);
2✔
142
                let newPathname = "";
2✔
143
                if (endsWith(parsed.pathname, "wfs") || endsWith(parsed.pathname, "wms") || endsWith(parsed.pathname, "ows") || endsWith(parsed.pathname, "wps")) {
2!
144
                    newPathname = parsed.pathname.replace(/(wms|ows|wps|wfs)$/, "wfs");
2✔
145
                }
146
                if ( !!parsed.query && !!parsed.query.service) {
2!
147
                    delete parsed.query.service;
×
148
                }
149
                const urlParsed = url.format(Object.assign({}, parsed, {search: null, pathname: newPathname }));
2✔
150
                let serviceOptions = Object.assign({}, {
2✔
151
                    url: urlParsed,
152
                    typeName: p.filterProps && p.filterProps.typeName || "",
4!
153
                    predicate: p.filterProps && p.filterProps.predicate || "ILIKE",
4!
154
                    blacklist: p.filterProps && p.filterProps.blacklist || [],
4!
155
                    maxFeatures: p.filterProps && p.filterProps.maxFeatures || 3,
4!
156
                    queriableAttributes: p.filterProps && p.filterProps.queriableAttributes || [],
4!
157
                    returnFullData: true,
158
                    startIndex: ((p.currentPage || 1) - 1) * (p.filterProps && p.filterProps.maxFeatures || 3),
8!
159
                    outputFormat: "application/json",
160
                    staticFilter: "",
161
                    fromTextToFilter: singleAttributeFilter,
162
                    item: {},
163
                    timeout: 60000,
164
                    headers: {'Accept': 'application/json', 'Content-Type': 'application/xml'},
165
                    srsName: p.filterProps && p.filterProps.srsName || "EPSG:4326",
6✔
166
                    ...parsed.query
167
                });
168
                return Rx.Observable.fromPromise((API.Utils.getService("wfs")(p.value, serviceOptions)
2✔
169
                    .then( data => {
170
                        return {fetchedData: { values: data.features.map(f => f.properties), size: data.totalFeatures, features: data.features, crs: p.filterProps && p.filterProps.srsName || "EPSG:4326"}, busy: false};
4✔
171
                    }))).catch(() => {
172
                    return Rx.Observable.of({fetchedData: {values: [], size: 0, features: []}, busy: false});
×
173
                }).startWith({busy: true});
174
            }
175
            return Rx.Observable.of({fetchedData: {values: [], size: 0, features: []}, busy: false});
6✔
176
        }).startWith({});
177

178
export default {
179
    createPagedUniqueAutompleteStream,
180
    createWFSFetchStream,
181
    singleAttributeFilter,
182
    createCustomPagedUniqueAutompleteStream
183
};
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