Skip to content

Commit e9fe0a3

Browse files
Merge branch 'use-applied-filter-to-display-label-on-search_contribute-7.6' into use-applied-filter-to-display-label-on-search_contribute-main
2 parents 5a42f39 + f655777 commit e9fe0a3

53 files changed

Lines changed: 1098 additions & 734 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/app/core/services/route.service.spec.ts

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { RouteService } from './route.service';
99
import { RouterMock } from '../../shared/mocks/router.mock';
1010
import { TestScheduler } from 'rxjs/testing';
1111
import { AddUrlToHistoryAction } from '../history/history.actions';
12+
import { ActivatedRouteStub } from 'src/app/shared/testing/active-router.stub';
13+
import { take } from 'rxjs/operators';
1214

1315
describe('RouteService', () => {
1416
let scheduler: TestScheduler;
@@ -29,23 +31,19 @@ describe('RouteService', () => {
2931
select: jasmine.createSpy('select')
3032
});
3133

34+
let route: ActivatedRouteStub;
3235
const router = new RouterMock();
3336
router.setParams(convertToParamMap(paramObject));
3437

3538
paramObject[paramName1] = paramValue1;
3639
paramObject[paramName2] = [paramValue2a, paramValue2b];
3740

3841
beforeEach(waitForAsync(() => {
42+
route = new ActivatedRouteStub(paramObject);
43+
3944
return TestBed.configureTestingModule({
4045
providers: [
41-
{
42-
provide: ActivatedRoute,
43-
useValue: {
44-
queryParams: observableOf(paramObject),
45-
params: observableOf(paramObject),
46-
queryParamMap: observableOf(convertToParamMap(paramObject))
47-
},
48-
},
46+
{ provide: ActivatedRoute, useValue: route },
4947
{ provide: Router, useValue: router },
5048
{ provide: Store, useValue: store },
5149
]
@@ -181,4 +179,51 @@ describe('RouteService', () => {
181179
});
182180
});
183181
});
182+
183+
describe('getParamsWithoutAppliedFilter', () => {
184+
beforeEach(() => {
185+
route.testParams = {
186+
'query': '',
187+
'spc.page': '1',
188+
'f.author': '1282121b-5394-4689-ab93-78d537764052,authority',
189+
'f.has_content_in_original_bundle': 'true,equals',
190+
};
191+
});
192+
193+
it('should remove the parameter completely if only one value is defined', (done: DoneFn) => {
194+
service.getParamsExceptValue('f.author', '1282121b-5394-4689-ab93-78d537764052,authority').pipe(take(1)).subscribe((params: Params) => {
195+
expect(params).toEqual({
196+
'query': '',
197+
'spc.page': '1',
198+
'f.has_content_in_original_bundle': 'true,equals',
199+
});
200+
done();
201+
});
202+
});
203+
204+
it('should remove the parameter completely if only one value is defined in an array', (done: DoneFn) => {
205+
route.testParams['f.author'] = ['1282121b-5394-4689-ab93-78d537764052,authority'];
206+
service.getParamsExceptValue('f.author', '1282121b-5394-4689-ab93-78d537764052,authority').pipe(take(1)).subscribe((params: Params) => {
207+
expect(params).toEqual({
208+
'query': '',
209+
'spc.page': '1',
210+
'f.has_content_in_original_bundle': 'true,equals',
211+
});
212+
done();
213+
});
214+
});
215+
216+
it('should return all params except the applied filter even when multiple filters of the same type are selected', (done: DoneFn) => {
217+
route.testParams['f.author'] = ['1282121b-5394-4689-ab93-78d537764052,authority', '71b91a28-c280-4352-a199-bd7fc3312501,authority'];
218+
service.getParamsExceptValue('f.author', '1282121b-5394-4689-ab93-78d537764052,authority').pipe(take(1)).subscribe((params: Params) => {
219+
expect(params).toEqual({
220+
'query': '',
221+
'spc.page': '1',
222+
'f.author': ['71b91a28-c280-4352-a199-bd7fc3312501,authority'],
223+
'f.has_content_in_original_bundle': 'true,equals',
224+
});
225+
done();
226+
});
227+
});
228+
});
184229
});

src/app/core/services/route.service.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,56 @@ export class RouteService {
225225
}
226226
);
227227
}
228+
229+
/**
230+
* Returns all the query parameters except for the one with the given name & value.
231+
*
232+
* @param name The name of the query param to exclude
233+
* @param value The optional value that the query param needs to have to be excluded
234+
*/
235+
getParamsExceptValue(name: string, value?: string): Observable<Params> {
236+
return this.route.queryParams.pipe(
237+
map((params: Params) => {
238+
const newParams: Params = Object.assign({}, params);
239+
const queryParamValues: string | string[] = newParams[name];
240+
241+
if (queryParamValues === value || value === undefined) {
242+
delete newParams[name];
243+
} else if (Array.isArray(queryParamValues) && queryParamValues.includes(value)) {
244+
newParams[name] = (queryParamValues as string[]).filter((paramValue: string) => paramValue !== value);
245+
if (newParams[name].length === 0) {
246+
delete newParams[name];
247+
}
248+
}
249+
return newParams;
250+
}),
251+
);
252+
}
253+
254+
/**
255+
* Returns all the existing query parameters and the new value pair with the given name & value.
256+
*
257+
* @param name The name of the query param for which you need to add the value
258+
* @param value The optional value that the query param needs to have in addition to the current ones
259+
*/
260+
getParamsWithAdditionalValue(name: string, value: string): Observable<Params> {
261+
return this.route.queryParams.pipe(
262+
map((params: Params) => {
263+
const newParams: Params = Object.assign({}, params);
264+
const queryParamValues: string | string[] = newParams[name];
265+
266+
if (queryParamValues === undefined) {
267+
newParams[name] = value;
268+
} else {
269+
if (Array.isArray(queryParamValues)) {
270+
newParams[name] = [...queryParamValues, value];
271+
} else {
272+
newParams[name] = [queryParamValues, value];
273+
}
274+
}
275+
return newParams;
276+
}),
277+
);
278+
}
279+
228280
}

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.u
1313
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
1414
import { RequestEntry } from '../../data/request-entry.model';
1515
import { SearchObjects } from '../../../shared/search/models/search-objects.model';
16+
import { AppliedFilter } from '../../../shared/search/models/applied-filter.model';
1617

