diff --git a/config/environment.default.js b/config/environment.default.js index ad7be69b9e..b44da0e248 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -18,7 +18,8 @@ module.exports = { // Caching settings cache: { // NOTE: how long should objects be cached for by default - msToLive: 15 * 60 * 1000, // 15 minute + msToLive: 15 * 60 * 1000, // 15 minutes + // msToLive: 1000, // 15 minutes control: 'max-age=60' // revalidate browser }, // Angular Universal settings diff --git a/package.json b/package.json index 710f62a257..ab875fd95e 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,7 @@ "@angular/compiler-cli": "^5.2.5", "@ngrx/store-devtools": "^5.1.0", "@ngtools/webpack": "^1.10.0", + "@types/acorn": "^4.0.3", "@types/cookie-parser": "1.4.1", "@types/deep-freeze": "0.1.1", "@types/express": "^4.11.1", diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 07f770825d..68dfcbe147 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -120,6 +120,10 @@ "dateIssued": { "placeholder": "Date", "head": "Date" + }, + "has_content_in_original_bundle": { + "placeholder": "Has files", + "head": "Has files" } } } diff --git a/src/app/+search-page/paginated-search-options.model.ts b/src/app/+search-page/paginated-search-options.model.ts new file mode 100644 index 0000000000..0c403af827 --- /dev/null +++ b/src/app/+search-page/paginated-search-options.model.ts @@ -0,0 +1,20 @@ +import { SortOptions } from '../core/cache/models/sort-options.model'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { isNotEmpty } from '../shared/empty.util'; +import { URLCombiner } from '../core/url-combiner/url-combiner'; +import { SearchOptions } from './search-options.model'; + +export class PaginatedSearchOptions extends SearchOptions { + pagination?: PaginationComponentOptions; + sort?: SortOptions; + toRestUrl(url: string, args: string[] = []): string { + if (isNotEmpty(this.sort)) { + args.push(`sort=${this.sort.field},${this.sort.direction}`); + } + if (isNotEmpty(this.pagination)) { + args.push(`page=${this.pagination.currentPage - 1}`); + args.push(`size=${this.pagination.pageSize}`); + } + return super.toRestUrl(url, args); + } +} diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.html index 114837ce65..aa71c5c24c 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.html @@ -2,26 +2,29 @@
+ [queryParams]="getRemoveParams(value)" queryParamsHandling="merge"> {{value}} - - + + + - {{value.value}} - - {{value.count}} - - - + {{value.value}} + + {{value.count}} + + + +
- {{"search.filters.filter.show-more" | translate}} - {{"search.filters.filter.show-less" + {{"search.filters.filter.show-less" | translate}}
diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 99501f346a..c7941ec5dc 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -1,10 +1,16 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { FacetValue } from '../../../search-service/facet-value.model'; import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; -import { Params, Router } from '@angular/router'; +import { Router } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { SearchFilterService } from '../search-filter.service'; -import { isNotEmpty } from '../../../../shared/empty.util'; +import { hasValue, isNotEmpty } from '../../../../shared/empty.util'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { SearchService } from '../../../search-service/search.service'; +import { SearchOptions } from '../../../search-options.model'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { Subscription } from 'rxjs/Subscription'; /** * This component renders a simple item page. @@ -15,21 +21,43 @@ import { isNotEmpty } from '../../../../shared/empty.util'; @Component({ selector: 'ds-search-facet-filter', styleUrls: ['./search-facet-filter.component.scss'], - templateUrl: './search-facet-filter.component.html', + templateUrl: './search-facet-filter.component.html' }) -export class SearchFacetFilterComponent implements OnInit { - @Input() filterValues: FacetValue[]; +export class SearchFacetFilterComponent implements OnInit, OnDestroy { @Input() filterConfig: SearchFilterConfig; @Input() selectedValues: string[]; + filterValues: Array>>> = []; + filterValues$: BehaviorSubject = new BehaviorSubject(this.filterValues); currentPage: Observable; filter: string; + pageChange = false; + sub: Subscription; - constructor(private filterService: SearchFilterService, private router: Router) { + constructor(private searchService: SearchService, private filterService: SearchFilterService, private router: Router) { } ngOnInit(): void { - this.currentPage = this.filterService.getPage(this.filterConfig.name); + this.currentPage = this.getCurrentPage(); + this.currentPage.distinctUntilChanged().subscribe((page) => this.pageChange = true); + this.filterService.getSearchOptions().distinctUntilChanged().subscribe((options) => this.updateFilterValueList(options)); + } + + updateFilterValueList(options: SearchOptions) { + if (!this.pageChange) { + this.showFirstPageOnly(); + } + this.pageChange = false; + + this.unsubscribe(); + + this.sub = this.currentPage.distinctUntilChanged().map((page) => { + return this.searchService.getFacetValuesFor(this.filterConfig, page, options); + }).subscribe((newValues$) => { + this.filterValues = [...this.filterValues, newValues$]; + this.filterValues$.next(this.filterValues); + }); + // this.filterValues.subscribe((c) => c.map((a) => a.subscribe((b) => console.log(b)))); } isChecked(value: FacetValue): Observable { @@ -37,23 +65,7 @@ export class SearchFacetFilterComponent implements OnInit { } getSearchLink() { - return this.filterService.searchLink; - } - - getQueryParamsWith(value: string): Observable { - return this.filterService.getQueryParamsWith(this.filterConfig, value); - } - - getQueryParamsWithout(value: string): Observable { - return this.filterService.getQueryParamsWithout(this.filterConfig, value); - } - - get facetCount(): Observable { - const resultCount = this.filterValues.length; - return this.currentPage.map((page: number) => { - const max = page * this.filterConfig.pageSize; - return max > resultCount ? resultCount : max; - }); + return this.searchService.getSearchLink(); } showMore() { @@ -61,6 +73,7 @@ export class SearchFacetFilterComponent implements OnInit { } showFirstPageOnly() { + this.filterValues = []; this.filterService.resetPage(this.filterConfig.name); } @@ -74,13 +87,39 @@ export class SearchFacetFilterComponent implements OnInit { onSubmit(data: any) { if (isNotEmpty(data)) { - const sub = this.getQueryParamsWith(data[this.filterConfig.paramName]).first().subscribe((params) => { - this.router.navigate([this.getSearchLink()], { queryParams: params } - ); - } - ); + this.router.navigate([this.getSearchLink()], { + queryParams: + { [this.filterConfig.paramName]: [...this.selectedValues, data[this.filterConfig.paramName]] }, + queryParamsHandling: 'merge' + }); this.filter = ''; - sub.unsubscribe(); + } + } + + hasValue(o: any): boolean { + return hasValue(o); + } + + isLastPage(): Observable { + return Observable.of(false); + // return this.filterValues.flatMap((map) => map.pop().map((rd: RemoteData>) => rd.payload.currentPage >= rd.payload.totalPages)); + } + + getRemoveParams(value: string) { + return { [this.filterConfig.paramName]: this.selectedValues.filter((v) => v !== value) }; + } + + getAddParams(value: string) { + return { [this.filterConfig.paramName]: [...this.selectedValues, value] }; + } + + ngOnDestroy(): void { + this.unsubscribe(); + } + + unsubscribe(): void { + if (this.sub !== undefined) { + this.sub.unsubscribe(); } } } diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-filter.component.html index f5acb42b6d..6cf9df9b05 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.html @@ -2,7 +2,6 @@
{{'search.filters.filter.' + filter.name + '.head'| translate}}
- +
\ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts index b51a2d70fa..90d3b50786 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts @@ -23,13 +23,11 @@ import { PaginatedList } from '../../../core/data/paginated-list'; export class SearchFilterComponent implements OnInit { @Input() filter: SearchFilterConfig; - filterValues: Observable>>; - constructor(private searchService: SearchService, private filterService: SearchFilterService) { + constructor(private filterService: SearchFilterService) { } ngOnInit() { - this.filterValues = this.searchService.getFacetValuesFor(this.filter.name, '', ''); const sub = this.filterService.isFilterActive(this.filter.paramName).first().subscribe((isActive) => { if (this.filter.isOpenByDefault || isActive) { this.initialExpand(); diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 8a909b6fa7..9c5e406a78 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -14,6 +14,11 @@ import { hasValue, } from '../../../shared/empty.util'; import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; import { SearchService } from '../../search-service/search.service'; import { RouteService } from '../../../shared/route.service'; +import ObjectExpression from 'rollup/dist/typings/ast/nodes/ObjectExpression'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SearchOptions } from '../../search-options.model'; +import { PaginatedSearchOptions } from '../../paginated-search-options.model'; const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; @@ -21,8 +26,7 @@ const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; export class SearchFilterService { constructor(private store: Store, - private routeService: RouteService, - private searchService: SearchService) { + private routeService: RouteService) { } isFilterActiveWithValue(paramName: string, filterValue: string): Observable { @@ -33,22 +37,85 @@ export class SearchFilterService { return this.routeService.hasQueryParam(paramName); } - getQueryParamsWithout(filterConfig: SearchFilterConfig, value: string) { - return this.routeService.removeQueryParameterValue(filterConfig.paramName, value); + getCurrentScope() { + return this.routeService.getQueryParameterValue('scope'); } - getQueryParamsWith(filterConfig: SearchFilterConfig, value: string) { - return this.routeService.addQueryParameterValue(filterConfig.paramName, value); + getCurrentQuery() { + return this.routeService.getQueryParameterValue('query'); + } + + getCurrentPagination(pagination: any = {}): Observable { + const page$ = this.routeService.getQueryParameterValue('page'); + const size$ = this.routeService.getQueryParameterValue('pageSize'); + return Observable.combineLatest(page$, size$, (page, size) => { + return Object.assign(new PaginationComponentOptions(), pagination, { + currentPage: page || 1, + pageSize: size || pagination.pageSize + }); + }); + } + + getCurrentSort(): Observable { + const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection'); + const sortField$ = this.routeService.getQueryParameterValue('sortField'); + return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => new SortOptions(sortField || undefined, SortDirection[sortDirection])); + } + + getCurrentFilters() { + return this.routeService.getQueryParamsWithPrefix('f.'); + } + + getCurrentView() { + return this.routeService.getQueryParameterValue('view'); + } + + getPaginatedSearchOptions(defaults: any = {}): Observable { + return Observable.combineLatest( + this.getCurrentPagination(defaults.pagination), + this.getCurrentSort(), + this.getCurrentView(), + this.getCurrentScope(), + this.getCurrentQuery(), + this.getCurrentFilters(), + (pagination, sort, view, scope, query, filters) => { + return Object.assign(new SearchOptions(), + defaults, + { + pagination: pagination, + sort: sort, + view: view, + scope: scope, + query: query, + filters: filters + }) + } + ) + } + + getSearchOptions(defaults: any = {}): Observable { + return Observable.combineLatest( + this.getCurrentView(), + this.getCurrentScope(), + this.getCurrentQuery(), + this.getCurrentFilters(), + (view, scope, query, filters) => { + return Object.assign(new SearchOptions(), + defaults, + { + view: view, + scope: scope, + query: query, + filters: filters + }) + } + ) } getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable { return this.routeService.getQueryParameterValues(filterConfig.paramName); } - get searchLink() { - return this.searchService.uiSearchRoute; - } - isCollapsed(filterName: string): Observable { return this.store.select(filterByNameSelector(filterName)) .map((object: SearchFilterState) => { diff --git a/src/app/+search-page/search-filters/search-filters.component.html b/src/app/+search-page/search-filters/search-filters.component.html index 7f375b1238..09782b68d4 100644 --- a/src/app/+search-page/search-filters/search-filters.component.html +++ b/src/app/+search-page/search-filters/search-filters.component.html @@ -1,5 +1,5 @@

{{"search.filters.head" | translate}}

-
+
diff --git a/src/app/+search-page/search-options.model.ts b/src/app/+search-page/search-options.model.ts index 7f93c3ace1..4164321680 100644 --- a/src/app/+search-page/search-options.model.ts +++ b/src/app/+search-page/search-options.model.ts @@ -1,5 +1,5 @@ -import { SortOptions } from '../core/cache/models/sort-options.model'; -import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { isNotEmpty } from '../shared/empty.util'; +import { URLCombiner } from '../core/url-combiner/url-combiner'; export enum ViewMode { List = 'list', @@ -7,7 +7,28 @@ export enum ViewMode { } export class SearchOptions { - pagination?: PaginationComponentOptions; - sort?: SortOptions; view?: ViewMode = ViewMode.List; + scope?: string; + query?: string; + filters?: any; + + toRestUrl(url: string, args: string[] = []): string { + + if (isNotEmpty(this.query)) { + args.push(`query=${this.query}`); + } + + if (isNotEmpty(this.scope)) { + args.push(`scope=${this.scope}`); + } + if (isNotEmpty(this.filters)) { + Object.entries(this.filters).forEach(([key, values]) => { + values.forEach((value) => args.push(`${key}=${value},equals`)); + }); + } + if (isNotEmpty(args)) { + url = new URLCombiner(url, `?${args.join('&')}`).toString(); + } + return url; + } } diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 9c5c9a7462..57fb02b730 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -15,6 +15,7 @@ import { SearchOptions, ViewMode } from './search-options.model'; import { SearchResult } from './search-result.model'; import { SearchService } from './search-service/search.service'; import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; +import { SearchFilterService } from './search-filters/search-filter/search-filter.service'; /** * This component renders a simple item page. @@ -36,85 +37,43 @@ export class SearchPageComponent implements OnInit, OnDestroy { query: string; scopeObjectRDObs: Observable>; - resultsRDObs: Observable> | PaginatedList>>>; + resultsRDObs: Observable>>>; currentParams = {}; searchOptions: SearchOptions; sortConfig: SortOptions; scopeListRDObs: Observable>>; isMobileView: Observable; + pageSize; + pageSizeOptions; + defaults = { + pagination: { + id: 'search-results-pagination', + pageSize: 10 + }, + query: '' + }; constructor(private service: SearchService, - private route: ActivatedRoute, private communityService: CommunityDataService, private sidebarService: SearchSidebarService, - private windowService: HostWindowService) { - this.isMobileView = Observable.combineLatest( + private windowService: HostWindowService, + private filterService: SearchFilterService) { + this.isMobileView = Observable.combineLatest( this.windowService.isXs(), this.windowService.isSm(), ((isXs, isSm) => isXs || isSm) ); this.scopeListRDObs = communityService.findAll(); - // Initial pagination config - const pagination: PaginationComponentOptions = new PaginationComponentOptions(); - pagination.id = 'search-results-pagination'; - pagination.currentPage = 1; - pagination.pageSize = 10; - - const sort: SortOptions = new SortOptions(); - this.sortConfig = sort; - this.searchOptions = this.service.searchOptions; } ngOnInit(): void { - this.sub = this.route - .queryParams - .subscribe((params) => { - // Save current parameters - this.currentParams = params; - this.query = params.query || ''; - this.scope = params.scope; - const page = +params.page || this.searchOptions.pagination.currentPage; - let pageSize = +params.pageSize || this.searchOptions.pagination.pageSize; - let pageSizeOptions: number[] = [5, 10, 20, 40, 60, 80, 100]; - - if (isNotEmpty(params.view) && params.view === ViewMode.Grid) { - pageSizeOptions = [12, 24, 36, 48 , 50, 62, 74, 84]; - if (pageSizeOptions.indexOf(pageSize) === -1) { - pageSize = 12; - } - } - if (isNotEmpty(params.view) && params.view === ViewMode.List) { - if (pageSizeOptions.indexOf(pageSize) === -1) { - pageSize = 10; - } - } - - const sortDirection = params.sortDirection || this.searchOptions.sort.direction; - const sortField = params.sortField || this.searchOptions.sort.field; - const pagination = Object.assign({}, - this.searchOptions.pagination, - { currentPage: page, pageSize: pageSize, pageSizeOptions: pageSizeOptions} - ); - const sort = Object.assign({}, - this.searchOptions.sort, - { direction: sortDirection, field: sortField } - ); - - this.updateSearchResults({ - pagination: pagination, - sort: sort - }); - if (isNotEmpty(this.scope)) { - this.scopeObjectRDObs = this.communityService.findById(this.scope); - } else { - this.scopeObjectRDObs = Observable.of(undefined); - } - } - ); + this.sub = this.filterService.getPaginatedSearchOptions(this.defaults).subscribe((options) => { + this.updateSearchResults(options); + }); } private updateSearchResults(searchOptions) { - this.resultsRDObs = this.service.search(this.query, this.scope, searchOptions); + this.resultsRDObs = this.service.search(searchOptions); this.searchOptions = searchOptions; } diff --git a/src/app/+search-page/search-service/filter-type.model.ts b/src/app/+search-page/search-service/filter-type.model.ts index fba0edfac4..354ca87f98 100644 --- a/src/app/+search-page/search-service/filter-type.model.ts +++ b/src/app/+search-page/search-service/filter-type.model.ts @@ -1,5 +1,6 @@ export enum FilterType { text, - range, - hierarchy + date, + hierarchical, + standard } diff --git a/src/app/+search-page/search-service/search-filter-config.model.ts b/src/app/+search-page/search-service/search-filter-config.model.ts index 1464300daa..2b77ef6768 100644 --- a/src/app/+search-page/search-service/search-filter-config.model.ts +++ b/src/app/+search-page/search-service/search-filter-config.model.ts @@ -1,17 +1,27 @@ -import { FilterType } from './filter-type.model'; + import { FilterType } from './filter-type.model'; + import { autoserialize, autoserializeAs } from 'cerialize'; -export class SearchFilterConfig { + export class SearchFilterConfig { - name: string; - type: FilterType; - hasFacets: boolean; - pageSize = 5; - isOpenByDefault: boolean; - /** - * Name of this configuration that can be used in a url - * @returns Parameter name - */ - get paramName(): string { - return 'f.' + this.name; + @autoserialize + name: string; + + @autoserializeAs(String, 'facetType') + type: FilterType; + + @autoserialize + hasFacets: boolean; + + // @autoserializeAs(String, 'facetLimit') - uncomment when fixed in rest + pageSize = 5; + + @autoserialize + isOpenByDefault: boolean; + /** + * Name of this configuration that can be used in a url + * @returns Parameter name + */ + get paramName(): string { + return 'f.' + this.name; + } } -} diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index b20d65cf53..2ef69e8578 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -6,7 +6,8 @@ import { ViewMode } from '../../+search-page/search-options.model'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { SortOptions } from '../../core/cache/models/sort-options.model'; import { - FacetValueMapSuccessResponse, FacetValueSuccessResponse, + FacetConfigSuccessResponse, + FacetValueSuccessResponse, SearchSuccessResponse } from '../../core/cache/response-cache.models'; import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; @@ -34,26 +35,17 @@ import { SearchQueryResponse } from './search-query-response.model'; import { PageInfo } from '../../core/shared/page-info.model'; import { getSearchResultFor } from './search-result-element-decorator'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; -import { FacetResponseParsingService } from '../../core/data/facet-response-parsing.service'; - -function shuffle(array: any[]) { - let i = 0; - let j = 0; - let temp = null; - - for (i = array.length - 1; i > 0; i -= 1) { - j = Math.floor(Math.random() * (i + 1)); - temp = array[i]; - array[i] = array[j]; - array[j] = temp; - } - return array; -} +import { FacetValueResponseParsingService } from '../../core/data/facet-value-response-parsing.service'; +import { FacetConfigResponseParsingService } from '../../core/data/facet-config-response-parsing.service'; +import { SearchFilterService } from '../search-filters/search-filter/search-filter.service'; +import { PaginatedSearchOptions } from '../paginated-search-options.model'; @Injectable() export class SearchService implements OnDestroy { private searchLinkPath = 'discover/search/objects'; - private facetLinkPath = 'discover/search/facets'; + private facetValueLinkPath = 'discover/search/facets'; + private facetValueLinkPathPrefix = 'discover/facets/'; + private facetConfigLinkPath = 'discover/facets'; private sub; uiSearchRoute = '/search'; @@ -62,7 +54,7 @@ export class SearchService implements OnDestroy { // Object.assign(new SearchFilterConfig(), // { // name: 'scope', - // type: FilterType.hierarchy, + // type: FilterType.hierarchical, // hasFacets: true, // isOpenByDefault: true // }), @@ -76,7 +68,7 @@ export class SearchService implements OnDestroy { Object.assign(new SearchFilterConfig(), { name: 'dateIssued', - type: FilterType.range, + type: FilterType.date, hasFacets: true, isOpenByDefault: false }), @@ -95,7 +87,6 @@ export class SearchService implements OnDestroy { private route: ActivatedRoute, protected responseCache: ResponseCacheService, protected requestService: RequestService, - private routeService: RouteService, private rdb: RemoteDataBuildService, private halService: HALEndpointService) { const pagination: PaginationComponentOptions = new PaginationComponentOptions(); @@ -103,36 +94,15 @@ export class SearchService implements OnDestroy { pagination.currentPage = 1; pagination.pageSize = 10; const sort: SortOptions = new SortOptions(); - this.searchOptions = { pagination: pagination, sort: sort }; - // this.searchOptions = new BehaviorSubject(searchOptions); + this.searchOptions = Object.assign(new SearchOptions(), { pagination: pagination, sort: sort }); } - search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable> | PaginatedList>>> { + search(searchOptions?: PaginatedSearchOptions): Observable>>> { const requestObs = this.halService.getEndpoint(this.searchLinkPath).pipe( map((url: string) => { - const args: string[] = []; - - if (isNotEmpty(query)) { - args.push(`query=${query}`); + if (hasValue(searchOptions)) { + url = searchOptions.toRestUrl(url); } - - if (isNotEmpty(scopeId)) { - args.push(`scope=${scopeId}`); - } - - if (isNotEmpty(searchOptions)) { - if (isNotEmpty(searchOptions.sort)) { - args.push(`sort=${searchOptions.sort.field},${searchOptions.sort.direction}`); - } - if (isNotEmpty(searchOptions.pagination)) { - args.push(`page=${searchOptions.pagination.currentPage - 1}`); - args.push(`size=${searchOptions.pagination.pageSize}`); - } - } - if (isNotEmpty(args)) { - url = new URLCombiner(url, `?${args.join('&')}`).toString(); - } - const request = new GetRequest(this.requestService.generateRequestId(), url); return Object.assign(request, { getResponseParser(): GenericConstructor { @@ -183,55 +153,25 @@ export class SearchService implements OnDestroy { }); }); - const pageInfoObs: Observable = responseCacheObs - .filter((entry: ResponseCacheEntry) => entry.response.isSuccessful) - .map((entry: ResponseCacheEntry) => { - if (hasValue((entry.response as SearchSuccessResponse).pageInfo)) { - const resPageInfo = (entry.response as SearchSuccessResponse).pageInfo; - if (isNotEmpty(resPageInfo) && resPageInfo.currentPage >= 0) { - return Object.assign({}, resPageInfo, { currentPage: resPageInfo.currentPage + 1 }); - } else { - return resPageInfo; - } - } - }); + const pageInfoObs: Observable = responseCacheObs.pipe( + map((entry: ResponseCacheEntry) => entry.response), + map((response: FacetValueSuccessResponse) => response.pageInfo) + ); const payloadObs = Observable.combineLatest(tDomainListObs, pageInfoObs, (tDomainList, pageInfo) => { - if (hasValue(pageInfo)) { - return new PaginatedList(pageInfo, tDomainList); - } else { - return tDomainList; - } + return new PaginatedList(pageInfo, tDomainList); }); return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); } - getConfig(): Observable> { - const requestPending = false; - const responsePending = false; - const isSuccessful = true; - const error = undefined; - return Observable.of(new RemoteData( - requestPending, - responsePending, - isSuccessful, - error, - this.config - )); - } - - getFacetValuesFor(searchFilterConfigName: string, query: string, scopeId: string): Observable>> { - const requestObs = this.halService.getEndpoint(this.facetLinkPath).pipe( + getConfig(scope?: string): Observable> { + const requestObs = this.halService.getEndpoint(this.facetConfigLinkPath).pipe( map((url: string) => { const args: string[] = []; - if (isNotEmpty(query)) { - args.push(`query=${query}`); - } - - if (isNotEmpty(scopeId)) { - args.push(`scope=${scopeId}`); + if (isNotEmpty(scope)) { + args.push(`scope=${scope}`); } if (isNotEmpty(args)) { @@ -241,7 +181,7 @@ export class SearchService implements OnDestroy { const request = new GetRequest(this.requestService.generateRequestId(), url); return Object.assign(request, { getResponseParser(): GenericConstructor { - return FacetResponseParsingService; + return FacetConfigResponseParsingService; } }); }), @@ -257,56 +197,56 @@ export class SearchService implements OnDestroy { ); // get search results from response cache - const facetValueResponseObs: Observable = responseCacheObs.pipe( + const facetConfigObs: Observable = responseCacheObs.pipe( map((entry: ResponseCacheEntry) => entry.response), - map((response: FacetValueMapSuccessResponse) => response.results[searchFilterConfigName]) + map((response: FacetConfigSuccessResponse) => response.results) + ); + + return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, facetConfigObs); + } + + getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions): Observable>> { + console.log('facetvalues'); + const requestObs = this.halService.getEndpoint(this.facetValueLinkPathPrefix + filterConfig.name).pipe( + map((url: string) => { + const args: string[] = [`page=${valuePage - 1}`, `size=${filterConfig.pageSize}`]; + if (hasValue(searchOptions)) { + url = searchOptions.toRestUrl(url, args); + } + const request = new GetRequest(this.requestService.generateRequestId(), url); + return Object.assign(request, { + getResponseParser(): GenericConstructor { + return FacetValueResponseParsingService; + } + }); + }), + tap((request: RestRequest) => this.requestService.configure(request)), + ); + + const requestEntryObs = requestObs.pipe( + flatMap((request: RestRequest) => this.requestService.getByHref(request.href)) + ); + + const responseCacheObs = requestObs.pipe( + flatMap((request: RestRequest) => this.responseCache.get(request.href)) ); // get search results from response cache - const facetValueObs: Observable = facetValueResponseObs.pipe( + const facetValueObs: Observable = responseCacheObs.pipe( + map((entry: ResponseCacheEntry) => entry.response), map((response: FacetValueSuccessResponse) => response.results) ); - const pageInfoObs: Observable = facetValueResponseObs.pipe( - map((response: FacetValueSuccessResponse) => { console.log(response); return response.pageInfo}) + const pageInfoObs: Observable = responseCacheObs.pipe( + map((entry: ResponseCacheEntry) => entry.response), + map((response: FacetValueSuccessResponse) => response.pageInfo) ); - const payloadObs = Observable.combineLatest(facetValueObs, pageInfoObs, (facetValue, pageInfo) => { - if (hasValue(pageInfo)) { - return new PaginatedList(pageInfo, facetValue); - } else { - return facetValue; - } - }); - return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); - // const filterConfig = this.config.find((config: SearchFilterConfig) => config.name === searchFilterConfigName); - // return this.routeService.getQueryParameterValues(filterConfig.paramName).map((selectedValues: string[]) => { - // const payload: FacetValue[] = []; - // const totalFilters = 13; - // for (let i = 0; i < totalFilters; i++) { - // const value = searchFilterConfigName + ' ' + (i + 1); - // if (!selectedValues.includes(value)) { - // payload.push({ - // value: value, - // count: Math.floor(Math.random() * 20) + 20 * (totalFilters - i), // make sure first results have the highest (random) count - // search: (decodeURI(this.router.url) + (this.router.url.includes('?') ? '&' : '?') + filterConfig.paramName + '=' + value) - // } - // ); - // } - // } - // const requestPending = false; - // const responsePending = false; - // const isSuccessful = true; - // const error = undefined; - // return new RemoteData( - // requestPending, - // responsePending, - // isSuccessful, - // error, - // payload - // ) - // } - // ) + const payloadObs = Observable.combineLatest(facetValueObs, pageInfoObs, (facetValue, pageInfo) => { + return new PaginatedList(pageInfo, facetValue); + }); + + return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); } getViewMode(): Observable { diff --git a/src/app/+search-page/search-settings/search-settings.component.ts b/src/app/+search-page/search-settings/search-settings.component.ts index bc1fb096fd..cc22da7176 100644 --- a/src/app/+search-page/search-settings/search-settings.component.ts +++ b/src/app/+search-page/search-settings/search-settings.component.ts @@ -3,6 +3,7 @@ import { SearchService } from '../search-service/search.service'; import { SearchOptions, ViewMode } from '../search-options.model'; import { SortDirection } from '../../core/cache/models/sort-options.model'; import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; +import { PaginatedSearchOptions } from '../paginated-search-options.model'; @Component({ selector: 'ds-search-settings', @@ -11,7 +12,7 @@ import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; }) export class SearchSettingsComponent implements OnInit { - @Input() searchOptions: SearchOptions; + @Input() searchOptions: PaginatedSearchOptions; /** * Declare SortDirection enumeration to use it in the template */ diff --git a/src/app/core/cache/response-cache.models.ts b/src/app/core/cache/response-cache.models.ts index 5da328931b..f061e78e6c 100644 --- a/src/app/core/cache/response-cache.models.ts +++ b/src/app/core/cache/response-cache.models.ts @@ -4,6 +4,7 @@ import { PageInfo } from '../shared/page-info.model'; import { BrowseDefinition } from '../shared/browse-definition.model'; import { ConfigObject } from '../shared/config/config.model'; import { FacetValue } from '../../+search-page/search-service/facet-value.model'; +import { SearchFilterConfig } from '../../+search-page/search-service/search-filter-config.model'; /* tslint:disable:max-classes-per-file */ export class RestResponse { @@ -33,6 +34,15 @@ export class SearchSuccessResponse extends RestResponse { } } +export class FacetConfigSuccessResponse extends RestResponse { + constructor( + public results: SearchFilterConfig[], + public statusCode: string + ) { + super(true, statusCode); + } +} + export class FacetValueMap { [name: string]: FacetValueSuccessResponse } diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts index 7cda10b4ae..ebb87bf1ee 100644 --- a/src/app/core/core.effects.ts +++ b/src/app/core/core.effects.ts @@ -8,5 +8,5 @@ export const coreEffects = [ ResponseCacheEffects, RequestEffects, ObjectCacheEffects, - UUIDIndexEffects, + UUIDIndexEffects ]; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 764e21efba..86abf87d62 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -41,7 +41,9 @@ import { SubmissionFormsConfigService } from './config/submission-forms-config.s import { SubmissionSectionsConfigService } from './config/submission-sections-config.service'; import { UUIDService } from './shared/uuid.service'; import { HALEndpointService } from './shared/hal-endpoint.service'; -import { FacetResponseParsingService } from './data/facet-response-parsing.service'; +import { FacetValueResponseParsingService } from './data/facet-value-response-parsing.service'; +import { FacetValueMapResponseParsingService } from './data/facet-value-map-response-parsing.service'; +import { FacetConfigResponseParsingService } from './data/facet-config-response-parsing.service'; const IMPORTS = [ CommonModule, @@ -73,7 +75,9 @@ const PROVIDERS = [ RequestService, ResponseCacheService, EndpointMapResponseParsingService, - FacetResponseParsingService, + FacetValueResponseParsingService, + FacetValueMapResponseParsingService, + FacetConfigResponseParsingService, DebugResponseParsingService, SearchResponseParsingService, ServerResponseService, diff --git a/src/app/core/data/base-response-parsing.service.ts b/src/app/core/data/base-response-parsing.service.ts index d8a4221420..9d6a5851e5 100644 --- a/src/app/core/data/base-response-parsing.service.ts +++ b/src/app/core/data/base-response-parsing.service.ts @@ -117,9 +117,13 @@ export abstract class BaseResponseParsingService { this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref); } - protected processPageInfo(pageObj: any): PageInfo { + processPageInfo(pageObj: any): PageInfo { if (isNotEmpty(pageObj)) { - return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj); + const pageInfoObject = new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj); + if (pageInfoObject.currentPage >= 0) { + Object.assign(pageInfoObject, { currentPage: pageInfoObject.currentPage + 1 }); + } + return pageInfoObject } else { return undefined; } diff --git a/src/app/core/data/facet-config-response-parsing.service.ts b/src/app/core/data/facet-config-response-parsing.service.ts new file mode 100644 index 0000000000..b0d89fb03e --- /dev/null +++ b/src/app/core/data/facet-config-response-parsing.service.ts @@ -0,0 +1,32 @@ +import { Inject, Injectable } from '@angular/core'; +import { + FacetConfigSuccessResponse, + RestResponse +} from '../cache/response-cache.models'; +import { ResponseParsingService } from './parsing.service'; +import { RestRequest } from './request.models'; +import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; +import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; +import { SearchFilterConfig } from '../../+search-page/search-service/search-filter-config.model'; +import { BaseResponseParsingService } from './base-response-parsing.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { GlobalConfig } from '../../../config/global-config.interface'; +import { GLOBAL_CONFIG } from '../../../config'; + +@Injectable() +export class FacetConfigResponseParsingService extends BaseResponseParsingService implements ResponseParsingService { + objectFactory = {}; + toCache = false; + constructor( + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + protected objectCache: ObjectCacheService, + ) { super(); + } + parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { + + const config = data.payload._embedded.facets; + const serializer = new DSpaceRESTv2Serializer(SearchFilterConfig); + const facetConfig = serializer.deserializeArray(config); + return new FacetConfigSuccessResponse(facetConfig, data.statusCode); + } +} diff --git a/src/app/core/data/facet-response-parsing.service.ts b/src/app/core/data/facet-value-map-response-parsing.service.ts similarity index 68% rename from src/app/core/data/facet-response-parsing.service.ts rename to src/app/core/data/facet-value-map-response-parsing.service.ts index 8295d026a3..dfd72c0cc5 100644 --- a/src/app/core/data/facet-response-parsing.service.ts +++ b/src/app/core/data/facet-value-map-response-parsing.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { FacetValueMap, FacetValueMapSuccessResponse, @@ -12,9 +12,22 @@ import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.seriali import { PageInfo } from '../shared/page-info.model'; import { isNotEmpty } from '../../shared/empty.util'; import { FacetValue } from '../../+search-page/search-service/facet-value.model'; +import { BaseResponseParsingService } from './base-response-parsing.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { GlobalConfig } from '../../../config/global-config.interface'; +import { GLOBAL_CONFIG } from '../../../config'; @Injectable() -export class FacetResponseParsingService implements ResponseParsingService { +export class FacetValueMapResponseParsingService extends BaseResponseParsingService implements ResponseParsingService { + objectFactory = {}; + toCache = false; + + constructor( + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + protected objectCache: ObjectCacheService, + ) { super(); + } + parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { const payload = data.payload; @@ -30,12 +43,4 @@ export class FacetResponseParsingService implements ResponseParsingService { return new FacetValueMapSuccessResponse(facetMap, data.statusCode); } - - protected processPageInfo(pageObj: any): PageInfo { - if (isNotEmpty(pageObj)) { - return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj); - } else { - return undefined; - } - } } diff --git a/src/app/core/data/facet-value-response-parsing.service.ts b/src/app/core/data/facet-value-response-parsing.service.ts new file mode 100644 index 0000000000..17f0730566 --- /dev/null +++ b/src/app/core/data/facet-value-response-parsing.service.ts @@ -0,0 +1,38 @@ +import { Inject, Injectable } from '@angular/core'; +import { + FacetValueMap, + FacetValueMapSuccessResponse, + FacetValueSuccessResponse, + RestResponse +} from '../cache/response-cache.models'; +import { ResponseParsingService } from './parsing.service'; +import { RestRequest } from './request.models'; +import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; +import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; +import { PageInfo } from '../shared/page-info.model'; +import { isNotEmpty } from '../../shared/empty.util'; +import { FacetValue } from '../../+search-page/search-service/facet-value.model'; +import { BaseResponseParsingService } from './base-response-parsing.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { GLOBAL_CONFIG } from '../../../config'; +import { GlobalConfig } from '../../../config/global-config.interface'; + +@Injectable() +export class FacetValueResponseParsingService extends BaseResponseParsingService implements ResponseParsingService { + objectFactory = {}; + toCache = false; + constructor( + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + protected objectCache: ObjectCacheService, + ) { super(); + } + parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { + const payload = data.payload; + + const serializer = new DSpaceRESTv2Serializer(FacetValue); + const values = payload._embedded.values.map((value) => {value.search = value._links.search.href; return value;}); + + const facetValues = serializer.deserializeArray(values); + return new FacetValueSuccessResponse(facetValues, data.statusCode, this.processPageInfo(data.payload.page)); + } +} diff --git a/src/app/core/data/paginated-list.ts b/src/app/core/data/paginated-list.ts index f1d076927d..7e4a57f84e 100644 --- a/src/app/core/data/paginated-list.ts +++ b/src/app/core/data/paginated-list.ts @@ -1,15 +1,17 @@ import { PageInfo } from '../shared/page-info.model'; +import { hasValue } from '../../shared/empty.util'; export class PaginatedList { - constructor( - private pageInfo: PageInfo, - public page: T[] - ) { + constructor(private pageInfo: PageInfo, + public page: T[]) { } get elementsPerPage(): number { - return this.pageInfo.elementsPerPage; + if (hasValue(this.pageInfo)) { + return this.pageInfo.elementsPerPage; + } + return this.page.length; } set elementsPerPage(value: number) { @@ -17,7 +19,10 @@ export class PaginatedList { } get totalElements(): number { - return this.pageInfo.totalElements; + if (hasValue(this.pageInfo)) { + return this.pageInfo.totalElements; + } + return this.page.length; } set totalElements(value: number) { @@ -25,7 +30,10 @@ export class PaginatedList { } get totalPages(): number { - return this.pageInfo.totalPages; + if (hasValue(this.pageInfo)) { + return this.pageInfo.totalPages; + } + return 1; } set totalPages(value: number) { @@ -33,7 +41,11 @@ export class PaginatedList { } get currentPage(): number { - return this.pageInfo.currentPage; + if (hasValue(this.pageInfo)) { + return this.pageInfo.currentPage; + } + return 1; + } set currentPage(value: number) { diff --git a/src/app/core/data/search-response-parsing.service.ts b/src/app/core/data/search-response-parsing.service.ts index 583d63ec9d..927c5ff010 100644 --- a/src/app/core/data/search-response-parsing.service.ts +++ b/src/app/core/data/search-response-parsing.service.ts @@ -56,14 +56,6 @@ export class SearchResponseParsingService implements ResponseParsingService { })); payload.objects = objects; const deserialized = new DSpaceRESTv2Serializer(SearchQueryResponse).deserialize(payload); - return new SearchSuccessResponse(deserialized, data.statusCode, this.processPageInfo(data.payload.page)); - } - - protected processPageInfo(pageObj: any): PageInfo { - if (isNotEmpty(pageObj)) { - return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj); - } else { - return undefined; - } + return new SearchSuccessResponse(deserialized, data.statusCode, this.dsoParser.processPageInfo(data.payload.page)); } } diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index b9bb1d587c..c81c3c792d 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -1,12 +1,12 @@ import { Observable } from 'rxjs/Observable'; -import { distinctUntilChanged, map, flatMap, startWith } from 'rxjs/operators'; +import { distinctUntilChanged, map, flatMap, startWith, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; import { GlobalConfig } from '../../../config/global-config.interface'; import { EndpointMap, EndpointMapSuccessResponse } from '../cache/response-cache.models'; import { EndpointMapRequest } from '../data/request.models'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; import { Inject, Injectable } from '@angular/core'; import { GLOBAL_CONFIG } from '../../../config'; @@ -21,6 +21,7 @@ export class HALEndpointService { @Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) { } + protected getRootHref(): string { return new RESTURLCombiner(this.EnvConfig, '/').toString(); } @@ -34,23 +35,35 @@ export class HALEndpointService { this.requestService.configure(request); return this.responseCache.get(request.href) .map((entry: ResponseCacheEntry) => entry.response) - .filter((response: EndpointMapSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap)) + .filter((response: EndpointMapSuccessResponse) => isNotEmpty(response)) .map((response: EndpointMapSuccessResponse) => response.endpointMap) .distinctUntilChanged(); } public getEndpoint(linkPath: string): Observable { - return this.getEndpointAt(...linkPath.split('/')); + const test = this.getEndpointAt(...linkPath.split('/')); + // test.subscribe((test) => console.log(linkPath, test)); + return test; } private getEndpointAt(...path: string[]): Observable { if (isEmpty(path)) { path = ['/']; } + let currentPath; const pipeArguments = path - .map((subPath: string) => [ + .map((subPath: string, index: number) => [ flatMap((href: string) => this.getEndpointMapAt(href)), - map((endpointMap: EndpointMap) => endpointMap[subPath]), + map((endpointMap: EndpointMap) => { + if (hasValue(endpointMap) && hasValue(endpointMap[subPath])) { + currentPath = endpointMap[subPath]; + return endpointMap[subPath]; + } else { + /*TODO remove if/else block once the rest response contains _links for facets*/ + currentPath += '/' + subPath; + return currentPath; + } + }), ]) .reduce((combined, thisElement) => [...combined, ...thisElement], []); return Observable.of(this.getRootHref()).pipe(...pipeArguments, distinctUntilChanged()); diff --git a/src/app/shared/route.service.spec.ts b/src/app/shared/route.service.spec.ts index 10bd147e1d..00b7d78be6 100644 --- a/src/app/shared/route.service.spec.ts +++ b/src/app/shared/route.service.spec.ts @@ -69,12 +69,12 @@ describe('RouteService', () => { describe('addQueryParameterValue', () => { it('should return a list of values that contains the added value when a new value is added and the parameter did not exist yet', () => { - service.addQueryParameterValue(nonExistingParamName, nonExistingParamValue).subscribe((params) => { + service.resolveRouteWithParameterValue(nonExistingParamName, nonExistingParamValue).subscribe((params) => { expect(params[nonExistingParamName]).toContain(nonExistingParamValue); }); }); it('should return a list of values that contains the existing values and the added value when a new value is added and the parameter already has values', () => { - service.addQueryParameterValue(paramName1, nonExistingParamValue).subscribe((params) => { + service.resolveRouteWithParameterValue(paramName1, nonExistingParamValue).subscribe((params) => { const values = params[paramName1]; expect(values).toContain(paramValue1); expect(values).toContain(nonExistingParamValue); @@ -84,7 +84,7 @@ describe('RouteService', () => { describe('removeQueryParameterValue', () => { it('should return a list of values that does not contain the removed value when the parameter value exists', () => { - service.removeQueryParameterValue(paramName2, paramValue2a).subscribe((params) => { + service.resolveRouteWithoutParameterValue(paramName2, paramValue2a).subscribe((params) => { const values = params[paramName2]; expect(values).toContain(paramValue2b); expect(values).not.toContain(paramValue2a); @@ -92,7 +92,7 @@ describe('RouteService', () => { }); it('should return a list of values that does contain all existing values when the removed parameter does not exist', () => { - service.removeQueryParameterValue(paramName2, nonExistingParamValue).subscribe((params) => { + service.resolveRouteWithoutParameterValue(paramName2, nonExistingParamValue).subscribe((params) => { const values = params[paramName2]; expect(values).toContain(paramValue2a); expect(values).toContain(paramValue2b); @@ -100,15 +100,15 @@ describe('RouteService', () => { }); }); - describe('removeQueryParameter', () => { + describe('getWithoutParameter', () => { it('should return a list of values that does not contain any values for the parameter anymore when the parameter exists', () => { - service.removeQueryParameter(paramName2).subscribe((params) => { + service.resolveRouteWithoutParameter(paramName2).subscribe((params) => { const values = params[paramName2]; expect(values).toEqual({}); }); }); it('should return a list of values that does not contain any values for the parameter when the parameter does not exist', () => { - service.removeQueryParameter(nonExistingParamName).subscribe((params) => { + service.resolveRouteWithoutParameter(nonExistingParamName).subscribe((params) => { const values = params[nonExistingParamName]; expect(values).toEqual({}); }); diff --git a/src/app/shared/route.service.ts b/src/app/shared/route.service.ts index f24fa0d00d..10a7eaecb9 100644 --- a/src/app/shared/route.service.ts +++ b/src/app/shared/route.service.ts @@ -1,6 +1,9 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; -import { ActivatedRoute, convertToParamMap, Params, } from '@angular/router'; +import { + ActivatedRoute, convertToParamMap, NavigationExtras, Params, + Router, +} from '@angular/router'; import { isNotEmpty } from './empty.util'; @Injectable() @@ -10,7 +13,7 @@ export class RouteService { } getQueryParameterValues(paramName: string): Observable { - return this.route.queryParamMap.map((map) => map.getAll(paramName)); + return this.route.queryParamMap.map((map) => [...map.getAll(paramName)]); } getQueryParameterValue(paramName: string): Observable { @@ -25,31 +28,16 @@ export class RouteService { return this.route.queryParamMap.map((map) => map.getAll(paramName).indexOf(paramValue) > -1); } - addQueryParameterValue(paramName: string, paramValue: string): Observable { - return this.route.queryParams.map((currentParams) => { - const newParam = {}; - newParam[paramName] = [...convertToParamMap(currentParams).getAll(paramName), paramValue]; - return Object.assign({}, currentParams, newParam); - }); - } - - removeQueryParameterValue(paramName: string, paramValue: string): Observable { - return this.route.queryParams.map((currentParams) => { - const newParam = {}; - const currentFilterParams = convertToParamMap(currentParams).getAll(paramName); - if (isNotEmpty(currentFilterParams)) { - newParam[paramName] = currentFilterParams.filter((param) => (param !== paramValue)); - } - return Object.assign({}, currentParams, newParam); - }); - } - - removeQueryParameter(paramName: string): Observable { - return this.route.queryParams.map((currentParams) => { - const newParam = {}; - newParam[paramName] = {}; - return Object.assign({}, currentParams, newParam); - }); - + getQueryParamsWithPrefix(prefix: string): Observable { + return this.route.queryParamMap + .map((map) => { + const params = {}; + map.keys + .filter((key) => key.startsWith(prefix)) + .forEach((key) => { + params[key] = [...map.getAll(key)]; + }); + return params; + }); } } diff --git a/src/tsconfig.test.json b/src/tsconfig.test.json index 712ad1ab1c..079eabc561 100644 --- a/src/tsconfig.test.json +++ b/src/tsconfig.test.json @@ -1,4 +1,4 @@ -{ +yarn add{ "extends": "../tsconfig.json", "compilerOptions": { "sourceMap": true diff --git a/yarn.lock b/yarn.lock index b6e461e9b6..e6e0aedf55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -122,6 +122,12 @@ version "2.0.1" resolved "https://registry.yarnpkg.com/@ngx-translate/http-loader/-/http-loader-2.0.1.tgz#aa67788e64bfa8652691a77b022b3b4031209113" +"@types/acorn@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.3.tgz#d1f3e738dde52536f9aad3d3380d14e448820afd" + dependencies: + "@types/estree" "*" + "@types/body-parser@*": version "1.16.8" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.16.8.tgz#687ec34140624a3bec2b1a8ea9268478ae8f3be3" @@ -139,6 +145,10 @@ version "0.1.1" resolved "https://registry.yarnpkg.com/@types/deep-freeze/-/deep-freeze-0.1.1.tgz#0e1ee6ceee06f51baeb663deec0bb7780bd72827" +"@types/estree@*": + version "0.0.38" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2" + "@types/events@*": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02"