mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #1086 from 4Science/CST-4009
[CST-4009] Discovery result sort options not reflecting what is configured
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
[configurationList]="(configurationList$ | async)"
|
||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
||||
[viewModeList]="viewModeList"
|
||||
[searchOptions]="(searchOptions$ | async)"
|
||||
[sortOptions]="(sortOptions$ | async)"
|
||||
[refreshFilters]="refreshFilters.asObservable()"
|
||||
[inPlaceSearch]="inPlaceSearch"></ds-search-sidebar>
|
||||
<div class="col-12 col-md-9">
|
||||
@@ -28,6 +30,8 @@
|
||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
||||
(toggleSidebar)="closeSidebar()"
|
||||
[ngClass]="{'active': !(isSidebarCollapsed() | async)}"
|
||||
[searchOptions]="(searchOptions$ | async)"
|
||||
[sortOptions]="(sortOptions$ | async)"
|
||||
[refreshFilters]="refreshFilters.asObservable()"
|
||||
[inPlaceSearch]="inPlaceSearch">
|
||||
</ds-search-sidebar>
|
||||
|
@@ -45,6 +45,7 @@ describe('MyDSpacePageComponent', () => {
|
||||
pagination.id = 'mydspace-results-pagination';
|
||||
pagination.currentPage = 1;
|
||||
pagination.pageSize = 10;
|
||||
const sortOption = { name: 'score', sortOrder: 'DESC', metadata: null };
|
||||
const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
|
||||
const mockResults = createSuccessfulRemoteDataObject$(['test', 'data']);
|
||||
const searchServiceStub = jasmine.createSpyObj('SearchService', {
|
||||
@@ -52,7 +53,8 @@ describe('MyDSpacePageComponent', () => {
|
||||
getEndpoint: observableOf('discover/search/objects'),
|
||||
getSearchLink: '/mydspace',
|
||||
getScopes: observableOf(['test-scope']),
|
||||
setServiceOptions: {}
|
||||
setServiceOptions: {},
|
||||
getSearchConfigurationFor: createSuccessfulRemoteDataObject$({ sortOptions: [sortOption]})
|
||||
});
|
||||
const configurationParam = 'default';
|
||||
const queryParam = 'test query';
|
||||
@@ -188,4 +190,24 @@ describe('MyDSpacePageComponent', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when stable', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should have initialized the sortOptions$ observable', (done) => {
|
||||
|
||||
comp.sortOptions$.subscribe((sortOptions) => {
|
||||
|
||||
expect(sortOptions.length).toEqual(2);
|
||||
expect(sortOptions[0]).toEqual(new SortOptions('score', SortDirection.ASC));
|
||||
expect(sortOptions[1]).toEqual(new SortOptions('score', SortDirection.DESC));
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@@ -8,7 +8,7 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
|
||||
import { map, switchMap, tap, } from 'rxjs/operators';
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
import { PaginatedList } from '../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
@@ -29,6 +29,8 @@ import { ViewMode } from '../core/shared/view-mode.model';
|
||||
import { MyDSpaceRequest } from '../core/data/request.models';
|
||||
import { SearchResult } from '../shared/search/search-result.model';
|
||||
import { Context } from '../core/shared/context.model';
|
||||
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
|
||||
export const MYDSPACE_ROUTE = '/mydspace';
|
||||
export const SEARCH_CONFIG_SERVICE: InjectionToken<SearchConfigurationService> = new InjectionToken<SearchConfigurationService>('searchConfigurationService');
|
||||
@@ -71,6 +73,11 @@ export class MyDSpacePageComponent implements OnInit {
|
||||
*/
|
||||
searchOptions$: Observable<PaginatedSearchOptions>;
|
||||
|
||||
/**
|
||||
* The current available sort options
|
||||
*/
|
||||
sortOptions$: Observable<SortOptions[]>;
|
||||
|
||||
/**
|
||||
* The current relevant scopes
|
||||
*/
|
||||
@@ -109,7 +116,8 @@ export class MyDSpacePageComponent implements OnInit {
|
||||
constructor(private service: SearchService,
|
||||
private sidebarService: SidebarService,
|
||||
private windowService: HostWindowService,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService,
|
||||
private routeService: RouteService) {
|
||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||
this.service.setServiceOptions(MyDSpaceResponseParsingService, MyDSpaceRequest);
|
||||
}
|
||||
@@ -151,6 +159,12 @@ export class MyDSpacePageComponent implements OnInit {
|
||||
})
|
||||
);
|
||||
|
||||
const configuration$ = this.searchConfigService.getCurrentConfiguration('workspace');
|
||||
const searchConfig$ = this.searchConfigService.getConfigurationSearchConfigObservable(configuration$, this.service);
|
||||
|
||||
this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(searchConfig$);
|
||||
this.searchConfigService.initializeSortOptionsFromConfiguration(searchConfig$);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -31,11 +31,14 @@
|
||||
<ng-template #sidebarContent>
|
||||
<ds-search-sidebar id="search-sidebar" *ngIf="!(isXsOrSm$ | async)"
|
||||
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
||||
[searchOptions]="(searchOptions$ | async)"
|
||||
[sortOptions]="(sortOptions$ | async)"
|
||||
[inPlaceSearch]="inPlaceSearch"></ds-search-sidebar>
|
||||
<ds-search-sidebar id="search-sidebar-sm" *ngIf="(isXsOrSm$ | async)"
|
||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
||||
(toggleSidebar)="closeSidebar()"
|
||||
>
|
||||
[searchOptions]="(searchOptions$ | async)"
|
||||
[sortOptions]="(sortOptions$ | async)"
|
||||
(toggleSidebar)="closeSidebar()">
|
||||
</ds-search-sidebar>
|
||||
</ng-template>
|
||||
|
||||
|
@@ -40,12 +40,14 @@ const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
||||
pagination.id = 'search-results-pagination';
|
||||
pagination.currentPage = 1;
|
||||
pagination.pageSize = 10;
|
||||
const sortOption = { name: 'score', sortOrder: 'DESC', metadata: null };
|
||||
const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
|
||||
const mockResults = createSuccessfulRemoteDataObject$(['test', 'data']);
|
||||
const searchServiceStub = jasmine.createSpyObj('SearchService', {
|
||||
search: mockResults,
|
||||
getSearchLink: '/search',
|
||||
getScopes: observableOf(['test-scope'])
|
||||
getScopes: observableOf(['test-scope']),
|
||||
getSearchConfigurationFor: createSuccessfulRemoteDataObject$({ sortOptions: [sortOption]})
|
||||
});
|
||||
const configurationParam = 'default';
|
||||
const queryParam = 'test query';
|
||||
@@ -181,4 +183,24 @@ describe('SearchComponent', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when stable', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should have initialized the sortOptions$ observable', (done) => {
|
||||
|
||||
comp.sortOptions$.subscribe((sortOptions) => {
|
||||
|
||||
expect(sortOptions.length).toEqual(2);
|
||||
expect(sortOptions[0]).toEqual(new SortOptions('score', SortDirection.ASC));
|
||||
expect(sortOptions[1]).toEqual(new SortOptions('score', SortDirection.DESC));
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import { startWith, switchMap, } from 'rxjs/operators';
|
||||
import { startWith, switchMap } from 'rxjs/operators';
|
||||
import { PaginatedList } from '../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { pushInOut } from '../shared/animations/push';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { hasValue, isEmpty } from '../shared/empty.util';
|
||||
import { getFirstSucceededRemoteData } from '../core/shared/operators';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
||||
@@ -18,6 +18,7 @@ import { SearchService } from '../core/shared/search/search.service';
|
||||
import { currentPath } from '../shared/utils/route.utils';
|
||||
import { Router} from '@angular/router';
|
||||
import { Context } from '../core/shared/context.model';
|
||||
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search',
|
||||
@@ -47,6 +48,11 @@ export class SearchComponent implements OnInit {
|
||||
*/
|
||||
searchOptions$: Observable<PaginatedSearchOptions>;
|
||||
|
||||
/**
|
||||
* The current available sort options
|
||||
*/
|
||||
sortOptions$: Observable<SortOptions[]>;
|
||||
|
||||
/**
|
||||
* The current relevant scopes
|
||||
*/
|
||||
@@ -129,9 +135,15 @@ export class SearchComponent implements OnInit {
|
||||
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
||||
switchMap((scopeId) => this.service.getScopes(scopeId))
|
||||
);
|
||||
if (!isNotEmpty(this.configuration$)) {
|
||||
this.configuration$ = this.routeService.getRouteParameterValue('configuration');
|
||||
if (isEmpty(this.configuration$)) {
|
||||
this.configuration$ = this.searchConfigService.getCurrentConfiguration('default');
|
||||
}
|
||||
|
||||
const searchConfig$ = this.searchConfigService.getConfigurationSearchConfigObservable(this.configuration$, this.service);
|
||||
|
||||
this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(searchConfig$);
|
||||
this.searchConfigService.initializeSortOptionsFromConfiguration(searchConfig$);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -161,6 +161,7 @@ import { ShortLivedToken } from './auth/models/short-lived-token.model';
|
||||
import { UsageReport } from './statistics/models/usage-report.model';
|
||||
import { RootDataService } from './data/root-data.service';
|
||||
import { Root } from './data/root.model';
|
||||
import { SearchConfig } from './shared/search/search-filters/search-config.model';
|
||||
|
||||
/**
|
||||
* When not in production, endpoint responses can be mocked for testing purposes
|
||||
@@ -340,6 +341,7 @@ export const models =
|
||||
Registration,
|
||||
UsageReport,
|
||||
Root,
|
||||
SearchConfig
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -1,8 +1,15 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, merge as observableMerge, Observable, Subscription } from 'rxjs';
|
||||
import { filter, map, startWith } from 'rxjs/operators';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
combineLatest as observableCombineLatest,
|
||||
merge as observableMerge,
|
||||
Observable,
|
||||
Subscription
|
||||
} from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { SearchOptions } from '../../../shared/search/search-options.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
@@ -11,9 +18,15 @@ import { RemoteData } from '../../data/remote-data';
|
||||
import { DSpaceObjectType } from '../dspace-object-type.model';
|
||||
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
|
||||
import { RouteService } from '../../services/route.service';
|
||||
import { getFirstSucceededRemoteData } from '../operators';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFirstSucceededRemoteData
|
||||
} from '../operators';
|
||||
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { SearchConfig } from './search-filters/search-config.model';
|
||||
import { SearchService } from './search.service';
|
||||
import { of } from 'rxjs/internal/observable/of';
|
||||
import { PaginationService } from '../../pagination/pagination.service';
|
||||
|
||||
/**
|
||||
@@ -194,6 +207,60 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
return this.routeService.getQueryParamsWithPrefix('f.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable of SearchConfig every time the configuration$ stream emits.
|
||||
* @param configuration$
|
||||
* @param service
|
||||
*/
|
||||
getConfigurationSearchConfigObservable(configuration$: Observable<string>, service: SearchService): Observable<SearchConfig> {
|
||||
return configuration$.pipe(
|
||||
distinctUntilChanged(),
|
||||
switchMap((configuration) => service.getSearchConfigurationFor(null, configuration)),
|
||||
getAllSucceededRemoteDataPayload());
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time searchConfig change (after a configuration change) it update the navigation with the default sort option
|
||||
* and emit the new paginateSearchOptions value.
|
||||
* @param configuration$
|
||||
* @param service
|
||||
*/
|
||||
initializeSortOptionsFromConfiguration(searchConfig$: Observable<SearchConfig>) {
|
||||
const subscription = searchConfig$.pipe(switchMap((searchConfig) => combineLatest([
|
||||
of(searchConfig),
|
||||
this.paginatedSearchOptions.pipe(take(1))
|
||||
]))).subscribe(([searchConfig, searchOptions]) => {
|
||||
const field = searchConfig.sortOptions[0].name;
|
||||
const direction = searchConfig.sortOptions[0].sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase() ? SortDirection.ASC : SortDirection.DESC;
|
||||
const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, {
|
||||
sort: new SortOptions(field, direction)
|
||||
});
|
||||
this.paginationService.updateRoute(this.paginationID,
|
||||
{
|
||||
sortDirection: updateValue.sort.direction,
|
||||
sortField: updateValue.sort.field,
|
||||
});
|
||||
this.paginatedSearchOptions.next(updateValue);
|
||||
});
|
||||
this.subs.push(subscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable of available SortOptions[] every time the searchConfig$ stream emits.
|
||||
* @param searchConfig$
|
||||
* @param service
|
||||
*/
|
||||
getConfigurationSortOptionsObservable(searchConfig$: Observable<SearchConfig>): Observable<SortOptions[]> {
|
||||
return searchConfig$.pipe(map((searchConfig) => {
|
||||
const sortOptions = [];
|
||||
searchConfig.sortOptions.forEach(sortOption => {
|
||||
sortOptions.push(new SortOptions(sortOption.name, SortDirection.ASC));
|
||||
sortOptions.push(new SortOptions(sortOption.name, SortDirection.DESC));
|
||||
});
|
||||
return sortOptions;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a subscription to all necessary parameters to make sure the searchOptions emits a new value every time they update
|
||||
* @param {SearchOptions} defaults Default values for when no parameters are available
|
||||
|
@@ -0,0 +1,76 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
|
||||
import { SEARCH_CONFIG } from './search-config.resource-type';
|
||||
import { typedObject } from '../../../cache/builders/build-decorators';
|
||||
import { CacheableObject } from '../../../cache/object-cache.reducer';
|
||||
import { HALLink } from '../../hal-link.model';
|
||||
import { ResourceType } from '../../resource-type';
|
||||
|
||||
/**
|
||||
* The configuration for a search
|
||||
*/
|
||||
@typedObject
|
||||
export class SearchConfig implements CacheableObject {
|
||||
static type = SEARCH_CONFIG;
|
||||
|
||||
/**
|
||||
* The id of this search configuration.
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The configured filters.
|
||||
*/
|
||||
@autoserialize
|
||||
filters: FilterConfig[];
|
||||
|
||||
/**
|
||||
* The configured sort options.
|
||||
*/
|
||||
@autoserialize
|
||||
sortOptions: SortOption[];
|
||||
|
||||
/**
|
||||
* The object type.
|
||||
*/
|
||||
@autoserialize
|
||||
type: ResourceType;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this Item
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
facets: HALLink;
|
||||
objects: HALLink;
|
||||
self: HALLink;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to model filter's configuration.
|
||||
*/
|
||||
export interface FilterConfig {
|
||||
filter: string;
|
||||
hasFacets: boolean;
|
||||
operators: OperatorConfig[];
|
||||
openByDefault: boolean;
|
||||
pageSize: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to model sort option's configuration.
|
||||
*/
|
||||
export interface SortOption {
|
||||
name: string;
|
||||
sortOrder: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to model operator's configuration.
|
||||
*/
|
||||
export interface OperatorConfig {
|
||||
operator: string;
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
import {ResourceType} from '../../resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for SearchConfig
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
export const SEARCH_CONFIG = new ResourceType('discover');
|
@@ -240,5 +240,55 @@ describe('SearchService', () => {
|
||||
expect((searchService as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: requestUrl }), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getSearchConfigurationFor is called without a scope', () => {
|
||||
const endPoint = 'http://endpoint.com/test/config';
|
||||
beforeEach(() => {
|
||||
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
|
||||
spyOn((searchService as any).rdb, 'buildFromHref').and.callThrough();
|
||||
/* tslint:disable:no-empty */
|
||||
searchService.getSearchConfigurationFor(null).subscribe((t) => {
|
||||
}); // subscribe to make sure all methods are called
|
||||
/* tslint:enable:no-empty */
|
||||
});
|
||||
|
||||
it('should call getEndpoint on the halService', () => {
|
||||
expect((searchService as any).halService.getEndpoint).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should send out the request on the request service', () => {
|
||||
expect((searchService as any).requestService.send).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call send containing a request with the correct request url', () => {
|
||||
expect((searchService as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: endPoint }), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getSearchConfigurationFor is called with a scope', () => {
|
||||
const endPoint = 'http://endpoint.com/test/config';
|
||||
const scope = 'test';
|
||||
const requestUrl = endPoint + '?scope=' + scope;
|
||||
beforeEach(() => {
|
||||
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
|
||||
/* tslint:disable:no-empty */
|
||||
searchService.getSearchConfigurationFor(scope).subscribe((t) => {
|
||||
}); // subscribe to make sure all methods are called
|
||||
/* tslint:enable:no-empty */
|
||||
});
|
||||
|
||||
it('should call getEndpoint on the halService', () => {
|
||||
expect((searchService as any).halService.getEndpoint).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should send out the request on the request service', () => {
|
||||
expect((searchService as any).requestService.send).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call send containing a request with the correct request url', () => {
|
||||
expect((searchService as any).requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({ href: requestUrl }), true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@@ -37,6 +37,7 @@ import { ListableObject } from '../../../shared/object-collection/shared/listabl
|
||||
import { getSearchResultFor } from '../../../shared/search/search-result-element-decorator';
|
||||
import { FacetConfigResponse } from '../../../shared/search/facet-config-response.model';
|
||||
import { FacetValues } from '../../../shared/search/facet-values.model';
|
||||
import { SearchConfig } from './search-filters/search-config.model';
|
||||
import { PaginationService } from '../../pagination/pagination.service';
|
||||
import { SearchConfigurationService } from './search-configuration.service';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
@@ -46,6 +47,12 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
|
||||
*/
|
||||
@Injectable()
|
||||
export class SearchService implements OnDestroy {
|
||||
|
||||
/**
|
||||
* Endpoint link path for retrieving search configurations
|
||||
*/
|
||||
private configurationLinkPath = 'discover/search';
|
||||
|
||||
/**
|
||||
* Endpoint link path for retrieving general search results
|
||||
*/
|
||||
@@ -229,15 +236,7 @@ export class SearchService implements OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the filter configuration for a given scope or the whole repository
|
||||
* @param {string} scope UUID of the object for which config the filter config is requested, when no scope is provided the configuration for the whole repository is loaded
|
||||
* @param {string} configurationName the name of the configuration
|
||||
* @returns {Observable<RemoteData<SearchFilterConfig[]>>} The found filter configuration
|
||||
*/
|
||||
getConfig(scope?: string, configurationName?: string): Observable<RemoteData<SearchFilterConfig[]>> {
|
||||
const href$ = this.halService.getEndpoint(this.facetLinkPathPrefix).pipe(
|
||||
map((url: string) => {
|
||||
private getConfigUrl(url: string, scope?: string, configurationName?: string) {
|
||||
const args: string[] = [];
|
||||
|
||||
if (isNotEmpty(scope)) {
|
||||
@@ -253,7 +252,17 @@ export class SearchService implements OnDestroy {
|
||||
}
|
||||
|
||||
return url;
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the filter configuration for a given scope or the whole repository
|
||||
* @param {string} scope UUID of the object for which config the filter config is requested, when no scope is provided the configuration for the whole repository is loaded
|
||||
* @param {string} configurationName the name of the configuration
|
||||
* @returns {Observable<RemoteData<SearchFilterConfig[]>>} The found filter configuration
|
||||
*/
|
||||
getConfig(scope?: string, configurationName?: string): Observable<RemoteData<SearchFilterConfig[]>> {
|
||||
const href$ = this.halService.getEndpoint(this.facetLinkPathPrefix).pipe(
|
||||
map((url: string) => this.getConfigUrl(url, scope, configurationName)),
|
||||
);
|
||||
|
||||
href$.pipe(take(1)).subscribe((url: string) => {
|
||||
@@ -398,6 +407,25 @@ export class SearchService implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the search configuration for a given scope or the whole repository
|
||||
* @param {string} scope UUID of the object for which config the filter config is requested, when no scope is provided the configuration for the whole repository is loaded
|
||||
* @param {string} configurationName the name of the configuration
|
||||
* @returns {Observable<RemoteData<SearchConfig[]>>} The found configuration
|
||||
*/
|
||||
getSearchConfigurationFor(scope?: string, configurationName?: string ): Observable<RemoteData<SearchConfig>> {
|
||||
const href$ = this.halService.getEndpoint(this.configurationLinkPath).pipe(
|
||||
map((url: string) => this.getConfigUrl(url, scope, configurationName)),
|
||||
);
|
||||
|
||||
href$.pipe(take(1)).subscribe((url: string) => {
|
||||
const request = new this.request(this.requestService.generateRequestId(), url);
|
||||
this.requestService.send(request, true);
|
||||
});
|
||||
|
||||
return this.rdb.buildFromHref(href$);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The base path to the search page
|
||||
*/
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<ng-container *ngVar="(searchOptions$ | async) as config">
|
||||
<ng-container *ngVar="searchOptions as config">
|
||||
<h3>{{ 'search.sidebar.settings.title' | translate}}</h3>
|
||||
<div class="result-order-settings">
|
||||
<ds-sidebar-dropdown
|
||||
@@ -7,7 +7,7 @@
|
||||
[label]="'search.sidebar.settings.sort-by'"
|
||||
(change)="reloadOrder($event)"
|
||||
>
|
||||
<option *ngFor="let sortOption of searchOptionPossibilities"
|
||||
<option *ngFor="let sortOption of sortOptions"
|
||||
[value]="sortOption.field + ',' + sortOption.direction.toString()"
|
||||
[selected]="sortOption.field === config?.sort.field && sortOption.direction === (config?.sort.direction)? 'selected': null">
|
||||
{{'sorting.' + sortOption.field + '.' + sortOption.direction | translate}}
|
||||
|
@@ -12,7 +12,6 @@ import { EnumKeysPipe } from '../../utils/enum-keys-pipe';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SearchFilterService } from '../../../core/shared/search/search-filter.service';
|
||||
import { VarDirective } from '../../utils/var.directive';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component';
|
||||
import { SidebarService } from '../../sidebar/sidebar.service';
|
||||
import { SidebarServiceStub } from '../../testing/sidebar-service.stub';
|
||||
@@ -91,7 +90,7 @@ describe('SearchSettingsComponent', () => {
|
||||
provide: SEARCH_CONFIG_SERVICE,
|
||||
useValue: {
|
||||
paginatedSearchOptions: observableOf(paginatedSearchOptions),
|
||||
getCurrentScope: observableOf('test-id')
|
||||
getCurrentScope: observableOf('test-id'),
|
||||
}
|
||||
},
|
||||
],
|
||||
@@ -103,6 +102,14 @@ describe('SearchSettingsComponent', () => {
|
||||
fixture = TestBed.createComponent(SearchSettingsComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
comp.sortOptions = [
|
||||
new SortOptions('score', SortDirection.DESC),
|
||||
new SortOptions('dc.title', SortDirection.ASC),
|
||||
new SortOptions('dc.title', SortDirection.DESC)
|
||||
];
|
||||
|
||||
comp.searchOptions = paginatedSearchOptions;
|
||||
|
||||
// SearchPageComponent test instance
|
||||
fixture.detectChanges();
|
||||
searchServiceObject = (comp as any).service;
|
||||
@@ -111,34 +118,24 @@ describe('SearchSettingsComponent', () => {
|
||||
|
||||
});
|
||||
|
||||
it('it should show the order settings with the respective selectable options', (done) => {
|
||||
(comp as any).searchOptions$.pipe(take(1)).subscribe((options) => {
|
||||
it('it should show the order settings with the respective selectable options', () => {
|
||||
fixture.detectChanges();
|
||||
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
||||
expect(orderSetting).toBeDefined();
|
||||
const childElements = orderSetting.queryAll(By.css('option'));
|
||||
expect(childElements.length).toEqual(comp.searchOptionPossibilities.length);
|
||||
done();
|
||||
});
|
||||
expect(childElements.length).toEqual(comp.sortOptions.length);
|
||||
});
|
||||
|
||||
it('it should show the size settings', (done) => {
|
||||
(comp as any).searchOptions$.pipe(take(1)).subscribe((options) => {
|
||||
it('it should show the size settings', () => {
|
||||
fixture.detectChanges();
|
||||
const pageSizeSetting = fixture.debugElement.query(By.css('page-size-settings'));
|
||||
expect(pageSizeSetting).toBeDefined();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should have the proper order value selected by default', (done) => {
|
||||
(comp as any).searchOptions$.pipe(take(1)).subscribe((options) => {
|
||||
it('should have the proper order value selected by default', () => {
|
||||
fixture.detectChanges();
|
||||
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
||||
const childElementToBeSelected = orderSetting.query(By.css('option[value="0"][selected="selected"]'));
|
||||
const childElementToBeSelected = orderSetting.query(By.css('option[value="score,DESC"][selected="selected"]'));
|
||||
expect(childElementToBeSelected).toBeDefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject, Input } from '@angular/core';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
@@ -17,16 +16,17 @@ import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
/**
|
||||
* This component represents the part of the search sidebar that contains the general search settings.
|
||||
*/
|
||||
export class SearchSettingsComponent implements OnInit {
|
||||
export class SearchSettingsComponent {
|
||||
|
||||
/**
|
||||
* The configuration for the current paginated search results
|
||||
*/
|
||||
searchOptions$: Observable<PaginatedSearchOptions>;
|
||||
@Input() searchOptions: PaginatedSearchOptions;
|
||||
|
||||
/**
|
||||
* All sort options that are shown in the settings
|
||||
*/
|
||||
searchOptionPossibilities = [new SortOptions('score', SortDirection.DESC), new SortOptions('dc.title', SortDirection.ASC), new SortOptions('dc.title', SortDirection.DESC)];
|
||||
@Input() sortOptions: SortOptions[];
|
||||
|
||||
constructor(private service: SearchService,
|
||||
private route: ActivatedRoute,
|
||||
@@ -35,13 +35,6 @@ export class SearchSettingsComponent implements OnInit {
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigurationService: SearchConfigurationService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize paginated search options
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.searchOptions$ = this.searchConfigurationService.paginatedSearchOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to change the current sort field and direction
|
||||
* @param {Event} event Change event containing the sort direction and sort field
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<div class="sidebar-content">
|
||||
<ds-search-switch-configuration [inPlaceSearch]="inPlaceSearch" *ngIf="configurationList" [configurationList]="configurationList"></ds-search-switch-configuration>
|
||||
<ds-search-filters [refreshFilters]="refreshFilters" [inPlaceSearch]="inPlaceSearch"></ds-search-filters>
|
||||
<ds-search-settings></ds-search-settings>
|
||||
<ds-search-settings [searchOptions]="searchOptions" [sortOptions]="sortOptions"></ds-search-settings>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -2,6 +2,8 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
import { SearchConfigurationOption } from '../search-switch-configuration/search-configuration-option.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||
import { SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -45,6 +47,16 @@ export class SearchSidebarComponent {
|
||||
*/
|
||||
@Input() inPlaceSearch;
|
||||
|
||||
/**
|
||||
* The configuration for the current paginated search results
|
||||
*/
|
||||
@Input() searchOptions: PaginatedSearchOptions;
|
||||
|
||||
/**
|
||||
* All sort options that are shown in the settings
|
||||
*/
|
||||
@Input() sortOptions: SortOptions[];
|
||||
|
||||
/**
|
||||
* Emits when the search filters values may be stale, and so they must be refreshed.
|
||||
*/
|
||||
|
@@ -3010,6 +3010,8 @@
|
||||
"search.results.empty": "Your search returned no results.",
|
||||
|
||||
|
||||
"default.search.results.head": "Search Results",
|
||||
|
||||
|
||||
"search.sidebar.close": "Back to results",
|
||||
|
||||
@@ -3043,8 +3045,21 @@
|
||||
|
||||
"sorting.dc.title.DESC": "Title Descending",
|
||||
|
||||
"sorting.score.DESC": "Relevance",
|
||||
"sorting.score.ASC": "Least Relevant",
|
||||
|
||||
"sorting.score.DESC": "Most Relevant",
|
||||
|
||||
"sorting.dc.date.issued.ASC": "Date Issued Ascending",
|
||||
|
||||
"sorting.dc.date.issued.DESC": "Date Issued Descending",
|
||||
|
||||
"sorting.dc.date.accessioned.ASC": "Accessioned Date Ascending",
|
||||
|
||||
"sorting.dc.date.accessioned.DESC": "Accessioned Date Descending",
|
||||
|
||||
"sorting.lastModified.ASC": "Last modified Ascending",
|
||||
|
||||
"sorting.lastModified.DESC": "Last modified Descending",
|
||||
|
||||
|
||||
"statistics.title": "Statistics",
|
||||
|
Reference in New Issue
Block a user