From 2330e96158dffe4dec6eafcb7703efc4ba3e4868 Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 17 Oct 2018 13:18:01 +0200 Subject: [PATCH] intermediate commit --- .../top-level-community-list.component.ts | 1 + .../search-service/search.service.spec.ts | 18 +-- .../search-service/search.service.ts | 54 ++++---- src/app/core/auth/auth-request.service.ts | 15 +-- .../auth-response-parsing.service.spec.ts | 2 +- .../auth/auth-response-parsing.service.ts | 2 +- src/app/core/browse/browse.service.spec.ts | 21 +-- src/app/core/browse/browse.service.ts | 35 ++--- .../builders/remote-data-build.service.ts | 62 ++++----- src/app/core/cache/response-cache.actions.ts | 72 ---------- .../core/cache/response-cache.effects.spec.ts | 38 ------ src/app/core/cache/response-cache.effects.ts | 27 ---- .../core/cache/response-cache.reducer.spec.ts | 124 ------------------ src/app/core/cache/response-cache.reducer.ts | 111 ---------------- .../core/cache/response-cache.service.spec.ts | 100 -------------- src/app/core/cache/response-cache.service.ts | 100 -------------- ...nse-cache.models.ts => response.models.ts} | 2 +- src/app/core/config/config.service.spec.ts | 12 -- src/app/core/config/config.service.ts | 14 +- .../submission-definitions-config.service.ts | 2 - .../config/submission-forms-config.service.ts | 2 - .../submission-sections-config.service.ts | 2 - src/app/core/core.effects.ts | 2 - src/app/core/core.module.ts | 2 - src/app/core/core.reducers.ts | 3 - ...e-entries-response-parsing.service.spec.ts | 2 +- ...browse-entries-response-parsing.service.ts | 2 +- ...wse-items-response-parsing-service.spec.ts | 2 +- .../browse-items-response-parsing-service.ts | 2 +- .../browse-response-parsing.service.spec.ts | 2 +- .../data/browse-response-parsing.service.ts | 2 +- src/app/core/data/collection-data.service.ts | 3 - src/app/core/data/comcol-data.service.spec.ts | 15 --- src/app/core/data/comcol-data.service.ts | 23 ++-- src/app/core/data/community-data.service.ts | 11 +- .../config-response-parsing.service.spec.ts | 2 +- .../data/config-response-parsing.service.ts | 2 +- src/app/core/data/data.service.spec.ts | 6 +- src/app/core/data/data.service.ts | 19 ++- .../data/debug-response-parsing.service.ts | 2 +- .../core/data/dso-response-parsing.service.ts | 8 +- .../core/data/dspace-object-data.service.ts | 8 +- .../endpoint-map-response-parsing.service.ts | 2 +- .../facet-config-response-parsing.service.ts | 2 +- ...acet-value-map-response-parsing.service.ts | 2 +- .../facet-value-response-parsing.service.ts | 2 +- src/app/core/data/item-data.service.spec.ts | 3 - src/app/core/data/item-data.service.ts | 6 +- .../data/metadataschema-parsing.service.ts | 2 +- src/app/core/data/parsing.service.ts | 2 +- ...tstreamformats-response-parsing.service.ts | 2 +- ...metadatafields-response-parsing.service.ts | 2 +- ...etadataschemas-response-parsing.service.ts | 2 +- src/app/core/data/request.actions.ts | 38 +++++- src/app/core/data/request.effects.ts | 43 ++++-- src/app/core/data/request.models.ts | 13 +- src/app/core/data/request.reducer.ts | 30 ++++- src/app/core/data/request.service.spec.ts | 5 - src/app/core/data/request.service.ts | 87 ++++++++---- .../data/search-response-parsing.service.ts | 2 +- src/app/core/integration/authority.service.ts | 2 - ...tegration-response-parsing.service.spec.ts | 2 +- .../integration-response-parsing.service.ts | 2 +- .../integration/integration.service.spec.ts | 15 +-- .../core/integration/integration.service.ts | 13 +- .../core/metadata/metadata.service.spec.ts | 8 +- .../core/registry/registry.service.spec.ts | 6 +- src/app/core/registry/registry.service.ts | 66 ++++------ .../core/shared/hal-endpoint.service.spec.ts | 18 --- src/app/core/shared/hal-endpoint.service.ts | 44 +++++-- src/app/core/shared/operators.spec.ts | 50 +------ src/app/core/shared/operators.ts | 29 ++-- .../mocks/mock-remote-data-build.service.ts | 3 +- .../mocks/mock-response-cache.service.ts | 16 --- src/server.ts | 1 + 75 files changed, 387 insertions(+), 1067 deletions(-) delete mode 100644 src/app/core/cache/response-cache.actions.ts delete mode 100644 src/app/core/cache/response-cache.effects.spec.ts delete mode 100644 src/app/core/cache/response-cache.effects.ts delete mode 100644 src/app/core/cache/response-cache.reducer.spec.ts delete mode 100644 src/app/core/cache/response-cache.reducer.ts delete mode 100644 src/app/core/cache/response-cache.service.spec.ts delete mode 100644 src/app/core/cache/response-cache.service.ts rename src/app/core/cache/{response-cache.models.ts => response.models.ts} (99%) diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts index 8e8c83ce5b..3fdb7e48a2 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts @@ -17,6 +17,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c changeDetection: ChangeDetectionStrategy.OnPush, animations: [fadeInOut] }) + export class TopLevelCommunityListComponent { communitiesRDObs: Observable>>; config: PaginationComponentOptions; diff --git a/src/app/+search-page/search-service/search.service.spec.ts b/src/app/+search-page/search-service/search.service.spec.ts index 6bfc9200ec..028a3fa446 100644 --- a/src/app/+search-page/search-service/search.service.spec.ts +++ b/src/app/+search-page/search-service/search.service.spec.ts @@ -8,21 +8,18 @@ import { SearchService } from './search.service'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { ActivatedRoute, Router, UrlTree } from '@angular/router'; import { RequestService } from '../../core/data/request.service'; -import { ResponseCacheService } from '../../core/cache/response-cache.service'; import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; import { RouterStub } from '../../shared/testing/router-stub'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { Observable, combineLatest as observableCombineLatest } from 'rxjs'; import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { RemoteData } from '../../core/data/remote-data'; -import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; import { RequestEntry } from '../../core/data/request.reducer'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; -import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; import { FacetConfigSuccessResponse, SearchSuccessResponse -} from '../../core/cache/response-cache.models'; +} from '../../core/cache/response.models'; import { SearchQueryResponse } from './search-query-response.model'; import { SearchFilterConfig } from './search-filter-config.model'; import { CommunityDataService } from '../../core/data/community-data.service'; @@ -54,7 +51,6 @@ describe('SearchService', () => { providers: [ { provide: Router, useValue: router }, { provide: ActivatedRoute, useValue: route }, - { provide: ResponseCacheService, useValue: getMockResponseCacheService() }, { provide: RequestService, useValue: getMockRequestService() }, { provide: RemoteDataBuildService, useValue: {} }, { provide: HALEndpointService, useValue: {} }, @@ -86,9 +82,8 @@ describe('SearchService', () => { }; const remoteDataBuildService = { - toRemoteDataObservable: (requestEntryObs: Observable, responseCacheObs: Observable, payloadObs: Observable) => { - return observableCombineLatest(requestEntryObs, - responseCacheObs, payloadObs).pipe( + toRemoteDataObservable: (requestEntryObs: Observable, payloadObs: Observable) => { + return observableCombineLatest(requestEntryObs, payloadObs).pipe( map(([req, res, pay]) => { return { req, res, pay }; }) @@ -113,7 +108,6 @@ describe('SearchService', () => { providers: [ { provide: Router, useValue: router }, { provide: ActivatedRoute, useValue: route }, - { provide: ResponseCacheService, useValue: getMockResponseCacheService() }, { provide: RequestService, useValue: getMockRequestService() }, { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, { provide: HALEndpointService, useValue: halService }, @@ -162,10 +156,8 @@ describe('SearchService', () => { const searchOptions = new PaginatedSearchOptions({}); const queryResponse = Object.assign(new SearchQueryResponse(), { objects: [] }); const response = new SearchSuccessResponse(queryResponse, '200'); - const responseEntry = Object.assign(new ResponseCacheEntry(), { response: response }); beforeEach(() => { spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint)); - (searchService as any).responseCache.get.and.returnValue(observableOf(responseEntry)); /* tslint:disable:no-empty */ searchService.search(searchOptions).subscribe((t) => { }); // subscribe to make sure all methods are called @@ -192,10 +184,8 @@ describe('SearchService', () => { const endPoint = 'http://endpoint.com/test/config'; const filterConfig = [new SearchFilterConfig()]; const response = new FacetConfigSuccessResponse(filterConfig, '200'); - const responseEntry = Object.assign(new ResponseCacheEntry(), { response: response }); beforeEach(() => { spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint)); - (searchService as any).responseCache.get.and.returnValue(observableOf(responseEntry)); /* tslint:disable:no-empty */ searchService.getConfig(null).subscribe((t) => { }); // subscribe to make sure all methods are called @@ -224,10 +214,8 @@ describe('SearchService', () => { const requestUrl = endPoint + '?scope=' + scope; const filterConfig = [new SearchFilterConfig()]; const response = new FacetConfigSuccessResponse(filterConfig, '200'); - const responseEntry = Object.assign(new ResponseCacheEntry(), { response: response }); beforeEach(() => { spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint)); - (searchService as any).responseCache.get.and.returnValue(observableOf(responseEntry)); /* tslint:disable:no-empty */ searchService.getConfig(scope).subscribe((t) => { }); // subscribe to make sure all methods are called diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 1503440eb0..c628fd350e 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -1,4 +1,4 @@ -import { of as observableOf, combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { Injectable, OnDestroy } from '@angular/core'; import { ActivatedRoute, @@ -7,15 +7,13 @@ import { Router, UrlSegmentGroup } from '@angular/router'; -import { flatMap, map, switchMap } from 'rxjs/operators'; +import { filter, flatMap, map, switchMap } from 'rxjs/operators'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { FacetConfigSuccessResponse, FacetValueSuccessResponse, SearchSuccessResponse -} from '../../core/cache/response-cache.models'; -import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; -import { ResponseCacheService } from '../../core/cache/response-cache.service'; +} from '../../core/cache/response.models'; import { PaginatedList } from '../../core/data/paginated-list'; import { ResponseParsingService } from '../../core/data/parsing.service'; import { RemoteData } from '../../core/data/remote-data'; @@ -24,7 +22,11 @@ import { RequestService } from '../../core/data/request.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; -import { configureRequest, getSucceededRemoteData } from '../../core/shared/operators'; +import { + configureRequest, + getResponseFromEntry, + getSucceededRemoteData +} from '../../core/shared/operators'; import { URLCombiner } from '../../core/url-combiner/url-combiner'; import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { NormalizedSearchResult } from '../normalized-search-result.model'; @@ -45,6 +47,7 @@ import { CommunityDataService } from '../../core/data/community-data.service'; import { ViewMode } from '../../core/shared/view-mode.model'; import { ResourceType } from '../../core/shared/resource-type'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; +import { RequestEntry } from '../../core/data/request.reducer'; /** * Service that performs all general actions that have to do with the search page @@ -68,7 +71,6 @@ export class SearchService implements OnDestroy { constructor(private router: Router, private route: ActivatedRoute, - protected responseCache: ResponseCacheService, protected requestService: RequestService, private rdb: RemoteDataBuildService, private halService: HALEndpointService, @@ -101,13 +103,9 @@ export class SearchService implements OnDestroy { 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 sqrObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const sqrObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: SearchSuccessResponse) => response.results) ); @@ -139,8 +137,8 @@ export class SearchService implements OnDestroy { }) ); - const pageInfoObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const pageInfoObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: FacetValueSuccessResponse) => response.pageInfo) ); @@ -150,7 +148,7 @@ export class SearchService implements OnDestroy { }) ); - return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); + return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); } /** @@ -185,18 +183,14 @@ export class SearchService implements OnDestroy { 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 facetConfigObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const facetConfigObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: FacetConfigSuccessResponse) => response.results.map((result: any) => Object.assign(new SearchFilterConfig(), result))) ); - return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, facetConfigObs); + return this.rdb.toRemoteDataObservable(requestEntryObs, facetConfigObs); } /** @@ -232,18 +226,14 @@ export class SearchService implements OnDestroy { 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 = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const facetValueObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: FacetValueSuccessResponse) => response.results) ); - const pageInfoObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const pageInfoObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: FacetValueSuccessResponse) => response.pageInfo) ); @@ -253,7 +243,7 @@ export class SearchService implements OnDestroy { }) ); - return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); + return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); } /** diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 7cb5fae7e4..0752149eae 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -2,15 +2,15 @@ import { Observable, of as observableOf, throwError as observableThrowError } fr import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; 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 { GLOBAL_CONFIG } from '../../../config'; import { GlobalConfig } from '../../../config/global-config.interface'; import { isNotEmpty } from '../../shared/empty.util'; import { AuthGetRequest, AuthPostRequest, PostRequest, RestRequest } from '../data/request.models'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { AuthStatusResponse, ErrorResponse } from '../cache/response-cache.models'; +import { AuthStatusResponse, ErrorResponse } from '../cache/response.models'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; +import { RequestEntry } from '../data/request.reducer'; +import { getResponseFromEntry } from '../shared/operators'; @Injectable() export class AuthRequestService { @@ -19,18 +19,17 @@ export class AuthRequestService { constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, protected halService: HALEndpointService, - protected responseCache: ResponseCacheService, protected requestService: RequestService) { } protected fetchRequest(request: RestRequest): Observable { - return this.responseCache.get(request.href).pipe( - map((entry: ResponseCacheEntry) => entry.response), + return this.requestService.getByHref(request.href).pipe( + getResponseFromEntry(), // TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed - tap(() => this.responseCache.remove(request.href)), + // tap(() => this.responseCache.remove(request.href)), mergeMap((response) => { if (response.isSuccessful && isNotEmpty(response)) { - return observableOf((response as AuthStatusResponse).response); + return observableOf((response as AuthStatusResponse).response); } else if (!response.isSuccessful) { return observableThrowError(new Error((response as ErrorResponse).errorMessage)); } diff --git a/src/app/core/auth/auth-response-parsing.service.spec.ts b/src/app/core/auth/auth-response-parsing.service.spec.ts index 138d0f1be3..e23e3ac56b 100644 --- a/src/app/core/auth/auth-response-parsing.service.spec.ts +++ b/src/app/core/auth/auth-response-parsing.service.spec.ts @@ -1,4 +1,4 @@ -import { AuthStatusResponse } from '../cache/response-cache.models'; +import { AuthStatusResponse } from '../cache/response.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { GlobalConfig } from '../../../config/global-config.interface'; diff --git a/src/app/core/auth/auth-response-parsing.service.ts b/src/app/core/auth/auth-response-parsing.service.ts index 8efa36f9e2..65d093de61 100644 --- a/src/app/core/auth/auth-response-parsing.service.ts +++ b/src/app/core/auth/auth-response-parsing.service.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core'; import { AuthObjectFactory } from './auth-object-factory'; import { BaseResponseParsingService } from '../data/base-response-parsing.service'; -import { AuthStatusResponse, RestResponse } from '../cache/response-cache.models'; +import { AuthStatusResponse, RestResponse } from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { GLOBAL_CONFIG } from '../../../config'; import { GlobalConfig } from '../../../config/global-config.interface'; diff --git a/src/app/core/browse/browse.service.spec.ts b/src/app/core/browse/browse.service.spec.ts index d43a26ed4b..4465eae1ee 100644 --- a/src/app/core/browse/browse.service.spec.ts +++ b/src/app/core/browse/browse.service.spec.ts @@ -2,10 +2,8 @@ import { cold, getTestScheduler, hot } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; -import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { BrowseEndpointRequest, BrowseEntriesRequest, BrowseItemsRequest } from '../data/request.models'; import { RequestService } from '../data/request.service'; import { BrowseDefinition } from '../shared/browse-definition.model'; @@ -14,7 +12,6 @@ import { BrowseService } from './browse.service'; describe('BrowseService', () => { let scheduler: TestScheduler; let service: BrowseService; - let responseCache: ResponseCacheService; let requestService: RequestService; let rdbService: RemoteDataBuildService; @@ -79,22 +76,10 @@ describe('BrowseService', () => { }) ]; - function initMockResponseCacheService(isSuccessful: boolean) { - const rcs = getMockResponseCacheService(); - (rcs.get as any).and.returnValue(cold('b-', { - b: { - response: { - isSuccessful, - payload: browseDefinitions, - } - } - })); - return rcs; - } + function initTestService() { return new BrowseService( - responseCache, requestService, halService, rdbService @@ -108,7 +93,6 @@ describe('BrowseService', () => { describe('getBrowseDefinitions', () => { beforeEach(() => { - responseCache = initMockResponseCacheService(true); requestService = getMockRequestService(); rdbService = getMockRemoteDataBuildService(); service = initTestService(); @@ -147,7 +131,6 @@ describe('BrowseService', () => { const mockAuthorName = 'Donald Smith'; beforeEach(() => { - responseCache = initMockResponseCacheService(true); requestService = getMockRequestService(); rdbService = getMockRemoteDataBuildService(); service = initTestService(); @@ -221,7 +204,6 @@ describe('BrowseService', () => { describe('if getBrowseDefinitions fires', () => { beforeEach(() => { - responseCache = initMockResponseCacheService(true); requestService = getMockRequestService(); rdbService = getMockRemoteDataBuildService(); service = initTestService(); @@ -277,7 +259,6 @@ describe('BrowseService', () => { describe('if getBrowseDefinitions doesn\'t fire', () => { it('should return undefined', () => { - responseCache = initMockResponseCacheService(true); requestService = getMockRequestService(); rdbService = getMockRemoteDataBuildService(); service = initTestService(); diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts index ddce277e7e..c1c893ec27 100644 --- a/src/app/core/browse/browse.service.ts +++ b/src/app/core/browse/browse.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { distinctUntilChanged, map, startWith } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, startWith, tap } from 'rxjs/operators'; import { - ensureArrayHasValue, + ensureArrayHasValue, hasValue, hasValueOperator, isEmpty, isNotEmpty, @@ -11,16 +11,13 @@ import { import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { SortOptions } from '../cache/models/sort-options.model'; -import { GenericSuccessResponse } from '../cache/response-cache.models'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { ResponseCacheService } from '../cache/response-cache.service'; +import { GenericSuccessResponse } from '../cache/response.models'; import { PaginatedList } from '../data/paginated-list'; import { RemoteData } from '../data/remote-data'; import { BrowseEndpointRequest, BrowseEntriesRequest, BrowseItemsRequest, - GetRequest, RestRequest } from '../data/request.models'; import { RequestService } from '../data/request.service'; @@ -29,14 +26,15 @@ import { BrowseEntry } from '../shared/browse-entry.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { configureRequest, - filterSuccessfulResponses, getBrowseDefinitionLinks, + filterSuccessfulResponses, + getBrowseDefinitionLinks, getRemoteDataPayload, - getRequestFromSelflink, - getResponseFromSelflink + getRequestFromSelflink } from '../shared/operators'; import { URLCombiner } from '../url-combiner/url-combiner'; import { Item } from '../shared/item.model'; import { DSpaceObject } from '../shared/dspace-object.model'; +import { RequestEntry } from '../data/request.reducer'; @Injectable() export class BrowseService { @@ -56,7 +54,6 @@ export class BrowseService { } constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected halService: HALEndpointService, private rdb: RemoteDataBuildService, @@ -73,10 +70,8 @@ export class BrowseService { const href$ = request$.pipe(map((request: RestRequest) => request.href)); const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); - const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); - const payload$ = responseCache$.pipe( + const payload$ = requestEntry$.pipe( filterSuccessfulResponses(), - map((entry: ResponseCacheEntry) => entry.response), map((response: GenericSuccessResponse) => response.payload), ensureArrayHasValue(), map((definitions: BrowseDefinition[]) => definitions @@ -84,7 +79,7 @@ export class BrowseService { distinctUntilChanged() ); - return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + return this.rdb.toRemoteDataObservable(requestEntry$, payload$); } getBrowseEntriesFor(definitionID: string, options: { @@ -118,11 +113,9 @@ export class BrowseService { const href$ = request$.pipe(map((request: RestRequest) => request.href)); const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); - const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); - const payload$ = responseCache$.pipe( + const payload$ = requestEntry$.pipe( filterSuccessfulResponses(), - map((entry: ResponseCacheEntry) => entry.response), map((response: GenericSuccessResponse) => new PaginatedList(response.pageInfo, response.payload)), map((list: PaginatedList) => Object.assign(list, { page: list.page ? list.page.map((entry: BrowseEntry) => Object.assign(new BrowseEntry(), entry)) : list.page @@ -130,7 +123,7 @@ export class BrowseService { distinctUntilChanged() ); - return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + return this.rdb.toRemoteDataObservable(requestEntry$, payload$); } /** @@ -175,11 +168,9 @@ export class BrowseService { const href$ = request$.pipe(map((request: RestRequest) => request.href)); const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); - const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); - const payload$ = responseCache$.pipe( + const payload$ = requestEntry$.pipe( filterSuccessfulResponses(), - map((entry: ResponseCacheEntry) => entry.response), map((response: GenericSuccessResponse) => new PaginatedList(response.pageInfo, response.payload)), map((list: PaginatedList) => Object.assign(list, { page: list.page ? list.page.map((item: DSpaceObject) => Object.assign(new Item(), item)) : list.page @@ -187,7 +178,7 @@ export class BrowseService { distinctUntilChanged() ); - return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + return this.rdb.toRemoteDataObservable(requestEntry$, payload$); } getBrowseURLFor(metadatumKey: string, linkPath: string): 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 fe7f56220f..27b5ddf50d 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -1,11 +1,11 @@ import { combineLatest as observableCombineLatest, - of as observableOf, Observable, + of as observableOf, race as observableRace } from 'rxjs'; import { Injectable } from '@angular/core'; -import { distinctUntilChanged, flatMap, map, startWith } from 'rxjs/operators'; +import { distinctUntilChanged, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators'; import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util'; import { PaginatedList } from '../../data/paginated-list'; import { RemoteData } from '../../data/remote-data'; @@ -16,22 +16,18 @@ import { RequestService } from '../../data/request.service'; import { NormalizedObject } from '../models/normalized-object.model'; import { ObjectCacheService } from '../object-cache.service'; -import { DSOSuccessResponse, ErrorResponse } from '../response-cache.models'; -import { ResponseCacheEntry } from '../response-cache.reducer'; -import { ResponseCacheService } from '../response-cache.service'; +import { DSOSuccessResponse, ErrorResponse } from '../response.models'; import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators'; import { PageInfo } from '../../shared/page-info.model'; import { + filterSuccessfulResponses, getRequestFromSelflink, - getResourceLinksFromResponse, - getResponseFromSelflink, - filterSuccessfulResponses + getResourceLinksFromResponse } from '../../shared/operators'; @Injectable() export class RemoteDataBuildService { constructor(protected objectCache: ObjectCacheService, - protected responseCache: ResponseCacheService, protected requestService: RequestService) { } @@ -39,19 +35,16 @@ export class RemoteDataBuildService { if (typeof href$ === 'string') { href$ = observableOf(href$); } - const requestHref$ = href$.pipe(flatMap((href: string) => - this.objectCache.getRequestHrefBySelfLink(href))); + const requestHref$ = href$.pipe( + switchMap((href: string) => + this.objectCache.getRequestHrefBySelfLink(href)), + ); const requestEntry$ = observableRace( href$.pipe(getRequestFromSelflink(this.requestService)), requestHref$.pipe(getRequestFromSelflink(this.requestService)) ); - const responseCache$ = observableRace( - href$.pipe(getResponseFromSelflink(this.responseCache)), - requestHref$.pipe(getResponseFromSelflink(this.responseCache)) - ); - // always use self link if that is cached, only if it isn't, get it via the response. const payload$ = observableCombineLatest( @@ -59,7 +52,7 @@ export class RemoteDataBuildService { flatMap((href: string) => this.objectCache.getBySelfLink(href)), startWith(undefined) ), - responseCache$.pipe( + requestEntry$.pipe( getResourceLinksFromResponse(), flatMap((resourceSelfLinks: string[]) => { if (isNotEmpty(resourceSelfLinks)) { @@ -86,21 +79,21 @@ export class RemoteDataBuildService { startWith(undefined), distinctUntilChanged() ); - return this.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + return this.toRemoteDataObservable(requestEntry$, payload$); } - toRemoteDataObservable(requestEntry$: Observable, responseCache$: Observable, payload$: Observable) { - return observableCombineLatest(requestEntry$, responseCache$.pipe(startWith(undefined)), payload$).pipe( - map(([reqEntry, resEntry, payload]) => { + toRemoteDataObservable(requestEntry$: Observable, payload$: Observable) { + return observableCombineLatest(requestEntry$, requestEntry$.pipe(startWith(undefined)), payload$).pipe( + map(([reqEntry, payload]) => { 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; - const errorMessage = isSuccessful === false ? (resEntry.response as ErrorResponse).errorMessage : undefined; + if (hasValue(reqEntry) && hasValue(reqEntry.response)) { + isSuccessful = reqEntry.response.isSuccessful; + const errorMessage = isSuccessful === false ? (reqEntry.response as ErrorResponse).errorMessage : undefined; if (hasValue(errorMessage)) { - error = new RemoteDataError(resEntry.response.statusCode, errorMessage); + error = new RemoteDataError(reqEntry.response.statusCode, errorMessage); } } return new RemoteData( @@ -120,9 +113,7 @@ export class RemoteDataBuildService { } const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); - const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); - - const tDomainList$ = responseCache$.pipe( + const tDomainList$ = requestEntry$.pipe( getResourceLinksFromResponse(), flatMap((resourceUUIDs: string[]) => { return this.objectCache.getList(resourceUUIDs).pipe( @@ -135,12 +126,12 @@ export class RemoteDataBuildService { startWith([]), distinctUntilChanged() ); - - const pageInfo$ = responseCache$.pipe( + // tDomainList$.subscribe((t) => {console.log('domainlist', t)}); + const pageInfo$ = requestEntry$.pipe( filterSuccessfulResponses(), - map((entry: ResponseCacheEntry) => { - if (hasValue((entry.response as DSOSuccessResponse).pageInfo)) { - const resPageInfo = (entry.response as DSOSuccessResponse).pageInfo; + map((response: DSOSuccessResponse) => { + if (hasValue((response as DSOSuccessResponse).pageInfo)) { + const resPageInfo = (response as DSOSuccessResponse).pageInfo; if (isNotEmpty(resPageInfo) && resPageInfo.currentPage >= 0) { return Object.assign({}, resPageInfo, { currentPage: resPageInfo.currentPage + 1 }); } else { @@ -156,7 +147,7 @@ export class RemoteDataBuildService { }) ); - return this.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + return this.toRemoteDataObservable(requestEntry$, payload$); } build(normalized: TNormalized): TDomain { @@ -204,8 +195,9 @@ export class RemoteDataBuildService { } } }); - const domainModel = getMapsTo(normalized.constructor); + // console.log('domain model', normalized); + return Object.assign(new domainModel(), normalized, links); } diff --git a/src/app/core/cache/response-cache.actions.ts b/src/app/core/cache/response-cache.actions.ts deleted file mode 100644 index 0389067690..0000000000 --- a/src/app/core/cache/response-cache.actions.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Action } from '@ngrx/store'; - -import { type } from '../../shared/ngrx/type'; -import { RestResponse } from './response-cache.models'; - -/** - * The list of ResponseCacheAction type definitions - */ -export const ResponseCacheActionTypes = { - ADD: type('dspace/core/cache/response/ADD'), - REMOVE: type('dspace/core/cache/response/REMOVE'), - RESET_TIMESTAMPS: type('dspace/core/cache/response/RESET_TIMESTAMPS') -}; - -/* tslint:disable:max-classes-per-file */ -export class ResponseCacheAddAction implements Action { - type = ResponseCacheActionTypes.ADD; - payload: { - key: string, - response: RestResponse - timeAdded: number; - msToLive: number; - }; - - constructor(key: string, response: RestResponse, timeAdded: number, msToLive: number) { - this.payload = { key, response, timeAdded, msToLive }; - } -} - -/** - * An ngrx action to remove a request from the cache - */ -export class ResponseCacheRemoveAction implements Action { - type = ResponseCacheActionTypes.REMOVE; - payload: string; - - /** - * Create a new ResponseCacheRemoveAction - * @param key - * The key of the request to remove - */ - constructor(key: string) { - this.payload = key; - } -} - -/** - * An ngrx action to reset the timeAdded property of all cached objects - */ -export class ResetResponseCacheTimestampsAction implements Action { - type = ResponseCacheActionTypes.RESET_TIMESTAMPS; - payload: number; - - /** - * Create a new ResetObjectCacheTimestampsAction - * - * @param newTimestamp - * the new timeAdded all objects should get - */ - constructor(newTimestamp: number) { - this.payload = newTimestamp; - } -} -/* tslint:enable:max-classes-per-file */ - -/** - * A type to encompass all ResponseCacheActions - */ -export type ResponseCacheAction - = ResponseCacheAddAction - | ResponseCacheRemoveAction - | ResetResponseCacheTimestampsAction; diff --git a/src/app/core/cache/response-cache.effects.spec.ts b/src/app/core/cache/response-cache.effects.spec.ts deleted file mode 100644 index 950049bfca..0000000000 --- a/src/app/core/cache/response-cache.effects.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { Observable } from 'rxjs'; -import { provideMockActions } from '@ngrx/effects/testing'; -import { cold, hot } from 'jasmine-marbles'; -import { StoreActionTypes } from '../../store.actions'; -import { ResponseCacheEffects } from './response-cache.effects'; -import { ResetResponseCacheTimestampsAction } from './response-cache.actions'; - -describe('ResponseCacheEffects', () => { - let cacheEffects: ResponseCacheEffects; - let actions: Observable; - const timestamp = 10000; - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - ResponseCacheEffects, - provideMockActions(() => actions), - // other providers - ], - }); - - cacheEffects = TestBed.get(ResponseCacheEffects); - }); - - describe('fixTimestampsOnRehydrate$', () => { - - it('should return a RESET_TIMESTAMPS action in response to a REHYDRATE action', () => { - spyOn(Date.prototype, 'getTime').and.callFake(() => { - return timestamp; - }); - actions = hot('--a-', { a: { type: StoreActionTypes.REHYDRATE, payload: {} } }); - - const expected = cold('--b-', { b: new ResetResponseCacheTimestampsAction(new Date().getTime()) }); - - expect(cacheEffects.fixTimestampsOnRehydrate).toBeObservable(expected); - }); - }); -}); diff --git a/src/app/core/cache/response-cache.effects.ts b/src/app/core/cache/response-cache.effects.ts deleted file mode 100644 index 5a1e53e20c..0000000000 --- a/src/app/core/cache/response-cache.effects.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { map } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; - -import { ResetResponseCacheTimestampsAction } from './response-cache.actions'; -import { StoreActionTypes } from '../../store.actions'; - -@Injectable() -export class ResponseCacheEffects { - - /** - * When the store is rehydrated in the browser, set all cache - * timestamps to 'now', because the time zone of the server can - * differ from the client. - * - * This assumes that the server cached everything a negligible - * time ago, and will likely need to be revisited later - */ - @Effect() fixTimestampsOnRehydrate = this.actions$ - .pipe(ofType(StoreActionTypes.REHYDRATE), - map(() => new ResetResponseCacheTimestampsAction(new Date().getTime())) - ); - - constructor(private actions$: Actions,) { - } - -} diff --git a/src/app/core/cache/response-cache.reducer.spec.ts b/src/app/core/cache/response-cache.reducer.spec.ts deleted file mode 100644 index 9037b20030..0000000000 --- a/src/app/core/cache/response-cache.reducer.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -import * as deepFreeze from 'deep-freeze'; - -import { responseCacheReducer, ResponseCacheState } from './response-cache.reducer'; - -import { - ResponseCacheRemoveAction, - ResetResponseCacheTimestampsAction, ResponseCacheAddAction -} from './response-cache.actions'; -import { RestResponse } from './response-cache.models'; - -class NullAction extends ResponseCacheRemoveAction { - type = null; - payload = null; - - constructor() { - super(null); - } -} - -describe('responseCacheReducer', () => { - const keys = ['125c17f89046283c5f0640722aac9feb', 'a06c3006a41caec5d635af099b0c780c']; - const msToLive = 900000; - const uuids = [ - '9e32a2e2-6b91-4236-a361-995ccdc14c60', - '598ce822-c357-46f3-ab70-63724d02d6ad', - 'be8325f7-243b-49f4-8a4b-df2b793ff3b5' - ]; - const testState: ResponseCacheState = { - [keys[0]]: { - key: keys[0], - response: new RestResponse(true, '200'), - timeAdded: new Date().getTime(), - msToLive: msToLive - }, - [keys[1]]: { - key: keys[1], - response: new RestResponse(true, '200'), - timeAdded: new Date().getTime(), - msToLive: msToLive - } - }; - deepFreeze(testState); - const errorState: {} = { - [keys[0]]: { - errorMessage: 'error', - resourceUUIDs: uuids - } - }; - deepFreeze(errorState); - - it('should return the current state when no valid actions have been made', () => { - const action = new NullAction(); - const newState = responseCacheReducer(testState, action); - - expect(newState).toEqual(testState); - }); - - it('should start with an empty cache', () => { - const action = new NullAction(); - const initialState = responseCacheReducer(undefined, action); - - expect(initialState).toEqual(Object.create(null)); - }); - - describe('ADD', () => { - const addTimeAdded = new Date().getTime(); - const addMsToLive = 5; - const addResponse = new RestResponse(true, '200'); - const action = new ResponseCacheAddAction(keys[0], addResponse, addTimeAdded, addMsToLive); - - it('should perform the action without affecting the previous state', () => { - // testState has already been frozen above - responseCacheReducer(testState, action); - }); - - it('should add the response to the cached request', () => { - const newState = responseCacheReducer(testState, action); - expect(newState[keys[0]].timeAdded).toBe(addTimeAdded); - expect(newState[keys[0]].msToLive).toBe(addMsToLive); - expect(newState[keys[0]].response).toBe(addResponse); - }); - }); - - describe('REMOVE', () => { - it('should perform the action without affecting the previous state', () => { - const action = new ResponseCacheRemoveAction(keys[0]); - // testState has already been frozen above - responseCacheReducer(testState, action); - }); - - it('should remove the specified request from the cache', () => { - const action = new ResponseCacheRemoveAction(keys[0]); - const newState = responseCacheReducer(testState, action); - expect(testState[keys[0]]).not.toBeUndefined(); - expect(newState[keys[0]]).toBeUndefined(); - }); - - it('shouldn\'t do anything when the specified key isn\'t cached', () => { - const wrongKey = 'this isn\'t cached'; - const action = new ResponseCacheRemoveAction(wrongKey); - const newState = responseCacheReducer(testState, action); - expect(testState[wrongKey]).toBeUndefined(); - expect(newState).toEqual(testState); - }); - }); - - describe('RESET_TIMESTAMPS', () => { - const newTimeStamp = new Date().getTime(); - const action = new ResetResponseCacheTimestampsAction(newTimeStamp); - - it('should perform the action without affecting the previous state', () => { - // testState has already been frozen above - responseCacheReducer(testState, action); - }); - - it('should set the timestamp of all requests in the cache', () => { - const newState = responseCacheReducer(testState, action); - Object.keys(newState).forEach((key) => { - expect(newState[key].timeAdded).toEqual(newTimeStamp); - }); - }); - - }); -}); diff --git a/src/app/core/cache/response-cache.reducer.ts b/src/app/core/cache/response-cache.reducer.ts deleted file mode 100644 index 73c680c1f5..0000000000 --- a/src/app/core/cache/response-cache.reducer.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - ResponseCacheAction, ResponseCacheActionTypes, - ResponseCacheRemoveAction, ResetResponseCacheTimestampsAction, - ResponseCacheAddAction -} from './response-cache.actions'; -import { CacheEntry } from './cache-entry'; -import { hasValue } from '../../shared/empty.util'; -import { RestResponse } from './response-cache.models'; - -/** - * An entry in the ResponseCache - */ -export class ResponseCacheEntry implements CacheEntry { - key: string; - response: RestResponse; - timeAdded: number; - msToLive: number; -} - -/** - * The ResponseCache State - */ -export interface ResponseCacheState { - [key: string]: ResponseCacheEntry -} - -// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`) -const initialState = Object.create(null); - -/** - * The ResponseCache Reducer - * - * @param state - * the current state - * @param action - * the action to perform on the state - * @return ResponseCacheState - * the new state - */ -export function responseCacheReducer(state = initialState, action: ResponseCacheAction): ResponseCacheState { - switch (action.type) { - - case ResponseCacheActionTypes.ADD: { - return addToCache(state, action as ResponseCacheAddAction); - } - - case ResponseCacheActionTypes.REMOVE: { - return removeFromCache(state, action as ResponseCacheRemoveAction); - } - - case ResponseCacheActionTypes.RESET_TIMESTAMPS: { - return resetResponseCacheTimestamps(state, action as ResetResponseCacheTimestampsAction) - } - - default: { - return state; - } - } -} - -function addToCache(state: ResponseCacheState, action: ResponseCacheAddAction): ResponseCacheState { - return Object.assign({}, state, { - [action.payload.key]: { - key: action.payload.key, - response: action.payload.response, - timeAdded: action.payload.timeAdded, - msToLive: action.payload.msToLive - } - }); -} - -/** - * Remove a request from the cache - * - * @param state - * the current state - * @param action - * an ResponseCacheRemoveAction - * @return ResponseCacheState - * the new state, with the request removed if it existed. - */ -function removeFromCache(state: ResponseCacheState, action: ResponseCacheRemoveAction): ResponseCacheState { - if (hasValue(state[action.payload])) { - const newCache = Object.assign({}, state); - delete newCache[action.payload]; - - return newCache; - } else { - return state; - } -} - -/** - * Set the timeAdded timestamp of every cached request to the specified value - * - * @param state - * the current state - * @param action - * a ResetResponseCacheTimestampsAction - * @return ResponseCacheState - * the new state, with all timeAdded timestamps set to the specified value - */ -function resetResponseCacheTimestamps(state: ResponseCacheState, action: ResetResponseCacheTimestampsAction): ResponseCacheState { - const newState = Object.create(null); - Object.keys(state).forEach((key) => { - newState[key] = Object.assign({}, state[key], { - timeAdded: action.payload - }); - }); - return newState; -} diff --git a/src/app/core/cache/response-cache.service.spec.ts b/src/app/core/cache/response-cache.service.spec.ts deleted file mode 100644 index 4fcd926343..0000000000 --- a/src/app/core/cache/response-cache.service.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Store } from '@ngrx/store'; - -import { ResponseCacheService } from './response-cache.service'; -import { of as observableOf } from 'rxjs'; -import { CoreState } from '../core.reducers'; -import { RestResponse } from './response-cache.models'; -import { ResponseCacheEntry } from './response-cache.reducer'; -import { first } from 'rxjs/operators'; -import * as ngrx from '@ngrx/store' -import { cold } from 'jasmine-marbles'; - -describe('ResponseCacheService', () => { - let service: ResponseCacheService; - let store: Store; - - const keys = ['125c17f89046283c5f0640722aac9feb', 'a06c3006a41caec5d635af099b0c780c']; - const timestamp = new Date().getTime(); - const validCacheEntry = (key) => { - return { - key: key, - response: new RestResponse(true, '200'), - timeAdded: timestamp, - msToLive: 24 * 60 * 60 * 1000 // a day - } - }; - const invalidCacheEntry = (key) => { - return { - key: key, - response: new RestResponse(true, '200'), - timeAdded: 0, - msToLive: 0 - } - }; - - beforeEach(() => { - store = new Store(undefined, undefined, undefined); - spyOn(store, 'dispatch'); - service = new ResponseCacheService(store); - spyOn(Date.prototype, 'getTime').and.callFake(() => { - return timestamp; - }); - }); - - describe('get', () => { - it('should return an observable of the cached request with the specified key', () => { - spyOnProperty(ngrx, 'select').and.callFake(() => { - return () => { - return () => observableOf(validCacheEntry(keys[1])); - }; - }); - let testObj: ResponseCacheEntry; - service.get(keys[1]).pipe(first()).subscribe((entry) => { - testObj = entry; - }); - expect(testObj.key).toEqual(keys[1]); - }); - - it('should not return a cached request that has exceeded its time to live', () => { - spyOnProperty(ngrx, 'select').and.callFake(() => { - return () => { - return () => observableOf(invalidCacheEntry(keys[1])); - }; - }); - - let getObsHasFired = false; - const subscription = service.get(keys[1]).subscribe((entry) => getObsHasFired = true); - expect(getObsHasFired).toBe(false); - subscription.unsubscribe(); - }); - }); - - describe('has', () => { - it('should return true if the request with the supplied key is cached and still valid', () => { - spyOnProperty(ngrx, 'select').and.callFake(() => { - return () => { - return () => observableOf(validCacheEntry(keys[1])); - }; - }); - expect(service.has(keys[1])).toBe(true); - }); - - it('should return false if the request with the supplied key isn\'t cached', () => { - spyOnProperty(ngrx, 'select').and.callFake(() => { - return () => { - return () => observableOf(undefined); - }; - }); - expect(service.has(keys[1])).toBe(false); - }); - - it('should return false if the request with the supplied key is cached but has exceeded its time to live', () => { - spyOnProperty(ngrx, 'select').and.callFake(() => { - return () => { - return () => observableOf(invalidCacheEntry(keys[1])); - }; - }); - expect(service.has(keys[1])).toBe(false); - }); - }); -}); diff --git a/src/app/core/cache/response-cache.service.ts b/src/app/core/cache/response-cache.service.ts deleted file mode 100644 index 973d3620ff..0000000000 --- a/src/app/core/cache/response-cache.service.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { filter, take, distinctUntilChanged, first } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; -import { MemoizedSelector, select, Store } from '@ngrx/store'; - -import { Observable } from 'rxjs'; - -import { ResponseCacheEntry } from './response-cache.reducer'; -import { hasNoValue } from '../../shared/empty.util'; -import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions'; -import { RestResponse } from './response-cache.models'; -import { coreSelector, CoreState } from '../core.reducers'; -import { pathSelector } from '../shared/selectors'; - -function entryFromKeySelector(key: string): MemoizedSelector { - return pathSelector(coreSelector, 'cache/response', key); -} - -/** - * A service to interact with the response cache - */ -@Injectable() -export class ResponseCacheService { - constructor( - private store: Store - ) { - } - - add(key: string, response: RestResponse, msToLive: number): Observable { - if (!this.has(key)) { - this.store.dispatch(new ResponseCacheAddAction(key, response, new Date().getTime(), msToLive)); - } - return this.get(key); - } - - /** - * Get an observable of the response with the specified key - * - * @param key - * the key of the response to get - * @return Observable - * an observable of the ResponseCacheEntry with the specified key - */ - get(key: string): Observable { - return this.store.pipe( - select(entryFromKeySelector(key)), - filter((entry: ResponseCacheEntry) => this.isValid(entry)), - distinctUntilChanged() - ) - } - - /** - * Check whether the response with the specified key is cached - * - * @param key - * the key of the response to check - * @return boolean - * true if the response with the specified key is cached, - * false otherwise - */ - has(key: string): boolean { - let result: boolean; - - this.store.pipe(select(entryFromKeySelector(key)), - first() - ).subscribe((entry: ResponseCacheEntry) => { - result = this.isValid(entry); - }); - - return result; - } - - remove(key: string): void { - if (this.has(key)) { - this.store.dispatch(new ResponseCacheRemoveAction(key)); - } - } - - /** - * Check whether a ResponseCacheEntry should still be cached - * - * @param entry - * the entry to check - * @return boolean - * false if the entry is null, undefined, or its time to - * live has been exceeded, true otherwise - */ - private isValid(entry: ResponseCacheEntry): boolean { - if (hasNoValue(entry)) { - return false; - } else { - const timeOutdated = entry.timeAdded + entry.msToLive; - const isOutDated = new Date().getTime() > timeOutdated; - if (isOutDated) { - this.store.dispatch(new ResponseCacheRemoveAction(entry.key)); - } - return !isOutDated; - } - } - -} diff --git a/src/app/core/cache/response-cache.models.ts b/src/app/core/cache/response.models.ts similarity index 99% rename from src/app/core/cache/response-cache.models.ts rename to src/app/core/cache/response.models.ts index 9566dcdc3c..fcec635655 100644 --- a/src/app/core/cache/response-cache.models.ts +++ b/src/app/core/cache/response.models.ts @@ -13,7 +13,7 @@ import { AuthStatus } from '../auth/models/auth-status.model'; /* tslint:disable:max-classes-per-file */ export class RestResponse { - public toCache = true; + public timeAdded: number; constructor( public isSuccessful: boolean, diff --git a/src/app/core/config/config.service.spec.ts b/src/app/core/config/config.service.spec.ts index 46c8fd1859..8e9f7db27a 100644 --- a/src/app/core/config/config.service.spec.ts +++ b/src/app/core/config/config.service.spec.ts @@ -1,7 +1,6 @@ import { cold, getTestScheduler, hot } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { ConfigService } from './config.service'; import { RequestService } from '../data/request.service'; import { ConfigRequest, FindAllOptions } from '../data/request.models'; @@ -16,7 +15,6 @@ class TestService extends ConfigService { protected browseEndpoint = BROWSE; constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected halService: HALEndpointService) { super(); @@ -26,7 +24,6 @@ class TestService extends ConfigService { describe('ConfigService', () => { let scheduler: TestScheduler; let service: TestService; - let responseCache: ResponseCacheService; let requestService: RequestService; let halService: any; @@ -39,17 +36,9 @@ describe('ConfigService', () => { const scopedEndpoint = `${serviceEndpoint}/${scopeName}`; const searchEndpoint = `${serviceEndpoint}/${BROWSE}?uuid=${scopeID}`; - function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService { - return jasmine.createSpyObj('responseCache', { - get: cold('c-', { - c: { response: { isSuccessful } } - }) - }); - } function initTestService(): TestService { return new TestService( - responseCache, requestService, halService ); @@ -57,7 +46,6 @@ describe('ConfigService', () => { beforeEach(() => { scheduler = getTestScheduler(); - responseCache = initMockResponseCacheService(true); requestService = getMockRequestService(); halService = new HALEndpointServiceStub(configEndpoint); service = initTestService(); diff --git a/src/app/core/config/config.service.ts b/src/app/core/config/config.service.ts index 872bc57c2b..c6c2e2e7d2 100644 --- a/src/app/core/config/config.service.ts +++ b/src/app/core/config/config.service.ts @@ -1,24 +1,25 @@ -import { Observable, of as observableOf, throwError as observableThrowError, merge as observableMerge } from 'rxjs'; +import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; -import { ConfigSuccessResponse } from '../cache/response-cache.models'; +import { ConfigSuccessResponse } from '../cache/response.models'; import { ConfigRequest, FindAllOptions, RestRequest } from '../data/request.models'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { ConfigData } from './config-data'; +import { RequestEntry } from '../data/request.reducer'; +import { getResponseFromEntry } from '../shared/operators'; export abstract class ConfigService { protected request: ConfigRequest; - protected abstract responseCache: ResponseCacheService; protected abstract requestService: RequestService; protected abstract linkPath: string; protected abstract browseEndpoint: string; protected abstract halService: HALEndpointService; protected getConfig(request: RestRequest): Observable { - const responses = this.responseCache.get(request.href).pipe(map((entry: ResponseCacheEntry) => entry.response)); + const responses = this.requestService.getByHref(request.href).pipe( + getResponseFromEntry() + ); const errorResponses = responses.pipe( filter((response) => !response.isSuccessful), mergeMap(() => observableThrowError(new Error(`Couldn't retrieve the config`))) @@ -94,7 +95,6 @@ export abstract class ConfigService { } public getConfigBySearch(options: FindAllOptions = {}): Observable { - console.log(this.halService.getEndpoint(this.linkPath)); return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getConfigSearchHref(endpoint, options)), filter((href: string) => isNotEmpty(href)), diff --git a/src/app/core/config/submission-definitions-config.service.ts b/src/app/core/config/submission-definitions-config.service.ts index 6cbe0c55b5..b7b0873c21 100644 --- a/src/app/core/config/submission-definitions-config.service.ts +++ b/src/app/core/config/submission-definitions-config.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { ConfigService } from './config.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; @@ -11,7 +10,6 @@ export class SubmissionDefinitionsConfigService extends ConfigService { protected browseEndpoint = 'search/findByCollection'; constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected halService: HALEndpointService) { super(); diff --git a/src/app/core/config/submission-forms-config.service.ts b/src/app/core/config/submission-forms-config.service.ts index 27eac78218..b688859ec9 100644 --- a/src/app/core/config/submission-forms-config.service.ts +++ b/src/app/core/config/submission-forms-config.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { ConfigService } from './config.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; @@ -11,7 +10,6 @@ export class SubmissionFormsConfigService extends ConfigService { protected browseEndpoint = ''; constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected halService: HALEndpointService) { super(); diff --git a/src/app/core/config/submission-sections-config.service.ts b/src/app/core/config/submission-sections-config.service.ts index 6d4d2ca825..c8bbc0dd97 100644 --- a/src/app/core/config/submission-sections-config.service.ts +++ b/src/app/core/config/submission-sections-config.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { ConfigService } from './config.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; @@ -11,7 +10,6 @@ export class SubmissionSectionsConfigService extends ConfigService { protected browseEndpoint = ''; constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected halService: HALEndpointService) { super(); diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts index 881f01aed0..c9a352c545 100644 --- a/src/app/core/core.effects.ts +++ b/src/app/core/core.effects.ts @@ -1,13 +1,11 @@ import { ObjectCacheEffects } from './cache/object-cache.effects'; -import { ResponseCacheEffects } from './cache/response-cache.effects'; import { UUIDIndexEffects } from './index/index.effects'; import { RequestEffects } from './data/request.effects'; import { AuthEffects } from './auth/auth.effects'; import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects'; export const coreEffects = [ - ResponseCacheEffects, RequestEffects, ObjectCacheEffects, UUIDIndexEffects, diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 73e97c7933..dcbdbd0049 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -32,7 +32,6 @@ import { ObjectCacheService } from './cache/object-cache.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; import { RequestService } from './data/request.service'; -import { ResponseCacheService } from './cache/response-cache.service'; import { EndpointMapResponseParsingService } from './data/endpoint-map-response-parsing.service'; import { ServerResponseService } from '../shared/services/server-response.service'; import { NativeWindowFactory, NativeWindowService } from '../shared/services/window.service'; @@ -102,7 +101,6 @@ const PROVIDERS = [ RegistryService, RemoteDataBuildService, RequestService, - ResponseCacheService, EndpointMapResponseParsingService, FacetValueResponseParsingService, FacetValueMapResponseParsingService, diff --git a/src/app/core/core.reducers.ts b/src/app/core/core.reducers.ts index 6905eb1300..1843e10671 100644 --- a/src/app/core/core.reducers.ts +++ b/src/app/core/core.reducers.ts @@ -1,6 +1,5 @@ import { ActionReducerMap, createFeatureSelector } from '@ngrx/store'; -import { responseCacheReducer, ResponseCacheState } from './cache/response-cache.reducer'; import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer'; import { indexReducer, IndexState } from './index/index.reducer'; import { requestReducer, RequestState } from './data/request.reducer'; @@ -9,7 +8,6 @@ import { serverSyncBufferReducer, ServerSyncBufferState } from './cache/server-s export interface CoreState { 'cache/object': ObjectCacheState, - 'cache/response': ResponseCacheState, 'cache/syncbuffer': ServerSyncBufferState, 'data/request': RequestState, 'index': IndexState, @@ -18,7 +16,6 @@ export interface CoreState { export const coreReducers: ActionReducerMap = { 'cache/object': objectCacheReducer, - 'cache/response': responseCacheReducer, 'cache/syncbuffer': serverSyncBufferReducer, 'data/request': requestReducer, 'index': indexReducer, diff --git a/src/app/core/data/browse-entries-response-parsing.service.spec.ts b/src/app/core/data/browse-entries-response-parsing.service.spec.ts index dd04e4f2f5..a61da7aa95 100644 --- a/src/app/core/data/browse-entries-response-parsing.service.spec.ts +++ b/src/app/core/data/browse-entries-response-parsing.service.spec.ts @@ -1,5 +1,5 @@ import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service'; -import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; +import { ErrorResponse, GenericSuccessResponse } from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service'; import { BrowseEntriesRequest } from './request.models'; diff --git a/src/app/core/data/browse-entries-response-parsing.service.ts b/src/app/core/data/browse-entries-response-parsing.service.ts index 171def60df..39600b637d 100644 --- a/src/app/core/data/browse-entries-response-parsing.service.ts +++ b/src/app/core/data/browse-entries-response-parsing.service.ts @@ -7,7 +7,7 @@ import { ErrorResponse, GenericSuccessResponse, RestResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { BrowseEntry } from '../shared/browse-entry.model'; diff --git a/src/app/core/data/browse-items-response-parsing-service.spec.ts b/src/app/core/data/browse-items-response-parsing-service.spec.ts index 6a141c01c4..99ea474dc6 100644 --- a/src/app/core/data/browse-items-response-parsing-service.spec.ts +++ b/src/app/core/data/browse-items-response-parsing-service.spec.ts @@ -1,5 +1,5 @@ import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service'; -import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; +import { ErrorResponse, GenericSuccessResponse } from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service'; import { BrowseEntriesRequest, BrowseItemsRequest } from './request.models'; diff --git a/src/app/core/data/browse-items-response-parsing-service.ts b/src/app/core/data/browse-items-response-parsing-service.ts index e513ad0898..218c25bac6 100644 --- a/src/app/core/data/browse-items-response-parsing-service.ts +++ b/src/app/core/data/browse-items-response-parsing-service.ts @@ -7,7 +7,7 @@ import { ErrorResponse, GenericSuccessResponse, RestResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { BaseResponseParsingService } from './base-response-parsing.service'; diff --git a/src/app/core/data/browse-response-parsing.service.spec.ts b/src/app/core/data/browse-response-parsing.service.spec.ts index 2b1703e38f..bedf5f03a7 100644 --- a/src/app/core/data/browse-response-parsing.service.spec.ts +++ b/src/app/core/data/browse-response-parsing.service.spec.ts @@ -1,6 +1,6 @@ import { BrowseResponseParsingService } from './browse-response-parsing.service'; import { BrowseEndpointRequest } from './request.models'; -import { GenericSuccessResponse, ErrorResponse } from '../cache/response-cache.models'; +import { GenericSuccessResponse, ErrorResponse } from '../cache/response.models'; import { BrowseDefinition } from '../shared/browse-definition.model'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; diff --git a/src/app/core/data/browse-response-parsing.service.ts b/src/app/core/data/browse-response-parsing.service.ts index 8feb1bc82b..523fffd565 100644 --- a/src/app/core/data/browse-response-parsing.service.ts +++ b/src/app/core/data/browse-response-parsing.service.ts @@ -2,7 +2,7 @@ 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 { GenericSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models'; +import { GenericSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models'; import { isNotEmpty } from '../../shared/empty.util'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { BrowseDefinition } from '../shared/browse-definition.model'; diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 2642c4b5e6..74c73e37f3 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,10 +1,8 @@ import { Inject, Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedCollection } from '../cache/models/normalized-collection.model'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; import { Collection } from '../shared/collection.model'; import { ComColDataService } from './comcol-data.service'; @@ -17,7 +15,6 @@ export class CollectionDataService extends ComColDataService, diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index bc9dc9114a..2125aff797 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -5,7 +5,6 @@ import { GlobalConfig } from '../../../config'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; @@ -23,7 +22,6 @@ class NormalizedTestObject extends NormalizedObject { class TestService extends ComColDataService { constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected store: Store, @@ -41,7 +39,6 @@ class TestService extends ComColDataService { describe('ComColDataService', () => { let scheduler: TestScheduler; let service: TestService; - let responseCache: ResponseCacheService; let requestService: RequestService; let cds: CommunityDataService; let objectCache: ObjectCacheService; @@ -68,14 +65,6 @@ describe('ComColDataService', () => { }); } - function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService { - return jasmine.createSpyObj('responseCache', { - get: cold('c-', { - c: { response: { isSuccessful } } - }) - }); - } - function initMockObjectCacheService(): ObjectCacheService { return jasmine.createSpyObj('objectCache', { getByUUID: cold('d-', { @@ -90,7 +79,6 @@ describe('ComColDataService', () => { function initTestService(): TestService { return new TestService( - responseCache, requestService, rdbService, store, @@ -111,7 +99,6 @@ describe('ComColDataService', () => { cds = initMockCommunityDataService(); requestService = getMockRequestService(); objectCache = initMockObjectCacheService(); - responseCache = initMockResponseCacheService(true); service = initTestService(); const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID); @@ -127,7 +114,6 @@ describe('ComColDataService', () => { cds = initMockCommunityDataService(); requestService = getMockRequestService(); objectCache = initMockObjectCacheService(); - responseCache = initMockResponseCacheService(true); service = initTestService(); }); @@ -150,7 +136,6 @@ describe('ComColDataService', () => { cds = initMockCommunityDataService(); requestService = getMockRequestService(); objectCache = initMockObjectCacheService(); - responseCache = initMockResponseCacheService(false); service = initTestService(); }); diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index c589c5bdc8..95a0015125 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -1,15 +1,16 @@ -import { distinctUntilChanged, filter, map, mergeMap, take, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, mergeMap, share, take, tap } from 'rxjs/operators'; import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs'; -import { isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { CommunityDataService } from './community-data.service'; import { DataService } from './data.service'; import { FindAllOptions, FindByIDRequest } from './request.models'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestEntry } from './request.reducer'; +import { getResponseFromEntry } from '../shared/operators'; export abstract class ComColDataService extends DataService { protected abstract cds: CommunityDataService; @@ -26,9 +27,9 @@ export abstract class ComColDataService } * an Observable containing the scoped URL */ - public getBrowseEndpoint(options: FindAllOptions = {}): Observable { + public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { if (isEmpty(options.scopeID)) { - return this.halService.getEndpoint(this.linkPath); + return this.halService.getEndpoint(linkPath); } else { const scopeCommunityHrefObs = this.cds.getEndpoint().pipe( mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, options.scopeID)), @@ -37,7 +38,7 @@ export abstract class ComColDataService { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, options.scopeID); this.requestService.configure(request); - }),); + })); // return scopeCommunityHrefObs.pipe( // mergeMap((href: string) => this.responseCache.get(href)), @@ -46,7 +47,7 @@ export abstract class ComColDataService = this.objectCache.getByUUID(scopeID); // return community$.pipe( - // map((community) => community._links[this.linkPath]), + // map((community) => community._links[linkPath]), // filter((href) => isNotEmpty(href)), // distinctUntilChanged() // ); @@ -57,8 +58,8 @@ export abstract class ComColDataService this.responseCache.get(href)), - map((entry: ResponseCacheEntry) => entry.response)); + mergeMap((href: string) => this.requestService.getByHref(href)), + getResponseFromEntry()); const errorResponses = responses.pipe( filter((response) => !response.isSuccessful), mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`))) @@ -66,11 +67,11 @@ export abstract class ComColDataService response.isSuccessful), mergeMap(() => this.objectCache.getByUUID(options.scopeID)), - map((nc: NormalizedCommunity) => nc._links[this.linkPath]), + map((nc: NormalizedCommunity) => nc._links[linkPath]), filter((href) => isNotEmpty(href)) ); - return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged()); + return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share()); } } } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index df0e739490..a037936202 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -1,12 +1,10 @@ - -import {mergeMap, filter, take} from 'rxjs/operators'; +import { filter, mergeMap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; import { Community } from '../shared/community.model'; import { ComColDataService } from './comcol-data.service'; @@ -25,7 +23,6 @@ export class CommunityDataService extends ComColDataService, @@ -40,12 +37,10 @@ export class CommunityDataService extends ComColDataService>> { - const hrefObs = this.halService.getEndpoint(this.topLinkPath).pipe(filter((href: string) => isNotEmpty(href)), - mergeMap((endpoint: string) => this.getFindAllHref(options)),); - + const hrefObs = this.getFindAllHref(options, this.topLinkPath); hrefObs.pipe( filter((href: string) => hasValue(href)), - take(1),) + take(1)) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); this.requestService.configure(request); diff --git a/src/app/core/data/config-response-parsing.service.spec.ts b/src/app/core/data/config-response-parsing.service.spec.ts index 654ee53651..a33c5cf5b5 100644 --- a/src/app/core/data/config-response-parsing.service.spec.ts +++ b/src/app/core/data/config-response-parsing.service.spec.ts @@ -1,4 +1,4 @@ -import { ConfigSuccessResponse, ErrorResponse } from '../cache/response-cache.models'; +import { ConfigSuccessResponse, ErrorResponse } from '../cache/response.models'; import { ConfigResponseParsingService } from './config-response-parsing.service'; import { ObjectCacheService } from '../cache/object-cache.service'; import { GlobalConfig } from '../../../config/global-config.interface'; diff --git a/src/app/core/data/config-response-parsing.service.ts b/src/app/core/data/config-response-parsing.service.ts index 2b1b923625..ddf884e02b 100644 --- a/src/app/core/data/config-response-parsing.service.ts +++ b/src/app/core/data/config-response-parsing.service.ts @@ -3,7 +3,7 @@ import { Inject, 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 { ConfigSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models'; +import { ConfigSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models'; import { isNotEmpty } from '../../shared/empty.util'; import { ConfigObjectFactory } from '../shared/config/config-object-factory'; diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index e60725c4c2..7da709abd5 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -1,6 +1,5 @@ import { DataService } from './data.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RequestService } from './request.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { CoreState } from '../core.reducers'; @@ -22,7 +21,6 @@ class NormalizedTestObject extends NormalizedObject { class TestService extends DataService { constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected store: Store, @@ -33,7 +31,7 @@ class TestService extends DataService { super(); } - public getBrowseEndpoint(options: FindAllOptions): Observable { + public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { return observableOf(endpoint); } } @@ -41,7 +39,6 @@ class TestService extends DataService { describe('DataService', () => { let service: TestService; let options: FindAllOptions; - const responseCache = {} as ResponseCacheService; const requestService = {} as RequestService; const halService = {} as HALEndpointService; const rdbService = {} as RemoteDataBuildService; @@ -57,7 +54,6 @@ describe('DataService', () => { function initTestService(): TestService { return new TestService( - responseCache, requestService, rdbService, store, diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index d23d7e8064..4a993e4ac6 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,9 +1,8 @@ -import { distinctUntilChanged, filter, first, map, take } from 'rxjs/operators'; +import { delay, distinctUntilChanged, filter, first, map, take, tap } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { URLCombiner } from '../url-combiner/url-combiner'; @@ -15,9 +14,9 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { compare, Operation } from 'fast-json-patch'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSpaceObject } from '../shared/dspace-object.model'; +import { of } from 'rxjs/internal/observable/of'; export abstract class DataService { - protected abstract responseCache: ResponseCacheService; protected abstract requestService: RequestService; protected abstract rdbService: RemoteDataBuildService; protected abstract store: Store; @@ -25,34 +24,31 @@ export abstract class DataService protected abstract halService: HALEndpointService; protected abstract objectCache: ObjectCacheService; - public abstract getBrowseEndpoint(options: FindAllOptions): Observable + public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable - protected getFindAllHref(options: FindAllOptions = {}): Observable { + protected getFindAllHref(options: FindAllOptions = {}, linkPath?: string): Observable { let result: Observable; const args = []; - result = this.getBrowseEndpoint(options).pipe(distinctUntilChanged()); - + result = this.getBrowseEndpoint(options, linkPath); if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { /* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */ args.push(`page=${options.currentPage - 1}`); } - if (hasValue(options.elementsPerPage)) { args.push(`size=${options.elementsPerPage}`); } - if (hasValue(options.sort)) { args.push(`sort=${options.sort.field},${options.sort.direction}`); } - if (hasValue(options.startsWith)) { args.push(`startsWith=${options.startsWith}`); } - if (isNotEmpty(args)) { return result.pipe(map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString())); } else { + result.subscribe((t) => console.log(t)); + return result; } } @@ -115,6 +111,7 @@ export abstract class DataService this.objectCache.addPatch(object.self, operations); } } + // TODO implement, after the structure of the REST server's POST response is finalized // create(dso: DSpaceObject): Observable> { // const postHrefObs = this.getEndpoint(); diff --git a/src/app/core/data/debug-response-parsing.service.ts b/src/app/core/data/debug-response-parsing.service.ts index d530948559..174abec897 100644 --- a/src/app/core/data/debug-response-parsing.service.ts +++ b/src/app/core/data/debug-response-parsing.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { RestResponse } from '../cache/response-cache.models'; +import { RestResponse } from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { ResponseParsingService } from './parsing.service'; import { RestRequest } from './request.models'; diff --git a/src/app/core/data/dso-response-parsing.service.ts b/src/app/core/data/dso-response-parsing.service.ts index aff450781f..568114be1a 100644 --- a/src/app/core/data/dso-response-parsing.service.ts +++ b/src/app/core/data/dso-response-parsing.service.ts @@ -7,7 +7,7 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { ResourceType } from '../shared/resource-type'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; -import { RestResponse, DSOSuccessResponse } from '../cache/response-cache.models'; +import { RestResponse, DSOSuccessResponse } from '../cache/response.models'; import { RestRequest } from './request.models'; import { ResponseParsingService } from './parsing.service'; @@ -23,12 +23,14 @@ export class DSOResponseParsingService extends BaseResponseParsingService implem constructor( @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, protected objectCache: ObjectCacheService, - ) { super(); + ) { + super(); } parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { - const processRequestDTO = this.process(data.payload, request.href); + const processRequestDTO = this.process(data.payload, request.href); let objectList = processRequestDTO; + if (hasNoValue(processRequestDTO)) { return new DSOSuccessResponse([], data.statusCode, undefined) } diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 03c602d2aa..324692c676 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -3,7 +3,6 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; import { DSpaceObject } from '../shared/dspace-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; @@ -18,7 +17,6 @@ class DataServiceImpl extends DataService protected linkPath = 'dso'; constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected store: Store, @@ -27,8 +25,8 @@ class DataServiceImpl extends DataService super(); } - getBrowseEndpoint(options: FindAllOptions): Observable { - return this.halService.getEndpoint(this.linkPath); + getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + return this.halService.getEndpoint(linkPath); } getFindByIDHref(endpoint, resourceID): string { @@ -46,7 +44,7 @@ export class DSpaceObjectDataService { protected rdbService: RemoteDataBuildService, protected halService: HALEndpointService, protected objectCache: ObjectCacheService) { - this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService, objectCache); + this.dataService = new DataServiceImpl(requestService, rdbService, null, halService, objectCache); } findById(uuid: string): Observable> { diff --git a/src/app/core/data/endpoint-map-response-parsing.service.ts b/src/app/core/data/endpoint-map-response-parsing.service.ts index b850e13932..a145477953 100644 --- a/src/app/core/data/endpoint-map-response-parsing.service.ts +++ b/src/app/core/data/endpoint-map-response-parsing.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@angular/core'; import { GLOBAL_CONFIG } from '../../../config'; import { GlobalConfig } from '../../../config/global-config.interface'; -import { ErrorResponse, RestResponse, EndpointMapSuccessResponse } from '../cache/response-cache.models'; +import { ErrorResponse, RestResponse, EndpointMapSuccessResponse } from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { ResponseParsingService } from './parsing.service'; import { RestRequest } from './request.models'; diff --git a/src/app/core/data/facet-config-response-parsing.service.ts b/src/app/core/data/facet-config-response-parsing.service.ts index b0d89fb03e..02b12dfa10 100644 --- a/src/app/core/data/facet-config-response-parsing.service.ts +++ b/src/app/core/data/facet-config-response-parsing.service.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core'; import { FacetConfigSuccessResponse, RestResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { ResponseParsingService } from './parsing.service'; import { RestRequest } from './request.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; diff --git a/src/app/core/data/facet-value-map-response-parsing.service.ts b/src/app/core/data/facet-value-map-response-parsing.service.ts index 8588e4aa0b..0fc5917847 100644 --- a/src/app/core/data/facet-value-map-response-parsing.service.ts +++ b/src/app/core/data/facet-value-map-response-parsing.service.ts @@ -4,7 +4,7 @@ import { FacetValueMapSuccessResponse, FacetValueSuccessResponse, RestResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { ResponseParsingService } from './parsing.service'; import { RestRequest } from './request.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; diff --git a/src/app/core/data/facet-value-response-parsing.service.ts b/src/app/core/data/facet-value-response-parsing.service.ts index bc3f4e5368..585172c22e 100644 --- a/src/app/core/data/facet-value-response-parsing.service.ts +++ b/src/app/core/data/facet-value-response-parsing.service.ts @@ -4,7 +4,7 @@ import { FacetValueMapSuccessResponse, FacetValueSuccessResponse, RestResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { ResponseParsingService } from './parsing.service'; import { RestRequest } from './request.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index 050c888de2..bb67fc8412 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -3,7 +3,6 @@ import { cold, getTestScheduler } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { BrowseService } from '../browse/browse.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; import { ItemDataService } from './item-data.service'; import { RequestService } from './request.service'; @@ -16,7 +15,6 @@ describe('ItemDataService', () => { let service: ItemDataService; let bs: BrowseService; const requestService = {} as RequestService; - const responseCache = {} as ResponseCacheService; const rdbService = {} as RemoteDataBuildService; const objectCache = {} as ObjectCacheService; const store = {} as Store; @@ -48,7 +46,6 @@ describe('ItemDataService', () => { function initTestService() { return new ItemDataService( - responseCache, requestService, rdbService, store, diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 22ca23a4e2..9c2c87119f 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -7,7 +7,6 @@ import { isNotEmpty } from '../../shared/empty.util'; import { BrowseService } from '../browse/browse.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedItem } from '../cache/models/normalized-item.model'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; import { Item } from '../shared/item.model'; import { URLCombiner } from '../url-combiner/url-combiner'; @@ -23,7 +22,6 @@ export class ItemDataService extends DataService { protected linkPath = 'items'; constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected store: Store, @@ -39,12 +37,12 @@ export class ItemDataService extends DataService { * @param {FindAllOptions} options * @returns {Observable} */ - public getBrowseEndpoint(options: FindAllOptions = {}): Observable { + public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { let field = 'dc.date.issued'; if (options.sort && options.sort.field) { field = options.sort.field; } - return this.bs.getBrowseURLFor(field, this.linkPath).pipe( + return this.bs.getBrowseURLFor(field, linkPath).pipe( filter((href: string) => isNotEmpty(href)), map((href: string) => new URLCombiner(href, `?scope=${options.scopeID}`).toString()), distinctUntilChanged(),); diff --git a/src/app/core/data/metadataschema-parsing.service.ts b/src/app/core/data/metadataschema-parsing.service.ts index cdd87c19d4..78a5257456 100644 --- a/src/app/core/data/metadataschema-parsing.service.ts +++ b/src/app/core/data/metadataschema-parsing.service.ts @@ -4,7 +4,7 @@ import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response. import { RestRequest } from './request.models'; import { ResponseParsingService } from './parsing.service'; import { Injectable } from '@angular/core'; -import { MetadataschemaSuccessResponse, RestResponse } from '../cache/response-cache.models'; +import { MetadataschemaSuccessResponse, RestResponse } from '../cache/response.models'; @Injectable() export class MetadataschemaParsingService implements ResponseParsingService { diff --git a/src/app/core/data/parsing.service.ts b/src/app/core/data/parsing.service.ts index a137b99079..ea8d1ea810 100644 --- a/src/app/core/data/parsing.service.ts +++ b/src/app/core/data/parsing.service.ts @@ -1,6 +1,6 @@ import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { RestRequest } from './request.models'; -import { RestResponse } from '../cache/response-cache.models'; +import { RestResponse } from '../cache/response.models'; export interface ResponseParsingService { parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse; diff --git a/src/app/core/data/registry-bitstreamformats-response-parsing.service.ts b/src/app/core/data/registry-bitstreamformats-response-parsing.service.ts index d981a12719..2ee3bbf75e 100644 --- a/src/app/core/data/registry-bitstreamformats-response-parsing.service.ts +++ b/src/app/core/data/registry-bitstreamformats-response-parsing.service.ts @@ -1,4 +1,4 @@ -import { RegistryBitstreamformatsSuccessResponse, RestResponse } from '../cache/response-cache.models'; +import { RegistryBitstreamformatsSuccessResponse, RestResponse } from '../cache/response.models'; import { RegistryBitstreamformatsResponse } from '../registry/registry-bitstreamformats-response.model'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; diff --git a/src/app/core/data/registry-metadatafields-response-parsing.service.ts b/src/app/core/data/registry-metadatafields-response-parsing.service.ts index 1fe8b1e15f..0b0982d048 100644 --- a/src/app/core/data/registry-metadatafields-response-parsing.service.ts +++ b/src/app/core/data/registry-metadatafields-response-parsing.service.ts @@ -1,7 +1,7 @@ import { RegistryMetadatafieldsSuccessResponse, RestResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { RestRequest } from './request.models'; import { ResponseParsingService } from './parsing.service'; diff --git a/src/app/core/data/registry-metadataschemas-response-parsing.service.ts b/src/app/core/data/registry-metadataschemas-response-parsing.service.ts index 2bb1302450..a70c985b15 100644 --- a/src/app/core/data/registry-metadataschemas-response-parsing.service.ts +++ b/src/app/core/data/registry-metadataschemas-response-parsing.service.ts @@ -1,4 +1,4 @@ -import { RegistryMetadataschemasSuccessResponse, RestResponse } from '../cache/response-cache.models'; +import { RegistryMetadataschemasSuccessResponse, RestResponse } from '../cache/response.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { RestRequest } from './request.models'; import { ResponseParsingService } from './parsing.service'; diff --git a/src/app/core/data/request.actions.ts b/src/app/core/data/request.actions.ts index 436c365caa..28149c2ead 100644 --- a/src/app/core/data/request.actions.ts +++ b/src/app/core/data/request.actions.ts @@ -1,6 +1,7 @@ import { Action } from '@ngrx/store'; import { type } from '../../shared/ngrx/type'; import { RestRequest } from './request.models'; +import { RestResponse } from '../cache/response.models'; /** * The list of RequestAction type definitions @@ -8,7 +9,8 @@ import { RestRequest } from './request.models'; export const RequestActionTypes = { CONFIGURE: type('dspace/core/data/request/CONFIGURE'), EXECUTE: type('dspace/core/data/request/EXECUTE'), - COMPLETE: type('dspace/core/data/request/COMPLETE') + COMPLETE: type('dspace/core/data/request/COMPLETE'), + RESET_TIMESTAMPS: type('dspace/core/data/request/RESET_TIMESTAMPS') }; /* tslint:disable:max-classes-per-file */ @@ -43,7 +45,10 @@ export class RequestExecuteAction implements Action { */ export class RequestCompleteAction implements Action { type = RequestActionTypes.COMPLETE; - payload: string; + payload: { + uuid: string, + response: RestResponse + }; /** * Create a new RequestCompleteAction @@ -51,10 +56,32 @@ export class RequestCompleteAction implements Action { * @param uuid * the request's uuid */ - constructor(uuid: string) { - this.payload = uuid; + constructor(uuid: string, response: RestResponse) { + this.payload = { + uuid, + response + }; } } + +/** + * An ngrx action to reset the timeAdded property of all responses in the cached objects + */ +export class ResetResponseTimestampsAction implements Action { + type = RequestActionTypes.RESET_TIMESTAMPS; + payload: number; + + /** + * Create a new ResetResponseTimestampsAction + * + * @param newTimestamp + * the new timeAdded all objects should get + */ + constructor(newTimestamp: number) { + this.payload = newTimestamp; + } +} + /* tslint:enable:max-classes-per-file */ /** @@ -63,4 +90,5 @@ export class RequestCompleteAction implements Action { export type RequestAction = RequestConfigureAction | RequestExecuteAction - | RequestCompleteAction; + | RequestCompleteAction + | ResetResponseTimestampsAction; diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index 6d42f792d1..bda91283bf 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -1,30 +1,33 @@ - -import {of as observableOf, Observable } from 'rxjs'; +import { Observable, of as observableOf } from 'rxjs'; import { Inject, Injectable, Injector } from '@angular/core'; -import { Request } from '@angular/http'; -import { RequestArgs } from '@angular/http/src/interfaces'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { isNotEmpty } from '../../shared/empty.util'; -import { ErrorResponse, RestResponse } from '../cache/response-cache.models'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service'; -import { RequestActionTypes, RequestCompleteAction, RequestExecuteAction } from './request.actions'; +import { + RequestActionTypes, + RequestCompleteAction, + RequestExecuteAction, + ResetResponseTimestampsAction +} from './request.actions'; import { RequestError, RestRequest } from './request.models'; import { RequestEntry } from './request.reducer'; import { RequestService } from './request.service'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; import { catchError, flatMap, map, take, tap } from 'rxjs/operators'; +import { ErrorResponse, RestResponse } from '../cache/response.models'; +import { StoreActionTypes } from '../../store.actions'; -export const addToResponseCacheAndCompleteAction = (request: RestRequest, responseCache: ResponseCacheService, envConfig: GlobalConfig) => - (source: Observable): Observable => +export const addToResponseCacheAndCompleteAction = (request: RestRequest, envConfig: GlobalConfig) => + (source: Observable): Observable => source.pipe( - tap((response: RestResponse) => responseCache.add(request.href, response, request.responseMsToLive ? request.responseMsToLive : envConfig.cache.msToLive.default)), - map((response: RestResponse) => new RequestCompleteAction(request.uuid)) + map((response: RestResponse) => { + return new RequestCompleteAction(request.uuid, response) + }) ); @Injectable() @@ -46,20 +49,32 @@ export class RequestEffects { } return this.restApi.request(request.method, request.href, body, request.options).pipe( map((data: DSpaceRESTV2Response) => this.injector.get(request.getResponseParser()).parse(request, data)), - addToResponseCacheAndCompleteAction(request, this.responseCache, this.EnvConfig), + addToResponseCacheAndCompleteAction(request, this.EnvConfig), catchError((error: RequestError) => observableOf(new ErrorResponse(error)).pipe( - addToResponseCacheAndCompleteAction(request, this.responseCache, this.EnvConfig) + addToResponseCacheAndCompleteAction(request, this.EnvConfig) )) ); }) ); + /** + * When the store is rehydrated in the browser, set all cache + * timestamps to 'now', because the time zone of the server can + * differ from the client. + * + * This assumes that the server cached everything a negligible + * time ago, and will likely need to be revisited later + */ + @Effect() fixTimestampsOnRehydrate = this.actions$ + .pipe(ofType(StoreActionTypes.REHYDRATE), + map(() => new ResetResponseTimestampsAction(new Date().getTime())) + ); + constructor( @Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig, private actions$: Actions, private restApi: DSpaceRESTv2Service, private injector: Injector, - private responseCache: ResponseCacheService, protected requestService: RequestService ) { } diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index cd6ac1dbe2..a96d9716b2 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -14,32 +14,36 @@ import { BrowseItemsResponseParsingService } from './browse-items-response-parsi /* tslint:disable:max-classes-per-file */ - export abstract class RestRequest { + public responseMsToLive = 0; constructor( public uuid: string, public href: string, public method: RestRequestMethod = RestRequestMethod.GET, public body?: any, public options?: HttpOptions, - public responseMsToLive?: number ) { } getResponseParser(): GenericConstructor { return DSOResponseParsingService; } + + get toCache(): boolean { + return this.responseMsToLive > 0; + } } export class GetRequest extends RestRequest { + public responseMsToLive = 60 * 15 * 1000; + constructor( public uuid: string, public href: string, public body?: any, public options?: HttpOptions, - public responseMsToLive?: number ) { - super(uuid, href, RestRequestMethod.GET, body, options, responseMsToLive) + super(uuid, href, RestRequestMethod.GET, body, options) } } @@ -212,6 +216,7 @@ export class IntegrationRequest extends GetRequest { return IntegrationResponseParsingService; } } + export class RequestError extends Error { statusText: string; } diff --git a/src/app/core/data/request.reducer.ts b/src/app/core/data/request.reducer.ts index 3ac35d2741..b0875f37b3 100644 --- a/src/app/core/data/request.reducer.ts +++ b/src/app/core/data/request.reducer.ts @@ -1,14 +1,16 @@ import { RequestActionTypes, RequestAction, RequestConfigureAction, - RequestExecuteAction, RequestCompleteAction + RequestExecuteAction, RequestCompleteAction, ResetResponseTimestampsAction } from './request.actions'; import { RestRequest } from './request.models'; +import { RestResponse } from '../cache/response.models'; export class RequestEntry { request: RestRequest; requestPending: boolean; responsePending: boolean; completed: boolean; + response: RestResponse } export interface RequestState { @@ -32,6 +34,9 @@ export function requestReducer(state = initialState, action: RequestAction): Req case RequestActionTypes.COMPLETE: { return completeRequest(state, action as RequestCompleteAction); } + case RequestActionTypes.RESET_TIMESTAMPS: { + return resetResponseTimestamps(state, action as ResetResponseTimestampsAction); + } default: { return state; @@ -45,7 +50,7 @@ function configureRequest(state: RequestState, action: RequestConfigureAction): request: action.payload, requestPending: true, responsePending: false, - completed: false + completed: false, } }); } @@ -70,10 +75,25 @@ function executeRequest(state: RequestState, action: RequestExecuteAction): Requ * the new state, with the response added to the request */ function completeRequest(state: RequestState, action: RequestCompleteAction): RequestState { - return Object.assign({}, state, { - [action.payload]: Object.assign({}, state[action.payload], { + const time = new Date().getTime(); + + const ob = Object.assign({}, state, { + [action.payload.uuid]: Object.assign({}, state[action.payload.uuid], { responsePending: false, - completed: true + completed: true, + response: Object.assign({}, action.payload.response, { timeAdded: time }) }) }); + console.log(ob); + return ob; +} + +function resetResponseTimestamps(state: RequestState, action: ResetResponseTimestampsAction) { + const newState = Object.create(null); + Object.keys(state).forEach((key) => { + newState[key] = Object.assign({}, state[key], + { response: Object.assign({}, state[key].response, { timeAdded: action.payload }) } + ); + }); + return newState; } diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index 39f29a9beb..5953d43c9f 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -1,10 +1,8 @@ import { cold, getTestScheduler, hot } from 'jasmine-marbles'; import { of as observableOf } from 'rxjs'; import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service'; -import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; import { defaultUUID, getMockUUIDService } from '../../shared/mocks/mock-uuid.service'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; import { UUIDService } from '../shared/uuid.service'; import { RequestConfigureAction, RequestExecuteAction } from './request.actions'; @@ -29,7 +27,6 @@ describe('RequestService', () => { let service: RequestService; let serviceAsAny: any; let objectCache: ObjectCacheService; - let responseCache: ResponseCacheService; let uuidService: UUIDService; let store: Store; @@ -49,7 +46,6 @@ describe('RequestService', () => { objectCache = getMockObjectCacheService(); (objectCache.hasBySelfLink as any).and.returnValue(false); - responseCache = getMockResponseCacheService(); (responseCache.has as any).and.returnValue(false); (responseCache.get as any).and.returnValue(observableOf(undefined)); @@ -65,7 +61,6 @@ describe('RequestService', () => { service = new RequestService( objectCache, - responseCache, uuidService, store ); diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 71d0189816..cc8c9816f8 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -1,14 +1,23 @@ -import { Observable, merge as observableMerge } from 'rxjs'; -import { filter, first, map, mergeMap, partition, take } from 'rxjs/operators'; +import { merge as observableMerge, Observable, of as observableOf } from 'rxjs'; +import { + filter, + find, + first, + map, + mergeMap, + reduce, + startWith, + switchMap, + take, + tap +} from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { MemoizedSelector, select, Store } from '@ngrx/store'; -import { hasValue } from '../../shared/empty.util'; +import { hasNoValue, hasValue } from '../../shared/empty.util'; import { CacheableObject } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { ResponseCacheService } from '../cache/response-cache.service'; +import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; import { coreSelector, CoreState } from '../core.reducers'; import { IndexName } from '../index/index.reducer'; import { pathSelector } from '../shared/selectors'; @@ -19,13 +28,13 @@ import { GetRequest, RestRequest } from './request.models'; import { RequestEntry } from './request.reducer'; import { CommitSSBAction } from '../cache/server-sync-buffer.actions'; import { RestRequestMethod } from './rest-request-method'; +import { getResponseFromEntry } from '../shared/operators'; @Injectable() export class RequestService { private requestsOnTheirWayToTheStore: string[] = []; constructor(private objectCache: ObjectCacheService, - private responseCache: ResponseCacheService, private uuidService: UUIDService, private store: Store) { } @@ -83,23 +92,27 @@ export class RequestService { private isCachedOrPending(request: GetRequest) { let isCached = this.objectCache.hasBySelfLink(request.href); - if (!isCached && this.responseCache.has(request.href)) { - const responses = this.responseCache.get(request.href).pipe( - take(1), - map((entry: ResponseCacheEntry) => entry.response) - ); + const responses: Observable = this.isReusable(request.uuid).pipe( + filter((reusable: boolean) => !isCached && reusable), + switchMap(() => { + return this.getByHref(request.href).pipe( + getResponseFromEntry(), + take(1) + ); + } + )); - const errorResponses = responses.pipe(filter((response) => !response.isSuccessful), map(() => true)); // TODO add a configurable number of retries in case of an error. - const dsoSuccessResponses = responses.pipe( - filter((response) => response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)), - map((response: DSOSuccessResponse) => response.resourceSelfLinks), - map((resourceSelfLinks: string[]) => resourceSelfLinks - .every((selfLink) => this.objectCache.hasBySelfLink(selfLink)) - )); - const otherSuccessResponses = responses.pipe(filter((response) => response.isSuccessful && !hasValue((response as DSOSuccessResponse).resourceSelfLinks)), map(() => true)); + const errorResponses = responses.pipe(filter((response) => !response.isSuccessful), map(() => true)); // TODO add a configurable number of retries in case of an error. + const dsoSuccessResponses = responses.pipe( + filter((response) => response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)), + map((response: DSOSuccessResponse) => response.resourceSelfLinks), + map((resourceSelfLinks: string[]) => resourceSelfLinks + .every((selfLink) => this.objectCache.hasBySelfLink(selfLink)) + )); + const otherSuccessResponses = responses.pipe(filter((response) => response.isSuccessful && !hasValue((response as DSOSuccessResponse).resourceSelfLinks)), map(() => true)); + + observableMerge(errorResponses, otherSuccessResponses, dsoSuccessResponses).subscribe((c) => isCached = c); - observableMerge(errorResponses, otherSuccessResponses, dsoSuccessResponses).subscribe((c) => isCached = c); - } const isPending = this.isPending(request); return isCached || isPending; } @@ -129,4 +142,34 @@ export class RequestService { commit(method?: RestRequestMethod) { this.store.dispatch(new CommitSSBAction(method)) } + + /** + * Check whether a ResponseCacheEntry should still be cached + * + * @param entry + * the entry to check + * @return boolean + * false if the entry is null, undefined, or its time to + * live has been exceeded, true otherwise + */ + private isReusable(uuid: string): Observable { + if (hasNoValue(uuid)) { + return observableOf(false); + } else { + const requestEntry$ = this.getByUUID(uuid); + return requestEntry$.pipe( + filter((entry: RequestEntry) => hasValue(entry) && hasValue(entry.response)), + map((entry: RequestEntry) => { + if (hasValue(entry) && entry.response.isSuccessful) { + const timeOutdated = entry.response.timeAdded + entry.request.responseMsToLive; + const isOutDated = new Date().getTime() > timeOutdated; + return !isOutDated; + } else { + return false; + } + }) + ); + return observableOf(false); + } + } } diff --git a/src/app/core/data/search-response-parsing.service.ts b/src/app/core/data/search-response-parsing.service.ts index 4039b8f761..7ee2b60f89 100644 --- a/src/app/core/data/search-response-parsing.service.ts +++ b/src/app/core/data/search-response-parsing.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { RestResponse, SearchSuccessResponse } from '../cache/response-cache.models'; +import { RestResponse, SearchSuccessResponse } from '../cache/response.models'; import { DSOResponseParsingService } from './dso-response-parsing.service'; import { ResponseParsingService } from './parsing.service'; import { RestRequest } from './request.models'; diff --git a/src/app/core/integration/authority.service.ts b/src/app/core/integration/authority.service.ts index cb2595adc4..a5fa3a8d09 100644 --- a/src/app/core/integration/authority.service.ts +++ b/src/app/core/integration/authority.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@angular/core'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RequestService } from '../data/request.service'; import { IntegrationService } from './integration.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; @@ -11,7 +10,6 @@ export class AuthorityService extends IntegrationService { protected browseEndpoint = 'entries'; constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected halService: HALEndpointService) { super(); diff --git a/src/app/core/integration/integration-response-parsing.service.spec.ts b/src/app/core/integration/integration-response-parsing.service.spec.ts index 9c3e5b0344..38741da4e2 100644 --- a/src/app/core/integration/integration-response-parsing.service.spec.ts +++ b/src/app/core/integration/integration-response-parsing.service.spec.ts @@ -1,4 +1,4 @@ -import { ErrorResponse, IntegrationSuccessResponse } from '../cache/response-cache.models'; +import { ErrorResponse, IntegrationSuccessResponse } from '../cache/response.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { GlobalConfig } from '../../../config/global-config.interface'; diff --git a/src/app/core/integration/integration-response-parsing.service.ts b/src/app/core/integration/integration-response-parsing.service.ts index 06c6b9620d..6eff7ab792 100644 --- a/src/app/core/integration/integration-response-parsing.service.ts +++ b/src/app/core/integration/integration-response-parsing.service.ts @@ -6,7 +6,7 @@ import { ErrorResponse, IntegrationSuccessResponse, RestResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { isNotEmpty } from '../../shared/empty.util'; import { IntegrationObjectFactory } from './integration-object-factory'; diff --git a/src/app/core/integration/integration.service.spec.ts b/src/app/core/integration/integration.service.spec.ts index f7e3769620..158f4b0680 100644 --- a/src/app/core/integration/integration.service.spec.ts +++ b/src/app/core/integration/integration.service.spec.ts @@ -1,7 +1,6 @@ import { cold, getTestScheduler } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RequestService } from '../data/request.service'; import { IntegrationRequest } from '../data/request.models'; @@ -18,7 +17,6 @@ class TestService extends IntegrationService { protected browseEndpoint = BROWSE; constructor( - protected responseCache: ResponseCacheService, protected requestService: RequestService, protected halService: HALEndpointService) { super(); @@ -28,7 +26,6 @@ class TestService extends IntegrationService { describe('IntegrationService', () => { let scheduler: TestScheduler; let service: TestService; - let responseCache: ResponseCacheService; let requestService: RequestService; let halService: any; let findOptions: IntegrationSearchOptions; @@ -43,24 +40,14 @@ describe('IntegrationService', () => { findOptions = new IntegrationSearchOptions(uuid, name, metadata); - function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService { - return jasmine.createSpyObj('responseCache', { - get: cold('c-', { - c: {response: {isSuccessful}} - }) - }); - } - - function initTestService(): TestService { + function initTestService(): TestService { return new TestService( - responseCache, requestService, halService ); } beforeEach(() => { - responseCache = initMockResponseCacheService(true); requestService = getMockRequestService(); scheduler = getTestScheduler(); halService = new HALEndpointServiceStub(integrationEndpoint); diff --git a/src/app/core/integration/integration.service.ts b/src/app/core/integration/integration.service.ts index 3c71ca5f3b..2ace710dc7 100644 --- a/src/app/core/integration/integration.service.ts +++ b/src/app/core/integration/integration.service.ts @@ -1,27 +1,26 @@ import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; -import { IntegrationSuccessResponse } from '../cache/response-cache.models'; +import { IntegrationSuccessResponse } from '../cache/response.models'; import { GetRequest, IntegrationRequest } from '../data/request.models'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { IntegrationData } from './integration-data'; import { IntegrationSearchOptions } from './models/integration-options.model'; +import { RequestEntry } from '../data/request.reducer'; +import { getResponseFromEntry } from '../shared/operators'; export abstract class IntegrationService { protected request: IntegrationRequest; - protected abstract responseCache: ResponseCacheService; protected abstract requestService: RequestService; protected abstract linkPath: string; protected abstract browseEndpoint: string; protected abstract halService: HALEndpointService; protected getData(request: GetRequest): Observable { - return this.responseCache.get(request.href).pipe( - map((entry: ResponseCacheEntry) => entry.response), - mergeMap((response) => { + return this.requestService.getByHref(request.href).pipe( + getResponseFromEntry(), + mergeMap((response) => { if (response.isSuccessful && isNotEmpty(response)) { const dataResponse = response as IntegrationSuccessResponse; return observableOf(new IntegrationData(dataResponse.pageInfo, dataResponse.dataDefinition)); diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 50ce4711ff..f8d6435a03 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -23,7 +23,6 @@ import { ItemDataService } from '../data/item-data.service'; import { ObjectCacheService } from '../cache/object-cache.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RequestService } from '../data/request.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; @@ -62,7 +61,6 @@ describe('MetadataService', () => { let store: Store; let objectCacheService: ObjectCacheService; - let responseCacheService: ResponseCacheService; let requestService: RequestService; let uuidService: UUIDService; let remoteDataBuildService: RemoteDataBuildService; @@ -82,10 +80,9 @@ describe('MetadataService', () => { spyOn(store, 'dispatch'); objectCacheService = new ObjectCacheService(store); - responseCacheService = new ResponseCacheService(store); uuidService = new UUIDService(); - requestService = new RequestService(objectCacheService, responseCacheService, uuidService, store); - remoteDataBuildService = new RemoteDataBuildService(objectCacheService, responseCacheService, requestService); + requestService = new RequestService(objectCacheService, uuidService, store); + remoteDataBuildService = new RemoteDataBuildService(objectCacheService, requestService); TestBed.configureTestingModule({ imports: [ @@ -108,7 +105,6 @@ describe('MetadataService', () => { ], providers: [ { provide: ObjectCacheService, useValue: objectCacheService }, - { provide: ResponseCacheService, useValue: responseCacheService }, { provide: RequestService, useValue: requestService }, { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, diff --git a/src/app/core/registry/registry.service.spec.ts b/src/app/core/registry/registry.service.spec.ts index d0ed1e5cb8..adb2ed8b05 100644 --- a/src/app/core/registry/registry.service.spec.ts +++ b/src/app/core/registry/registry.service.spec.ts @@ -1,24 +1,21 @@ import { TestBed } from '@angular/core/testing'; import { RegistryService } from './registry.service'; import { CommonModule } from '@angular/common'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RequestService } from '../data/request.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { Observable, of as observableOf, combineLatest as observableCombineLatest } from 'rxjs'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { RequestEntry } from '../data/request.reducer'; import { RemoteData } from '../data/remote-data'; import { PageInfo } from '../shared/page-info.model'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; -import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; import { RegistryBitstreamformatsSuccessResponse, RegistryMetadatafieldsSuccessResponse, RegistryMetadataschemasSuccessResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { Component } from '@angular/core'; import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model'; import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model'; @@ -146,7 +143,6 @@ describe('RegistryService', () => { DummyComponent ], providers: [ - { provide: ResponseCacheService, useValue: getMockResponseCacheService() }, { provide: RequestService, useValue: getMockRequestService() }, { provide: RemoteDataBuildService, useValue: rdbStub }, { provide: HALEndpointService, useValue: halServiceStub }, diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts index 7e7c18f69e..ef92d42ce9 100644 --- a/src/app/core/registry/registry.service.ts +++ b/src/app/core/registry/registry.service.ts @@ -6,29 +6,29 @@ import { PageInfo } from '../shared/page-info.model'; import { MetadataSchema } from '../metadata/metadataschema.model'; import { MetadataField } from '../metadata/metadatafield.model'; import { BitstreamFormat } from './mock-bitstream-format.model'; -import { flatMap, map, tap } from 'rxjs/operators'; +import { filter, flatMap, map, tap } from 'rxjs/operators'; import { GetRequest, RestRequest } from '../data/request.models'; import { GenericConstructor } from '../shared/generic-constructor'; import { ResponseParsingService } from '../data/parsing.service'; import { RegistryMetadataschemasResponseParsingService } from '../data/registry-metadataschemas-response-parsing.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RequestService } from '../data/request.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { RegistryBitstreamformatsSuccessResponse, RegistryMetadatafieldsSuccessResponse, RegistryMetadataschemasSuccessResponse -} from '../cache/response-cache.models'; +} from '../cache/response.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RegistryMetadatafieldsResponseParsingService } from '../data/registry-metadatafields-response-parsing.service'; import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model'; -import { isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { URLCombiner } from '../url-combiner/url-combiner'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { RegistryBitstreamformatsResponseParsingService } from '../data/registry-bitstreamformats-response-parsing.service'; import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model'; +import { RequestEntry } from '../data/request.reducer'; +import { getResponseFromEntry } from '../shared/operators'; @Injectable() export class RegistryService { @@ -37,8 +37,7 @@ export class RegistryService { private metadataFieldsPath = 'metadatafields'; private bitstreamFormatsPath = 'bitstreamformats'; - constructor(protected responseCache: ResponseCacheService, - protected requestService: RequestService, + constructor(protected requestService: RequestService, private rdb: RemoteDataBuildService, private halService: HALEndpointService) { @@ -51,12 +50,8 @@ export class RegistryService { flatMap((request: RestRequest) => this.requestService.getByHref(request.href)) ); - const responseCacheObs = requestObs.pipe( - flatMap((request: RestRequest) => this.responseCache.get(request.href)) - ); - - const rmrObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const rmrObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: RegistryMetadataschemasSuccessResponse) => response.metadataschemasResponse) ); @@ -64,8 +59,8 @@ export class RegistryService { map((rmr: RegistryMetadataschemasResponse) => rmr.metadataschemas) ); - const pageInfoObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const pageInfoObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: RegistryMetadataschemasSuccessResponse) => response.pageInfo) ); @@ -75,7 +70,7 @@ export class RegistryService { }) ); - return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); + return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); } public getMetadataSchemaByName(schemaName: string): Observable> { @@ -90,12 +85,8 @@ export class RegistryService { flatMap((request: RestRequest) => this.requestService.getByHref(request.href)) ); - const responseCacheObs = requestObs.pipe( - flatMap((request: RestRequest) => this.responseCache.get(request.href)) - ); - - const rmrObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const rmrObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: RegistryMetadataschemasSuccessResponse) => response.metadataschemasResponse) ); @@ -104,7 +95,7 @@ export class RegistryService { map((metadataSchemas: MetadataSchema[]) => metadataSchemas.filter((value) => value.prefix === schemaName)[0]) ); - return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, metadataschemaObs); + return this.rdb.toRemoteDataObservable(requestEntryObs, metadataschemaObs); } public getMetadataFieldsBySchema(schema: MetadataSchema, pagination: PaginationComponentOptions): Observable>> { @@ -114,12 +105,8 @@ export class RegistryService { flatMap((request: RestRequest) => this.requestService.getByHref(request.href)) ); - const responseCacheObs = requestObs.pipe( - flatMap((request: RestRequest) => this.responseCache.get(request.href)) - ); - - const rmrObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const rmrObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: RegistryMetadatafieldsSuccessResponse) => response.metadatafieldsResponse) ); @@ -128,8 +115,9 @@ export class RegistryService { map((metadataFields: MetadataField[]) => metadataFields.filter((field) => field.schema.id === schema.id)) ); - const pageInfoObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const pageInfoObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), + map((response: RegistryMetadatafieldsSuccessResponse) => response.pageInfo) ); @@ -139,7 +127,7 @@ export class RegistryService { }) ); - return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); + return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); } public getBitstreamFormats(pagination: PaginationComponentOptions): Observable>> { @@ -149,12 +137,8 @@ export class RegistryService { flatMap((request: RestRequest) => this.requestService.getByHref(request.href)) ); - const responseCacheObs = requestObs.pipe( - flatMap((request: RestRequest) => this.responseCache.get(request.href)) - ); - - const rbrObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const rbrObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: RegistryBitstreamformatsSuccessResponse) => response.bitstreamformatsResponse) ); @@ -162,8 +146,8 @@ export class RegistryService { map((rbr: RegistryBitstreamformatsResponse) => rbr.bitstreamformats) ); - const pageInfoObs: Observable = responseCacheObs.pipe( - map((entry: ResponseCacheEntry) => entry.response), + const pageInfoObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), map((response: RegistryBitstreamformatsSuccessResponse) => response.pageInfo) ); @@ -173,7 +157,7 @@ export class RegistryService { }) ); - return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs); + return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); } private getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable { diff --git a/src/app/core/shared/hal-endpoint.service.spec.ts b/src/app/core/shared/hal-endpoint.service.spec.ts index 0c2afe938b..d36da207ca 100644 --- a/src/app/core/shared/hal-endpoint.service.spec.ts +++ b/src/app/core/shared/hal-endpoint.service.spec.ts @@ -1,14 +1,12 @@ import { cold, hot } from 'jasmine-marbles'; import { GlobalConfig } from '../../../config/global-config.interface'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; -import { ResponseCacheService } from '../cache/response-cache.service'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from './hal-endpoint.service'; import { EndpointMapRequest } from '../data/request.models'; describe('HALEndpointService', () => { let service: HALEndpointService; - let responseCache: ResponseCacheService; let requestService: RequestService; let envConfig: GlobalConfig; @@ -19,14 +17,6 @@ describe('HALEndpointService', () => { describe('getRootEndpointMap', () => { beforeEach(() => { - responseCache = jasmine.createSpyObj('responseCache', { - get: hot('a-', { - a: { - response: { endpointMap: endpointMap } - } - }) - }); - requestService = getMockRequestService(); envConfig = { @@ -34,7 +24,6 @@ describe('HALEndpointService', () => { } as any; service = new HALEndpointService( - responseCache, requestService, envConfig ); @@ -60,12 +49,6 @@ describe('HALEndpointService', () => { envConfig = { rest: { baseUrl: 'https://rest.api/' } } as any; - - service = new HALEndpointService( - responseCache, - requestService, - envConfig - ); }); it('should return the endpoint URL for the service\'s linkPath', () => { @@ -89,7 +72,6 @@ describe('HALEndpointService', () => { describe('isEnabledOnRestApi', () => { beforeEach(() => { service = new HALEndpointService( - responseCache, requestService, envConfig ); diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index d72b9e9a9f..8ef65c4dd1 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -1,21 +1,27 @@ -import {of as observableOf, Observable } from 'rxjs'; -import {filter, distinctUntilChanged, map, flatMap, startWith, tap } from 'rxjs/operators'; +import { Observable, of as observableOf } from 'rxjs'; +import { + distinctUntilChanged, + filter, + flatMap, + map, + startWith, + switchMap, + 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 { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; import { Inject, Injectable } from '@angular/core'; import { GLOBAL_CONFIG } from '../../../config'; +import { EndpointMap, EndpointMapSuccessResponse } from '../cache/response.models'; +import { getResponseFromEntry } from './operators'; @Injectable() export class HALEndpointService { - constructor(private responseCache: ResponseCacheService, - private requestService: RequestService, + constructor(private requestService: RequestService, @Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) { } @@ -29,12 +35,22 @@ export class HALEndpointService { private getEndpointMapAt(href): Observable { const request = new EndpointMapRequest(this.requestService.generateRequestId(), href); - this.requestService.configure(request); - return this.responseCache.get(request.href).pipe( - map((entry: ResponseCacheEntry) => entry.response), - filter((response: EndpointMapSuccessResponse) => isNotEmpty(response)), + + this.requestService.getByUUID(request.uuid).pipe( + getResponseFromEntry(), map((response: EndpointMapSuccessResponse) => response.endpointMap), - distinctUntilChanged(),); + distinctUntilChanged()).subscribe((t) => console.log('uuid', t)); + this.requestService.getByHref(request.href).pipe( + getResponseFromEntry(), + map((response: EndpointMapSuccessResponse) => response.endpointMap), + distinctUntilChanged()).subscribe((t) => console.log('href', t)); + + this.requestService.configure(request); + return this.requestService.getByHref(request.href).pipe( /*<-- changing this to UUID breaks it */ + getResponseFromEntry(), + map((response: EndpointMapSuccessResponse) => response.endpointMap), + distinctUntilChanged()); + } public getEndpoint(linkPath: string): Observable { @@ -48,7 +64,7 @@ export class HALEndpointService { let currentPath; const pipeArguments = path .map((subPath: string, index: number) => [ - flatMap((href: string) => this.getEndpointMapAt(href)), + switchMap((href: string) => this.getEndpointMapAt(href)), map((endpointMap: EndpointMap) => { if (hasValue(endpointMap) && hasValue(endpointMap[subPath])) { currentPath = endpointMap[subPath]; diff --git a/src/app/core/shared/operators.spec.ts b/src/app/core/shared/operators.spec.ts index 16bf633705..0684308fe9 100644 --- a/src/app/core/shared/operators.spec.ts +++ b/src/app/core/shared/operators.spec.ts @@ -1,17 +1,15 @@ import { cold, getTestScheduler, hot } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; -import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { ResponseCacheService } from '../cache/response-cache.service'; -import { GetRequest, RestRequest } from '../data/request.models'; +import { GetRequest } from '../data/request.models'; import { RequestEntry } from '../data/request.reducer'; import { RequestService } from '../data/request.service'; import { configureRequest, - filterSuccessfulResponses, getRemoteDataPayload, - getRequestFromSelflink, getResourceLinksFromResponse, - getResponseFromSelflink + filterSuccessfulResponses, + getRemoteDataPayload, + getRequestFromSelflink, + getResourceLinksFromResponse, } from './operators'; describe('Core Module - RxJS Operators', () => { @@ -64,44 +62,6 @@ describe('Core Module - RxJS Operators', () => { }); }); - describe('getResponseFromSelflink', () => { - let responseCacheService: ResponseCacheService; - - beforeEach(() => { - scheduler = getTestScheduler(); - }); - - it('should return the ResponseCacheEntry corresponding to the self link in the source', () => { - responseCacheService = getMockResponseCacheService(); - - const source = hot('a', { a: testSelfLink }); - const result = source.pipe(getResponseFromSelflink(responseCacheService)); - const expected = cold('a', { a: new ResponseCacheEntry()}); - - expect(result).toBeObservable(expected) - }); - - it('should use the responseCacheService to fetch the response by the request\'s link', () => { - responseCacheService = getMockResponseCacheService(); - - const source = hot('a', { a: testSelfLink }); - scheduler.schedule(() => source.pipe(getResponseFromSelflink(responseCacheService)).subscribe()); - scheduler.flush(); - - expect(responseCacheService.get).toHaveBeenCalledWith(testSelfLink) - }); - - it('shouldn\'t return anything if there is no response matching the request\'s link', () => { - responseCacheService = getMockResponseCacheService(undefined, cold('a', { a: undefined })); - - const source = hot('a', { a: testSelfLink }); - const result = source.pipe(getResponseFromSelflink(responseCacheService)); - const expected = cold('-'); - - expect(result).toBeObservable(expected) - }); - }); - describe('filterSuccessfulResponses', () => { it('should only return responses for which isSuccessful === true', () => { const source = hot('abcde', testRCEs); diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 476119399b..e9ab8794ff 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -1,9 +1,7 @@ import { Observable } from 'rxjs'; import { filter, first, flatMap, map, tap } from 'rxjs/operators'; -import { hasValueOperator, isNotEmpty } from '../../shared/empty.util'; -import { DSOSuccessResponse } from '../cache/response-cache.models'; -import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { ResponseCacheService } from '../cache/response-cache.service'; +import { hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util'; +import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; import { RemoteData } from '../data/remote-data'; import { RestRequest } from '../data/request.models'; import { RequestEntry } from '../data/request.reducer'; @@ -24,22 +22,25 @@ export const getRequestFromSelflink = (requestService: RequestService) => hasValueOperator() ); -export const getResponseFromSelflink = (responseCache: ResponseCacheService) => - (source: Observable): Observable => +export const filterSuccessfulResponses = () => + (source: Observable): Observable => source.pipe( - flatMap((href: string) => responseCache.get(href)), - hasValueOperator() + getResponseFromEntry(), + filter((response: RestResponse) => response.isSuccessful === true), ); -export const filterSuccessfulResponses = () => - (source: Observable): Observable => - source.pipe(filter((entry: ResponseCacheEntry) => entry.response.isSuccessful === true)); +export const getResponseFromEntry = () => + (source: Observable): Observable => + source.pipe( + filter((entry: RequestEntry) => hasValue(entry) && hasValue(entry.response)), + map((entry: RequestEntry) => entry.response) + ); export const getResourceLinksFromResponse = () => - (source: Observable): Observable => + (source: Observable): Observable => source.pipe( filterSuccessfulResponses(), - map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks), + map((response: DSOSuccessResponse) => response.resourceSelfLinks), ); export const configureRequest = (requestService: RequestService) => @@ -60,7 +61,7 @@ export const toDSpaceObjectListRD = () => map((rd: RemoteData>>) => { const dsoPage: T[] = rd.payload.page.map((searchResult: SearchResult) => searchResult.dspaceObject); const payload = Object.assign(rd.payload, { page: dsoPage }) as PaginatedList; - return Object.assign(rd, {payload: payload}); + return Object.assign(rd, { payload: payload }); }) ); diff --git a/src/app/shared/mocks/mock-remote-data-build.service.ts b/src/app/shared/mocks/mock-remote-data-build.service.ts index ea263239ea..675e539d90 100644 --- a/src/app/shared/mocks/mock-remote-data-build.service.ts +++ b/src/app/shared/mocks/mock-remote-data-build.service.ts @@ -1,14 +1,13 @@ import {of as observableOf, Observable } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; -import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; import { RemoteData } from '../../core/data/remote-data'; import { RequestEntry } from '../../core/data/request.reducer'; import { hasValue } from '../empty.util'; export function getMockRemoteDataBuildService(toRemoteDataObservable$?: Observable>): RemoteDataBuildService { return { - toRemoteDataObservable: (requestEntry$: Observable, responseCache$: Observable, payload$: Observable) => { + toRemoteDataObservable: (requestEntry$: Observable, payload$: Observable) => { if (hasValue(toRemoteDataObservable$)) { return toRemoteDataObservable$; diff --git a/src/app/shared/mocks/mock-response-cache.service.ts b/src/app/shared/mocks/mock-response-cache.service.ts index a5a999873d..e69de29bb2 100644 --- a/src/app/shared/mocks/mock-response-cache.service.ts +++ b/src/app/shared/mocks/mock-response-cache.service.ts @@ -1,16 +0,0 @@ -import {of as observableOf, Observable } from 'rxjs'; -import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; -import { ResponseCacheService } from '../../core/cache/response-cache.service'; - -export function getMockResponseCacheService( - add$: Observable = observableOf(new ResponseCacheEntry()), - get$: Observable = observableOf(new ResponseCacheEntry()), - has: boolean = false -): ResponseCacheService { - return jasmine.createSpyObj('ResponseCacheService', { - add: add$, - get: get$, - has, - }); - -} diff --git a/src/server.ts b/src/server.ts index 0e68e4f139..13d0b2fd89 100644 --- a/src/server.ts +++ b/src/server.ts @@ -53,6 +53,7 @@ export function startServer(bootstrap: Type<{}> | NgModuleFactory<{}>) { function ngApp(req, res) { function onHandleError(parentZoneDelegate, currentZone, targetZone, error) { + console.error('Error:', error); console.warn('Error in SSR, serving for direct CSR'); res.sendFile('index.csr.html', { root: './src' }); }