Skip to content

Commit 1eadb41

Browse files
111731: Made the SearchLabelComponent use the AppliedFilter to display the label instead of value
1 parent 61e9466 commit 1eadb41

7 files changed

Lines changed: 138 additions & 139 deletions

File tree

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

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-
77
import { SearchFilter } from '../../../shared/search/models/search-filter.model';
88
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
99
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
10-
import { map } from 'rxjs/operators';
10+
import { map, take } from 'rxjs/operators';
1111
import { RemoteData } from '../../data/remote-data';
1212
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
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 { Params } from '@angular/router';
17+
import { addOperatorToFilterValue } from '../../../shared/search/search.utils';
18+
import { AppliedFilter } from '../../../shared/search/models/applied-filter.model';
1619

1720
describe('SearchConfigurationService', () => {
1821
let service: SearchConfigurationService;
@@ -44,7 +47,7 @@ describe('SearchConfigurationService', () => {
4447
const paginationService = new PaginationServiceStub();
4548

4649

47-
const activatedRoute: any = new ActivatedRouteStub();
50+
const activatedRoute: ActivatedRouteStub = new ActivatedRouteStub();
4851
const linkService: any = {};
4952
const requestService: any = getMockRequestService();
5053
const halService: any = {
@@ -70,7 +73,7 @@ describe('SearchConfigurationService', () => {
7073
}
7174
};
7275
beforeEach(() => {
73-
service = new SearchConfigurationService(routeService, paginationService as any, activatedRoute, linkService, halService, requestService, rdb);
76+
service = new SearchConfigurationService(routeService, paginationService as any, activatedRoute as any, linkService, halService, requestService, rdb);
7477
});
7578

7679
describe('when the scope is called', () => {
@@ -279,4 +282,62 @@ describe('SearchConfigurationService', () => {
279282
expect((service as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: requestUrl }), true);
280283
});
281284
});
285+
286+
describe('getParamsWithoutAppliedFilter', () => {
287+
let appliedFilter: AppliedFilter;
288+
289+
beforeEach(() => {
290+
appliedFilter = Object.assign(new AppliedFilter(), {
291+
filter: 'author',
292+
operator: 'authority',
293+
value: '1282121b-5394-4689-ab93-78d537764052',
294+
label: 'Odinson, Thor',
295+
});
296+
activatedRoute.testParams = {
297+
'query': '',
298+
'spc.page': '1',
299+
'f.author': addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator),
300+
'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'),
301+
'f.dateIssued.max': '2000',
302+
};
303+
});
304+
305+
it('should return all params except the applied filter', (done: DoneFn) => {
306+
service.getParamsWithoutAppliedFilter(appliedFilter.filter, appliedFilter.value, appliedFilter.operator).pipe(take(1)).subscribe((params: Params) => {
307+
expect(params).toEqual({
308+
'query': '',
309+
'spc.page': '1',
310+
'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'),
311+
'f.dateIssued.max': '2000',
312+
});
313+
done();
314+
});
315+
});
316+
317+
it('should return all params except the applied filter even when multiple filters of the same type are selected', (done: DoneFn) => {
318+
activatedRoute.testParams['f.author'] = [addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator), addOperatorToFilterValue('71b91a28-c280-4352-a199-bd7fc3312501', 'authority')];
319+
service.getParamsWithoutAppliedFilter(appliedFilter.filter, appliedFilter.value, appliedFilter.operator).pipe(take(1)).subscribe((params: Params) => {
320+
expect(params).toEqual({
321+
'query': '',
322+
'spc.page': '1',
323+
'f.author': [addOperatorToFilterValue('71b91a28-c280-4352-a199-bd7fc3312501', 'authority')],
324+
'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'),
325+
'f.dateIssued.max': '2000',
326+
});
327+
done();
328+
});
329+
});
330+
331+
it('should be able to remove AppliedFilter without operator', (done: DoneFn) => {
332+
service.getParamsWithoutAppliedFilter('dateIssued.max', '2000').pipe(take(1)).subscribe((params: Params) => {
333+
expect(params).toEqual({
334+
'query': '',
335+
'spc.page': '1',
336+
'f.author': addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator),
337+
'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'),
338+
});
339+
done();
340+
});
341+
});
342+
});
282343
});

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

Lines changed: 18 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,23 @@ export class SearchConfigurationService implements OnDestroy {
525526
);
526527
}
527528

529+
getParamsWithoutAppliedFilter(filterName: string, value: string, operator?: string): Observable<Params> {
530+
return this.route.queryParams.pipe(
531+
map((params: Params) => {
532+
const newParams: Params = Object.assign({}, params);
533+
const queryParamValues: string | string[] = newParams[`f.${filterName}`];
534+
const excludeValue = hasValue(operator) ? addOperatorToFilterValue(value, operator) : value;
535+
536+
if (queryParamValues === excludeValue) {
537+
delete newParams[`f.${filterName}`];
538+
} else if (queryParamValues?.includes(excludeValue)) {
539+
newParams[`f.${filterName}`] = (queryParamValues as string[])
540+
.filter((paramValue: string) => paramValue !== excludeValue);
541+
}
542+
return newParams;
543+
}),
544+
);
545+
}
528546