1718
describe('SearchConfigurationService', () => {
1819
let service: SearchConfigurationService;
@@ -38,13 +39,14 @@ describe('SearchConfigurationService', () => {
3839
const routeService = jasmine.createSpyObj('RouteService', {
3940
getQueryParameterValue: observableOf(value1),
4041
getQueryParamsWithPrefix: observableOf(prefixFilter),
41-
getRouteParameterValue: observableOf('')
42+
getRouteParameterValue: observableOf(''),
43+
getParamsExceptValue: observableOf({}),
4244
});
4345

4446
const paginationService = new PaginationServiceStub();
4547

4648

47-
const activatedRoute: any = new ActivatedRouteStub();
49+
const activatedRoute: ActivatedRouteStub = new ActivatedRouteStub();
4850
const linkService: any = {};
4951
const requestService: any = getMockRequestService();
5052
const halService: any = {
@@ -70,7 +72,7 @@ describe('SearchConfigurationService', () => {
7072
}
7173
};
7274
beforeEach(() => {
73-
service = new SearchConfigurationService(routeService, paginationService as any, activatedRoute, linkService, halService, requestService, rdb);
75+
service = new SearchConfigurationService(routeService, paginationService as any, activatedRoute as any, linkService, halService, requestService, rdb);
7476
});
7577

7678
describe('when the scope is called', () => {
@@ -279,4 +281,29 @@ describe('SearchConfigurationService', () => {
279281
expect((service as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: requestUrl }), true);
280282
});
281283
});
284+
285+
describe('unselectAppliedFilterParams', () => {
286+
let appliedFilter: AppliedFilter;
287+
288+
beforeEach(() => {
289+
appliedFilter = Object.assign(new AppliedFilter(), {
290+
filter: 'author',
291+
operator: 'authority',
292+
value: '1282121b-5394-4689-ab93-78d537764052',
293+
label: 'Odinson, Thor',
294+
});
295+
});
296+
297+
it('should return all params except the applied filter', () => {
298+
service.unselectAppliedFilterParams(appliedFilter.filter, appliedFilter.value, appliedFilter.operator);
299+
300+
expect(routeService.getParamsExceptValue).toHaveBeenCalledWith('f.author', '1282121b-5394-4689-ab93-78d537764052,authority');
301+
});
302+
303+
it('should be able to remove AppliedFilter without operator', () => {
304+
service.unselectAppliedFilterParams('dateIssued.max', '2000');
305+
306+
expect(routeService.getParamsExceptValue).toHaveBeenCalledWith('f.dateIssued.max', '2000');
307+
});
308+
});
282309
});

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { FacetConfigResponseParsingService } from '../../data/facet-config-respo
2828
import { ViewMode } from '../view-mode.model';
2929
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
3030
import { FacetConfigResponse } from '../../../shared/search/models/facet-config-response.model';
31+
import { addOperatorToFilterValue } from '../../../shared/search/search.utils';
3132

3233
/**
3334
* Service that performs all actions that have to do with the current search configuration
@@ -525,6 +526,27 @@ export class SearchConfigurationService implements OnDestroy {
525526
);
526527
}
527528

529+
/**
530+
* Calculates the {@link Params} of the search after removing a filter with a certain value
531+
*
532+
* @param filterName The {@link AppliedFilter}'s name
533+
* @param value The {@link AppliedFilter}'s value
534+
* @param operator The {@link AppliedFilter}'s optional operator
535+
*/
536+
unselectAppliedFilterParams(filterName: string, value: string, operator?: string): Observable<Params> {
537+
return this.routeService.getParamsExceptValue(`f.${filterName}`, hasValue(operator) ? addOperatorToFilterValue(value, operator) : value);
538+
}
539+
540+
/**
541+
* Calculates the {@link Params} of the search after removing a filter with a certain value
542+
*
543+
* @param filterName The {@link AppliedFilter}'s name
544+
* @param value The {@link AppliedFilter}'s value
545+
* @param operator The {@link AppliedFilter}'s optional operator
546+
*/
547+
selectNewAppliedFilterParams(filterName: string, value: string, operator?: string): Observable<Params> {
548+
return this.routeService.getParamsWithAdditionalValue(`f.${filterName}`, hasValue(operator) ? addOperatorToFilterValue(value, operator) : value);
549+
}
528550

529551
/**
530552
* @returns {Observable<Params>} Emits the current view mode as a partial SearchOptions object

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs';
22
import { distinctUntilChanged, map } from 'rxjs/operators';
3-
import { Injectable, InjectionToken } from '@angular/core';
3+
import { Injectable, InjectionToken, EventEmitter } from '@angular/core';
44
import {
55
SearchFiltersState,
66
SearchFilterState
@@ -21,12 +21,14 @@ import { SortDirection, SortOptions } from '../../cache/models/sort-options.mode
2121
import { RouteService } from '../../services/route.service';
2222
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
2323
import { Params } from '@angular/router';
24+
import { AppliedFilter } from '../../../shared/search/models/applied-filter.model';
2425

2526
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
2627

2728
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
2829
export const IN_PLACE_SEARCH: InjectionToken<boolean> = new InjectionToken<boolean>('inPlaceSearch');
2930
export const REFRESH_FILTER: InjectionToken<BehaviorSubject<any>> = new InjectionToken<boolean>('refreshFilters');
31+
export const CHANGE_APPLIED_FILTERS: InjectionToken<EventEmitter<AppliedFilter[]>> = new InjectionToken('changeAppliedFilters');
3032

3133
/**
3234
* Service that performs all actions that have to do with search filters and facets
@@ -61,15 +63,15 @@ export class SearchFilterService {
6163
* Fetch the current active scope from the query parameters
6264
* @returns {Observable<string>}
6365
*/
64-
getCurrentScope() {
66+
getCurrentScope(): Observable<string> {
6567
return this.routeService.getQueryParameterValue('scope');
6668
}
6769

6870
/**
6971
* Fetch the current query from the query parameters
7072
* @returns {Observable<string>}
7173
*/
72-
getCurrentQuery() {
74+
getCurrentQuery(): Observable<string> {
7375
return this.routeService.getQueryParameterValue('query');
7476
}
7577

@@ -111,15 +113,15 @@ export class SearchFilterService {
111113
* Fetch the current active filters from the query parameters
112114
* @returns {Observable<Params>}
113115
*/
114-
getCurrentFilters() {
116+
getCurrentFilters(): Observable<Params> {
115117
return this.routeService.getQueryParamsWithPrefix('f.');
116118
}
117119

118120
/**
119121
* Fetch the current view from the query parameters
120122
* @returns {Observable<string>}
121123
*/
122-
getCurrentView() {
124+
getCurrentView(): Observable<string> {
123125
return this.routeService.getQueryParameterValue('view');
124126
}
125127

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { autoserialize } from 'cerialize';
2+
3+
export class AppliedFilter {
4+
5+
@autoserialize
6+
filter: string;
7+
8+
@autoserialize
9+
operator: string;
10+
11+
@autoserialize
12+
value: string;
13+
14+
@autoserialize
15+
label: string;
16+
17+
}

src/app/shared/search/models/search-query-response.model.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { autoserialize } from 'cerialize';
1+
import { autoserialize, autoserializeAs } from 'cerialize';
22
import { PageInfo } from '../../../core/shared/page-info.model';
33
import { PaginatedList } from '../../../core/data/paginated-list.model';
4+
import { AppliedFilter } from './applied-filter.model';
5+
import { SearchResultSorting } from './search-result-sorting.model';
46

57
/**
68
* Class representing the response returned by the server when performing a search request
@@ -21,14 +23,14 @@ export abstract class SearchQueryResponse<T> extends PaginatedList<T> {
2123
/**
2224
* The currently active filters used in the search request
2325
*/
24-
@autoserialize
25-
appliedFilters: any[]; // TODO
26+
@autoserializeAs(AppliedFilter)
27+
appliedFilters: AppliedFilter[];
2628

2729
/**
2830
* The sort parameters used in the search request
2931
*/
30-
@autoserialize
31-
sort: any; // TODO
32+
@autoserializeAs(SearchResultSorting)
33+
sort: SearchResultSorting;
3234

3335
/**
3436
* The sort parameters used in the search request
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { autoserialize } from 'cerialize';
2+
3+
export class SearchResultSorting {
4+
5+
@autoserialize
6+
by: string;
7+
8+
@autoserialize
9+
order: string;
10+
11+
}

src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<div>
22
<div class="filters py-2">
3-
<ds-search-facet-selected-option *ngFor="let value of (selectedValues$ | async)" [selectedValue]="value" [filterConfig]="filterConfig" [selectedValues$]="selectedValues$" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-selected-option>
4-
<ng-container *ngFor="let page of (filterValues$ | async)?.payload">
3+
<ds-search-facet-selected-option *ngFor="let value of (selectedAppliedFilters$ | async)" [selectedValue]="value" [filterConfig]="filterConfig" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-selected-option>
4+
<ng-container *ngFor="let page of (facetValues$ | async)">
55
<div [@facetLoad]="animationState">
6-
<ds-search-facet-option *ngFor="let value of page.page; trackBy: trackUpdate" [filterConfig]="filterConfig" [filterValue]="value" [selectedValues$]="selectedValues$" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-option>
6+
<ds-search-facet-option *ngFor="let value of page.page; trackBy: trackUpdate" [filterConfig]="filterConfig" [filterValue]="value" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-option>
77
</div>
88
</ng-container>
99
<div class="clearfix toggle-more-filters">

src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<div>
22
<div class="filters py-2">
3-
<ds-search-facet-selected-option *ngFor="let value of (selectedValues$ | async)" [selectedValue]="value" [filterConfig]="filterConfig" [selectedValues$]="selectedValues$" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-selected-option>
4-
<ng-container *ngFor="let page of (filterValues$ | async)?.payload">
3+
<ds-search-facet-selected-option *ngFor="let value of (selectedAppliedFilters$ | async)" [selectedValue]="value" [filterConfig]="filterConfig" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-selected-option>
4+
<ng-container *ngFor="let page of (facetValues$ | async)">
55
<div [@facetLoad]="animationState">
6-
<ds-search-facet-option *ngFor="let value of page.page; trackBy: trackUpdate" [filterConfig]="filterConfig" [filterValue]="value" [selectedValues$]="selectedValues$" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-option>
6+
<ds-search-facet-option *ngFor="let value of page.page; trackBy: trackUpdate" [filterConfig]="filterConfig" [filterValue]="value" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-option>
77
</div>
88
</ng-container>
99
<div class="clearfix toggle-more-filters">

0 commit comments

Comments
 (0)