From 9b8ada0326049ad2e17d6684a304b2a99202a931 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 6 Apr 2021 10:06:15 +0200 Subject: [PATCH 01/10] [CST-4009] Discovery result sort options not reflecting what is configured --- .../my-dspace-page.component.html | 4 + .../my-dspace-page.component.spec.ts | 24 +++++- .../my-dspace-page.component.ts | 34 ++++++++- src/app/core/core.module.ts | 2 + .../search-filters/search-config.model.ts | 75 ++++++++++++++++++ .../search-config.resource-type.ts | 9 +++ .../core/shared/search/search.service.spec.ts | 50 ++++++++++++ src/app/core/shared/search/search.service.ts | 76 +++++++++++++------ .../search-settings.component.html | 6 +- .../search-settings.component.spec.ts | 51 ++++++------- .../search-settings.component.ts | 19 ++--- .../search-sidebar.component.html | 2 +- .../search-sidebar.component.ts | 12 +++ 13 files changed, 292 insertions(+), 72 deletions(-) create mode 100644 src/app/core/shared/search/search-filters/search-config.model.ts create mode 100644 src/app/core/shared/search/search-filters/search-config.resource-type.ts diff --git a/src/app/+my-dspace-page/my-dspace-page.component.html b/src/app/+my-dspace-page/my-dspace-page.component.html index c911e2c319..32e3a0d710 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.html +++ b/src/app/+my-dspace-page/my-dspace-page.component.html @@ -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">
@@ -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"> diff --git a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts index 59581d0da8..909239b61c 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts @@ -45,6 +45,7 @@ describe('MyDSpacePageComponent', () => { pagination.id = 'mydspace-results-pagination'; pagination.currentPage = 1; pagination.pageSize = 10; + const sortOption = { name: 'score', 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(); + }); + + }); + + }); }); diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts index 5ee2a47d9f..ba5c0e5cc7 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.ts @@ -7,8 +7,8 @@ import { OnInit } from '@angular/core'; -import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; -import { map, switchMap, tap, } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, Subject, Subscription } from 'rxjs'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { PaginatedList } from '../core/data/paginated-list.model'; import { RemoteData } from '../core/data/remote-data'; @@ -19,7 +19,7 @@ import { PaginatedSearchOptions } from '../shared/search/paginated-search-option import { SearchService } from '../core/shared/search/search.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { hasValue } from '../shared/empty.util'; -import { getFirstSucceededRemoteData } from '../core/shared/operators'; +import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service'; import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model'; import { RoleType } from '../core/roles/role-types'; @@ -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 { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; +import { SearchConfig } from '../core/shared/search/search-filters/search-config.model'; export const MYDSPACE_ROUTE = '/mydspace'; export const SEARCH_CONFIG_SERVICE: InjectionToken = new InjectionToken('searchConfigurationService'); @@ -71,6 +73,11 @@ export class MyDSpacePageComponent implements OnInit { */ searchOptions$: Observable; + /** + * The current available sort options + */ + sortOptions$: Observable; + /** * The current relevant scopes */ @@ -151,6 +158,27 @@ export class MyDSpacePageComponent implements OnInit { }) ); + this.sortOptions$ = this.context$.pipe( + switchMap((context) => this.service.getSearchConfigurationFor(null, context)), + getFirstSucceededRemoteDataPayload(), + map((searchConfig: 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; + })); + + combineLatest([ + this.sortOptions$, + this.searchConfigService.paginatedSearchOptions + ]).pipe(take(1)) + .subscribe(([sortOptions, searchOptions]) => { + const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, { sort: sortOptions[0]}); + this.searchConfigService.paginatedSearchOptions.next(updateValue); + }); + } /** diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index f73bfd0bdf..62265fdedf 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -162,6 +162,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 @@ -342,6 +343,7 @@ export const models = Registration, UsageReport, Root, + SearchConfig ]; @NgModule({ diff --git a/src/app/core/shared/search/search-filters/search-config.model.ts b/src/app/core/shared/search/search-filters/search-config.model.ts new file mode 100644 index 0000000000..dd7a799f37 --- /dev/null +++ b/src/app/core/shared/search/search-filters/search-config.model.ts @@ -0,0 +1,75 @@ +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; +} + +/** + * Interface to model operator's configuration. + */ +export interface OperatorConfig { + operator: string; +} diff --git a/src/app/core/shared/search/search-filters/search-config.resource-type.ts b/src/app/core/shared/search/search-filters/search-config.resource-type.ts new file mode 100644 index 0000000000..967a654006 --- /dev/null +++ b/src/app/core/shared/search/search-filters/search-config.resource-type.ts @@ -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'); diff --git a/src/app/core/shared/search/search.service.spec.ts b/src/app/core/shared/search/search.service.spec.ts index 06208094bd..d09444ad6c 100644 --- a/src/app/core/shared/search/search.service.spec.ts +++ b/src/app/core/shared/search/search.service.spec.ts @@ -230,5 +230,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); + }); + }); + }); }); diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index b380a70d44..7747717830 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -37,12 +37,19 @@ 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'; /** * Service that performs all general actions that have to do with the search page */ @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 */ @@ -224,6 +231,24 @@ export class SearchService implements OnDestroy { ); } + private getConfigUrl(url: string, scope?: string, configurationName?: string) { + const args: string[] = []; + + if (isNotEmpty(scope)) { + args.push(`scope=${scope}`); + } + + if (isNotEmpty(configurationName)) { + args.push(`configuration=${configurationName}`); + } + + if (isNotEmpty(args)) { + url = new URLCombiner(url, `?${args.join('&')}`).toString(); + } + + 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 @@ -232,33 +257,17 @@ export class SearchService implements OnDestroy { */ getConfig(scope?: string, configurationName?: string): Observable> { const href$ = this.halService.getEndpoint(this.facetLinkPathPrefix).pipe( - map((url: string) => { - const args: string[] = []; - - if (isNotEmpty(scope)) { - args.push(`scope=${scope}`); - } - - if (isNotEmpty(configurationName)) { - args.push(`configuration=${configurationName}`); - } - - if (isNotEmpty(args)) { - url = new URLCombiner(url, `?${args.join('&')}`).toString(); - } - - return url; - }), + map((url: string) => this.getConfigUrl(url, scope, configurationName)), ); href$.pipe(take(1)).subscribe((url: string) => { - let request = new this.request(this.requestService.generateRequestId(), url); - request = Object.assign(request, { - getResponseParser(): GenericConstructor { - return FacetConfigResponseParsingService; - } - }); - this.requestService.send(request, true); + let request = new this.request(this.requestService.generateRequestId(), url); + request = Object.assign(request, { + getResponseParser(): GenericConstructor { + return FacetConfigResponseParsingService; + } + }); + this.requestService.send(request, true); }); return this.rdb.buildFromHref(href$).pipe( @@ -397,6 +406,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>} The found configuration + */ + getSearchConfigurationFor(scope?: string, configurationName?: string ): Observable> { + 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 */ diff --git a/src/app/shared/search/search-settings/search-settings.component.html b/src/app/shared/search/search-settings/search-settings.component.html index f6806e6843..a31678743d 100644 --- a/src/app/shared/search/search-settings/search-settings.component.html +++ b/src/app/shared/search/search-settings/search-settings.component.html @@ -1,4 +1,4 @@ - +