529547
/**
530548
* @returns {Observable<Params>} Emits the current view mode as a partial SearchOptions object
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<a class="badge badge-primary mr-1 mb-1 text-capitalize"
22
[routerLink]="searchLink"
3-
[queryParams]="(removeParameters | async)" queryParamsHandling="merge">
4-
{{('search.filters.applied.' + key) | translate}}: {{'search.filters.' + filterName + '.' + value | translate: {default: normalizeFilterValue(value)} }}
3+
[queryParams]="(removeParameters | async)">
4+
{{('search.filters.applied.f.' + appliedFilter.filter) | translate}}: {{'search.filters.' + appliedFilter.filter + '.' + appliedFilter.label | translate: {default: appliedFilter.label} }}
55
<span> ×</span>
66
</a>
Lines changed: 42 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,71 @@
11
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2-
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
32
import { TranslateModule } from '@ngx-translate/core';
4-
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
5-
import { FormsModule } from '@angular/forms';
6-
import { Observable, of as observableOf } from 'rxjs';
7-
import { Params, Router } from '@angular/router';
3+
import { Params, ActivatedRoute } from '@angular/router';
84
import { SearchLabelComponent } from './search-label.component';
9-
import { ObjectKeysPipe } from '../../../utils/object-keys-pipe';
10-
import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component';
115
import { SearchServiceStub } from '../../../testing/search-service.stub';
12-
import { SearchConfigurationServiceStub } from '../../../testing/search-configuration-service.stub';
136
import { SearchService } from '../../../../core/shared/search/search.service';
14-
import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model';
15-
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
16-
import { PaginationService } from '../../../../core/pagination/pagination.service';
7+
import { ActivatedRouteStub } from '../../../testing/active-router.stub';
8+
import { AppliedFilter } from '../../models/applied-filter.model';
9+
import { addOperatorToFilterValue } from '../../search.utils';
10+
import { RouterTestingModule } from '@angular/router/testing';
1711
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
18-
import { PaginationServiceStub } from '../../../testing/pagination-service.stub';
19-
import { FindListOptions } from '../../../../core/data/find-list-options.model';
12+
import { SearchConfigurationServiceStub } from '../../../testing/search-configuration-service.stub';
2013

2114
describe('SearchLabelComponent', () => {
2215
let comp: SearchLabelComponent;
2316
let fixture: ComponentFixture<SearchLabelComponent>;
2417

18+
let route: ActivatedRouteStub;
19+
let searchConfigurationService: SearchConfigurationServiceStub;
20+
2521
const searchLink = '/search';
26-
let searchService;
22+
let appliedFilter: AppliedFilter;
23+
let initialRouteParams: Params;
2724

28-
const key1 = 'author';
29-
const key2 = 'subject';
30-
const value1 = 'Test, Author';
31-
const normValue1 = 'Test, Author';
32-
const value2 = 'TestSubject';
33-
const value3 = 'Test, Authority,authority';
34-
const normValue3 = 'Test, Authority';
35-
const filter1 = [key1, value1];
36-
const filter2 = [key2, value2];
37-
const mockFilters = [
38-
filter1,
39-
filter2
40-
];
25+
function init(): void {
26+
appliedFilter = Object.assign(new AppliedFilter(), {
27+
filter: 'author',
28+
operator: 'authority',
29+
value: '1282121b-5394-4689-ab93-78d537764052',
30+
label: 'Odinson, Thor',
31+
});
32+
initialRouteParams = {
33+
'query': '',
34+
'spc.page': '1',
35+
'f.author': addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator),
36+
'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'),
37+
};
38+
}
4139

42-
const pagination = Object.assign(new PaginationComponentOptions(), { id: 'page-id', currentPage: 1, pageSize: 20 });
43-
const paginationService = new PaginationServiceStub(pagination);
40+
beforeEach(waitForAsync(async () => {
41+
init();
42+
route = new ActivatedRouteStub(initialRouteParams);
43+
searchConfigurationService = new SearchConfigurationServiceStub();
4444

45-
beforeEach(waitForAsync(() => {
46-
TestBed.configureTestingModule({
47-
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
48-
declarations: [SearchLabelComponent, ObjectKeysPipe],
45+
await TestBed.configureTestingModule({
46+
imports: [
47+
RouterTestingModule,
48+
TranslateModule.forRoot(),
49+
],
50+
declarations: [
51+
SearchLabelComponent,
52+
],
4953
providers: [
54+
{ provide: SearchConfigurationService, useValue: searchConfigurationService },
5055
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
51-
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
52-
{ provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() },
53-
{ provide: PaginationService, useValue: paginationService },
54-
{ provide: Router, useValue: {} }
55-
// { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} }
56+
{ provide: ActivatedRoute, useValue: route },
5657
],
57-
schemas: [NO_ERRORS_SCHEMA]
58-
}).overrideComponent(SearchLabelComponent, {
59-
set: { changeDetection: ChangeDetectionStrategy.Default }
6058
}).compileComponents();
6159
}));
6260

6361
beforeEach(() => {
6462
fixture = TestBed.createComponent(SearchLabelComponent);
6563
comp = fixture.componentInstance;
66-
searchService = (comp as any).searchService;
67-
comp.key = key1;
68-
comp.value = value1;
69-
(comp as any).appliedFilters = observableOf(mockFilters);
64+
comp.appliedFilter = appliedFilter;
7065
fixture.detectChanges();
7166
});
7267

73-
describe('when getRemoveParams is called', () => {
74-
let obs: Observable<Params>;
75-
76-
beforeEach(() => {
77-
obs = comp.getRemoveParams();
78-
});
79-
80-
it('should return all params but the provided filter', () => {
81-
obs.subscribe((params) => {
82-
// Should contain only filter2 and page: length == 2
83-
expect(Object.keys(params).length).toBe(2);
84-
});
85-
});
86-
});
87-
88-
describe('when normalizeFilterValue is called', () => {
89-
it('should return properly filter value', () => {
90-
let result: string;
91-
92-
result = comp.normalizeFilterValue(value1);
93-
expect(result).toBe(normValue1);
94-
95-
result = comp.normalizeFilterValue(value3);
96-
expect(result).toBe(normValue3);
97-
});
68+
it('should create', () => {
69+
expect(comp).toBeTruthy();
9870
});
9971
});
Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { Component, Input, OnInit } from '@angular/core';
22
import { Observable } from 'rxjs';
33
import { Params, Router } from '@angular/router';
4-
import { map } from 'rxjs/operators';
5-
import { hasValue, isNotEmpty } from '../../../empty.util';
64
import { SearchService } from '../../../../core/shared/search/search.service';
75
import { currentPath } from '../../../utils/route.utils';
8-
import { PaginationService } from '../../../../core/pagination/pagination.service';
6+
import { AppliedFilter } from '../../models/applied-filter.model';
97
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
108

119
@Component({
@@ -17,50 +15,24 @@ import { SearchConfigurationService } from '../../../../core/shared/search/searc
1715
* Component that represents the label containing the currently active filters
1816
*/
1917
export class SearchLabelComponent implements OnInit {
20-
@Input() key: string;
21-
@Input() value: string;
2218
@Input() inPlaceSearch: boolean;
23-
@Input() appliedFilters: Observable<Params>;
19+
@Input() appliedFilter: AppliedFilter;
2420
searchLink: string;
2521
removeParameters: Observable<Params>;
2622

27-
/**
28-
* The name of the filter without the f. prefix
29-
*/
30-
filterName: string;
31-
3223
/**
3324
* Initialize the instance variable
3425
*/
3526
constructor(
36-
private searchService: SearchService,
37-
private paginationService: PaginationService,
38-
private searchConfigurationService: SearchConfigurationService,
39-
private router: Router) {
27+
protected searchConfigurationService: SearchConfigurationService,
28+
protected searchService: SearchService,
29+
protected router: Router,
30+
) {
4031
}
4132

4233
ngOnInit(): void {
4334
this.searchLink = this.getSearchLink();
44-
this.removeParameters = this.getRemoveParams();
45-
this.filterName = this.getFilterName();
46-
}
47-
48-
/**
49-
* Calculates the parameters that should change if a given value for the given filter would be removed from the active filters
50-
* @returns {Observable<Params>} The changed filter parameters
51-
*/
52-
getRemoveParams(): Observable<Params> {
53-
return this.appliedFilters.pipe(
54-
map((filters) => {
55-
const field: string = Object.keys(filters).find((f) => f === this.key);
56-
const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== this.value) : null;
57-
const page = this.paginationService.getPageParam(this.searchConfigurationService.paginationID);
58-
return {
59-
[field]: isNotEmpty(newValues) ? newValues : null,
60-
[page]: 1
61-
};
62-
})
63-
);
35+
this.removeParameters = this.searchConfigurationService.getParamsWithoutAppliedFilter(this.appliedFilter.filter, this.appliedFilter.value, this.appliedFilter.operator);
6436
}
6537

6638
/**
@@ -73,20 +45,4 @@ export class SearchLabelComponent implements OnInit {
7345
return this.searchService.getSearchLink();
7446
}
7547

76-
/**
77-
* TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved
78-
* Strips authority operator from filter value
79-
* e.g. 'test ,authority' => 'test'
80-
*
81-
* @param value
82-
*/
83-
normalizeFilterValue(value: string) {
84-
// const pattern = /,[^,]*$/g;
85-
const pattern = /,authority*$/g;
86-
return value.replace(pattern, '');
87-
}
88-
89-
private getFilterName(): string {
90-
return this.key.startsWith('f.') ? this.key.substring(2) : this.key;
91-
}
9248
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="labels">
22
<ng-container *ngFor="let appliedFilters of (appliedFilters | keyvalue)">
3-
<ng-container *ngFor="let appliedFilter of appliedFilters.value">{{appliedFilter.label}}</ng-container>
3+
<ds-search-label *ngFor="let appliedFilter of appliedFilters.value" [inPlaceSearch]="inPlaceSearch" [appliedFilter]="appliedFilter"></ds-search-label>
44
</ng-container>
55
</div>

0 commit comments

Comments
 (0)