Skip to content

Commit 2dbf69e

Browse files
Merge branch 'advanced-search_contribute-7.6' into advanced-search_contribute-main
# Conflicts: # src/app/core/shared/search/search-filter.service.ts # src/app/core/shared/search/search.service.ts # src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts # src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts # src/app/shared/search/search-filters/search-filter/search-filter.component.spec.ts # src/app/shared/search/search-filters/search-filter/search-filter.component.ts # src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts # src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts # src/app/shared/testing/search-service.stub.ts
2 parents 2226cc4 + e66035c commit 2dbf69e

13 files changed

Lines changed: 250 additions & 261 deletions

src/app/core/shared/search/search-filter.service.spec.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,17 +160,6 @@ describe('SearchFilterService', () => {
160160
});
161161
});
162162

163-
describe('when the getSelectedValuesForFilter method is called', () => {
164-
beforeEach(() => {
165-
spyOn(routeServiceStub, 'getQueryParameterValues');
166-
service.getSelectedValuesForFilter(mockFilterConfig);
167-
});
168-
169-
it('should call getQueryParameterValues on the route service with the same parameters', () => {
170-
expect(routeServiceStub.getQueryParameterValues).toHaveBeenCalledWith(mockFilterConfig.paramName);
171-
});
172-
});
173-
174163
describe('when the getCurrentScope method is called', () => {
175164
beforeEach(() => {
176165
spyOn(routeServiceStub, 'getQueryParameterValue');

src/app/core/shared/search/search-filter.service.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -154,27 +154,6 @@ export class SearchFilterService {
154154
return this.routeService.getQueryParameterValue('view');
155155
}
156156

157-
/**
158-
* Requests the active filter values set for a given filter
159-
* @param {SearchFilterConfig} filterConfig The configuration for which the filters are active
160-
* @returns {Observable<string[]>} Emits the active filters for the given filter configuration
161-
*/
162-
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
163-
const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName);
164-
const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').pipe(
165-
map((params: Params) => [].concat(...Object.values(params))),
166-
);
167-
return observableCombineLatest(values$, prefixValues$).pipe(
168-
map(([values, prefixValues]) => {
169-
if (isNotEmpty(values)) {
170-
return values;
171-
}
172-
return prefixValues;
173-
},
174-
),
175-
);
176-
}
177-
178157
/**
179158
* Updates the found facet value suggestions for a given query
180159
* Transforms the found values into display values

src/app/core/shared/search/search.service.ts

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
/* eslint-disable max-classes-per-file */
2-
import {
3-
Injectable,
4-
OnDestroy,
5-
} from '@angular/core';
2+
import { Injectable } from '@angular/core';
63
import { Angulartics2 } from 'angulartics2';
74
import {
85
BehaviorSubject,
96
combineLatest as observableCombineLatest,
107
Observable,
118
} from 'rxjs';
129
import {
10+
distinctUntilChanged,
1311
map,
1412
switchMap,
1513
take,
@@ -81,7 +79,7 @@ class SearchDataService extends BaseDataService<any> {
8179
* Service that performs all general actions that have to do with the search page
8280
*/
8381
@Injectable({ providedIn: 'root' })
84-
export class SearchService implements OnDestroy {
82+
export class SearchService {
8583

8684
/**
8785
* Endpoint link path for retrieving general search results
@@ -98,11 +96,6 @@ export class SearchService implements OnDestroy {
9896
*/
9997
private request: GenericConstructor<RestRequest> = GetRequest;
10098

101-
/**
102-
* Subscription to unsubscribe from
103-
*/
104-
private sub;
105-
10699
/**
107100
* Instance of SearchDataService to forward data service methods to
108101
*/
@@ -123,6 +116,18 @@ export class SearchService implements OnDestroy {
123116
this.searchDataService = new SearchDataService();
124117
}
125118

119+
/**
120+
* Get the currently {@link AppliedFilter}s for the given filter.
121+
*
122+
* @param filterName The name of the filter
123+
*/
124+
getSelectedValuesForFilter(filterName: string): Observable<AppliedFilter[]> {
125+
return this.appliedFilters$.pipe(
126+
map((appliedFilters: AppliedFilter[]) => appliedFilters.filter((appliedFilter: AppliedFilter) => appliedFilter.filter === filterName)),
127+
distinctUntilChanged((previous: AppliedFilter[], next: AppliedFilter[]) => JSON.stringify(previous) === JSON.stringify(next)),
128+
);
129+
}
130+
126131
/**
127132
* Method to set service options
128133
* @param {GenericConstructor<ResponseParsingService>} parser The ResponseParsingService constructor name
@@ -196,26 +201,11 @@ export class SearchService implements OnDestroy {
196201
return this.directlyAttachIndexableObjects(sqr$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
197202
}
198203

199-
/**
200-
* Method to retrieve request entries for search results from the server
201-
* @param {PaginatedSearchOptions} searchOptions The configuration necessary to perform this search
202-
* @returns {Observable<RemoteData<SearchObjects<T>>>} Emits a paginated list with all search results found
203-
*/
204-
searchEntries<T extends DSpaceObject>(searchOptions?: PaginatedSearchOptions): Observable<RemoteData<SearchObjects<T>>> {
205-
const href$ = this.getEndpoint(searchOptions);
206-
207-
const sqr$ = href$.pipe(
208-
switchMap((href: string) => this.rdb.buildFromHref<SearchObjects<T>>(href)),
209-
);
210-
211-
return this.directlyAttachIndexableObjects(sqr$);
212-
}
213-
214204
/**
215205
* Method to directly attach the indexableObjects to search results, instead of using RemoteData.
216206
* For compatibility with the way the search was written originally
217207
*
218-
* @param sqr$: a SearchObjects RemotaData Observable without its
208+
* @param sqr$ A {@link SearchObjects} {@link RemoteData} Observable without its
219209
* indexableObjects attached
220210
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
221211
* no valid cached version. Defaults to true
@@ -404,12 +394,4 @@ export class SearchService implements OnDestroy {
404394
return '/search';
405395
}
406396

407-
/**
408-
* Unsubscribe from the subscription
409-
*/
410-
ngOnDestroy(): void {
411-
if (this.sub !== undefined) {
412-
this.sub.unsubscribe();
413-
}
414-
}
415397
}

src/app/shared/search/advanced-search/advanced-search.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export class AdvancedSearchComponent implements OnInit, OnDestroy {
136136

137137
applyFilter(): void {
138138
if (isNotEmpty(this.currentValue)) {
139+
this.searchFilterService.minimizeAll();
139140
this.subs.push(this.searchConfigurationService.selectNewAppliedFilterParams(this.currentFilter, this.currentValue.trim(), this.currentOperator).pipe(take(1)).subscribe((params: Params) => {
140141
void this.router.navigate([this.searchService.getSearchLink()], {
141142
queryParams: params,

src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import { FormsModule } from '@angular/forms';
1111
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
1212
import { Router } from '@angular/router';
1313
import { TranslateModule } from '@ngx-translate/core';
14+
import { cold } from 'jasmine-marbles';
1415
import {
1516
BehaviorSubject,
1617
of as observableOf,
1718
} from 'rxjs';
1819

1920
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
21+
import { PageInfo } from '../../../../../core/shared/page-info.model';
2022
import { SearchService } from '../../../../../core/shared/search/search.service';
2123
import { SearchFilterService } from '../../../../../core/shared/search/search-filter.service';
2224
import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-configuration.service';
@@ -38,34 +40,47 @@ describe('SearchFacetFilterComponent', () => {
3840
const value1 = 'testvalue1';
3941
const value2 = 'test2';
4042
const value3 = 'another value3';
43+
const value4 = '52d629dc-7d2f-47b9-aa2d-258b92e45ae1';
4144
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
4245
name: filterName1,
4346
filterType: FilterType.text,
4447
hasFacets: false,
4548
isOpenByDefault: false,
4649
pageSize: 2,
4750
});
51+
const appliedFilter1: AppliedFilter = Object.assign(new AppliedFilter(), {
52+
filter: filterName1,
53+
operator: 'equals',
54+
label: value1,
55+
value: value1,
56+
});
57+
const appliedFilter2: AppliedFilter = Object.assign(new AppliedFilter(), {
58+
filter: filterName1,
59+
operator: 'equals',
60+
label: value2,
61+
value: value2,
62+
});
63+
const appliedFilter3: AppliedFilter = Object.assign(new AppliedFilter(), {
64+
filter: filterName1,
65+
operator: 'equals',
66+
label: value3,
67+
value: value3,
68+
});
69+
const appliedFilter4: AppliedFilter = Object.assign(new AppliedFilter(), {
70+
filter: filterName1,
71+
operator: 'notauthority',
72+
label: value4,
73+
value: value4,
74+
});
4875
const values: Partial<FacetValues> = {
4976
appliedFilters: [
50-
{
51-
filter: filterName1,
52-
operator: 'equals',
53-
label: value1,
54-
value: value1,
55-
},
56-
{
57-
filter: filterName1,
58-
operator: 'equals',
59-
label: value2,
60-
value: value2,
61-
},
62-
{
63-
filter: filterName1,
64-
operator: 'equals',
65-
label: value3,
66-
value: value3,
67-
},
77+
appliedFilter1,
78+
appliedFilter2,
79+
appliedFilter3,
6880
],
81+
pageInfo: Object.assign(new PageInfo(), {
82+
currentPage: 0,
83+
}),
6984
};
7085

7186
const searchLink = '/search';
@@ -209,4 +224,26 @@ describe('SearchFacetFilterComponent', () => {
209224
expect(comp.filter).toEqual('');
210225
});
211226
});
227+
228+
describe('when new values are detected for a filter', () => {
229+
let selectedValues$: BehaviorSubject<AppliedFilter[]>;
230+
231+
beforeEach(() => {
232+
selectedValues$ = new BehaviorSubject([appliedFilter1, appliedFilter2, appliedFilter3]);
233+
spyOn(searchService, 'getSelectedValuesForFilter').and.returnValue(selectedValues$);
234+
comp.ngOnInit();
235+
});
236+
237+
it('should updated the selectedAppliedFilters$ when they are AppliedFilters that should be displayed in the search facets', () => {
238+
expect(comp.selectedAppliedFilters$).toBeObservable(cold('a', {
239+
a: [appliedFilter1, appliedFilter2, appliedFilter3],
240+
}));
241+
242+
selectedValues$.next([appliedFilter1, appliedFilter2, appliedFilter3, appliedFilter4]);
243+
244+
expect(comp.selectedAppliedFilters$).toBeObservable(cold('a', {
245+
a: [appliedFilter1, appliedFilter2, appliedFilter3],
246+
}));
247+
});
248+
});
212249
});

src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@ import {
2424
Subscription,
2525
} from 'rxjs';
2626
import {
27-
debounceTime,
2827
distinctUntilChanged,
29-
filter,
3028
map,
31-
mergeMap,
3229
switchMap,
3330
take,
3431
tap,
@@ -51,7 +48,15 @@ import { FacetValue } from '../../../models/facet-value.model';
5148
import { FacetValues } from '../../../models/facet-values.model';
5249
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
5350
import { SearchOptions } from '../../../models/search-options.model';
54-
import { stripOperatorFromFilterValue } from '../../../search.utils';
51+
52+
/**
53+
* The operators the {@link AppliedFilter} should have in order to be shown in the facets
54+
*/
55+
export const FACET_OPERATORS: string[] = [
56+
'equals',
57+
'authority',
58+
'range',
59+
];
5560

5661
@Component({
5762
selector: 'ds-search-facet-filter',
@@ -157,14 +162,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
157162
);
158163
this.subs.push(
159164
this.searchOptions$.subscribe(() => this.updateFilterValueList()),
160-
this.refreshFilters.asObservable().pipe(
161-
filter((toRefresh: boolean) => toRefresh),
162-
// NOTE This is a workaround, otherwise retrieving filter values returns tha old cached response
163-
debounceTime((100)),
164-
mergeMap(() => this.retrieveFilterValues(false)),
165-
).subscribe(),
165+
this.retrieveFilterValues().subscribe(),
166+
);
167+
this.selectedAppliedFilters$ = this.searchService.getSelectedValuesForFilter(this.filterConfig.name).pipe(
168+
map((allAppliedFilters: AppliedFilter[]) => allAppliedFilters.filter((appliedFilter: AppliedFilter) => FACET_OPERATORS.includes(appliedFilter.operator))),
169+
distinctUntilChanged((previous: AppliedFilter[], next: AppliedFilter[]) => JSON.stringify(previous) === JSON.stringify(next)),
166170
);
167-
this.retrieveFilterValues().subscribe();
168171
}
169172

170173
/**
@@ -264,6 +267,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
264267
*/
265268
protected applyFilterValue(data: string): void {
266269
if (data.match(new RegExp(`^.+,(equals|query|authority)$`))) {
270+
this.filterService.minimizeAll();
267271
const valueParts = data.split(',');
268272
this.subs.push(this.searchConfigService.selectNewAppliedFilterParams(this.filterConfig.name, valueParts.slice(0, valueParts.length - 1).join(), valueParts[valueParts.length - 1]).pipe(take(1)).subscribe((params: Params) => {
269273
void this.router.navigate(this.getSearchLinkParts(), {
@@ -275,9 +279,13 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
275279
}
276280
}
277281

278-
protected retrieveFilterValues(useCachedVersionIfAvailable = true): Observable<FacetValues[]> {
282+
/**
283+
* Retrieves all the filter value suggestion pages that need to be displayed in the facet and combines it into one
284+
* list.
285+
*/
286+
protected retrieveFilterValues(): Observable<FacetValues[]> {
279287
return observableCombineLatest([this.searchOptions$, this.currentPage]).pipe(
280-
switchMap(([options, page]: [SearchOptions, number]) => this.searchService.getFacetValuesFor(this.filterConfig, page, options, null, useCachedVersionIfAvailable).pipe(
288+
switchMap(([options, page]: [SearchOptions, number]) => this.searchService.getFacetValuesFor(this.filterConfig, page, options).pipe(
281289
getFirstSucceededRemoteDataPayload(),
282290
tap((facetValues: FacetValues) => {
283291
this.isLastPage$.next(hasNoValue(facetValues?.next));
@@ -300,26 +308,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
300308
return filterValues;
301309
}),
302310
tap((allFacetValues: FacetValues[]) => {
303-
this.setAppliedFilter(allFacetValues);
304311
this.animationState = 'ready';
305312
this.facetValues$.next(allFacetValues);
306313
}),
307314
);
308315
}
309316

310-
setAppliedFilter(allFacetValues: FacetValues[]): void {
311-
const allAppliedFilters: AppliedFilter[] = [].concat(...allFacetValues.map((facetValues: FacetValues) => facetValues.appliedFilters))
312-
.filter((appliedFilter: AppliedFilter) => hasValue(appliedFilter));
313-
314-
this.selectedAppliedFilters$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe(
315-
map((selectedValues: string[]) => {
316-
return selectedValues.map((value: string) => {
317-
return allAppliedFilters.find((appliedFilter: AppliedFilter) => appliedFilter.value === stripOperatorFromFilterValue(value));
318-
}).filter((appliedFilter: AppliedFilter) => hasValue(appliedFilter));
319-
}),
320-
);
321-
}
322-
323317
/**
324318
* Prevent unnecessary rerendering
325319
*/

0 commit comments

Comments
 (0)