111731: Moved findSuggestions logic to SearchFilterService in order to reuse it in the advanced search component

This commit is contained in:
Alexandre Vryghem
2024-04-12 13:46:42 +02:00
parent b9fa479519
commit 89eb314fe7
7 changed files with 95 additions and 100 deletions

View File

@@ -1,3 +1,4 @@
import { waitForAsync, TestBed } from '@angular/core/testing';
import { SearchFilterService } from './search-filter.service';
import { Store } from '@ngrx/store';
import {
@@ -12,9 +13,13 @@ import {
import { SearchFiltersState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
import { FilterType } from '../../../shared/search/models/filter-type.model';
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
import { of as observableOf } from 'rxjs';
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
import { SearchServiceStub } from '../../../shared/testing/search-service.stub';
import { SearchService } from './search.service';
import { RouteService } from '../../services/route.service';
import { routeServiceStub } from '../../../shared/testing/route-service.stub';
import { SearchOptions } from '../../../shared/search/models/search-options.model';
describe('SearchFilterService', () => {
let service: SearchFilterService;
@@ -28,7 +33,8 @@ describe('SearchFilterService', () => {
});
const value1 = 'random value';
// const value2 = 'another value';
let searchService: SearchServiceStub;
const store: Store<SearchFiltersState> = jasmine.createSpyObj('store', {
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
dispatch: {},
@@ -36,36 +42,20 @@ describe('SearchFilterService', () => {
select: observableOf(true)
});
const routeServiceStub: any = {
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
hasQueryParamWithValue: (param: string, value: string) => {
},
hasQueryParam: (param: string) => {
},
removeQueryParameterValue: (param: string, value: string) => {
},
addQueryParameterValue: (param: string, value: string) => {
},
getQueryParameterValue: (param: string) => {
},
getQueryParameterValues: (param: string) => {
return observableOf({});
},
getQueryParamsWithPrefix: (param: string) => {
return observableOf({});
},
getRouteParameterValue: (param: string) => {
}
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
};
const activatedRoute: any = new ActivatedRouteStub();
const searchServiceStub: any = {
uiSearchRoute: '/search'
};
beforeEach(waitForAsync(() => {
searchService = new SearchServiceStub();
beforeEach(() => {
service = new SearchFilterService(store, routeServiceStub);
});
TestBed.configureTestingModule({
providers: [
SearchFilterService,
{ provide: SearchService, useValue: searchService },
{ provide: Store, useValue: store },
{ provide: RouteService, useValue: routeServiceStub },
],
});
service = TestBed.inject(SearchFilterService);
}));
describe('when the initializeFilter method is triggered', () => {
beforeEach(() => {
@@ -269,4 +259,18 @@ describe('SearchFilterService', () => {
});
});
describe('when findSuggestions is called with query \'test\'', () => {
const query = 'test';
const searchOptions = new SearchOptions({});
beforeEach(() => {
spyOn(searchService, 'getFacetValuesFor').and.returnValue(observableOf());
service.findSuggestions(mockFilterConfig, searchOptions, query);
});
it('should call getFacetValuesFor on the component\'s SearchService with the right query', () => {
expect(searchService.getFacetValuesFor).toHaveBeenCalledWith(mockFilterConfig, 1, searchOptions, query);
});
});
});

View File

@@ -1,4 +1,4 @@
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { Injectable, InjectionToken, EventEmitter } from '@angular/core';
import {
@@ -22,6 +22,15 @@ import { RouteService } from '../../services/route.service';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { Params } from '@angular/router';
import { AppliedFilter } from '../../../shared/search/models/applied-filter.model';
import { SearchOptions } from '../../../shared/search/models/search-options.model';
import { getFirstSucceededRemoteData } from '../operators';
import { FacetValue } from '../../../shared/search/models/facet-value.model';
import { PaginatedList } from '../../data/paginated-list.model';
import { RemoteData } from '../../data/remote-data';
import { stripOperatorFromFilterValue, getFacetValueForType } from '../../../shared/search/search.utils';
import { EmphasizePipe } from '../../../shared/utils/emphasize.pipe';
import { SearchService } from './search.service';
import { InputSuggestion } from '../../../shared/input-suggestions/input-suggestions.model';
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
@@ -36,8 +45,11 @@ export const CHANGE_APPLIED_FILTERS: InjectionToken<EventEmitter<AppliedFilter[]
@Injectable()
export class SearchFilterService {
constructor(private store: Store<SearchFiltersState>,
private routeService: RouteService) {
constructor(
protected searchService: SearchService,
protected store: Store<SearchFiltersState>,
protected routeService: RouteService,
) {
}
/**
@@ -146,6 +158,42 @@ export class SearchFilterService {
);
}
/**
* Updates the found facet value suggestions for a given query
* Transforms the found values into display values
*
* @param searchFilterConfig The search filter config
* @param searchOptions The search options
* @param query The query for which is being searched
*/
findSuggestions(searchFilterConfig: SearchFilterConfig, searchOptions: SearchOptions, query: string): Observable<InputSuggestion[]> {
if (isNotEmpty(query)) {
return this.searchService.getFacetValuesFor(searchFilterConfig, 1, searchOptions, query.toLowerCase()).pipe(
getFirstSucceededRemoteData(),
map((rd: RemoteData<PaginatedList<FacetValue>>) => rd.payload.page.map((facet) => {
return {
displayValue: this.getDisplayValue(facet, query),
query: getFacetValueForType(facet, searchFilterConfig),
value: stripOperatorFromFilterValue(getFacetValueForType(facet, searchFilterConfig))
};
})),
);
} else {
return observableOf([]);
}
}
/**
* Transforms the facet value string, so if the query matches part of the value, it's emphasized in the value
*
* @param facet The value of the facet as returned by the server
* @param query The query that was used to search facet values
* @returns {string} The facet value with the query part emphasized
*/
getDisplayValue(facet: FacetValue, query: string): string {
return `${new EmphasizePipe().transform(facet.value, query)} (${facet.count})`;
}
/**
* Checks if the state of a given filter is currently collapsed or not
* @param {string} filterName The filtername for which the collapsed state is checked

View File

@@ -17,7 +17,7 @@
</a>
</div>
</div>
<ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)"
<ds-filter-input-suggestions [suggestions]="(filterSearchResults$ | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder' | translate"
[label]="'search.filters.filter.' + filterConfig.name + '.label' | translate"
[action]="currentUrl"

View File

@@ -215,15 +215,4 @@ describe('SearchFacetFilterComponent', () => {
expect(comp.filter).toEqual('');
});
});
describe('when findSuggestions is called with query \'test\'', () => {
const query = 'test';
beforeEach(() => {
comp.findSuggestions(query);
});
it('should call getFacetValuesFor on the component\'s SearchService with the right query', () => {
expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 1, {}, query);
});
});
});

View File

@@ -6,21 +6,18 @@ import { BehaviorSubject, combineLatest as observableCombineLatest, Observable,
import { debounceTime, distinctUntilChanged, filter, map, mergeMap, switchMap, take, tap } from 'rxjs/operators';
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
import { PaginatedList } from '../../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../../core/data/remote-data';
import { hasNoValue, hasValue, isNotEmpty } from '../../../../empty.util';
import { EmphasizePipe } from '../../../../utils/emphasize.pipe';
import { FacetValue } from '../../../models/facet-value.model';
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
import { SearchService } from '../../../../../core/shared/search/search.service';
import { FILTER_CONFIG, IN_PLACE_SEARCH, REFRESH_FILTER, SearchFilterService, CHANGE_APPLIED_FILTERS } from '../../../../../core/shared/search/search-filter.service';
import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service';
import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
import { getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
import { InputSuggestion } from '../../../../input-suggestions/input-suggestions.model';
import { SearchOptions } from '../../../models/search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component';
import { currentPath } from '../../../../utils/route.utils';
import { getFacetValueForType, stripOperatorFromFilterValue, addOperatorToFilterValue } from '../../../search.utils';
import { stripOperatorFromFilterValue, addOperatorToFilterValue } from '../../../search.utils';
import { FacetValues } from '../../../models/facet-values.model';
import { AppliedFilter } from '../../../models/applied-filter.model';
@@ -61,7 +58,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
/**
* Emits the result values for this filter found by the current filter query
*/
filterSearchResults: Observable<InputSuggestion[]> = observableOf([]);
filterSearchResults$: Observable<InputSuggestion[]> = observableOf([]);
/**
* Emits the active values for this filter
@@ -200,34 +197,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
.forEach((sub) => sub.unsubscribe());
}
/**
* Updates the found facet value suggestions for a given query
* Transforms the found values into display values
* @param data The query for which is being searched
*/
findSuggestions(data): void {
if (isNotEmpty(data)) {
this.searchOptions$.pipe(take(1)).subscribe(
(options) => {
this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase())
.pipe(
getFirstSucceededRemoteData(),
map(
(rd: RemoteData<PaginatedList<FacetValue>>) => {
return rd.payload.page.map((facet) => {
return {
displayValue: this.getDisplayValue(facet, data),
query: this.getFacetValue(facet),
value: stripOperatorFromFilterValue(this.getFacetValue(facet))
};
});
}
));
}
);
} else {
this.filterSearchResults = observableOf([]);
}
findSuggestions(query: string): void {
this.filterSearchResults$ = this.filterService.findSuggestions(this.filterConfig, this.searchOptions$.value, query);
}
/**
@@ -250,18 +221,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
});
this.filter = '';
}
this.filterSearchResults = observableOf([]);
this.filterSearchResults$ = observableOf([]);
});
}
}
/**
* Retrieve facet value
*/
protected getFacetValue(facet: FacetValue): string {
return getFacetValueForType(facet, this.filterConfig);
}
protected retrieveFilterValues(useCachedVersionIfAvailable = true): Observable<FacetValues[]> {
return observableCombineLatest([this.searchOptions$, this.currentPage]).pipe(
switchMap(([options, page]: [SearchOptions, number]) => this.searchService.getFacetValuesFor(this.filterConfig, page, options, null, useCachedVersionIfAvailable).pipe(
@@ -309,16 +273,6 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
);
}
/**
* Transforms the facet value string, so if the query matches part of the value, it's emphasized in the value
* @param {FacetValue} facet The value of the facet as returned by the server
* @param {string} query The query that was used to search facet values
* @returns {string} The facet value with the query part emphasized
*/
getDisplayValue(facet: FacetValue, query: string): string {
return new EmphasizePipe().transform(facet.value, query) + ' (' + facet.count + ')';
}
/**
* Prevent unnecessary rerendering
*/

View File

@@ -17,7 +17,7 @@
</a>
</div>
</div>
<ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)"
<ds-filter-input-suggestions [suggestions]="(filterSearchResults$ | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder' | translate"
[label]="'search.filters.filter.' + filterConfig.name + '.label' | translate"
[action]="currentUrl"

View File

@@ -17,7 +17,7 @@
</a>
</div>
</div>
<ds-filter-input-suggestions [suggestions]="(filterSearchResults | async)"
<ds-filter-input-suggestions [suggestions]="(filterSearchResults$ | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder' | translate"
[label]="'search.filters.filter.' + filterConfig.name + '.label' | translate"
[action]="currentUrl"