mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 23:13:04 +00:00
fixed configuration
This commit is contained in:
@@ -30,14 +30,5 @@
|
|||||||
| translate}}</a>
|
| translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-input-suggestions [suggestions]="(filterSearchResults | async)"
|
|
||||||
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate"
|
|
||||||
[action]="getCurrentUrl()"
|
|
||||||
[name]="filterConfig.paramName"
|
|
||||||
[(ngModel)]="filter"
|
|
||||||
(submitSuggestion)="onSubmit($event)"
|
|
||||||
(clickSuggestion)="onClick($event)"
|
|
||||||
(findSuggestions)="findSuggestions($event)"
|
|
||||||
ngDefaultControl
|
|
||||||
></ds-input-suggestions>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -21,3 +21,5 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -6,7 +6,6 @@ import {
|
|||||||
SearchFacetFilterComponent
|
SearchFacetFilterComponent
|
||||||
} from '../search-facet-filter/search-facet-filter.component';
|
} from '../search-facet-filter/search-facet-filter.component';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-boolean-filter',
|
selector: 'ds-search-boolean-filter',
|
||||||
styleUrls: ['./search-boolean-filter.component.scss'],
|
styleUrls: ['./search-boolean-filter.component.scss'],
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
|
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
|
||||||
@@ -12,12 +12,12 @@ import { SearchService } from '../../../search-service/search.service';
|
|||||||
import { SearchServiceStub } from '../../../../shared/testing/search-service-stub';
|
import { SearchServiceStub } from '../../../../shared/testing/search-service-stub';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { SearchOptions } from '../../../search-options.model';
|
|
||||||
import { RouterStub } from '../../../../shared/testing/router-stub';
|
import { RouterStub } from '../../../../shared/testing/router-stub';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { SearchFacetFilterComponent } from './search-facet-filter.component';
|
import { SearchFacetFilterComponent } from './search-facet-filter.component';
|
||||||
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
|
||||||
|
|
||||||
describe('SearchFacetFilterComponent', () => {
|
describe('SearchFacetFilterComponent', () => {
|
||||||
let comp: SearchFacetFilterComponent;
|
let comp: SearchFacetFilterComponent;
|
||||||
@@ -66,6 +66,7 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
{ provide: Router, useValue: new RouterStub() },
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
{ provide: FILTER_CONFIG, useValue: new SearchFilterConfig() },
|
{ provide: FILTER_CONFIG, useValue: new SearchFilterConfig() },
|
||||||
{ provide: RemoteDataBuildService, useValue: {aggregate: () => Observable.of({})} },
|
{ provide: RemoteDataBuildService, useValue: {aggregate: () => Observable.of({})} },
|
||||||
|
{ provide: SearchConfigurationService, useValue: {getSearchOptions: () => Observable.of({})} },
|
||||||
{
|
{
|
||||||
provide: SearchFilterService, useValue: {
|
provide: SearchFilterService, useValue: {
|
||||||
getSelectedValuesForFilter: () => Observable.of(selectedValues),
|
getSelectedValuesForFilter: () => Observable.of(selectedValues),
|
||||||
@@ -75,8 +76,7 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
incrementPage: (filterName: string) => {
|
incrementPage: (filterName: string) => {
|
||||||
},
|
},
|
||||||
resetPage: (filterName: string) => {
|
resetPage: (filterName: string) => {
|
||||||
},
|
}
|
||||||
getSearchOptions: () => Observable.of({}),
|
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import { FacetValue } from '../../../search-service/facet-value.model';
|
|||||||
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
import { SearchService } from '../../../search-service/search.service';
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
|
import { FILTER_CONFIG, SearchFilterService } from '../search-filter.service';
|
||||||
|
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-filter',
|
selector: 'ds-search-facet-filter',
|
||||||
@@ -68,6 +69,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
constructor(protected searchService: SearchService,
|
constructor(protected searchService: SearchService,
|
||||||
protected filterService: SearchFilterService,
|
protected filterService: SearchFilterService,
|
||||||
|
protected searchConfigService: SearchConfigurationService,
|
||||||
protected rdbs: RemoteDataBuildService,
|
protected rdbs: RemoteDataBuildService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig) {
|
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig) {
|
||||||
@@ -80,7 +82,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined));
|
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined));
|
||||||
this.currentPage = this.getCurrentPage().distinctUntilChanged();
|
this.currentPage = this.getCurrentPage().distinctUntilChanged();
|
||||||
this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
||||||
const searchOptions = this.filterService.getSearchOptions().distinctUntilChanged();
|
const searchOptions = this.searchConfigService.getSearchOptions().distinctUntilChanged();
|
||||||
this.subs.push(searchOptions.subscribe((options) => this.updateFilterValueList()));
|
this.subs.push(searchOptions.subscribe((options) => this.updateFilterValueList()));
|
||||||
|
|
||||||
const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => {
|
const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => {
|
||||||
@@ -240,7 +242,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
findSuggestions(data): void {
|
findSuggestions(data): void {
|
||||||
if (isNotEmpty(data)) {
|
if (isNotEmpty(data)) {
|
||||||
this.filterService.getSearchOptions().first().subscribe(
|
this.searchConfigService.getSearchOptions().first().subscribe(
|
||||||
(options) => {
|
(options) => {
|
||||||
this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase())
|
this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase())
|
||||||
.first()
|
.first()
|
||||||
|
@@ -55,7 +55,7 @@ describe('SearchFilterService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
service = new SearchFilterService(store, routeServiceStub, activatedRoute);
|
service = new SearchFilterService(store, routeServiceStub);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the initialCollapse method is triggered', () => {
|
describe('when the initialCollapse method is triggered', () => {
|
||||||
|
@@ -33,8 +33,8 @@ export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionTo
|
|||||||
export class SearchFilterService {
|
export class SearchFilterService {
|
||||||
|
|
||||||
constructor(private store: Store<SearchFiltersState>,
|
constructor(private store: Store<SearchFiltersState>,
|
||||||
private routeService: RouteService,
|
private routeService: RouteService
|
||||||
private route: ActivatedRoute) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,141 +56,6 @@ export class SearchFilterService {
|
|||||||
return this.routeService.hasQueryParam(paramName);
|
return this.routeService.hasQueryParam(paramName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<string>} Emits the current scope's identifier
|
|
||||||
*/
|
|
||||||
getCurrentScope() {
|
|
||||||
return this.routeService.getQueryParameterValue('scope');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<string>} Emits the current query string
|
|
||||||
*/
|
|
||||||
getCurrentQuery() {
|
|
||||||
return this.routeService.getQueryParameterValue('query');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<string>} Emits the current pagination settings
|
|
||||||
*/
|
|
||||||
getCurrentPagination(pagination: any = {}): Observable<PaginationComponentOptions> {
|
|
||||||
const page$ = this.routeService.getQueryParameterValue('page');
|
|
||||||
const size$ = this.routeService.getQueryParameterValue('pageSize');
|
|
||||||
return Observable.combineLatest(page$, size$, (page, size) => {
|
|
||||||
return Object.assign(new PaginationComponentOptions(), pagination, {
|
|
||||||
currentPage: page || 1,
|
|
||||||
pageSize: size || pagination.pageSize
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<string>} Emits the current sorting settings
|
|
||||||
*/
|
|
||||||
getCurrentSort(defaultSort: SortOptions): Observable<SortOptions> {
|
|
||||||
const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection');
|
|
||||||
const sortField$ = this.routeService.getQueryParameterValue('sortField');
|
|
||||||
return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => {
|
|
||||||
// Dirty fix because sometimes the observable value is null somehow
|
|
||||||
sortField = this.route.snapshot.queryParamMap.get('sortField');
|
|
||||||
|
|
||||||
const field = sortField || defaultSort.field;
|
|
||||||
const direction = SortDirection[sortDirection] || defaultSort.direction;
|
|
||||||
return new SortOptions(field, direction)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<Params>} Emits the current active filters with their values as they are sent to the backend
|
|
||||||
*/
|
|
||||||
getCurrentFilters(): Observable<Params> {
|
|
||||||
return this.routeService.getQueryParamsWithPrefix('f.').map((filterParams) => {
|
|
||||||
if (isNotEmpty(filterParams)) {
|
|
||||||
const params = {};
|
|
||||||
Object.keys(filterParams).forEach((key) => {
|
|
||||||
if (key.endsWith('.min') || key.endsWith('.max')) {
|
|
||||||
const realKey = key.slice(0, -4);
|
|
||||||
if (isEmpty(params[realKey])) {
|
|
||||||
const min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*';
|
|
||||||
const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*';
|
|
||||||
params[realKey] = ['[' + min + ' TO ' + max + ']'];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
params[key] = filterParams[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
return filterParams;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<Params>} Emits the current active filters with their values as they are displayed in the frontend URL
|
|
||||||
*/
|
|
||||||
getCurrentFrontendFilters(): Observable<Params> {
|
|
||||||
return this.routeService.getQueryParamsWithPrefix('f.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Observable<string>} Emits the current UI list view
|
|
||||||
*/
|
|
||||||
getCurrentView() {
|
|
||||||
return this.routeService.getQueryParameterValue('view');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param defaults The default values for the search options, that will be used if nothing is explicitly set
|
|
||||||
* @returns {Observable<PaginatedSearchOptions>} Emits the current paginated search options
|
|
||||||
*/
|
|
||||||
getPaginatedSearchOptions(defaults: any = {}): Observable<PaginatedSearchOptions> {
|
|
||||||
return Observable.combineLatest(
|
|
||||||
this.getCurrentPagination(defaults.pagination),
|
|
||||||
this.getCurrentSort(defaults.sort),
|
|
||||||
this.getCurrentView(),
|
|
||||||
this.getCurrentScope(),
|
|
||||||
this.getCurrentQuery(),
|
|
||||||
this.getCurrentFilters()).pipe(
|
|
||||||
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),
|
|
||||||
map(([pagination, sort, view, scope, query, filters]) => {
|
|
||||||
return Object.assign(new PaginatedSearchOptions(),
|
|
||||||
defaults,
|
|
||||||
{
|
|
||||||
pagination: pagination,
|
|
||||||
sort: sort,
|
|
||||||
view: view,
|
|
||||||
scope: scope || defaults.scope,
|
|
||||||
query: query,
|
|
||||||
filters: filters
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param defaults The default values for the search options, that will be used if nothing is explicitly set
|
|
||||||
* @returns {Observable<PaginatedSearchOptions>} Emits the current search options
|
|
||||||
*/
|
|
||||||
getSearchOptions(defaults: any = {}): Observable<SearchOptions> {
|
|
||||||
return Observable.combineLatest(
|
|
||||||
this.getCurrentView(),
|
|
||||||
this.getCurrentScope(),
|
|
||||||
this.getCurrentQuery(),
|
|
||||||
this.getCurrentFilters(),
|
|
||||||
(view, scope, query, filters) => {
|
|
||||||
return Object.assign(new SearchOptions(),
|
|
||||||
defaults,
|
|
||||||
{
|
|
||||||
view: view,
|
|
||||||
scope: scope || defaults.scope,
|
|
||||||
query: query,
|
|
||||||
filters: filters
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the active filter values set for a given filter
|
* Requests the active filter values set for a given filter
|
||||||
* @param {SearchFilterConfig} filterConfig The configuration for which the filters are active
|
* @param {SearchFilterConfig} filterConfig The configuration for which the filters are active
|
||||||
|
@@ -18,6 +18,7 @@ import { PageInfo } from '../../../../core/shared/page-info.model';
|
|||||||
import { SearchRangeFilterComponent } from './search-range-filter.component';
|
import { SearchRangeFilterComponent } from './search-range-filter.component';
|
||||||
import { RouteService } from '../../../../shared/services/route.service';
|
import { RouteService } from '../../../../shared/services/route.service';
|
||||||
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
|
||||||
|
|
||||||
describe('SearchRangeFilterComponent', () => {
|
describe('SearchRangeFilterComponent', () => {
|
||||||
let comp: SearchRangeFilterComponent;
|
let comp: SearchRangeFilterComponent;
|
||||||
@@ -72,6 +73,9 @@ describe('SearchRangeFilterComponent', () => {
|
|||||||
{ provide: FILTER_CONFIG, useValue: mockFilterConfig },
|
{ provide: FILTER_CONFIG, useValue: mockFilterConfig },
|
||||||
{ provide: RemoteDataBuildService, useValue: {aggregate: () => Observable.of({})} },
|
{ provide: RemoteDataBuildService, useValue: {aggregate: () => Observable.of({})} },
|
||||||
{ provide: RouteService, useValue: {getQueryParameterValue: () => Observable.of({})} },
|
{ provide: RouteService, useValue: {getQueryParameterValue: () => Observable.of({})} },
|
||||||
|
{ provide: SearchConfigurationService, useValue: {
|
||||||
|
getSearchOptions: () => Observable.of({}) }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: SearchFilterService, useValue: {
|
provide: SearchFilterService, useValue: {
|
||||||
getSelectedValuesForFilter: () => selectedValues,
|
getSelectedValuesForFilter: () => selectedValues,
|
||||||
@@ -81,8 +85,7 @@ describe('SearchRangeFilterComponent', () => {
|
|||||||
incrementPage: (filterName: string) => {
|
incrementPage: (filterName: string) => {
|
||||||
},
|
},
|
||||||
resetPage: (filterName: string) => {
|
resetPage: (filterName: string) => {
|
||||||
},
|
}
|
||||||
getSearchOptions: () => Observable.of({}),
|
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +116,6 @@ describe('SearchRangeFilterComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('when the onSubmit method is called with data', () => {
|
describe('when the onSubmit method is called with data', () => {
|
||||||
const searchUrl = '/search/path';
|
const searchUrl = '/search/path';
|
||||||
// const data = { [mockFilterConfig.paramName + minSuffix]: '1900', [mockFilterConfig.paramName + maxSuffix]: '1950' };
|
// const data = { [mockFilterConfig.paramName + minSuffix]: '1900', [mockFilterConfig.paramName + maxSuffix]: '1950' };
|
||||||
|
@@ -16,6 +16,7 @@ import { Observable } from 'rxjs/Observable';
|
|||||||
import { RouteService } from '../../../../shared/services/route.service';
|
import { RouteService } from '../../../../shared/services/route.service';
|
||||||
import { hasValue } from '../../../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -61,12 +62,13 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
|||||||
|
|
||||||
constructor(protected searchService: SearchService,
|
constructor(protected searchService: SearchService,
|
||||||
protected filterService: SearchFilterService,
|
protected filterService: SearchFilterService,
|
||||||
|
protected searchConfigService: SearchConfigurationService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected rdbs: RemoteDataBuildService,
|
protected rdbs: RemoteDataBuildService,
|
||||||
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
private route: RouteService) {
|
private route: RouteService) {
|
||||||
super(searchService, filterService, rdbs, router, filterConfig);
|
super(searchService, filterService, searchConfigService, rdbs, router, filterConfig);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ import { SearchFilterService } from './search-filter/search-filter.service';
|
|||||||
import { SearchFiltersComponent } from './search-filters.component';
|
import { SearchFiltersComponent } from './search-filters.component';
|
||||||
import { SearchService } from '../search-service/search.service';
|
import { SearchService } from '../search-service/search.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
|
||||||
describe('SearchFiltersComponent', () => {
|
describe('SearchFiltersComponent', () => {
|
||||||
let comp: SearchFiltersComponent;
|
let comp: SearchFiltersComponent;
|
||||||
@@ -23,7 +24,7 @@ describe('SearchFiltersComponent', () => {
|
|||||||
}
|
}
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
};
|
};
|
||||||
const searchFilterServiceStub = jasmine.createSpyObj('SearchFilterService', {
|
const searchConfigServiceStub = jasmine.createSpyObj('SearchConfigurationService', {
|
||||||
getCurrentFrontendFilters: Observable.of({})
|
getCurrentFrontendFilters: Observable.of({})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ describe('SearchFiltersComponent', () => {
|
|||||||
declarations: [SearchFiltersComponent],
|
declarations: [SearchFiltersComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SearchService, useValue: searchServiceStub },
|
{ provide: SearchService, useValue: searchServiceStub },
|
||||||
{ provide: SearchFilterService, useValue: searchFilterServiceStub },
|
{ provide: SearchConfigurationService, useValue: searchConfigServiceStub },
|
||||||
|
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -3,7 +3,7 @@ import { SearchService } from '../search-service/search.service';
|
|||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { SearchFilterConfig } from '../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../search-service/search-filter-config.model';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { SearchFilterService } from './search-filter/search-filter.service';
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-filters',
|
selector: 'ds-search-filters',
|
||||||
@@ -29,11 +29,11 @@ export class SearchFiltersComponent {
|
|||||||
/**
|
/**
|
||||||
* Initialize instance variables
|
* Initialize instance variables
|
||||||
* @param {SearchService} searchService
|
* @param {SearchService} searchService
|
||||||
* @param {SearchFilterService} filterService
|
* @param {SearchConfigurationService} searchConfigService
|
||||||
*/
|
*/
|
||||||
constructor(private searchService: SearchService, private filterService: SearchFilterService) {
|
constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService) {
|
||||||
this.filters = searchService.getConfig();
|
this.filters = searchService.getConfig();
|
||||||
this.clearParams = filterService.getCurrentFrontendFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;});
|
this.clearParams = searchConfigService.getCurrentFrontendFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -9,7 +9,7 @@ import { SearchServiceStub } from '../../shared/testing/search-service-stub';
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe';
|
import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe';
|
||||||
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
|
||||||
describe('SearchLabelsComponent', () => {
|
describe('SearchLabelsComponent', () => {
|
||||||
let comp: SearchLabelsComponent;
|
let comp: SearchLabelsComponent;
|
||||||
@@ -35,7 +35,7 @@ describe('SearchLabelsComponent', () => {
|
|||||||
declarations: [SearchLabelsComponent, ObjectKeysPipe],
|
declarations: [SearchLabelsComponent, ObjectKeysPipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
|
||||||
{ provide: SearchFilterService, useValue: {getCurrentFrontendFilters : () => Observable.of({})} }
|
{ provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => Observable.of({})} }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(SearchLabelsComponent, {
|
}).overrideComponent(SearchLabelsComponent, {
|
||||||
|
@@ -3,8 +3,8 @@ import { SearchService } from '../search-service/search.service';
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
|
||||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-labels',
|
selector: 'ds-search-labels',
|
||||||
@@ -23,8 +23,8 @@ export class SearchLabelsComponent {
|
|||||||
/**
|
/**
|
||||||
* Initialize the instance variable
|
* Initialize the instance variable
|
||||||
*/
|
*/
|
||||||
constructor(private searchService: SearchService, private filterService: SearchFilterService) {
|
constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService) {
|
||||||
this.appliedFilters = this.filterService.getCurrentFrontendFilters();
|
this.appliedFilters = this.searchConfigService.getCurrentFrontendFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,40 +1,40 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="search-page row">
|
<div class="search-page row">
|
||||||
<ds-search-sidebar *ngIf="!(isXsOrSm$ | async)" class="col-3 sidebar-md-sticky"
|
<ds-search-sidebar *ngIf="!(isXsOrSm$ | async)" class="col-3 sidebar-md-sticky"
|
||||||
id="search-sidebar"
|
id="search-sidebar"
|
||||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"></ds-search-sidebar>
|
[resultCount]="(resultsRD$ | async)?.payload.totalElements"></ds-search-sidebar>
|
||||||
<div class="col-12 col-md-9">
|
<div class="col-12 col-md-9">
|
||||||
<ds-search-form id="search-form"
|
<ds-search-form id="search-form"
|
||||||
[query]="(searchOptions$ | async)?.query"
|
[query]="(searchOptions$ | async)?.query"
|
||||||
[scope]="(searchOptions$ | async)?.scope"
|
[scope]="(searchOptions$ | async)?.scope"
|
||||||
[currentUrl]="getSearchLink()"
|
[currentUrl]="getSearchLink()"
|
||||||
[scopes]="(scopeListRD$ | async)">
|
[scopes]="(scopeListRD$ | async)">
|
||||||
</ds-search-form>
|
</ds-search-form>
|
||||||
<ds-search-labels></ds-search-labels>
|
<ds-search-labels></ds-search-labels>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="search-body"
|
<div id="search-body"
|
||||||
class="row-offcanvas row-offcanvas-left"
|
class="row-offcanvas row-offcanvas-left"
|
||||||
[@pushInOut]="(isSidebarCollapsed() | async) ? 'collapsed' : 'expanded'">
|
[@pushInOut]="(isSidebarCollapsed() | async) ? 'collapsed' : 'expanded'">
|
||||||
<ds-search-sidebar *ngIf="(isXsOrSm$ | async)" class="col-12"
|
<ds-search-sidebar *ngIf="(isXsOrSm$ | async)" class="col-12"
|
||||||
id="search-sidebar-sm"
|
id="search-sidebar-sm"
|
||||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
||||||
(toggleSidebar)="closeSidebar()"
|
(toggleSidebar)="closeSidebar()"
|
||||||
[ngClass]="{'active': !(isSidebarCollapsed() | async)}">
|
[ngClass]="{'active': !(isSidebarCollapsed() | async)}">
|
||||||
</ds-search-sidebar>
|
</ds-search-sidebar>
|
||||||
<div id="search-content" class="col-12">
|
<div id="search-content" class="col-12">
|
||||||
<div class="d-block d-md-none search-controls clearfix">
|
<div class="d-block d-md-none search-controls clearfix">
|
||||||
<ds-view-mode-switch></ds-view-mode-switch>
|
<ds-view-mode-switch></ds-view-mode-switch>
|
||||||
<button (click)="openSidebar()" aria-controls="#search-body"
|
<button (click)="openSidebar()" aria-controls="#search-body"
|
||||||
class="btn btn-outline-primary float-right open-sidebar"><i
|
class="btn btn-outline-primary float-right open-sidebar"><i
|
||||||
class="fa fa-sliders"></i> {{"search.sidebar.open"
|
class="fa fa-sliders"></i> {{"search.sidebar.open"
|
||||||
| translate}}
|
| translate}}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
<ds-search-results [searchResults]="resultsRD$ | async"
|
||||||
|
[searchConfig]="searchOptions$ | async"></ds-search-results>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-search-results [searchResults]="resultsRD$ | async"
|
|
||||||
[searchConfig]="searchOptions$ | async"></ds-search-results>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -19,6 +19,7 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||||
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
||||||
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
|
|
||||||
describe('SearchPageComponent', () => {
|
describe('SearchPageComponent', () => {
|
||||||
let comp: SearchPageComponent;
|
let comp: SearchPageComponent;
|
||||||
@@ -89,7 +90,10 @@ describe('SearchPageComponent', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SearchFilterService,
|
provide: SearchFilterService,
|
||||||
useValue: jasmine.createSpyObj('SearchFilterService', {
|
useValue: {}
|
||||||
|
},{
|
||||||
|
provide: SearchConfigurationService,
|
||||||
|
useValue: jasmine.createSpyObj('SearchConfigurationService', {
|
||||||
getPaginatedSearchOptions: hot('a', {
|
getPaginatedSearchOptions: hot('a', {
|
||||||
a: paginatedSearchOptions
|
a: paginatedSearchOptions
|
||||||
}),
|
}),
|
||||||
|
@@ -15,6 +15,7 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
|||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -55,19 +56,6 @@ export class SearchPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
isXsOrSm$: Observable<boolean>;
|
isXsOrSm$: Observable<boolean>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Default values for the Search Options
|
|
||||||
*/
|
|
||||||
defaults = {
|
|
||||||
pagination: {
|
|
||||||
id: 'search-results-pagination',
|
|
||||||
pageSize: 10
|
|
||||||
},
|
|
||||||
sort: new SortOptions('score', SortDirection.DESC),
|
|
||||||
query: '',
|
|
||||||
scope: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription to unsubscribe from
|
* Subscription to unsubscribe from
|
||||||
*/
|
*/
|
||||||
@@ -76,7 +64,8 @@ export class SearchPageComponent implements OnInit {
|
|||||||
constructor(private service: SearchService,
|
constructor(private service: SearchService,
|
||||||
private sidebarService: SearchSidebarService,
|
private sidebarService: SearchSidebarService,
|
||||||
private windowService: HostWindowService,
|
private windowService: HostWindowService,
|
||||||
private filterService: SearchFilterService) {
|
private filterService: SearchFilterService,
|
||||||
|
private searchConfigService: SearchConfigurationService) {
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,11 +77,11 @@ export class SearchPageComponent implements OnInit {
|
|||||||
* If something changes, update the list of scopes for the dropdown
|
* If something changes, update the list of scopes for the dropdown
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.searchOptions$ = this.filterService.getPaginatedSearchOptions(this.defaults);
|
this.searchOptions$ = this.searchConfigService.getPaginatedSearchOptions();
|
||||||
this.sub = this.searchOptions$.subscribe((searchOptions) =>
|
this.sub = this.searchOptions$.subscribe((searchOptions) =>
|
||||||
this.service.search(searchOptions).filter((rd) => !rd.isLoading).first().subscribe((results) => this.resultsRD$.next(results)));
|
this.service.search(searchOptions).filter((rd) => !rd.isLoading).first().subscribe((results) => this.resultsRD$.next(results)));
|
||||||
|
|
||||||
this.scopeListRD$ = this.filterService.getCurrentScope().pipe(
|
this.scopeListRD$ = this.searchConfigService.getCurrentScope().pipe(
|
||||||
flatMap((scopeId) => this.service.getScopes(scopeId))
|
flatMap((scopeId) => this.service.getScopes(scopeId))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ import { SearchTextFilterComponent } from './search-filters/search-filter/search
|
|||||||
import { SearchFacetFilterWrapperComponent } from './search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component';
|
import { SearchFacetFilterWrapperComponent } from './search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component';
|
||||||
import { SearchBooleanFilterComponent } from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component';
|
import { SearchBooleanFilterComponent } from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component';
|
||||||
import { SearchHierarchyFilterComponent } from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component';
|
import { SearchHierarchyFilterComponent } from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component';
|
||||||
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
|
|
||||||
const effects = [
|
const effects = [
|
||||||
SearchSidebarEffects
|
SearchSidebarEffects
|
||||||
@@ -66,7 +67,8 @@ const effects = [
|
|||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
SearchService,
|
||||||
SearchSidebarService,
|
SearchSidebarService,
|
||||||
SearchFilterService
|
SearchFilterService,
|
||||||
|
SearchConfigurationService
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
ItemSearchResultListElementComponent,
|
ItemSearchResultListElementComponent,
|
||||||
|
@@ -0,0 +1,130 @@
|
|||||||
|
import { SearchConfigurationService } from './search-configuration.service';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { fakeAsync, tick } from '@angular/core/testing';
|
||||||
|
|
||||||
|
describe('SearchConfigurationService', () => {
|
||||||
|
let service: SearchConfigurationService;
|
||||||
|
const value1 = 'random value';
|
||||||
|
const value2 = 'another value';
|
||||||
|
const prefixFilter = {
|
||||||
|
'f.author': ['another value'],
|
||||||
|
'f.date.min': ['2013'],
|
||||||
|
'f.date.max': ['2018']
|
||||||
|
};
|
||||||
|
const defaults = Observable.of(new RemoteData(false, false, true, null, {}))
|
||||||
|
const backendFilters = { 'f.author': ['another value'], 'f.date': ['[2013 TO 2018]'] };
|
||||||
|
|
||||||
|
const spy = jasmine.createSpyObj('SearchConfigurationService', {
|
||||||
|
getQueryParameterValue: Observable.of([value1, value2])
|
||||||
|
,
|
||||||
|
getQueryParamsWithPrefix: Observable.of(prefixFilter)
|
||||||
|
});
|
||||||
|
|
||||||
|
const activatedRoute: any = new ActivatedRouteStub();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new SearchConfigurationService(spy, activatedRoute);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentScope is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentScope();
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'scope\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('scope');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentQuery is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentQuery();
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'query\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('query');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentFrontendFilters is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentFrontendFilters();
|
||||||
|
});
|
||||||
|
it('should call getQueryParamsWithPrefix on the routeService with parameter prefix \'f.\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParamsWithPrefix).toHaveBeenCalledWith('f.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentFilters is called', () => {
|
||||||
|
let parsedValues$;
|
||||||
|
beforeEach(() => {
|
||||||
|
parsedValues$ = service.getCurrentFilters();
|
||||||
|
});
|
||||||
|
it('should call getQueryParamsWithPrefix on the routeService with parameter prefix \'f.\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParamsWithPrefix).toHaveBeenCalledWith('f.');
|
||||||
|
parsedValues$.subscribe((values) => {
|
||||||
|
expect(values).toEqual(backendFilters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentSort is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentSort({} as any);
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'sortDirection\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortDirection');
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'sortField\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortField');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('when getCurrentPagination is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentPagination({});
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'page\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('page');
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'pageSize\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('pageSize');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fdescribe('when getPaginatedSearchOptions or getSearchOptions is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getCurrentPagination');
|
||||||
|
spyOn(service, 'getCurrentSort');
|
||||||
|
spyOn(service, 'getCurrentScope');
|
||||||
|
spyOn(service, 'getCurrentQuery');
|
||||||
|
spyOn(service, 'getCurrentFilters');
|
||||||
|
});
|
||||||
|
describe('when getPaginatedSearchOptions is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getPaginatedSearchOptions(defaults);
|
||||||
|
});
|
||||||
|
it('should call all getters it needs', fakeAsync(() => {
|
||||||
|
defaults.subscribe(() => {
|
||||||
|
tick();
|
||||||
|
expect(service.getCurrentPagination).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentSort).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentScope).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentQuery).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentFilters).toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
describe('when getSearchOptions is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getSearchOptions();
|
||||||
|
});
|
||||||
|
it('should call all getters it needs', () => {
|
||||||
|
expect(service.getCurrentPagination).not.toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentSort).not.toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentScope).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentQuery).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentFilters).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,174 @@
|
|||||||
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SearchOptions } from '../search-options.model';
|
||||||
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { RouteService } from '../../shared/services/route.service';
|
||||||
|
import { hasNoValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that performs all actions that have to do with the current search configuration
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class SearchConfigurationService {
|
||||||
|
private defaultPagination = { id: 'search-page-configuration', pageSize: 10 };
|
||||||
|
|
||||||
|
private defaultSort = new SortOptions('score', SortDirection.DESC);
|
||||||
|
|
||||||
|
private defaultScope = '';
|
||||||
|
|
||||||
|
private _defaults;
|
||||||
|
|
||||||
|
constructor(private routeService: RouteService,
|
||||||
|
private route: ActivatedRoute) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Observable<string>} Emits the current scope's identifier
|
||||||
|
*/
|
||||||
|
getCurrentScope() {
|
||||||
|
return this.routeService.getQueryParameterValue('scope');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Observable<string>} Emits the current query string
|
||||||
|
*/
|
||||||
|
getCurrentQuery() {
|
||||||
|
return this.routeService.getQueryParameterValue('query');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Observable<string>} Emits the current pagination settings
|
||||||
|
*/
|
||||||
|
getCurrentPagination(pagination: any = {}): Observable<PaginationComponentOptions> {
|
||||||
|
const page$ = this.routeService.getQueryParameterValue('page');
|
||||||
|
const size$ = this.routeService.getQueryParameterValue('pageSize');
|
||||||
|
return Observable.combineLatest(page$, size$, (page, size) => {
|
||||||
|
return Object.assign(new PaginationComponentOptions(), pagination, {
|
||||||
|
currentPage: page || 1,
|
||||||
|
pageSize: size || pagination.pageSize
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Observable<string>} Emits the current sorting settings
|
||||||
|
*/
|
||||||
|
getCurrentSort(defaultSort: SortOptions): Observable<SortOptions> {
|
||||||
|
const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection');
|
||||||
|
const sortField$ = this.routeService.getQueryParameterValue('sortField');
|
||||||
|
return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => {
|
||||||
|
// Dirty fix because sometimes the observable value is null somehow
|
||||||
|
sortField = this.route.snapshot.queryParamMap.get('sortField');
|
||||||
|
|
||||||
|
const field = sortField || defaultSort.field;
|
||||||
|
const direction = SortDirection[sortDirection] || defaultSort.direction;
|
||||||
|
return new SortOptions(field, direction)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Observable<Params>} Emits the current active filters with their values as they are sent to the backend
|
||||||
|
*/
|
||||||
|
getCurrentFilters(): Observable<Params> {
|
||||||
|
return this.routeService.getQueryParamsWithPrefix('f.').map((filterParams) => {
|
||||||
|
if (isNotEmpty(filterParams)) {
|
||||||
|
const params = {};
|
||||||
|
Object.keys(filterParams).forEach((key) => {
|
||||||
|
if (key.endsWith('.min') || key.endsWith('.max')) {
|
||||||
|
const realKey = key.slice(0, -4);
|
||||||
|
if (isEmpty(params[realKey])) {
|
||||||
|
const min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*';
|
||||||
|
const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*';
|
||||||
|
params[realKey] = ['[' + min + ' TO ' + max + ']'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params[key] = filterParams[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
return filterParams;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Observable<Params>} Emits the current active filters with their values as they are displayed in the frontend URL
|
||||||
|
*/
|
||||||
|
getCurrentFrontendFilters(): Observable<Params> {
|
||||||
|
return this.routeService.getQueryParamsWithPrefix('f.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param defaults The default values for the search options, that will be used if nothing is explicitly set
|
||||||
|
* @returns {Observable<PaginatedSearchOptions>} Emits the current paginated search options
|
||||||
|
*/
|
||||||
|
getPaginatedSearchOptions(defaults: Observable<RemoteData<any>> = this.defaults): Observable<PaginatedSearchOptions> {
|
||||||
|
return defaults.flatMap((defaultConfig) => {
|
||||||
|
const defaultValues = defaultConfig.payload;
|
||||||
|
return Observable.combineLatest(
|
||||||
|
this.getCurrentPagination(defaultValues.pagination),
|
||||||
|
this.getCurrentSort(defaultValues.sort),
|
||||||
|
this.getCurrentScope(),
|
||||||
|
this.getCurrentQuery(),
|
||||||
|
this.getCurrentFilters()).pipe(
|
||||||
|
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),
|
||||||
|
map(([pagination, sort, scope, query, filters]) => {
|
||||||
|
return Object.assign(new PaginatedSearchOptions(),
|
||||||
|
defaults,
|
||||||
|
{
|
||||||
|
pagination: pagination,
|
||||||
|
sort: sort,
|
||||||
|
scope: scope || defaultValues.scope,
|
||||||
|
query: query,
|
||||||
|
filters: filters
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param defaults The default values for the search options, that will be used if nothing is explicitly set
|
||||||
|
* @returns {Observable<PaginatedSearchOptions>} Emits the current search options
|
||||||
|
*/
|
||||||
|
getSearchOptions(defaults: Observable<RemoteData<any>> = this.defaults): Observable<SearchOptions> {
|
||||||
|
return defaults.flatMap((defaultConfig) => {
|
||||||
|
const defaultValues = defaultConfig.payload;
|
||||||
|
return Observable.combineLatest(
|
||||||
|
this.getCurrentScope(),
|
||||||
|
this.getCurrentQuery(),
|
||||||
|
this.getCurrentFilters(),
|
||||||
|
(scope, query, filters) => {
|
||||||
|
return Object.assign(new SearchOptions(),
|
||||||
|
{
|
||||||
|
scope: scope || defaultValues.scope,
|
||||||
|
query: query,
|
||||||
|
filters: filters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for the Search Options
|
||||||
|
*/
|
||||||
|
get defaults() {
|
||||||
|
if (hasNoValue(this._defaults)) {
|
||||||
|
const options = {
|
||||||
|
pagination: this.defaultPagination,
|
||||||
|
sort: this.defaultSort,
|
||||||
|
scope: this.defaultScope
|
||||||
|
};
|
||||||
|
this._defaults = Observable.of(new RemoteData(false, false, true, null, options));
|
||||||
|
}
|
||||||
|
return this._defaults;
|
||||||
|
}
|
||||||
|
}
|
@@ -25,9 +25,9 @@ import {
|
|||||||
} from '../../core/cache/response-cache.models';
|
} from '../../core/cache/response-cache.models';
|
||||||
import { SearchQueryResponse } from './search-query-response.model';
|
import { SearchQueryResponse } from './search-query-response.model';
|
||||||
import { SearchFilterConfig } from './search-filter-config.model';
|
import { SearchFilterConfig } from './search-filter-config.model';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
import { PIDService } from '../../core/data/pid.service';
|
||||||
|
|
||||||
@Component({ template: '' })
|
@Component({ template: '' })
|
||||||
class DummyComponent {
|
class DummyComponent {
|
||||||
@@ -57,7 +57,7 @@ describe('SearchService', () => {
|
|||||||
{ provide: RemoteDataBuildService, useValue: {} },
|
{ provide: RemoteDataBuildService, useValue: {} },
|
||||||
{ provide: HALEndpointService, useValue: {} },
|
{ provide: HALEndpointService, useValue: {} },
|
||||||
{ provide: CommunityDataService, useValue: {}},
|
{ provide: CommunityDataService, useValue: {}},
|
||||||
{ provide: CollectionDataService, useValue: {}},
|
{ provide: PIDService, useValue: {}},
|
||||||
SearchService
|
SearchService
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -114,7 +114,7 @@ describe('SearchService', () => {
|
|||||||
{ provide: RemoteDataBuildService, useValue: remoteDataBuildService },
|
{ provide: RemoteDataBuildService, useValue: remoteDataBuildService },
|
||||||
{ provide: HALEndpointService, useValue: halService },
|
{ provide: HALEndpointService, useValue: halService },
|
||||||
{ provide: CommunityDataService, useValue: {}},
|
{ provide: CommunityDataService, useValue: {}},
|
||||||
{ provide: CollectionDataService, useValue: {}},
|
{ provide: PIDService, useValue: {}},
|
||||||
SearchService
|
SearchService
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@@ -26,7 +26,7 @@ import { GenericConstructor } from '../../core/shared/generic-constructor';
|
|||||||
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||||
import { configureRequest } from '../../core/shared/operators';
|
import { configureRequest } from '../../core/shared/operators';
|
||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||||
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { NormalizedSearchResult } from '../normalized-search-result.model';
|
import { NormalizedSearchResult } from '../normalized-search-result.model';
|
||||||
import { SearchOptions } from '../search-options.model';
|
import { SearchOptions } from '../search-options.model';
|
||||||
import { SearchResult } from '../search-result.model';
|
import { SearchResult } from '../search-result.model';
|
||||||
@@ -42,10 +42,9 @@ import { FacetConfigResponseParsingService } from '../../core/data/facet-config-
|
|||||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
|
||||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
import { PIDService } from '../../core/data/pid.service';
|
||||||
|
import { ResourceType } from '../../core/shared/resource-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that performs all general actions that have to do with the search page
|
* Service that performs all general actions that have to do with the search page
|
||||||
@@ -74,7 +73,8 @@ export class SearchService implements OnDestroy {
|
|||||||
private rdb: RemoteDataBuildService,
|
private rdb: RemoteDataBuildService,
|
||||||
private halService: HALEndpointService,
|
private halService: HALEndpointService,
|
||||||
private communityService: CommunityDataService,
|
private communityService: CommunityDataService,
|
||||||
private collectionService: CollectionDataService) {
|
private pidService: PIDService
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,7 +258,7 @@ export class SearchService implements OnDestroy {
|
|||||||
*/
|
*/
|
||||||
getScopes(scopeId?: string): Observable<DSpaceObject[]> {
|
getScopes(scopeId?: string): Observable<DSpaceObject[]> {
|
||||||
|
|
||||||
if (hasNoValue(scopeId)) {
|
if (isEmpty(scopeId)) {
|
||||||
const top: Observable<Community[]> = this.communityService.findTop({ elementsPerPage: 9999 }).pipe(
|
const top: Observable<Community[]> = this.communityService.findTop({ elementsPerPage: 9999 }).pipe(
|
||||||
map(
|
map(
|
||||||
(communities: RemoteData<PaginatedList<Community>>) => communities.payload.page
|
(communities: RemoteData<PaginatedList<Community>>) => communities.payload.page
|
||||||
@@ -267,24 +267,22 @@ export class SearchService implements OnDestroy {
|
|||||||
return top;
|
return top;
|
||||||
}
|
}
|
||||||
|
|
||||||
const communityScope: Observable<RemoteData<Community>> = this.communityService.findById(scopeId).filter((communityRD: RemoteData<Community>) => !communityRD.isLoading);
|
const scopeObject: Observable<RemoteData<DSpaceObject>> = this.pidService.findById(scopeId).filter((dsoRD: RemoteData<DSpaceObject>) => !dsoRD.isLoading);
|
||||||
const scopeObject: Observable<DSpaceObject[]> = communityScope.pipe(
|
const scopeList: Observable<DSpaceObject[]> = scopeObject.pipe(
|
||||||
flatMap((communityRD: RemoteData<Community>) => {
|
flatMap((dsoRD: RemoteData<DSpaceObject>) => {
|
||||||
if (hasValue(communityRD.payload)) {
|
if (dsoRD.payload.type === ResourceType.Community) {
|
||||||
const community: Community = communityRD.payload;
|
const community: Community = dsoRD.payload as Community;
|
||||||
// const subcommunities$ = community.subcommunities.filter((subcommunitiesRD: RemoteData<PaginatedList<Community>>) => !subcommunitiesRD.isLoading).first();
|
|
||||||
// const collections$ = community.subcommunities.filter((subcommunitiesRD: RemoteData<PaginatedList<Community>>) => !subcommunitiesRD.isLoading).first();
|
|
||||||
return Observable.combineLatest(community.subcommunities, community.collections, (subCommunities, collections) => {
|
return Observable.combineLatest(community.subcommunities, community.collections, (subCommunities, collections) => {
|
||||||
/*if this is a community, we also need to show the direct children*/
|
/*if this is a community, we also need to show the direct children*/
|
||||||
return [community, ...subCommunities.payload.page, ...collections.payload.page]
|
return [community, ...subCommunities.payload.page, ...collections.payload.page]
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return this.collectionService.findById(scopeId).pipe(map((collectionRD: RemoteData<Collection>) => [collectionRD.payload]));
|
return Observable.of([dsoRD.payload]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
return scopeObject;
|
return scopeList;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
||||||
import { hot } from 'jasmine-marbles';
|
import { hot } from 'jasmine-marbles';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
|
||||||
describe('SearchSettingsComponent', () => {
|
describe('SearchSettingsComponent', () => {
|
||||||
|
|
||||||
@@ -68,7 +69,11 @@ describe('SearchSettingsComponent', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SearchFilterService,
|
provide: SearchFilterService,
|
||||||
useValue: jasmine.createSpyObj('SearchFilterService', {
|
useValue: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: SearchConfigurationService,
|
||||||
|
useValue: jasmine.createSpyObj('SearchConfigurationService', {
|
||||||
getPaginatedSearchOptions: hot('a', {
|
getPaginatedSearchOptions: hot('a', {
|
||||||
a: paginatedSearchOptions
|
a: paginatedSearchOptions
|
||||||
}),
|
}),
|
||||||
|
@@ -5,6 +5,7 @@ import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
|||||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { SearchConfigurationService } from '../search-service/search-configuration.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-settings',
|
selector: 'ds-search-settings',
|
||||||
@@ -27,30 +28,17 @@ export class SearchSettingsComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
searchOptionPossibilities = [new SortOptions('score', SortDirection.DESC), new SortOptions('dc.title', SortDirection.ASC), new SortOptions('dc.title', SortDirection.DESC)];
|
searchOptionPossibilities = [new SortOptions('score', SortDirection.DESC), new SortOptions('dc.title', SortDirection.ASC), new SortOptions('dc.title', SortDirection.DESC)];
|
||||||
|
|
||||||
/**
|
|
||||||
* Default values for the Search Options
|
|
||||||
*/
|
|
||||||
defaults = {
|
|
||||||
pagination: {
|
|
||||||
id: 'search-results-pagination',
|
|
||||||
pageSize: 10
|
|
||||||
},
|
|
||||||
sort: new SortOptions('score', SortDirection.DESC),
|
|
||||||
query: '',
|
|
||||||
scope: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(private service: SearchService,
|
constructor(private service: SearchService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private filterService: SearchFilterService) {
|
private searchConfigurationService: SearchConfigurationService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize paginated search options
|
* Initialize paginated search options
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.searchOptions$ = this.filterService.getPaginatedSearchOptions(this.defaults);
|
this.searchOptions$ = this.searchConfigurationService.getPaginatedSearchOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -23,7 +23,6 @@ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject>
|
|||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected store: Store<CoreState>,
|
protected store: Store<CoreState>,
|
||||||
private bs: BrowseService,
|
|
||||||
protected halService: HALEndpointService) {
|
protected halService: HALEndpointService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -46,7 +45,7 @@ export class PIDService {
|
|||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected halService: HALEndpointService) {
|
protected halService: HALEndpointService) {
|
||||||
this.dataService = new DataServiceImpl(null, requestService, rdbService, null, null, halService);
|
this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService);
|
||||||
}
|
}
|
||||||
|
|
||||||
findById(id: string): Observable<RemoteData<DSpaceObject>> {
|
findById(id: string): Observable<RemoteData<DSpaceObject>> {
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { SearchService } from '../../+search-page/search-service/search.service';
|
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util';
|
import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util';
|
||||||
@@ -31,6 +30,7 @@ export class SearchFormComponent {
|
|||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
scope = '';
|
scope = '';
|
||||||
|
|
||||||
@Input() currentUrl: string;
|
@Input() currentUrl: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -31,7 +31,6 @@ export class DebounceDirective implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
private isFirstChange = true;
|
private isFirstChange = true;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subject to unsubscribe from
|
* Subject to unsubscribe from
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user