Skip to content

Commit 203530b

Browse files
authored
#12479 Prevent reload of current layer catalog page when switching layout (#12507)
#Fix #12479 Prevent reload of current layer catalog page when switching layout
1 parent 61e34fa commit 203530b

4 files changed

Lines changed: 295 additions & 12 deletions

File tree

web/client/components/catalog/__tests__/Catalog-test.jsx

Lines changed: 201 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,30 +64,226 @@ describe('Test Catalog panel', () => {
6464
}}
6565
/>, document.getElementById("container"));
6666
});
67-
it('resets new service status before autoload search', (done) => {
67+
it('clears new service status without duplicate autoload search', (done) => {
6868
const SERVICE = {
6969
type: "csw",
7070
url: "http://sample.service/catalog",
7171
title: "csw",
7272
autoload: true
7373
};
7474
const actions = {
75-
setNewServiceStatus: () => {}
75+
setNewServiceStatus: () => {},
76+
onSearch: () => {}
7677
};
7778
const spyOnStatus = expect.spyOn(actions, 'setNewServiceStatus');
78-
actions.onSearch = () => {
79+
const spyOnSearch = expect.spyOn(actions, 'onSearch');
80+
ReactDOM.render(<Catalog
81+
services={{ "csw": SERVICE}}
82+
selectedService="csw"
83+
selectedFormat="csw"
84+
onSearch={actions.onSearch}
85+
isNewServiceAdded
86+
setNewServiceStatus={actions.setNewServiceStatus}
87+
/>, document.getElementById("container"));
88+
89+
setTimeout(() => {
90+
expect(spyOnSearch.calls.length).toBe(0);
7991
expect(spyOnStatus).toHaveBeenCalled();
8092
expect(spyOnStatus.calls[0].arguments[0]).toBe(false);
8193
done();
94+
}, 0);
95+
});
96+
it('does not autoload again when the current service already has results', (done) => {
97+
const SERVICE = {
98+
type: "csw",
99+
url: "http://sample.service/catalog",
100+
title: "csw",
101+
autoload: true
82102
};
103+
const actions = {
104+
onSearch: () => {},
105+
clearSelection: () => {}
106+
};
107+
const spyOnSearch = expect.spyOn(actions, 'onSearch');
108+
const spyOnClearSelection = expect.spyOn(actions, 'clearSelection');
83109
ReactDOM.render(<Catalog
84110
services={{ "csw": SERVICE}}
85111
selectedService="csw"
86112
selectedFormat="csw"
113+
result={{ numberOfRecordsMatched: 24 }}
114+
searchOptions={{ url: 'http://sample.service/catalog', startPosition: 13 }}
115+
records={[{ identifier: 'csw-1', title: 'Catalog Layer', references: [] }]}
116+
selected={[]}
117+
layers={[]}
118+
loadingLayers={[]}
119+
onSelect={() => {}}
120+
onAddSelected={() => {}}
121+
onAddLayer={() => {}}
122+
onSearch={actions.onSearch}
123+
clearSelection={actions.clearSelection}
124+
/>, document.getElementById("container"));
125+
126+
setTimeout(() => {
127+
const activePage = document.querySelector('.pagination .active a, .pagination .active span');
128+
expect(spyOnSearch.calls.length).toBe(0);
129+
expect(spyOnClearSelection.calls.length).toBe(0);
130+
expect(activePage.textContent).toBe('2');
131+
done();
132+
}, 0);
133+
});
134+
it('does not clear selected records on initial render', (done) => {
135+
const SERVICE = {
136+
type: "csw",
137+
url: "http://sample.service/catalog",
138+
title: "csw"
139+
};
140+
const actions = {
141+
clearSelection: () => {}
142+
};
143+
const spyOnClearSelection = expect.spyOn(actions, 'clearSelection');
144+
ReactDOM.render(<Catalog
145+
services={{ "csw": SERVICE}}
146+
selectedService="csw"
147+
selectedFormat="csw"
148+
records={[{ identifier: 'csw-1', title: 'Catalog Layer', references: [] }]}
149+
selected={[{ identifier: 'csw-1', title: 'Catalog Layer', references: [] }]}
150+
layers={[]}
151+
loadingLayers={[]}
152+
onSelect={() => {}}
153+
onAddSelected={() => {}}
154+
onAddLayer={() => {}}
155+
clearSelection={actions.clearSelection}
156+
/>, document.getElementById("container"));
157+
158+
setTimeout(() => {
159+
expect(spyOnClearSelection.calls.length).toBe(0);
160+
done();
161+
}, 0);
162+
});
163+
it('uses filters and sort from current search options after remount', (done) => {
164+
const SERVICE = {
165+
type: "geonode",
166+
url: "http://sample.service/geonode",
167+
title: "GeoNode"
168+
};
169+
const filters = {'filter{category.identifier.in}': ['environment']};
170+
const actions = {
171+
onSearch: () => {}
172+
};
173+
const spyOnSearch = expect.spyOn(actions, 'onSearch');
174+
ReactDOM.render(<Catalog
175+
services={{ "geonode": SERVICE}}
176+
selectedService="geonode"
177+
selectedFormat="geonode"
178+
result={{ numberOfRecordsMatched: 24 }}
179+
searchOptions={{
180+
url: 'http://sample.service/geonode',
181+
startPosition: 1,
182+
filters,
183+
sort: 'title'
184+
}}
185+
records={[{ identifier: 'geo-1', title: 'Geo Layer', references: [], isValid: true }]}
186+
selected={[]}
187+
layers={[]}
188+
loadingLayers={[]}
189+
onSelect={() => {}}
190+
onAddSelected={() => {}}
191+
onAddLayer={() => {}}
192+
onSearch={actions.onSearch}
193+
/>, document.getElementById("container"));
194+
195+
setTimeout(() => {
196+
const page2 = [...document.querySelectorAll('.pagination a')]
197+
.find((node) => node.textContent === '2');
198+
TestUtils.Simulate.click(page2);
199+
expect(spyOnSearch.calls.length).toBe(1);
200+
expect(spyOnSearch.calls[0].arguments[0]).toEqual({
201+
format: 'geonode',
202+
url: 'http://sample.service/geonode',
203+
startPosition: 13,
204+
maxRecords: 12,
205+
text: '',
206+
options: {
207+
filters,
208+
sort: 'title',
209+
service: SERVICE
210+
}
211+
});
212+
done();
213+
}, 0);
214+
});
215+
it('does not autoload again while catalog is loading', (done) => {
216+
const SERVICE = {
217+
type: "csw",
218+
url: "http://sample.service/catalog",
219+
title: "csw",
220+
autoload: true
221+
};
222+
const actions = {
223+
onSearch: () => {}
224+
};
225+
const spyOnSearch = expect.spyOn(actions, 'onSearch');
226+
ReactDOM.render(<Catalog
227+
services={{ "csw": SERVICE}}
228+
selectedService="csw"
229+
selectedFormat="csw"
230+
loading
231+
onSearch={actions.onSearch}
232+
/>, document.getElementById("container"));
233+
234+
setTimeout(() => {
235+
expect(spyOnSearch.calls.length).toBe(0);
236+
done();
237+
}, 0);
238+
});
239+
it('does not autoload again when current catalog has a loading error', (done) => {
240+
const SERVICE = {
241+
type: "csw",
242+
url: "http://sample.service/catalog",
243+
title: "csw",
244+
autoload: true
245+
};
246+
const actions = {
247+
onSearch: () => {}
248+
};
249+
const spyOnSearch = expect.spyOn(actions, 'onSearch');
250+
ReactDOM.render(<Catalog
251+
services={{ "csw": SERVICE}}
252+
selectedService="csw"
253+
selectedFormat="csw"
254+
loadingError="error"
255+
onSearch={actions.onSearch}
256+
/>, document.getElementById("container"));
257+
258+
setTimeout(() => {
259+
expect(spyOnSearch.calls.length).toBe(0);
260+
done();
261+
}, 0);
262+
});
263+
it('does not autoload in edit mode', (done) => {
264+
const SERVICE = {
265+
type: "csw",
266+
url: "http://sample.service/catalog",
267+
title: "csw",
268+
autoload: true
269+
};
270+
const actions = {
271+
onSearch: () => {}
272+
};
273+
const spyOnSearch = expect.spyOn(actions, 'onSearch');
274+
ReactDOM.render(<Catalog
275+
services={{ "csw": SERVICE}}
276+
selectedService="csw"
277+
selectedFormat="csw"
278+
mode="edit"
87279
onSearch={actions.onSearch}
88-
isNewServiceAdded
89-
setNewServiceStatus={actions.setNewServiceStatus}
90280
/>, document.getElementById("container"));
281+
282+
setTimeout(() => {
283+
expect(spyOnSearch.calls.length).toBe(0);
284+
expect(document.querySelector('.ms-catalog-service-editor')).toBeTruthy();
285+
done();
286+
}, 0);
91287
});
92288
it('renders editor in edit mode', () => {
93289
const SERVICE = {

web/client/components/catalog/datasets/Catalog.jsx

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* This source code is licensed under the BSD-style license found in the
66
* LICENSE file in the root directory of this source tree.
77
*/
8-
import React, { useState, useEffect, useMemo } from 'react';
8+
import React, { useState, useEffect, useMemo, useRef } from 'react';
99
import PropTypes from 'prop-types';
1010
import castArray from 'lodash/castArray';
1111
import { buildServiceUrl } from '../../../utils/CatalogUtils';
@@ -35,6 +35,32 @@ const shouldAutoload = (service, services) => {
3535
services[service].autoload;
3636
};
3737

38+
const getCurrentSearchOptions = ({
39+
searchOptions,
40+
selectedService,
41+
services
42+
}) => {
43+
const service = selectedService && services?.[selectedService];
44+
return service && searchOptions?.url === buildServiceUrl(service)
45+
? searchOptions
46+
: {};
47+
};
48+
49+
const shouldPreserveCurrentRequest = ({
50+
loading,
51+
isNewServiceAdded,
52+
loadingError,
53+
result,
54+
searchOptions,
55+
selectedService,
56+
services
57+
}) => {
58+
if (loading || isNewServiceAdded || (loadingError !== undefined && loadingError !== null)) {
59+
return true;
60+
}
61+
return !!(result && getCurrentSearchOptions({ searchOptions, selectedService, services })?.url);
62+
};
63+
3864
const Catalog = ({
3965
serviceTypes = [
4066
{ name: "csw", label: "CSW" },
@@ -121,10 +147,12 @@ const Catalog = ({
121147
API = defaultAPI
122148
}, context) => {
123149
const { messages } = context;
150+
const currentSearchOptions = getCurrentSearchOptions({ searchOptions, selectedService, services });
124151
const [showFilters, setShowFilters] = useState(false);
125-
const [filters, setFilters] = useState({});
126-
const [sort, setSort] = useState('-date');
152+
const [filters, setFilters] = useState(currentSearchOptions.filters || {});
153+
const [sort, setSort] = useState(currentSearchOptions.sort || '-date');
127154
const [selectedServiceInitialized, setSelectedServiceInitialized] = useState(false);
155+
const servicesEffectInitialized = useRef(false);
128156
const serviceCapabilities = API[selectedFormat]?.getCapabilities?.() || {
129157
filterSupport: false,
130158
orderBySupport: false
@@ -149,8 +177,23 @@ const Catalog = ({
149177
};
150178

151179
useEffect(() => {
152-
clearSelection?.();
153-
if (shouldAutoload(selectedService, services)) {
180+
const preserveCurrentRequest = shouldPreserveCurrentRequest({
181+
loading,
182+
isNewServiceAdded,
183+
loadingError,
184+
result,
185+
searchOptions,
186+
selectedService,
187+
services
188+
});
189+
if (!preserveCurrentRequest && servicesEffectInitialized.current) {
190+
clearSelection?.();
191+
}
192+
servicesEffectInitialized.current = true;
193+
if (isNewServiceAdded) {
194+
setNewServiceStatus(false);
195+
}
196+
if (!preserveCurrentRequest && mode === 'view' && shouldAutoload(selectedService, services)) {
154197
search({ searchText, filters, sort });
155198
}
156199
setShowFilters(false);

web/client/epics/__tests__/catalog-test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,42 @@ describe('catalog Epics', () => {
406406
newService: service
407407
} });
408408
});
409+
it('newCatalogServiceAdded preserves current search options when editing a service', (done) => {
410+
const NUM_ACTIONS = 2;
411+
const filters = {'filter{category.identifier.in}': ['environment']};
412+
const service = {
413+
type: "csw",
414+
url: "base/web/client/test-resources/csw/getRecordsResponseDC.xml",
415+
oldService: "cswCatalog"
416+
};
417+
testEpic(addTimeoutEpic(newCatalogServiceAdded), NUM_ACTIONS, addService(), (actions) => {
418+
expect(actions.length).toBe(NUM_ACTIONS);
419+
actions.map((action) => {
420+
switch (action.type) {
421+
case TEXT_SEARCH:
422+
expect(action.format).toBe(service.type);
423+
expect(action.url).toBe(service.url);
424+
expect(action.startPosition).toBe(1);
425+
expect(action.maxRecords).toBe(12);
426+
expect(action.text).toBe("roads");
427+
expect(action.options).toEqual({service, isNewService: true, filters, sort: "-date"});
428+
break;
429+
case TEST_TIMEOUT:
430+
break;
431+
default:
432+
expect(true).toBe(false);
433+
}
434+
});
435+
done();
436+
}, { catalog: {
437+
newService: service,
438+
searchOptions: {
439+
text: "roads",
440+
filters,
441+
sort: "-date"
442+
}
443+
} });
444+
});
409445
it('recordSearchEpic with network error', (done) => {
410446
const NUM_ACTIONS = 2;
411447
testEpic(addTimeoutEpic(recordSearchEpic), NUM_ACTIONS, textSearch({

web/client/epics/catalog.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,19 @@ export default (API) => ({
431431
const state = store.getState();
432432
const newService = newServiceSelector(state);
433433
const maxRecords = pageSizeSelector(state);
434+
const currentSearchOptions = searchOptionsSelector(state) || {};
434435
return Rx.Observable.of(newService)
435436
// validate
436437
.switchMap((service) => API[service.type]?.preprocess?.(service) ?? ( Rx.Observable.of(service)))
437438
.switchMap((service) => API[service.type]?.validate?.(service) ?? ( Rx.Observable.of(service)))
438439
.switchMap((service) => {
440+
const preserveCurrentRequest = !!service.oldService;
441+
const preservedSearchOptions = preserveCurrentRequest
442+
? {
443+
...(currentSearchOptions.filters !== undefined && { filters: currentSearchOptions.filters }),
444+
...(currentSearchOptions.sort !== undefined && { sort: currentSearchOptions.sort })
445+
}
446+
: {};
439447
// Dispatch action to test service and add records to catalog after successful saving of the service,
440448
// this prevents duplicate calls being fired for all the services
441449
return Rx.Observable.of(
@@ -444,8 +452,8 @@ export default (API) => ({
444452
url: service.url,
445453
startPosition: 1,
446454
maxRecords,
447-
text: "",
448-
options: {service, isNewService: true, ...options}
455+
text: preserveCurrentRequest ? currentSearchOptions.text || "" : "",
456+
options: {service, isNewService: true, ...preservedSearchOptions, ...options}
449457
})
450458
);
451459
})

0 commit comments

Comments
 (0)