mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
111731: Moved findSuggestions logic to SearchFilterService in order to reuse it in the advanced search component
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
Reference in New Issue
Block a user