diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index ca349ce5bc..e0043f2845 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -22,6 +22,7 @@ import { Observable } from 'rxjs/Observable'; selector: 'ds-collection-page', styleUrls: ['./collection-page.component.scss'], templateUrl: './collection-page.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) export class CollectionPageComponent implements OnInit, OnDestroy { collectionData: RemoteData; @@ -37,8 +38,7 @@ export class CollectionPageComponent implements OnInit, OnDestroy { private route: ActivatedRoute) { this.paginationConfig = new PaginationComponentOptions(); this.paginationConfig.id = 'collection-page-pagination'; - this.paginationConfig.pageSizeOptions = [4]; - this.paginationConfig.pageSize = 4; + this.paginationConfig.pageSize = 5; this.paginationConfig.currentPage = 1; this.sortConfig = new SortOptions(); } diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index 2bd19b0f06..4ed516637d 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; @@ -13,6 +13,7 @@ import { hasValue } from '../shared/empty.util'; selector: 'ds-community-page', styleUrls: ['./community-page.component.scss'], templateUrl: './community-page.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) export class CommunityPageComponent implements OnInit, OnDestroy { communityData: RemoteData; diff --git a/src/app/+home/home.component.ts b/src/app/+home/home.component.ts index d1222b8ae0..d9127a13a6 100644 --- a/src/app/+home/home.component.ts +++ b/src/app/+home/home.component.ts @@ -1,9 +1,10 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'ds-home', styleUrls: ['./home.component.scss'], - templateUrl: './home.component.html' + templateUrl: './home.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) export class HomeComponent { diff --git a/src/app/+home/top-level-community-list/top-level-community-list.component.ts b/src/app/+home/top-level-community-list/top-level-community-list.component.ts index a3882d7036..783265d110 100644 --- a/src/app/+home/top-level-community-list/top-level-community-list.component.ts +++ b/src/app/+home/top-level-community-list/top-level-community-list.component.ts @@ -40,5 +40,7 @@ export class TopLevelCommunityListComponent { elementsPerPage: data.pageSize, sort: { field: data.sortField, direction: data.sortDirection } }); + this.cds.getScopedEndpoint('7669c72a-3f2a-451f-a3b9-9210e7a4c02f') + .subscribe((c) => console.log('communities', c)) } } diff --git a/src/app/+item-page/simple/item-page.component.ts b/src/app/+item-page/simple/item-page.component.ts index dd37036c21..9ed694e80e 100644 --- a/src/app/+item-page/simple/item-page.component.ts +++ b/src/app/+item-page/simple/item-page.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; @@ -16,6 +16,7 @@ import { Bitstream } from '../../core/shared/bitstream.model'; selector: 'ds-item-page', styleUrls: ['./item-page.component.scss'], templateUrl: './item-page.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) export class ItemPageComponent implements OnInit { diff --git a/src/app/browser-app.module.ts b/src/app/browser-app.module.ts index a8e6b08312..8201fbc092 100644 --- a/src/app/browser-app.module.ts +++ b/src/app/browser-app.module.ts @@ -40,6 +40,7 @@ export function createTranslateLoader(http: HttpClient) { // forRoot ensures the providers are only created once IdlePreloadModule.forRoot(), RouterModule.forRoot([], { + // enableTracing: true, useHash: false, preloadingStrategy: IdlePreload diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts new file mode 100644 index 0000000000..0e4ff9da37 --- /dev/null +++ b/src/app/core/browse/browse.service.ts @@ -0,0 +1,62 @@ +import { Inject, Injectable } from '@angular/core'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { ResponseCacheService } from '../cache/response-cache.service'; +import { RequestService } from '../data/request.service'; +import { GlobalConfig } from '../../../config/global-config.interface'; +import { GLOBAL_CONFIG } from '../../../config'; +import { BrowseEndpointRequest, RestRequest } from '../data/request.models'; +import { ResponseCacheEntry } from '../cache/response-cache.reducer'; +import { BrowseSuccessResponse } from '../cache/response-cache.models'; +import { isNotEmpty } from '../../shared/empty.util'; +import { BrowseDefinition } from '../shared/browse-definition.model'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class BrowseService extends HALEndpointService { + protected linkName = 'browses'; + + private static toSearchKeyArray(metadatumKey: string): string[] { + const keyParts = metadatumKey.split('.'); + const searchFor = []; + searchFor.push('*'); + for (let i = 0; i < keyParts.length - 1; i++) { + const prevParts = keyParts.slice(0, i + 1); + const nextPart = [...prevParts, '*'].join('.'); + searchFor.push(nextPart); + } + searchFor.push(metadatumKey); + return searchFor; + } + + constructor( + protected responseCache: ResponseCacheService, + protected requestService: RequestService, + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig) { + super(); + } + + getBrowseURLFor(metadatumKey: string, linkName: string): Observable { + const searchKeyArray = BrowseService.toSearchKeyArray(metadatumKey); + return this.getEndpoint() + .filter((href: string) => isNotEmpty(href)) + .distinctUntilChanged() + .map((endpointURL: string) => new BrowseEndpointRequest(endpointURL)) + .do((request: RestRequest) => { + setTimeout(() => { + this.requestService.configure(request); + }, 0); + }) + .flatMap((request: RestRequest) => this.responseCache.get(request.href) + .map((entry: ResponseCacheEntry) => entry.response) + .filter((response: BrowseSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.browseDefinitions)) + .map((response: BrowseSuccessResponse) => response.browseDefinitions) + .map((browseDefinitions: BrowseDefinition[]) => browseDefinitions + .find((def: BrowseDefinition) => { + const matchingKeys = def.metadataKeys.find((key: string) => searchKeyArray.indexOf(key) >= 0); + return matchingKeys.length > 0 + }) + ).map((def: BrowseDefinition) => def._links[linkName]) + ); + } + +} 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 3c6ca663c6..da42ea5a9b 100644 --- a/src/app/core/cache/models/normalized-dspace-object.model.ts +++ b/src/app/core/cache/models/normalized-dspace-object.model.ts @@ -66,4 +66,14 @@ export abstract class NormalizedDSpaceObject extends NormalizedObject { @autoserialize owner: string; + /** + * The links to all related resources returned by the rest api. + * + * Repeated here to make the serialization work, + * inheritSerialization doesn't seem to work for more than one level + */ + @autoserialize + _links: { + [name: string]: string + } } diff --git a/src/app/core/cache/models/normalized-object.model.ts b/src/app/core/cache/models/normalized-object.model.ts index 7c4154eae9..b26bd90b2a 100644 --- a/src/app/core/cache/models/normalized-object.model.ts +++ b/src/app/core/cache/models/normalized-object.model.ts @@ -17,4 +17,8 @@ export abstract class NormalizedObject implements CacheableObject { @autoserialize uuid: string; + @autoserialize + _links: { + [name: string]: string + } } diff --git a/src/app/core/cache/response-cache.models.ts b/src/app/core/cache/response-cache.models.ts index 8444a86490..4e3939b425 100644 --- a/src/app/core/cache/response-cache.models.ts +++ b/src/app/core/cache/response-cache.models.ts @@ -1,5 +1,6 @@ import { RequestError } from '../data/request.models'; import { PageInfo } from '../shared/page-info.model'; +import { BrowseDefinition } from '../shared/browse-definition.model'; /* tslint:disable:max-classes-per-file */ export class RestResponse { @@ -32,6 +33,15 @@ export class RootSuccessResponse extends RestResponse { } } +export class BrowseSuccessResponse extends RestResponse { + constructor( + public browseDefinitions: BrowseDefinition[], + public statusCode: string + ) { + super(true, statusCode); + } +} + export class ErrorResponse extends RestResponse { errorMessage: string; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 9742a6b500..3372e06e47 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -24,6 +24,8 @@ import { HostWindowService } from '../shared/host-window.service'; import { NativeWindowFactory, NativeWindowService } from '../shared/window.service'; import { ServerResponseService } from '../shared/server-response.service'; +import { BrowseService } from './browse/browse.service'; +import { BrowseResponseParsingService } from './data/browse-response-parsing.service'; const IMPORTS = [ CommonModule, @@ -54,6 +56,8 @@ const PROVIDERS = [ ResponseCacheService, RootResponseParsingService, ServerResponseService, + BrowseResponseParsingService, + BrowseService, { provide: NativeWindowService, useFactory: NativeWindowFactory } ]; diff --git a/src/app/core/data/browse-response-parsing.service.ts b/src/app/core/data/browse-response-parsing.service.ts new file mode 100644 index 0000000000..8633e7269a --- /dev/null +++ b/src/app/core/data/browse-response-parsing.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { ResponseParsingService } from './parsing.service'; +import { RestRequest } from './request.models'; +import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; +import { BrowseSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models'; +import { isNotEmpty } from '../../shared/empty.util'; +import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; +import { BrowseDefinition } from '../shared/browse-definition.model'; + +@Injectable() +export class BrowseResponseParsingService implements ResponseParsingService { + + parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { + if (isNotEmpty(data.payload) && isNotEmpty(data.payload._embedded) + && Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) { + const serializer = new DSpaceRESTv2Serializer(BrowseDefinition); + const browseDefinitions = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]); + return new BrowseSuccessResponse(browseDefinitions, data.statusCode); + } else { + return new ErrorResponse( + Object.assign( + new Error('Unexpected response from browse endpoint'), + { statusText: data.statusCode } + ) + ); + } + } +} diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index ec765c3cb1..e3c43910ab 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -9,20 +9,54 @@ import { CoreState } from '../core.reducers'; import { RequestService } from './request.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; +import { Observable } from 'rxjs/Observable'; +import { CommunityDataService } from './community-data.service'; +import { FindByIDRequest } from './request.models'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { NormalizedCommunity } from '../cache/models/normalized-community.model'; +import { isNotEmpty } from '../../shared/empty.util'; @Injectable() export class CollectionDataService extends DataService { protected linkName = 'collections'; - protected browseEndpoint = '/discover/browses/dateissued/collections'; constructor( protected responseCache: ResponseCacheService, protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected store: Store, - @Inject(GLOBAL_CONFIG) EnvConfig: GlobalConfig + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + private cds: CommunityDataService, + protected objectCache: ObjectCacheService ) { - super(NormalizedCollection, EnvConfig); + super(NormalizedCollection); } + /** + * Get the scoped endpoint URL by fetching the object with + * the given scopeID and returning its HAL link with this + * data-service's linkName + * + * @param {string} scopeID + * the id of the scope object + * @return { Observable } + * an Observable containing the scoped URL + */ + public getScopedEndpoint(scopeID: string): Observable { + this.cds.getEndpoint() + .map((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID)) + .filter((href: string) => isNotEmpty(href)) + .take(1) + .subscribe((href: string) => { + const request = new FindByIDRequest(href, scopeID); + setTimeout(() => { + this.requestService.configure(request); + }, 0); + }); + + return this.objectCache.getByUUID(scopeID, NormalizedCommunity) + .map((nc: NormalizedCommunity) => nc._links[this.linkName]) + .filter((href) => isNotEmpty(href)) + .distinctUntilChanged(); + } } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 532bce5ee6..5fdf3f4026 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -10,20 +10,52 @@ import { CoreState } from '../core.reducers'; import { RequestService } from './request.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; +import { Observable } from 'rxjs/Observable'; +import { isNotEmpty } from '../../shared/empty.util'; +import { FindByIDRequest } from './request.models'; +import { ObjectCacheService } from '../cache/object-cache.service'; @Injectable() export class CommunityDataService extends DataService { protected linkName = 'communities'; - protected browseEndpoint = '/discover/browses/dateissued/communities'; constructor( protected responseCache: ResponseCacheService, protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected store: Store, - @Inject(GLOBAL_CONFIG) EnvConfig: GlobalConfig + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + protected objectCache: ObjectCacheService ) { - super(NormalizedCommunity, EnvConfig); + super(NormalizedCommunity); + } + + /** + * Get the scoped endpoint URL by fetching the object with + * the given scopeID and returning its HAL link with this + * data-service's linkName + * + * @param {string} scopeID + * the id of the scope object + * @return { Observable } + * an Observable containing the scoped URL + */ + public getScopedEndpoint(scopeID: string): Observable { + this.getEndpoint() + .map((endpoint: string) => this.getFindByIDHref(endpoint, scopeID)) + .filter((href: string) => isNotEmpty(href)) + .take(1) + .subscribe((href: string) => { + const request = new FindByIDRequest(href, scopeID); + setTimeout(() => { + this.requestService.configure(request); + }, 0); + }); + + return this.objectCache.getByUUID(scopeID, NormalizedCommunity) + .map((nc: NormalizedCommunity) => nc._links[this.linkName]) + .filter((href) => isNotEmpty(href)) + .distinctUntilChanged(); } } diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index e48e7a8bb8..cabf452f01 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,71 +1,41 @@ -import { ResponseCacheService } from '../cache/response-cache.service'; -import { CacheableObject } from '../cache/object-cache.reducer'; -import { hasValue, isNotEmpty } from '../../shared/empty.util'; -import { RemoteData } from './remote-data'; -import { - FindAllOptions, FindAllRequest, FindByIDRequest, RestRequest, - RootEndpointRequest -} from './request.models'; import { Store } from '@ngrx/store'; -import { CoreState } from '../core.reducers'; -import { RequestService } from './request.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { GenericConstructor } from '../shared/generic-constructor'; -import { GlobalConfig } from '../../../config'; -import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; import { Observable } from 'rxjs/Observable'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { EndpointMap, RootSuccessResponse } from '../cache/response-cache.models'; +import { GlobalConfig } from '../../../config'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { CacheableObject } from '../cache/object-cache.reducer'; +import { ResponseCacheService } from '../cache/response-cache.service'; +import { CoreState } from '../core.reducers'; +import { GenericConstructor } from '../shared/generic-constructor'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RemoteData } from './remote-data'; +import { FindAllOptions, FindAllRequest, FindByIDRequest, RestRequest } from './request.models'; +import { RequestService } from './request.service'; -export abstract class DataService { +export abstract class DataService extends HALEndpointService { protected abstract responseCache: ResponseCacheService; protected abstract requestService: RequestService; protected abstract rdbService: RemoteDataBuildService; protected abstract store: Store; protected abstract linkName: string; - protected abstract browseEndpoint: string; + protected abstract EnvConfig: GlobalConfig constructor( private normalizedResourceType: GenericConstructor, - protected EnvConfig: GlobalConfig ) { - + super(); } - private getEndpointMap(): Observable { - const request = new RootEndpointRequest(this.EnvConfig); - this.requestService.configure(request); - return this.responseCache.get(request.href) - .map((entry: ResponseCacheEntry) => entry.response) - .filter((response: RootSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap)) - .map((response: RootSuccessResponse) => response.endpointMap) - .distinctUntilChanged(); - } + public abstract getScopedEndpoint(scope: string): Observable - public getEndpoint(): Observable { - const request = new RootEndpointRequest(this.EnvConfig); - this.requestService.configure(request); - return this.getEndpointMap() - .map((map: EndpointMap) => map[this.linkName]) - .distinctUntilChanged(); - } - - public isEnabledOnRestApi(): Observable { - return this.getEndpointMap() - .map((map: EndpointMap) => isNotEmpty(map[this.linkName])) - .startWith(undefined) - .distinctUntilChanged(); - } - - protected getFindAllHref(endpoint, options: FindAllOptions = {}): string { + protected getFindAllHref(endpoint, options: FindAllOptions = {}): Observable { let result; const args = []; if (hasValue(options.scopeID)) { - result = new RESTURLCombiner(this.EnvConfig, this.browseEndpoint).toString(); - args.push(`scope=${options.scopeID}`); + result = this.getScopedEndpoint(options.scopeID); } else { - result = endpoint; + result = Observable.of(endpoint); } if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { @@ -86,25 +56,28 @@ export abstract class DataService } if (isNotEmpty(args)) { - result = `${result}?${args.join('&')}`; + return result.map((href: string) => `${href}?${args.join('&')}`); + } else { + return result; } - return result; } findAll(options: FindAllOptions = {}): RemoteData { const hrefObs = this.getEndpoint() - .map((endpoint: string) => this.getFindAllHref(endpoint, options)); + .flatMap((endpoint: string) => this.getFindAllHref(endpoint, options)); hrefObs .subscribe((href: string) => { const request = new FindAllRequest(href, options); - this.requestService.configure(request); + setTimeout(() => { + this.requestService.configure(request); + }, 0); }); return this.rdbService.buildList(hrefObs, this.normalizedResourceType); } - protected getFindByIDHref(endpoint, resourceID): string { + getFindByIDHref(endpoint, resourceID): string { return `${endpoint}/${resourceID}`; } @@ -115,14 +88,18 @@ export abstract class DataService hrefObs .subscribe((href: string) => { const request = new FindByIDRequest(href, id); - this.requestService.configure(request); + setTimeout(() => { + this.requestService.configure(request); + }, 0); }); return this.rdbService.buildSingle(hrefObs, this.normalizedResourceType); } findByHref(href: string): RemoteData { - this.requestService.configure(new RestRequest(href)); + setTimeout(() => { + this.requestService.configure(new RestRequest(href)); + }, 0); return this.rdbService.buildSingle(href, this.normalizedResourceType); // return this.rdbService.buildSingle(href)); } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index d155910b4e..bbb03acb1d 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -10,19 +10,29 @@ import { NormalizedItem } from '../cache/models/normalized-item.model'; import { RequestService } from './request.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; +import { Observable } from 'rxjs/Observable'; +import { BrowseService } from '../browse/browse.service'; +import { isNotEmpty } from '../../shared/empty.util'; @Injectable() export class ItemDataService extends DataService { protected linkName = 'items'; - protected browseEndpoint = '/discover/browses/dateissued/items'; constructor( protected responseCache: ResponseCacheService, protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected store: Store, - @Inject(GLOBAL_CONFIG) EnvConfig: GlobalConfig + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + private bs: BrowseService ) { - super(NormalizedItem, EnvConfig); + super(NormalizedItem); } + + public getScopedEndpoint(scopeID: string): Observable { + return this.bs.getBrowseURLFor('dc.date.issued', this.linkName) + .filter((href) => isNotEmpty(href)) + .distinctUntilChanged(); + } + } diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 8c415e71ef..ab3e38d9cd 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -5,6 +5,7 @@ import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; import { DSOResponseParsingService } from './dso-response-parsing.service'; import { ResponseParsingService } from './parsing.service'; import { RootResponseParsingService } from './root-response-parsing.service'; +import { BrowseResponseParsingService } from './browse-response-parsing.service'; /* tslint:disable:max-classes-per-file */ export class RestRequest { @@ -53,6 +54,16 @@ export class RootEndpointRequest extends RestRequest { } } +export class BrowseEndpointRequest extends RestRequest { + constructor(href: string) { + super(href); + } + + getResponseParser(): GenericConstructor { + return BrowseResponseParsingService; + } +} + export class RequestError extends Error { statusText: string; } diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index e6b4f816f1..4231f9efbb 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -6,7 +6,7 @@ import { Observable } from 'rxjs/Observable'; import { hasValue } from '../../shared/empty.util'; import { CacheableObject } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { DSOSuccessResponse } from '../cache/response-cache.models'; +import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; @@ -47,16 +47,20 @@ export class RequestService { configure(request: RestRequest): void { let isCached = this.objectCache.hasBySelfLink(request.href); - + // console.log('request.href', request.href); if (!isCached && this.responseCache.has(request.href)) { - const [dsoSuccessResponse, otherSuccessResponse] = this.responseCache.get(request.href) + const [successResponse, errorResponse] = this.responseCache.get(request.href) .take(1) - .filter((entry: ResponseCacheEntry) => entry.response.isSuccessful) .map((entry: ResponseCacheEntry) => entry.response) + .share() + .partition((response: RestResponse) => response.isSuccessful); + + const [dsoSuccessResponse, otherSuccessResponse] = successResponse .share() .partition((response: DSOSuccessResponse) => hasValue(response.resourceSelfLinks)); Observable.merge( + errorResponse.map(() => true), // TODO add a configurable number of retries in case of an error. otherSuccessResponse.map(() => true), dsoSuccessResponse // a DSOSuccessResponse should only be considered cached if all its resources are cached .map((response: DSOSuccessResponse) => response.resourceSelfLinks) diff --git a/src/app/core/data/root-response-parsing.service.ts b/src/app/core/data/root-response-parsing.service.ts index 016a501685..a3e7fc22a3 100644 --- a/src/app/core/data/root-response-parsing.service.ts +++ b/src/app/core/data/root-response-parsing.service.ts @@ -19,12 +19,7 @@ export class RootResponseParsingService implements ResponseParsingService { if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links)) { const links = data.payload._links; for (const link of Object.keys(links)) { - let href = links[link].href; - // TODO temporary workaround as these endpoint paths are relative, but should be absolute - if (isNotEmpty(href) && !href.startsWith('http')) { - href = new RESTURLCombiner(this.EnvConfig, href.substring(this.EnvConfig.rest.nameSpace.length)).toString(); - } - links[link] = href; + links[link] = links[link].href; } return new RootSuccessResponse(links, data.statusCode); } else { diff --git a/src/app/core/shared/browse-definition.model.ts b/src/app/core/shared/browse-definition.model.ts new file mode 100644 index 0000000000..bdb91167b0 --- /dev/null +++ b/src/app/core/shared/browse-definition.model.ts @@ -0,0 +1,24 @@ +import { autoserialize, autoserializeAs } from 'cerialize'; +import { SortOption } from './sort-option.model'; + +export class BrowseDefinition { + @autoserialize + metadataBrowse: boolean; + + @autoserialize + sortOptions: SortOption[]; + + @autoserializeAs('order') + defaultSortOrder: string; + + @autoserialize + type: string; + + @autoserializeAs('metadata') + metadataKeys: string[]; + + @autoserialize + _links: { + [name: string]: string + } +} diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts new file mode 100644 index 0000000000..58c647ead8 --- /dev/null +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -0,0 +1,46 @@ +import { Observable } from 'rxjs/Observable'; +import { RequestService } from '../data/request.service'; +import { ResponseCacheService } from '../cache/response-cache.service'; +import { GlobalConfig } from '../../../config/global-config.interface'; +import { EndpointMap, RootSuccessResponse } from '../cache/response-cache.models'; +import { RootEndpointRequest } from '../data/request.models'; +import { ResponseCacheEntry } from '../cache/response-cache.reducer'; +import { isNotEmpty } from '../../shared/empty.util'; + +export abstract class HALEndpointService { + protected abstract responseCache: ResponseCacheService; + protected abstract requestService: RequestService; + protected abstract linkName: string; + protected abstract EnvConfig: GlobalConfig; + + protected getEndpointMap(): Observable { + const request = new RootEndpointRequest(this.EnvConfig); + setTimeout(() => { + this.requestService.configure(request); + }, 0); + return this.responseCache.get(request.href) + .map((entry: ResponseCacheEntry) => entry.response) + .filter((response: RootSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap)) + .map((response: RootSuccessResponse) => response.endpointMap) + .distinctUntilChanged(); + } + + public getEndpoint(): Observable { + return this.getEndpointMap() + .do((map: EndpointMap) => { + if (!this.linkName) { + console.log('map', this) + } + }) + .map((map: EndpointMap) => map[this.linkName]) + .distinctUntilChanged(); + } + + public isEnabledOnRestApi(): Observable { + return this.getEndpointMap() + .map((map: EndpointMap) => isNotEmpty(map[this.linkName])) + .startWith(undefined) + .distinctUntilChanged(); + } + +} diff --git a/src/app/core/shared/sort-option.model.ts b/src/app/core/shared/sort-option.model.ts new file mode 100644 index 0000000000..c735e87b9a --- /dev/null +++ b/src/app/core/shared/sort-option.model.ts @@ -0,0 +1,9 @@ +import { autoserialize } from 'cerialize'; + +export class SortOption { + @autoserialize + name: string; + + @autoserialize + metadata: string; +}