mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
111731: Made the code completely dependent on the SearchService#appliedFilters$ instead of relying on the route query parameters
This commit is contained in:
@@ -153,17 +153,6 @@ describe('SearchFilterService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the getSelectedValuesForFilter method is called', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(routeServiceStub, 'getQueryParameterValues');
|
||||
service.getSelectedValuesForFilter(mockFilterConfig);
|
||||
});
|
||||
|
||||
it('should call getQueryParameterValues on the route service with the same parameters', () => {
|
||||
expect(routeServiceStub.getQueryParameterValues).toHaveBeenCalledWith(mockFilterConfig.paramName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the getCurrentScope method is called', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(routeServiceStub, 'getQueryParameterValue');
|
||||
|
@@ -136,27 +136,6 @@ export class SearchFilterService {
|
||||
return this.routeService.getQueryParameterValue('view');
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the active filter values set for a given filter
|
||||
* @param {SearchFilterConfig} filterConfig The configuration for which the filters are active
|
||||
* @returns {Observable<string[]>} Emits the active filters for the given filter configuration
|
||||
*/
|
||||
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
|
||||
const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName);
|
||||
const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').pipe(
|
||||
map((params: Params) => [].concat(...Object.values(params))),
|
||||
);
|
||||
return observableCombineLatest(values$, prefixValues$).pipe(
|
||||
map(([values, prefixValues]) => {
|
||||
if (isNotEmpty(values)) {
|
||||
return values;
|
||||
}
|
||||
return prefixValues;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the found facet value suggestions for a given query
|
||||
* Transforms the found values into display values
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import { combineLatest as observableCombineLatest, Observable, BehaviorSubject } from 'rxjs';
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, switchMap, take, tap, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { ResponseParsingService } from '../../data/parsing.service';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
@@ -61,7 +61,7 @@ class SearchDataService extends BaseDataService<any> {
|
||||
* Service that performs all general actions that have to do with the search page
|
||||
*/
|
||||
@Injectable()
|
||||
export class SearchService implements OnDestroy {
|
||||
export class SearchService {
|
||||
|
||||
/**
|
||||
* Endpoint link path for retrieving general search results
|
||||
@@ -78,11 +78,6 @@ export class SearchService implements OnDestroy {
|
||||
*/
|
||||
private request: GenericConstructor<RestRequest> = GetRequest;
|
||||
|
||||
/**
|
||||
* Subscription to unsubscribe from
|
||||
*/
|
||||
private sub;
|
||||
|
||||
/**
|
||||
* Instance of SearchDataService to forward data service methods to
|
||||
*/
|
||||
@@ -103,6 +98,18 @@ export class SearchService implements OnDestroy {
|
||||
this.searchDataService = new SearchDataService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently {@link AppliedFilter}s for the given filter.
|
||||
*
|
||||
* @param filterName The name of the filter
|
||||
*/
|
||||
getSelectedValuesForFilter(filterName: string): Observable<AppliedFilter[]> {
|
||||
return this.appliedFilters$.pipe(
|
||||
map((appliedFilters: AppliedFilter[]) => appliedFilters.filter((appliedFilter: AppliedFilter) => appliedFilter.filter === filterName)),
|
||||
distinctUntilChanged((previous: AppliedFilter[], next: AppliedFilter[]) => JSON.stringify(previous) === JSON.stringify(next)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set service options
|
||||
* @param {GenericConstructor<ResponseParsingService>} parser The ResponseParsingService constructor name
|
||||
@@ -176,26 +183,11 @@ export class SearchService implements OnDestroy {
|
||||
return this.directlyAttachIndexableObjects(sqr$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to retrieve request entries for search results from the server
|
||||
* @param {PaginatedSearchOptions} searchOptions The configuration necessary to perform this search
|
||||
* @returns {Observable<RemoteData<SearchObjects<T>>>} Emits a paginated list with all search results found
|
||||
*/
|
||||
searchEntries<T extends DSpaceObject>(searchOptions?: PaginatedSearchOptions): Observable<RemoteData<SearchObjects<T>>> {
|
||||
const href$ = this.getEndpoint(searchOptions);
|
||||
|
||||
const sqr$ = href$.pipe(
|
||||
switchMap((href: string) => this.rdb.buildFromHref<SearchObjects<T>>(href))
|
||||
);
|
||||
|
||||
return this.directlyAttachIndexableObjects(sqr$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to directly attach the indexableObjects to search results, instead of using RemoteData.
|
||||
* For compatibility with the way the search was written originally
|
||||
*
|
||||
* @param sqr$: a SearchObjects RemotaData Observable without its
|
||||
* @param sqr$ A {@link SearchObjects} {@link RemoteData} Observable without its
|
||||
* indexableObjects attached
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
@@ -384,12 +376,4 @@ export class SearchService implements OnDestroy {
|
||||
return '/search';
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the subscription
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (this.sub !== undefined) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Router, Params } from '@angular/router';
|
||||
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, filter, map, mergeMap, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
|
||||
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
||||
import { hasNoValue, hasValue } from '../../../../empty.util';
|
||||
@@ -17,7 +17,6 @@ import { InputSuggestion } from '../../../../input-suggestions/input-suggestions
|
||||
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 { stripOperatorFromFilterValue } from '../../../search.utils';
|
||||
import { FacetValues } from '../../../models/facet-values.model';
|
||||
import { AppliedFilter } from '../../../models/applied-filter.model';
|
||||
|
||||
@@ -103,14 +102,9 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
this.searchOptions$ = this.searchConfigService.searchOptions;
|
||||
this.subs.push(
|
||||
this.searchOptions$.subscribe(() => this.updateFilterValueList()),
|
||||
this.refreshFilters.asObservable().pipe(
|
||||
filter((toRefresh: boolean) => toRefresh),
|
||||
// NOTE This is a workaround, otherwise retrieving filter values returns tha old cached response
|
||||
debounceTime((100)),
|
||||
mergeMap(() => this.retrieveFilterValues(false))
|
||||
).subscribe()
|
||||
this.retrieveFilterValues().subscribe(),
|
||||
);
|
||||
this.retrieveFilterValues().subscribe();
|
||||
this.selectedAppliedFilters$ = this.searchService.getSelectedValuesForFilter(this.filterConfig.name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,9 +211,13 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
protected retrieveFilterValues(useCachedVersionIfAvailable = true): Observable<FacetValues[]> {
|
||||
/**
|
||||
* Retrieves all the filter value suggestion pages that need to be displayed in the facet and combines it into one
|
||||
* list.
|
||||
*/
|
||||
protected retrieveFilterValues(): Observable<FacetValues[]> {
|
||||
return observableCombineLatest([this.searchOptions$, this.currentPage]).pipe(
|
||||
switchMap(([options, page]: [SearchOptions, number]) => this.searchService.getFacetValuesFor(this.filterConfig, page, options, null, useCachedVersionIfAvailable).pipe(
|
||||
switchMap(([options, page]: [SearchOptions, number]) => this.searchService.getFacetValuesFor(this.filterConfig, page, options).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
tap((facetValues: FacetValues) => {
|
||||
this.isLastPage$.next(hasNoValue(facetValues?.next));
|
||||
@@ -242,27 +240,12 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
return filterValues;
|
||||
}),
|
||||
tap((allFacetValues: FacetValues[]) => {
|
||||
this.setAppliedFilter(allFacetValues);
|
||||
this.animationState = 'ready';
|
||||
this.facetValues$.next(allFacetValues);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setAppliedFilter(allFacetValues: FacetValues[]): void {
|
||||
const allAppliedFilters: AppliedFilter[] = [].concat(...allFacetValues.map((facetValues: FacetValues) => facetValues.appliedFilters))
|
||||
.filter((appliedFilter: AppliedFilter) => hasValue(appliedFilter));
|
||||
|
||||
this.selectedAppliedFilters$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe(
|
||||
map((selectedValues: string[]) => {
|
||||
const appliedFilters: AppliedFilter[] = selectedValues.map((value: string) => {
|
||||
return allAppliedFilters.find((appliedFilter: AppliedFilter) => appliedFilter.value === stripOperatorFromFilterValue(value));
|
||||
}).filter((appliedFilter: AppliedFilter) => hasValue(appliedFilter));
|
||||
return appliedFilters;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent unnecessary rerendering
|
||||
*/
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
@@ -14,51 +14,28 @@ import { SearchConfigurationServiceStub } from '../../../testing/search-configur
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component';
|
||||
import { SequenceService } from '../../../../core/shared/sequence.service';
|
||||
import { BrowserOnlyMockPipe } from '../../../testing/browser-only-mock.pipe';
|
||||
import { SearchServiceStub } from '../../../testing/search-service.stub';
|
||||
import { SearchFilterServiceStub } from '../../../testing/search-filter-service.stub';
|
||||
|
||||
describe('SearchFilterComponent', () => {
|
||||
let comp: SearchFilterComponent;
|
||||
let fixture: ComponentFixture<SearchFilterComponent>;
|
||||
const filterName1 = 'test name';
|
||||
const filterName2 = 'test2';
|
||||
const filterName3 = 'another name3';
|
||||
const nonExistingFilter1 = 'non existing 1';
|
||||
const nonExistingFilter2 = 'non existing 2';
|
||||
|
||||
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
|
||||
name: filterName1,
|
||||
filterType: FilterType.text,
|
||||
hasFacets: false,
|
||||
isOpenByDefault: false
|
||||
});
|
||||
const mockFilterService = {
|
||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
||||
toggle: (filter) => {
|
||||
},
|
||||
collapse: (filter) => {
|
||||
},
|
||||
expand: (filter) => {
|
||||
},
|
||||
initializeFilter: (filter) => {
|
||||
},
|
||||
getSelectedValuesForFilter: (filter) => {
|
||||
return observableOf([filterName1, filterName2, filterName3]);
|
||||
},
|
||||
isFilterActive: (filter) => {
|
||||
return observableOf([filterName1, filterName2, filterName3].indexOf(filter) >= 0);
|
||||
},
|
||||
isCollapsed: (filter) => {
|
||||
return observableOf(true);
|
||||
}
|
||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
||||
|
||||
};
|
||||
let filterService;
|
||||
let searchFilterService: SearchFilterServiceStub;
|
||||
let sequenceService;
|
||||
const mockResults = observableOf(['test', 'data']);
|
||||
const searchServiceStub = {
|
||||
getFacetValuesFor: (filter) => mockResults
|
||||
};
|
||||
let searchService: SearchServiceStub;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
searchFilterService = new SearchFilterServiceStub();
|
||||
searchService = new SearchServiceStub();
|
||||
sequenceService = jasmine.createSpyObj('sequenceService', { next: 17 });
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
@@ -68,26 +45,23 @@ describe('SearchFilterComponent', () => {
|
||||
BrowserOnlyMockPipe,
|
||||
],
|
||||
providers: [
|
||||
{ provide: SearchService, useValue: searchServiceStub },
|
||||
{
|
||||
provide: SearchFilterService,
|
||||
useValue: mockFilterService
|
||||
},
|
||||
{ provide: SearchService, useValue: searchService },
|
||||
{ provide: SearchFilterService, useValue: searchFilterService },
|
||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||
{ provide: SequenceService, useValue: sequenceService },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).overrideComponent(SearchFilterComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockResults);
|
||||
fixture = TestBed.createComponent(SearchFilterComponent);
|
||||
comp = fixture.componentInstance; // SearchPageComponent test instance
|
||||
comp.filter = mockFilterConfig;
|
||||
fixture.detectChanges();
|
||||
filterService = (comp as any).filterService;
|
||||
});
|
||||
|
||||
it('should generate unique IDs', () => {
|
||||
@@ -98,54 +72,30 @@ describe('SearchFilterComponent', () => {
|
||||
|
||||
describe('when the toggle method is triggered', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(filterService, 'toggle');
|
||||
spyOn(searchFilterService, 'toggle');
|
||||
comp.toggle();
|
||||
});
|
||||
|
||||
it('should call toggle with the correct filter configuration name', () => {
|
||||
expect(filterService.toggle).toHaveBeenCalledWith(mockFilterConfig.name);
|
||||
expect(searchFilterService.toggle).toHaveBeenCalledWith(mockFilterConfig.name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the initializeFilter method is triggered', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(filterService, 'initializeFilter');
|
||||
spyOn(searchFilterService, 'initializeFilter');
|
||||
comp.initializeFilter();
|
||||
});
|
||||
|
||||
it('should call initialCollapse with the correct filter configuration name', () => {
|
||||
expect(filterService.initializeFilter).toHaveBeenCalledWith(mockFilterConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getSelectedValues is called', () => {
|
||||
let valuesObservable: Observable<string[]>;
|
||||
beforeEach(() => {
|
||||
valuesObservable = (comp as any).getSelectedValues();
|
||||
});
|
||||
|
||||
it('should return an observable containing the existing filters', () => {
|
||||
const sub = valuesObservable.subscribe((values) => {
|
||||
expect(values).toContain(filterName1);
|
||||
expect(values).toContain(filterName2);
|
||||
expect(values).toContain(filterName3);
|
||||
});
|
||||
sub.unsubscribe();
|
||||
});
|
||||
|
||||
it('should return an observable that does not contain the non-existing filters', () => {
|
||||
const sub = valuesObservable.subscribe((values) => {
|
||||
expect(values).not.toContain(nonExistingFilter1);
|
||||
expect(values).not.toContain(nonExistingFilter2);
|
||||
});
|
||||
sub.unsubscribe();
|
||||
expect(searchFilterService.initializeFilter).toHaveBeenCalledWith(mockFilterConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when isCollapsed is called and the filter is collapsed', () => {
|
||||
let isActive: Observable<boolean>;
|
||||
beforeEach(() => {
|
||||
filterService.isCollapsed = () => observableOf(true);
|
||||
searchFilterService.isCollapsed = () => observableOf(true);
|
||||
isActive = (comp as any).isCollapsed();
|
||||
});
|
||||
|
||||
@@ -160,7 +110,7 @@ describe('SearchFilterComponent', () => {
|
||||
describe('when isCollapsed is called and the filter is not collapsed', () => {
|
||||
let isActive: Observable<boolean>;
|
||||
beforeEach(() => {
|
||||
filterService.isCollapsed = () => observableOf(false);
|
||||
searchFilterService.isCollapsed = () => observableOf(false);
|
||||
isActive = (comp as any).isCollapsed();
|
||||
});
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Component, Inject, Input, OnInit } from '@angular/core';
|
||||
import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, of as observableOf, combineLatest, Subscription } from 'rxjs';
|
||||
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
|
||||
import { SearchFilterConfig } from '../../models/search-filter-config.model';
|
||||
@@ -11,6 +11,10 @@ import { SearchService } from '../../../../core/shared/search/search.service';
|
||||
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component';
|
||||
import { SequenceService } from '../../../../core/shared/sequence.service';
|
||||
import { FacetValues } from '../../models/facet-values.model';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { AppliedFilter } from '../../models/applied-filter.model';
|
||||
import { SearchOptions } from '../../models/search-options.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-filter',
|
||||
@@ -22,7 +26,7 @@ import { SequenceService } from '../../../../core/shared/sequence.service';
|
||||
/**
|
||||
* Represents a part of the filter section for a single type of filter
|
||||
*/
|
||||
export class SearchFilterComponent implements OnInit {
|
||||
export class SearchFilterComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* The filter config for this component
|
||||
*/
|
||||
@@ -61,13 +65,15 @@ export class SearchFilterComponent implements OnInit {
|
||||
/**
|
||||
* Emits all currently selected values for this filter
|
||||
*/
|
||||
selectedValues$: Observable<string[]>;
|
||||
appliedFilters$: Observable<AppliedFilter[]>;
|
||||
|
||||
/**
|
||||
* Emits true when the current filter is supposed to be shown
|
||||
*/
|
||||
active$: Observable<boolean>;
|
||||
|
||||
subs: Subscription[] = [];
|
||||
|
||||
private readonly sequenceId: number;
|
||||
|
||||
constructor(
|
||||
@@ -85,15 +91,19 @@ export class SearchFilterComponent implements OnInit {
|
||||
* Else, the filter should initially be collapsed
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.selectedValues$ = this.getSelectedValues();
|
||||
this.appliedFilters$ = this.searchService.getSelectedValuesForFilter(this.filter.name);
|
||||
this.active$ = this.isActive();
|
||||
this.collapsed$ = this.isCollapsed();
|
||||
this.initializeFilter();
|
||||
this.selectedValues$.pipe(take(1)).subscribe((selectedValues) => {
|
||||
this.subs.push(this.appliedFilters$.pipe(take(1)).subscribe((selectedValues: AppliedFilter[]) => {
|
||||
if (isNotEmpty(selectedValues)) {
|
||||
this.filterService.expand(this.filter.name);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.forEach((sub: Subscription) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,13 +128,6 @@ export class SearchFilterComponent implements OnInit {
|
||||
this.filterService.initializeFilter(this.filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<string[]>} Emits a list of all values that are currently active for this filter
|
||||
*/
|
||||
private getSelectedValues(): Observable<string[]> {
|
||||
return this.filterService.getSelectedValuesForFilter(this.filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to change this.collapsed to false when the slide animation ends and is sliding open
|
||||
* @param event The animation event
|
||||
@@ -164,20 +167,20 @@ export class SearchFilterComponent implements OnInit {
|
||||
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
|
||||
*/
|
||||
private isActive(): Observable<boolean> {
|
||||
return this.selectedValues$.pipe(
|
||||
switchMap((isActive) => {
|
||||
if (isNotEmpty(isActive)) {
|
||||
return combineLatest([
|
||||
this.appliedFilters$,
|
||||
this.searchConfigService.searchOptions,
|
||||
]).pipe(
|
||||
switchMap(([selectedValues, options]: [AppliedFilter[], SearchOptions]) => {
|
||||
if (isNotEmpty(selectedValues)) {
|
||||
return observableOf(true);
|
||||
} else {
|
||||
return this.searchConfigService.searchOptions.pipe(
|
||||
switchMap((options) => {
|
||||
return this.searchService.getFacetValuesFor(this.filter, 1, options).pipe(
|
||||
filter((RD) => !RD.isLoading),
|
||||
map((valuesRD) => {
|
||||
return valuesRD.payload?.totalElements > 0;
|
||||
}),);
|
||||
}
|
||||
));
|
||||
return this.searchService.getFacetValuesFor(this.filter, 1, options).pipe(
|
||||
filter((RD: RemoteData<FacetValues>) => !RD.isLoading),
|
||||
map((valuesRD: RemoteData<FacetValues>) => {
|
||||
return valuesRD.payload?.totalElements > 0;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}),
|
||||
startWith(true));
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { SearchHierarchyFilterComponent } from './search-hierarchy-filter.component';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { DebugElement, EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { DebugElement, EventEmitter, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service';
|
||||
import { of as observableOf, BehaviorSubject } from 'rxjs';
|
||||
@@ -26,6 +26,7 @@ import { SearchConfigurationServiceStub } from '../../../../testing/search-confi
|
||||
import { VocabularyEntryDetail } from '../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||
import { AppliedFilter } from '../../../models/applied-filter.model';
|
||||
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||
import { SearchServiceStub } from '../../../../testing/search-service.stub';
|
||||
|
||||
describe('SearchHierarchyFilterComponent', () => {
|
||||
|
||||
@@ -38,10 +39,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
||||
select: new EventEmitter<VocabularyEntryDetail>(),
|
||||
};
|
||||
|
||||
const searchService = {
|
||||
getSearchLink: () => testSearchLink,
|
||||
getFacetValuesFor: () => observableOf([]),
|
||||
};
|
||||
let searchService: SearchServiceStub;
|
||||
const searchFilterService = {
|
||||
getPage: () => observableOf(0),
|
||||
};
|
||||
@@ -56,6 +54,8 @@ describe('SearchHierarchyFilterComponent', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
searchService = new SearchServiceStub();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -77,7 +77,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
||||
{ provide: FILTER_CONFIG, useValue: Object.assign(new SearchFilterConfig(), { name: testSearchFilter }) },
|
||||
{ provide: REFRESH_FILTER, useValue: new BehaviorSubject<boolean>(false) },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, of as observableOf , Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest } from 'rxjs';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||
import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core';
|
||||
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
||||
import { FilterType } from '../../../models/filter-type.model';
|
||||
import { renderFacetFor } from '../search-filter-type-decorator';
|
||||
@@ -19,8 +19,6 @@ import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-p
|
||||
import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service';
|
||||
import { RouteService } from '../../../../../core/services/route.service';
|
||||
import { hasValue } from '../../../../empty.util';
|
||||
import { AppliedFilter } from '../../../models/applied-filter.model';
|
||||
import { FacetValues } from '../../../models/facet-values.model';
|
||||
import { yearFromString } from 'src/app/shared/date.util';
|
||||
|
||||
/**
|
||||
@@ -49,7 +47,7 @@ export const RANGE_FILTER_MAX_SUFFIX = '.max';
|
||||
* Component that represents a range facet for a specific filter configuration
|
||||
*/
|
||||
@renderFacetFor(FilterType.range)
|
||||
export class SearchRangeFilterComponent extends SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
export class SearchRangeFilterComponent extends SearchFacetFilterComponent implements OnInit {
|
||||
/**
|
||||
* Fallback minimum for the range
|
||||
*/
|
||||
@@ -65,11 +63,6 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
||||
*/
|
||||
range: [number | undefined, number | undefined];
|
||||
|
||||
/**
|
||||
* Subscription to unsubscribe from
|
||||
*/
|
||||
sub: Subscription;
|
||||
|
||||
/**
|
||||
* Whether the sider is being controlled by the keyboard.
|
||||
* Supresses any changes until the key is released.
|
||||
@@ -100,25 +93,13 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
||||
this.max = yearFromString(this.filterConfig.maxValue) || this.max;
|
||||
const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX).pipe(startWith(undefined));
|
||||
const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX).pipe(startWith(undefined));
|
||||
this.sub = observableCombineLatest([iniMin, iniMax]).pipe(
|
||||
this.subs.push(observableCombineLatest([iniMin, iniMax]).pipe(
|
||||
map(([min, max]: [string, string]) => {
|
||||
const minimum = hasValue(min) ? Number(min) : this.min;
|
||||
const maximum = hasValue(max) ? Number(max) : this.max;
|
||||
return [minimum, maximum];
|
||||
})
|
||||
).subscribe((minmax: [number, number]) => this.range = minmax);
|
||||
}
|
||||
|
||||
setAppliedFilter(allFacetValues: FacetValues[]): void {
|
||||
const appliedFilters: AppliedFilter[] = [].concat(...allFacetValues.map((facetValues: FacetValues) => facetValues.appliedFilters))
|
||||
.filter((appliedFilter: AppliedFilter) => hasValue(appliedFilter))
|
||||
.filter((appliedFilter: AppliedFilter) => appliedFilter.filter === this.filterConfig.name)
|
||||
// TODO this should ideally be fixed in the backend
|
||||
.map((appliedFilter: AppliedFilter) => Object.assign({}, appliedFilter, {
|
||||
operator: 'range',
|
||||
}));
|
||||
|
||||
this.selectedAppliedFilters$ = observableOf(appliedFilters);
|
||||
).subscribe((minmax: [number, number]) => this.range = minmax));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,13 +140,4 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
||||
return isPlatformBrowser(this.platformId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from all subscriptions
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
if (hasValue(this.sub)) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@ import { SearchFilterConfig } from '../search/models/search-filter-config.model'
|
||||
import { Params } from '@angular/router';
|
||||
|
||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
||||
/**
|
||||
* Stub class of {@link SearchFilterService}
|
||||
*/
|
||||
export class SearchFilterServiceStub {
|
||||
|
||||
isFilterActiveWithValue(_paramName: string, _filterValue: string): Observable<boolean> {
|
||||
@@ -39,10 +42,6 @@ export class SearchFilterServiceStub {
|
||||
return observableOf(undefined);
|
||||
}
|
||||
|
||||
getSelectedValuesForFilter(_filterConfig: SearchFilterConfig): Observable<string[]> {
|
||||
return observableOf([]);
|
||||
}
|
||||
|
||||
isCollapsed(_filterName: string): Observable<boolean> {
|
||||
return observableOf(true);
|
||||
}
|
||||
|
@@ -2,7 +2,11 @@ import {of as observableOf, Observable , BehaviorSubject } from 'rxjs';
|
||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||
import { SearchFilterConfig } from '../search/models/search-filter-config.model';
|
||||
import { PaginatedSearchOptions } from '../search/models/paginated-search-options.model';
|
||||
import { AppliedFilter } from '../search/models/applied-filter.model';
|
||||
|
||||
/**
|
||||
* Stub class of {@link SearchService}
|
||||
*/
|
||||
export class SearchServiceStub {
|
||||
|
||||
private _viewMode: ViewMode;
|
||||
@@ -14,6 +18,10 @@ export class SearchServiceStub {
|
||||
this.setViewMode(ViewMode.ListElement);
|
||||
}
|
||||
|
||||
getSelectedValuesForFilter(_filterName: string): Observable<AppliedFilter[]> {
|
||||
return observableOf([]);
|
||||
}
|
||||
|
||||
getViewMode(): Observable<ViewMode> {
|
||||
return this.viewMode;
|
||||
}
|
||||
|
Reference in New Issue
Block a user