diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index a0af63c28d..94f317103a 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -1,12 +1,15 @@ import { Inject, Injectable, OnDestroy } from '@angular/core'; import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; import { Observable } from 'rxjs/Observable'; -import { map } from 'rxjs/operators'; +import { map, flatMap, tap, filter } from 'rxjs/operators'; import { ViewMode } from '../../+search-page/search-options.model'; import { GLOBAL_CONFIG } from '../../../config'; import { GlobalConfig } from '../../../config/global-config.interface'; +import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; +import { NormalizedDSpaceObject } from '../../core/cache/models/normalized-dspace-object.model'; import { SortOptions } from '../../core/cache/models/sort-options.model'; -import { RestResponse } from '../../core/cache/response-cache.models'; +import { RestResponse, SearchSuccessResponse } from '../../core/cache/response-cache.models'; +import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; import { ResponseCacheService } from '../../core/cache/response-cache.service'; import { DebugResponseParsingService } from '../../core/data/debug-response-parsing.service'; import { DSOResponseParsingService } from '../../core/data/dso-response-parsing.service'; @@ -15,6 +18,7 @@ import { PaginatedList } from '../../core/data/paginated-list'; import { ResponseParsingService } from '../../core/data/parsing.service'; import { RemoteData } from '../../core/data/remote-data'; import { GetRequest, EndpointMapRequest, RestRequest } from '../../core/data/request.models'; +import { RequestEntry } from '../../core/data/request.reducer'; import { RequestService } from '../../core/data/request.service'; import { DSpaceRESTV2Response } from '../../core/dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @@ -23,16 +27,19 @@ import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { Item } from '../../core/shared/item.model'; import { Metadatum } from '../../core/shared/metadatum.model'; import { PageInfo } from '../../core/shared/page-info.model'; +import { URLCombiner } from '../../core/url-combiner/url-combiner'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { ItemSearchResult } from '../../shared/object-collection/shared/item-search-result.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { RouteService } from '../../shared/route.service'; +import { NormalizedSearchResult } from '../normalized-search-result.model'; import { SearchOptions } from '../search-options.model'; import { SearchResult } from '../search-result.model'; import { FacetValue } from './facet-value.model'; import { FilterType } from './filter-type.model'; import { SearchFilterConfig } from './search-filter-config.model'; import { SearchResponseParsingService } from '../../core/data/search-response-parsing.service'; +import { SearchQueryResponse } from './search-query-response.model'; function shuffle(array: any[]) { let i = 0; @@ -95,6 +102,7 @@ export class SearchService extends HALEndpointService implements OnDestroy { @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, private routeService: RouteService, private route: ActivatedRoute, + private rdb: RemoteDataBuildService, private router: Router ) { super(); @@ -108,18 +116,62 @@ export class SearchService extends HALEndpointService implements OnDestroy { } search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable>>> { - const searchEndpointUrlObs = this.getEndpoint(); - searchEndpointUrlObs.pipe( + const requestObs = this.getEndpoint().pipe( map((url: string) => { + const args: string[] = []; + + if (isNotEmpty(query)) { + args.push(`query=${query}`); + } + + if (isNotEmpty(scopeId)) { + args.push(`scope=${scopeId}`); + } + + if (isNotEmpty(args)) { + url = new URLCombiner(url, `?${args.join('&')}`).toString(); + } + const request = new GetRequest(this.requestService.generateRequestId(), url); return Object.assign(request, { getResponseParser(): GenericConstructor { return SearchResponseParsingService; } }); + }), + 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)) + ); + + const sqrObs = responseCacheObs.pipe( + map((entry: ResponseCacheEntry) => entry.response), + map((response: SearchSuccessResponse) => response.results) + ); + + const dsoObs = sqrObs.pipe( + map((sqr: SearchQueryResponse) => { + return sqr.objects.map((nsr: NormalizedSearchResult) => + this.rdb.buildSingle(nsr.dspaceObject, NormalizedDSpaceObject)); + }), + flatMap((input: Array>>) => this.rdb.aggregate(input)) + ); + + const payloadObs = Observable.combineLatest(sqrObs, dsoObs, (sqr: SearchQueryResponse, dsos: RemoteData) => { + return sqr.objects.map((object: NormalizedSearchResult, index: number) => { + return Object.assign({}, object, { + dspaceObject: dsos.payload[index] + }); }) - ).subscribe((request: RestRequest) => this.requestService.configure(request)); - return Observable.of(undefined); + }); + + return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); } getConfig(): Observable> { diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 9ed43c242b..bb4877980a 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -1,18 +1,24 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; +import { map, tap } from 'rxjs/operators'; +import { NormalizedSearchResult } from '../../../+search-page/normalized-search-result.model'; +import { SearchResult } from '../../../+search-page/search-result.model'; +import { SearchQueryResponse } from '../../../+search-page/search-service/search-query-response.model'; import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { PaginatedList } from '../../data/paginated-list'; import { RemoteData } from '../../data/remote-data'; import { RemoteDataError } from '../../data/remote-data-error'; -import { GetRequest } from '../../data/request.models'; +import { GetRequest, RestRequest } from '../../data/request.models'; import { RequestEntry } from '../../data/request.reducer'; import { RequestService } from '../../data/request.service'; +import { DSpaceObject } from '../../shared/dspace-object.model'; import { GenericConstructor } from '../../shared/generic-constructor'; +import { NormalizedDSpaceObject } from '../models/normalized-dspace-object.model'; import { NormalizedObjectFactory } from '../models/normalized-object-factory'; import { CacheableObject } from '../object-cache.reducer'; import { ObjectCacheService } from '../object-cache.service'; -import { DSOSuccessResponse, ErrorResponse } from '../response-cache.models'; +import { DSOSuccessResponse, ErrorResponse, SearchSuccessResponse } from '../response-cache.models'; import { ResponseCacheEntry } from '../response-cache.reducer'; import { ResponseCacheService } from '../response-cache.service'; import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators'; @@ -37,7 +43,7 @@ export class RemoteDataBuildService { const requestHrefObs = hrefObs.flatMap((href: string) => this.objectCache.getRequestHrefBySelfLink(href)); - const requestObs = Observable.race( + const requestEntryObs = Observable.race( hrefObs.flatMap((href: string) => this.requestService.getByHref(href)) .filter((entry) => hasValue(entry)), requestHrefObs.flatMap((requestHref) => @@ -80,20 +86,22 @@ export class RemoteDataBuildService { }) .startWith(undefined) .distinctUntilChanged(); - return this.toRemoteDataObservable(hrefObs, requestObs, responseCacheObs, payloadObs); + return this.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); } - private toRemoteDataObservable(hrefObs: Observable, requestObs: Observable, responseCacheObs: Observable, payloadObs: Observable) { - return Observable.combineLatest(hrefObs, requestObs, responseCacheObs.startWith(undefined), payloadObs, - (href: string, reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => { + toRemoteDataObservable(requestEntryObs: Observable, responseCacheObs: Observable, payloadObs: Observable) { + return Observable.combineLatest(requestEntryObs, responseCacheObs.startWith(undefined), payloadObs, + (reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => { const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true; const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false; let isSuccessful: boolean; let error: RemoteDataError; if (hasValue(resEntry) && hasValue(resEntry.response)) { - isSuccessful = resEntry.response.isSuccessful; + isSuccessful = !responsePending && resEntry.response.isSuccessful; const errorMessage = isSuccessful === false ? (resEntry.response as ErrorResponse).errorMessage : undefined; - error = new RemoteDataError(resEntry.response.statusCode, errorMessage); + if (hasValue(errorMessage)) { + error = new RemoteDataError(resEntry.response.statusCode, errorMessage); + } } return new RemoteData( @@ -114,7 +122,7 @@ export class RemoteDataBuildService { hrefObs = Observable.of(hrefObs); } - const requestObs = hrefObs.flatMap((href: string) => this.requestService.getByHref(href)) + const requestEntryObs = hrefObs.flatMap((href: string) => this.requestService.getByHref(href)) .filter((entry) => hasValue(entry)); const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href)) .filter((entry) => hasValue(entry)); @@ -154,10 +162,10 @@ export class RemoteDataBuildService { } }); - return this.toRemoteDataObservable(hrefObs, requestObs, responseCacheObs, payloadObs); + return this.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); } - build(normalized: TNormalized): TDomain { + build(normalized: TNormalized): TDomain { const links: any = {}; const relationships = getRelationships(normalized.constructor) || []; diff --git a/src/app/core/cache/models/normalized-dspace-object.model.ts b/src/app/core/cache/models/normalized-dspace-object.model.ts index da42ea5a9b..92174c40f7 100644 --- a/src/app/core/cache/models/normalized-dspace-object.model.ts +++ b/src/app/core/cache/models/normalized-dspace-object.model.ts @@ -1,13 +1,16 @@ -import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; +import { autoserialize, autoserializeAs } from 'cerialize'; +import { DSpaceObject } from '../../shared/dspace-object.model'; import { Metadatum } from '../../shared/metadatum.model'; import { ResourceType } from '../../shared/resource-type'; +import { mapsTo } from '../builders/build-decorators'; import { NormalizedObject } from './normalized-object.model'; /** - * An abstract model class for a DSpaceObject. + * An model class for a DSpaceObject. */ -export abstract class NormalizedDSpaceObject extends NormalizedObject { +@mapsTo(DSpaceObject) +export class NormalizedDSpaceObject extends NormalizedObject { /** * The link to the rest endpoint where this object can be found diff --git a/src/app/core/shared/dspace-object.model.ts b/src/app/core/shared/dspace-object.model.ts index 8f96f2485a..63cedf84f4 100644 --- a/src/app/core/shared/dspace-object.model.ts +++ b/src/app/core/shared/dspace-object.model.ts @@ -9,7 +9,7 @@ import { Observable } from 'rxjs/Observable'; /** * An abstract model class for a DSpaceObject. */ -export abstract class DSpaceObject implements CacheableObject, ListableObject { +export class DSpaceObject implements CacheableObject, ListableObject { self: string; diff --git a/src/app/core/shared/resource-type.ts b/src/app/core/shared/resource-type.ts index f3554e18cf..1b73cb0bba 100644 --- a/src/app/core/shared/resource-type.ts +++ b/src/app/core/shared/resource-type.ts @@ -1,8 +1,5 @@ -/** - * TODO replace with actual string enum after upgrade to TypeScript 2.4: - * https://github.com/Microsoft/TypeScript/pull/15486 - */ export enum ResourceType { + DSpaceObject = 'dspaceobject', Bundle = 'bundle', Bitstream = 'bitstream', BitstreamFormat = 'bitstreamformat',