{{ 'search.sidebar.settings.title' | translate}}

-
-
\ No newline at end of file +
diff --git a/src/app/shared/search/search-settings/search-settings.component.spec.ts b/src/app/shared/search/search-settings/search-settings.component.spec.ts index cd4a872815..221e3a0dea 100644 --- a/src/app/shared/search/search-settings/search-settings.component.spec.ts +++ b/src/app/shared/search/search-settings/search-settings.component.spec.ts @@ -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'; @@ -81,7 +80,7 @@ describe('SearchSettingsComponent', () => { provide: SEARCH_CONFIG_SERVICE, useValue: { paginatedSearchOptions: observableOf(paginatedSearchOptions), - getCurrentScope: observableOf('test-id') + getCurrentScope: observableOf('test-id'), } }, ], @@ -93,6 +92,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; @@ -101,34 +108,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) => { - 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(); - }); + 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.sortOptions.length); }); - it('it should show the size settings', (done) => { - (comp as any).searchOptions$.pipe(take(1)).subscribe((options) => { - fixture.detectChanges(); - const pageSizeSetting = fixture.debugElement.query(By.css('page-size-settings')); - expect(pageSizeSetting).toBeDefined(); - done(); - } - ); + it('it should show the size settings', () => { + fixture.detectChanges(); + const pageSizeSetting = fixture.debugElement.query(By.css('page-size-settings')); + expect(pageSizeSetting).toBeDefined(); }); - it('should have the proper order value selected by default', (done) => { - (comp as any).searchOptions$.pipe(take(1)).subscribe((options) => { - fixture.detectChanges(); - const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings')); - const childElementToBeSelected = orderSetting.query(By.css('option[value="0"][selected="selected"]')); - expect(childElementToBeSelected).toBeDefined(); - done(); - }); + 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="score,DESC"][selected="selected"]')); + expect(childElementToBeSelected).toBeDefined(); }); }); diff --git a/src/app/shared/search/search-settings/search-settings.component.ts b/src/app/shared/search/search-settings/search-settings.component.ts index 45d7c7b432..d85f234aa3 100644 --- a/src/app/shared/search/search-settings/search-settings.component.ts +++ b/src/app/shared/search/search-settings/search-settings.component.ts @@ -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 { SortOptions } from '../../../core/cache/models/sort-options.model'; import { ActivatedRoute, NavigationExtras, 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'; @@ -16,16 +15,17 @@ import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.c /** * 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; + @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, @@ -33,13 +33,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 diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.html b/src/app/shared/search/search-sidebar/search-sidebar.component.html index 74abeadfd8..624d094d22 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.html +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.html @@ -12,7 +12,7 @@
diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.ts b/src/app/shared/search/search-sidebar/search-sidebar.component.ts index 2060e0f345..7f134cacd3 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.ts +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.ts @@ -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. */ From 4d85c0270f31332fad98e6effb68bf50e68b0ea7 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 6 Apr 2021 10:30:26 +0200 Subject: [PATCH 02/10] [CST-4009] sorting options translations --- src/assets/i18n/en.json5 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 2924a3a47e..20a21f93e8 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3037,8 +3037,13 @@ "sorting.dc.title.DESC": "Title Descending", - "sorting.score.DESC": "Relevance", + "sorting.score.ASC": "Relevance Ascending", + "sorting.score.DESC": "Relevance Descending", + + "sorting.dc.date.issued.ASC": "Date Issued Ascending", + + "sorting.dc.date.issued.DESC": "Date Issued Descending", "statistics.title": "Statistics", From b4686deb63e1df21fb0bf3889a4b32ac52e038d4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 6 Apr 2021 17:49:06 +0200 Subject: [PATCH 03/10] [CST-4009] Retrieve sort options also for search page --- src/app/+search-page/search.component.html | 7 +++- src/app/+search-page/search.component.spec.ts | 24 ++++++++++- src/app/+search-page/search.component.ts | 40 ++++++++++++++++--- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/app/+search-page/search.component.html b/src/app/+search-page/search.component.html index 7cb16caebe..d8aa25e4a3 100644 --- a/src/app/+search-page/search.component.html +++ b/src/app/+search-page/search.component.html @@ -31,11 +31,14 @@ + [searchOptions]="(searchOptions$ | async)" + [sortOptions]="(sortOptions$ | async)" + (toggleSidebar)="closeSidebar()"> diff --git a/src/app/+search-page/search.component.spec.ts b/src/app/+search-page/search.component.spec.ts index 989aed403d..06061c1d40 100644 --- a/src/app/+search-page/search.component.spec.ts +++ b/src/app/+search-page/search.component.spec.ts @@ -40,12 +40,14 @@ const pagination: PaginationComponentOptions = new PaginationComponentOptions(); pagination.id = 'search-results-pagination'; pagination.currentPage = 1; pagination.pageSize = 10; +const sortOption = { name: 'score', 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(); + }); + + }); + + }); }); diff --git a/src/app/+search-page/search.component.ts b/src/app/+search-page/search.component.ts index 84077ebdc8..43535278a1 100644 --- a/src/app/+search-page/search.component.ts +++ b/src/app/+search-page/search.component.ts @@ -1,14 +1,14 @@ import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; -import { startWith, switchMap, } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; +import { map, startWith, switchMap, take, } 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 { getFirstSucceededRemoteData } from '../core/shared/operators'; +import { hasValue, isEmpty } from '../shared/empty.util'; +import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { RouteService } from '../core/services/route.service'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model'; @@ -18,6 +18,8 @@ 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 { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; +import { SearchConfig } from '../core/shared/search/search-filters/search-config.model'; @Component({ selector: 'ds-search', @@ -47,6 +49,11 @@ export class SearchComponent implements OnInit { */ searchOptions$: Observable; + /** + * The current available sort options + */ + sortOptions$: Observable; + /** * The current relevant scopes */ @@ -129,9 +136,32 @@ export class SearchComponent implements OnInit { this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe( switchMap((scopeId) => this.service.getScopes(scopeId)) ); - if (!isNotEmpty(this.configuration$)) { + if (isEmpty(this.configuration$)) { this.configuration$ = this.routeService.getRouteParameterValue('configuration'); } + + this.sortOptions$ = this.configuration$.pipe( + switchMap((configuration) => this.service.getSearchConfigurationFor(null, configuration)), + getFirstSucceededRemoteDataPayload(), + map((searchConfig: SearchConfig) => { + const sortOptions = []; + searchConfig.sortOptions.forEach(sortOption => { + sortOptions.push(new SortOptions(sortOption.name, SortDirection.ASC)); + sortOptions.push(new SortOptions(sortOption.name, SortDirection.DESC)); + }); + console.log(searchConfig, sortOptions); + return sortOptions; + })); + + combineLatest([ + this.sortOptions$, + this.searchConfigService.paginatedSearchOptions + ]).pipe(take(1)) + .subscribe(([sortOptions, searchOptions]) => { + const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, { sort: sortOptions[0]}); + console.log(updateValue); + this.searchConfigService.paginatedSearchOptions.next(updateValue); + }); } /** From d54b7d9f7c8cbcffea2c9165e35861d4b7d2a1c1 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Thu, 8 Apr 2021 15:43:07 +0200 Subject: [PATCH 04/10] [CST-4009] fix sort options on pagination change --- .../my-dspace-page.component.ts | 29 +++------ src/app/+search-page/search.component.ts | 29 ++------- .../search/search-configuration.service.ts | 63 +++++++++++++++++-- src/assets/i18n/en.json5 | 4 ++ 4 files changed, 77 insertions(+), 48 deletions(-) diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts index ba5c0e5cc7..de51d9afd3 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.ts @@ -31,6 +31,8 @@ import { SearchResult } from '../shared/search/search-result.model'; import { Context } from '../core/shared/context.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { SearchConfig } from '../core/shared/search/search-filters/search-config.model'; +import {RouteService} from '../core/services/route.service'; +import {Router} from '@angular/router'; export const MYDSPACE_ROUTE = '/mydspace'; export const SEARCH_CONFIG_SERVICE: InjectionToken = new InjectionToken('searchConfigurationService'); @@ -116,7 +118,9 @@ 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, + private router: Router) { this.isXsOrSm$ = this.windowService.isXsOrSm(); this.service.setServiceOptions(MyDSpaceResponseParsingService, MyDSpaceRequest); } @@ -158,26 +162,11 @@ export class MyDSpacePageComponent implements OnInit { }) ); - this.sortOptions$ = this.context$.pipe( - switchMap((context) => this.service.getSearchConfigurationFor(null, context)), - getFirstSucceededRemoteDataPayload(), - map((searchConfig: 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; - })); + const configuration$ = this.routeService.getRouteParameterValue('configuration'); - combineLatest([ - this.sortOptions$, - this.searchConfigService.paginatedSearchOptions - ]).pipe(take(1)) - .subscribe(([sortOptions, searchOptions]) => { - const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, { sort: sortOptions[0]}); - this.searchConfigService.paginatedSearchOptions.next(updateValue); - }); + this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(configuration$, this.service); + + this.searchConfigService.initializeSortOptionsFromConfiguration(this.sortOptions$, this.router); } diff --git a/src/app/+search-page/search.component.ts b/src/app/+search-page/search.component.ts index 43535278a1..c5813a2fd1 100644 --- a/src/app/+search-page/search.component.ts +++ b/src/app/+search-page/search.component.ts @@ -16,10 +16,9 @@ import { SearchResult } from '../shared/search/search-result.model'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; import { SearchService } from '../core/shared/search/search.service'; import { currentPath } from '../shared/utils/route.utils'; -import { Router } from '@angular/router'; +import { Router} from '@angular/router'; import { Context } from '../core/shared/context.model'; -import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; -import { SearchConfig } from '../core/shared/search/search-filters/search-config.model'; +import { SortOptions } from '../core/cache/models/sort-options.model'; @Component({ selector: 'ds-search', @@ -140,28 +139,10 @@ export class SearchComponent implements OnInit { this.configuration$ = this.routeService.getRouteParameterValue('configuration'); } - this.sortOptions$ = this.configuration$.pipe( - switchMap((configuration) => this.service.getSearchConfigurationFor(null, configuration)), - getFirstSucceededRemoteDataPayload(), - map((searchConfig: SearchConfig) => { - const sortOptions = []; - searchConfig.sortOptions.forEach(sortOption => { - sortOptions.push(new SortOptions(sortOption.name, SortDirection.ASC)); - sortOptions.push(new SortOptions(sortOption.name, SortDirection.DESC)); - }); - console.log(searchConfig, sortOptions); - return sortOptions; - })); + this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(this.configuration$, this.service); + + this.searchConfigService.initializeSortOptionsFromConfiguration(this.sortOptions$, this.router); - combineLatest([ - this.sortOptions$, - this.searchConfigService.paginatedSearchOptions - ]).pipe(take(1)) - .subscribe(([sortOptions, searchOptions]) => { - const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, { sort: sortOptions[0]}); - console.log(updateValue); - this.searchConfigService.paginatedSearchOptions.next(updateValue); - }); } /** diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index edd3982319..c114ee6616 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -1,8 +1,15 @@ import { Injectable, OnDestroy } from '@angular/core'; -import { ActivatedRoute, Params } from '@angular/router'; +import { ActivatedRoute, NavigationExtras, Params, Router } 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,12 @@ 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 { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } 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'; /** * Service that performs all actions that have to do with the current search configuration @@ -209,6 +219,51 @@ export class SearchConfigurationService implements OnDestroy { return this.routeService.getQueryParamsWithPrefix('f.'); } + /** + * Creates an observable of SortOptions[] every time the configuration$ stream emits. + * @param configuration$ + * @param service + */ + getConfigurationSortOptionsObservable(configuration$: Observable, service: SearchService): Observable { + return configuration$.pipe( + distinctUntilChanged(), + switchMap((configuration) => service.getSearchConfigurationFor(null, configuration)), + getFirstSucceededRemoteDataPayload(), + map((searchConfig: 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; + })); + } + + /** + * Every time sortOptions 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(sortOptions$: Observable, router: Router) { + const subscription = sortOptions$.pipe(switchMap((sortOptions) => combineLatest([ + of(sortOptions), + this.paginatedSearchOptions.pipe(take(1)) + ]))).subscribe(([sortOptions, searchOptions]) => { + const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, { sort: sortOptions[0]}); + const navigationExtras: NavigationExtras = { + queryParams: { + sortDirection: updateValue.sort.direction, + sortField: updateValue.sort.field, + }, + queryParamsHandling: 'merge' + }; + router.navigate([], navigationExtras); + this.paginatedSearchOptions.next(updateValue); + }); + this.subs.push(subscription); + } + /** * 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 diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 20a21f93e8..bcf2a92666 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3045,6 +3045,10 @@ "sorting.dc.date.issued.DESC": "Date Issued Descending", + "sorting.dc.date.accessioned.ASC": "Accessioned Date Ascending", + + "sorting.dc.date.accessioned.DESC": "Accessioned Date Descending", + "statistics.title": "Statistics", From 23fe338c5d68fc15e3e2f3abfb6af0ec825f1a69 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Thu, 8 Apr 2021 16:18:31 +0200 Subject: [PATCH 05/10] [CST-4009] fix sort options on pagination change --- .../+my-dspace-page/my-dspace-page.component.ts | 17 +++++++---------- src/app/+search-page/search.component.ts | 2 +- .../search/search-configuration.service.ts | 13 +++++-------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts index de51d9afd3..b95a296ed1 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.ts @@ -7,8 +7,8 @@ import { OnInit } from '@angular/core'; -import { BehaviorSubject, combineLatest, Observable, Subject, Subscription } from 'rxjs'; -import { map, switchMap, take, tap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; +import { map, switchMap, tap } from 'rxjs/operators'; import { PaginatedList } from '../core/data/paginated-list.model'; import { RemoteData } from '../core/data/remote-data'; @@ -19,7 +19,7 @@ import { PaginatedSearchOptions } from '../shared/search/paginated-search-option import { SearchService } from '../core/shared/search/search.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { hasValue } from '../shared/empty.util'; -import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; +import { getFirstSucceededRemoteData } from '../core/shared/operators'; import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service'; import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model'; import { RoleType } from '../core/roles/role-types'; @@ -29,10 +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 { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; -import { SearchConfig } from '../core/shared/search/search-filters/search-config.model'; -import {RouteService} from '../core/services/route.service'; -import {Router} from '@angular/router'; +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 = new InjectionToken('searchConfigurationService'); @@ -119,8 +117,7 @@ export class MyDSpacePageComponent implements OnInit { private sidebarService: SidebarService, private windowService: HostWindowService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService, - private routeService: RouteService, - private router: Router) { + private routeService: RouteService) { this.isXsOrSm$ = this.windowService.isXsOrSm(); this.service.setServiceOptions(MyDSpaceResponseParsingService, MyDSpaceRequest); } @@ -166,7 +163,7 @@ export class MyDSpacePageComponent implements OnInit { this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(configuration$, this.service); - this.searchConfigService.initializeSortOptionsFromConfiguration(this.sortOptions$, this.router); + this.searchConfigService.initializeSortOptionsFromConfiguration(this.sortOptions$); } diff --git a/src/app/+search-page/search.component.ts b/src/app/+search-page/search.component.ts index c5813a2fd1..bffa958ee5 100644 --- a/src/app/+search-page/search.component.ts +++ b/src/app/+search-page/search.component.ts @@ -141,7 +141,7 @@ export class SearchComponent implements OnInit { this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(this.configuration$, this.service); - this.searchConfigService.initializeSortOptionsFromConfiguration(this.sortOptions$, this.router); + this.searchConfigService.initializeSortOptionsFromConfiguration(this.sortOptions$); } diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index 339eefdc6b..e10ab668cc 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -1,5 +1,5 @@ import { Injectable, OnDestroy } from '@angular/core'; -import { ActivatedRoute, NavigationExtras, Params, Router } from '@angular/router'; +import { ActivatedRoute, Params } from '@angular/router'; import { BehaviorSubject, @@ -230,20 +230,17 @@ export class SearchConfigurationService implements OnDestroy { * @param configuration$ * @param service */ - initializeSortOptionsFromConfiguration(sortOptions$: Observable, router: Router) { + initializeSortOptionsFromConfiguration(sortOptions$: Observable) { const subscription = sortOptions$.pipe(switchMap((sortOptions) => combineLatest([ of(sortOptions), this.paginatedSearchOptions.pipe(take(1)) ]))).subscribe(([sortOptions, searchOptions]) => { const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, { sort: sortOptions[0]}); - const navigationExtras: NavigationExtras = { - queryParams: { + this.paginationService.updateRoute(this.paginationID, + { sortDirection: updateValue.sort.direction, sortField: updateValue.sort.field, - }, - queryParamsHandling: 'merge' - }; - router.navigate([], navigationExtras); + }); this.paginatedSearchOptions.next(updateValue); }); this.subs.push(subscription); From a205aa02b3316d97c1bc9650987375f827cc79b2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 15 Apr 2021 13:01:31 +0200 Subject: [PATCH 06/10] [CST-4009] Retrieve configuration by search config service --- src/app/+my-dspace-page/my-dspace-page.component.ts | 2 +- src/app/+search-page/search.component.ts | 2 +- src/assets/i18n/en.json5 | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts index b95a296ed1..a5dcfe96be 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.ts @@ -159,7 +159,7 @@ export class MyDSpacePageComponent implements OnInit { }) ); - const configuration$ = this.routeService.getRouteParameterValue('configuration'); + const configuration$ = this.searchConfigService.getCurrentConfiguration('workspace'); this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(configuration$, this.service); diff --git a/src/app/+search-page/search.component.ts b/src/app/+search-page/search.component.ts index bffa958ee5..ce08d2c365 100644 --- a/src/app/+search-page/search.component.ts +++ b/src/app/+search-page/search.component.ts @@ -136,7 +136,7 @@ export class SearchComponent implements OnInit { switchMap((scopeId) => this.service.getScopes(scopeId)) ); if (isEmpty(this.configuration$)) { - this.configuration$ = this.routeService.getRouteParameterValue('configuration'); + this.configuration$ = this.searchConfigService.getCurrentConfiguration('default'); } this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(this.configuration$, this.service); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4ea4d1de42..a63d57d0d8 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3053,6 +3053,10 @@ "sorting.dc.date.accessioned.DESC": "Accessioned Date Descending", + "sorting.lastModified.ASC": "Last modified Ascending", + + "sorting.lastModified.DESC": "Last modified Descending", + "statistics.title": "Statistics", From b6ab3d2067031dfed44e4015112bd7e46d8758c8 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 21 Apr 2021 16:06:15 +0200 Subject: [PATCH 07/10] [CST-4087] fix issue with mydspace result default order --- .../shared/search/search-configuration.service.ts | 11 +++++++---- .../search/search-filters/search-config.model.ts | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index e10ab668cc..5de30fc4a7 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -21,7 +21,7 @@ import { RouteService } from '../../services/route.service'; import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } 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 { SearchConfig, SortOption } from './search-filters/search-config.model'; import { SearchService } from './search.service'; import { of } from 'rxjs/internal/observable/of'; import { PaginationService } from '../../pagination/pagination.service'; @@ -216,9 +216,12 @@ export class SearchConfigurationService implements OnDestroy { getFirstSucceededRemoteDataPayload(), map((searchConfig: SearchConfig) => { const sortOptions = []; - searchConfig.sortOptions.forEach(sortOption => { - sortOptions.push(new SortOptions(sortOption.name, SortDirection.ASC)); - sortOptions.push(new SortOptions(sortOption.name, SortDirection.DESC)); + searchConfig.sortOptions.forEach((sortOption: SortOption) => { + console.log(sortOption); + const firstOrder = (sortOption.sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase()) ? SortDirection.ASC : SortDirection.DESC; + const secondOrder = (sortOption.sortOrder.toLowerCase() !== SortDirection.ASC.toLowerCase()) ? SortDirection.ASC : SortDirection.DESC; + sortOptions.push(new SortOptions(sortOption.name, firstOrder)); + sortOptions.push(new SortOptions(sortOption.name, secondOrder)); }); return sortOptions; })); diff --git a/src/app/core/shared/search/search-filters/search-config.model.ts b/src/app/core/shared/search/search-filters/search-config.model.ts index dd7a799f37..725761fe7b 100644 --- a/src/app/core/shared/search/search-filters/search-config.model.ts +++ b/src/app/core/shared/search/search-filters/search-config.model.ts @@ -65,6 +65,7 @@ export interface FilterConfig { */ export interface SortOption { name: string; + sortOrder: string; } /** From 7c0d9acbf1bca7a340cf040d80b00a80322f1c1e Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Fri, 23 Apr 2021 16:46:44 +0200 Subject: [PATCH 08/10] [CST-4009] default sort option configured with default sort order --- .../my-dspace-page.component.spec.ts | 2 +- .../my-dspace-page.component.ts | 6 +-- src/app/+search-page/search.component.spec.ts | 2 +- src/app/+search-page/search.component.ts | 11 ++-- .../search/search-configuration.service.ts | 51 +++++++++++-------- 5 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts index 909239b61c..b4b75b42a0 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts @@ -45,7 +45,7 @@ describe('MyDSpacePageComponent', () => { pagination.id = 'mydspace-results-pagination'; pagination.currentPage = 1; pagination.pageSize = 10; - const sortOption = { name: 'score', metadata: null }; + 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', { diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts index a5dcfe96be..3ded17191e 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.ts @@ -160,10 +160,10 @@ export class MyDSpacePageComponent implements OnInit { ); const configuration$ = this.searchConfigService.getCurrentConfiguration('workspace'); + const searchConfig$ = this.searchConfigService.getConfigurationSearchConfigObservable(configuration$, this.service); - this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(configuration$, this.service); - - this.searchConfigService.initializeSortOptionsFromConfiguration(this.sortOptions$); + this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(searchConfig$); + this.searchConfigService.initializeSortOptionsFromConfiguration(searchConfig$); } diff --git a/src/app/+search-page/search.component.spec.ts b/src/app/+search-page/search.component.spec.ts index 06061c1d40..bcbf3f0f77 100644 --- a/src/app/+search-page/search.component.spec.ts +++ b/src/app/+search-page/search.component.spec.ts @@ -40,7 +40,7 @@ const pagination: PaginationComponentOptions = new PaginationComponentOptions(); pagination.id = 'search-results-pagination'; pagination.currentPage = 1; pagination.pageSize = 10; -const sortOption = { name: 'score', metadata: null }; +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', { diff --git a/src/app/+search-page/search.component.ts b/src/app/+search-page/search.component.ts index ce08d2c365..b817c82a57 100644 --- a/src/app/+search-page/search.component.ts +++ b/src/app/+search-page/search.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; -import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; -import { map, startWith, switchMap, take, } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +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'; @@ -8,7 +8,7 @@ import { pushInOut } from '../shared/animations/push'; import { HostWindowService } from '../shared/host-window.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { hasValue, isEmpty } from '../shared/empty.util'; -import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; +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'; import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model'; @@ -139,9 +139,10 @@ export class SearchComponent implements OnInit { this.configuration$ = this.searchConfigService.getCurrentConfiguration('default'); } - this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(this.configuration$, this.service); + const searchConfig$ = this.searchConfigService.getConfigurationSearchConfigObservable(this.configuration$, this.service); - this.searchConfigService.initializeSortOptionsFromConfiguration(this.sortOptions$); + this.sortOptions$ = this.searchConfigService.getConfigurationSortOptionsObservable(searchConfig$); + this.searchConfigService.initializeSortOptionsFromConfiguration(searchConfig$); } diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index 5de30fc4a7..c209d79e40 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -21,7 +21,7 @@ import { RouteService } from '../../services/route.service'; import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../operators'; import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; -import { SearchConfig, SortOption } from './search-filters/search-config.model'; +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'; @@ -205,40 +205,33 @@ export class SearchConfigurationService implements OnDestroy { } /** - * Creates an observable of SortOptions[] every time the configuration$ stream emits. + * Creates an observable of SearchConfig every time the configuration$ stream emits. * @param configuration$ * @param service */ - getConfigurationSortOptionsObservable(configuration$: Observable, service: SearchService): Observable { + getConfigurationSearchConfigObservable(configuration$: Observable, service: SearchService): Observable { return configuration$.pipe( distinctUntilChanged(), switchMap((configuration) => service.getSearchConfigurationFor(null, configuration)), - getFirstSucceededRemoteDataPayload(), - map((searchConfig: SearchConfig) => { - const sortOptions = []; - searchConfig.sortOptions.forEach((sortOption: SortOption) => { - console.log(sortOption); - const firstOrder = (sortOption.sortOrder.toLowerCase() === SortDirection.ASC.toLowerCase()) ? SortDirection.ASC : SortDirection.DESC; - const secondOrder = (sortOption.sortOrder.toLowerCase() !== SortDirection.ASC.toLowerCase()) ? SortDirection.ASC : SortDirection.DESC; - sortOptions.push(new SortOptions(sortOption.name, firstOrder)); - sortOptions.push(new SortOptions(sortOption.name, secondOrder)); - }); - return sortOptions; - })); + getFirstSucceededRemoteDataPayload()); } /** - * Every time sortOptions change (after a configuration change) it update the navigation with the default sort option + * 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(sortOptions$: Observable) { - const subscription = sortOptions$.pipe(switchMap((sortOptions) => combineLatest([ - of(sortOptions), + initializeSortOptionsFromConfiguration(searchConfig$: Observable) { + const subscription = searchConfig$.pipe(switchMap((searchConfig) => combineLatest([ + of(searchConfig), this.paginatedSearchOptions.pipe(take(1)) - ]))).subscribe(([sortOptions, searchOptions]) => { - const updateValue = Object.assign(new PaginatedSearchOptions({}), searchOptions, { sort: sortOptions[0]}); + ]))).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, @@ -249,6 +242,22 @@ export class SearchConfigurationService implements OnDestroy { this.subs.push(subscription); } + /** + * Creates an observable of available SortOptions[] every time the searchConfig$ stream emits. + * @param searchConfig$ + * @param service + */ + getConfigurationSortOptionsObservable(searchConfig$: Observable): Observable { + 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 From bdc2dd5f9ca98ddb0e89371e02201cdb5ef1d94a Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 27 Apr 2021 09:53:38 +0200 Subject: [PATCH 09/10] [CST-4009] fixed search configuration stream --- src/app/core/shared/search/search-configuration.service.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index c209d79e40..6a1373c87e 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -18,7 +18,10 @@ 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, getFirstSucceededRemoteDataPayload } 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'; @@ -213,7 +216,7 @@ export class SearchConfigurationService implements OnDestroy { return configuration$.pipe( distinctUntilChanged(), switchMap((configuration) => service.getSearchConfigurationFor(null, configuration)), - getFirstSucceededRemoteDataPayload()); + getAllSucceededRemoteDataPayload()); } /** From ad7824460b713bc3fd85d725cac8069838f1ae4a Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Wed, 28 Apr 2021 15:28:02 +0200 Subject: [PATCH 10/10] [CST-4009] update en.json5 --- src/assets/i18n/en.json5 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a63d57d0d8..23c889847f 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3008,6 +3008,8 @@ "search.results.empty": "Your search returned no results.", + "default.search.results.head": "Search Results", + "search.sidebar.close": "Back to results", @@ -3041,9 +3043,9 @@ "sorting.dc.title.DESC": "Title Descending", - "sorting.score.ASC": "Relevance Ascending", + "sorting.score.ASC": "Least Relevant", - "sorting.score.DESC": "Relevance Descending", + "sorting.score.DESC": "Most Relevant", "sorting.dc.date.issued.ASC": "Date Issued Ascending",