diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 0752149eae..284f588040 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -24,9 +24,12 @@ export class AuthRequestService { protected fetchRequest(request: RestRequest): Observable { return this.requestService.getByHref(request.href).pipe( + tap((t) => console.log(t)), + getResponseFromEntry(), // TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed // tap(() => this.responseCache.remove(request.href)), + tap((t) => console.log(t)), mergeMap((response) => { if (response.isSuccessful && isNotEmpty(response)) { return observableOf((response as AuthStatusResponse).response); diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 229c44bcfa..4c520e8f30 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -116,6 +116,7 @@ export class AuthService { options.headers = headers; return this.authRequestService.postToEndpoint('login', body, options).pipe( map((status: AuthStatus) => { + console.log('yey response'); if (status.authenticated) { return status; } else { 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 ad5707bbf6..3877c19ff9 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -5,7 +5,15 @@ import { race as observableRace } from 'rxjs'; import { Injectable } from '@angular/core'; -import { distinctUntilChanged, first, flatMap, map, startWith, switchMap } from 'rxjs/operators'; +import { + distinctUntilChanged, + first, + flatMap, + map, + startWith, + switchMap, + takeUntil, 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'; @@ -32,8 +40,6 @@ export class RemoteDataBuildService { } buildSingle(href$: string | Observable): Observable> { - console.log('call buildSingle', href$); - if (typeof href$ === 'string') { href$ = observableOf(href$); } @@ -45,7 +51,9 @@ export class RemoteDataBuildService { const requestEntry$ = observableRace( href$.pipe(getRequestFromSelflink(this.requestService)), requestHref$.pipe(getRequestFromSelflink(this.requestService)), - ).pipe(first()); + ).pipe( + first() + ); // always use self link if that is cached, only if it isn't, get it via the response. const payload$ = diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 95a0015125..63c11dd8cb 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -1,4 +1,13 @@ -import { distinctUntilChanged, filter, map, mergeMap, share, take, tap } from 'rxjs/operators'; +import { + distinctUntilChanged, + filter, + first, + map, + mergeMap, + share, + take, + tap +} from 'rxjs/operators'; import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs'; import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { NormalizedCommunity } from '../cache/models/normalized-community.model'; @@ -59,7 +68,8 @@ export abstract class ComColDataService this.requestService.getByHref(href)), - getResponseFromEntry()); + getResponseFromEntry() + ); const errorResponses = responses.pipe( filter((response) => !response.isSuccessful), mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`))) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 4a993e4ac6..6a7916854b 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -47,8 +47,6 @@ export abstract class DataService if (isNotEmpty(args)) { return result.pipe(map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString())); } else { - result.subscribe((t) => console.log(t)); - return result; } } diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index bda91283bf..537a0b69b6 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -3,7 +3,7 @@ import { Inject, Injectable, Injector } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; -import { isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service'; @@ -18,7 +18,7 @@ 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 { catchError, filter, flatMap, map, take, tap } from 'rxjs/operators'; import { ErrorResponse, RestResponse } from '../cache/response.models'; import { StoreActionTypes } from '../../store.actions'; @@ -40,7 +40,9 @@ export class RequestEffects { take(1) ); }), + filter((entry: RequestEntry) => hasValue(entry)), map((entry: RequestEntry) => entry.request), + tap((entry: RequestEntry) => console.log(entry)), flatMap((request: RestRequest) => { let body; if (isNotEmpty(request.body)) { diff --git a/src/app/core/data/request.reducer.ts b/src/app/core/data/request.reducer.ts index a680de2d6b..e6a9097dce 100644 --- a/src/app/core/data/request.reducer.ts +++ b/src/app/core/data/request.reducer.ts @@ -53,6 +53,14 @@ function configureRequest(state: RequestState, action: RequestConfigureAction): completed: false, } }); + console.log(Object.assign({}, state, { + [action.payload.uuid]: { + request: action.payload, + requestPending: true, + responsePending: false, + completed: false, + } + });); } function executeRequest(state: RequestState, action: RequestExecuteAction): RequestState { diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index 5232d7efab..debddb748c 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -170,11 +170,11 @@ describe('RequestService', () => { it('should return an Observable of undefined', () => { const result = service.getByUUID(testUUID); - const expected = cold('b', { - b: undefined - }); + // const expected = cold('b', { + // b: undefined + // }); - expect(result).toBeObservable(expected); + scheduler.expectObservable(result).toBe('b', {b: undefined}); }); }); diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 564b19de75..4b7a7c9b49 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -12,10 +12,11 @@ import { take, tap } from 'rxjs/operators'; +import { race as observableRace } from 'rxjs'; import { Injectable } from '@angular/core'; import { MemoizedSelector, select, Store } from '@ngrx/store'; -import { hasNoValue, hasValue } from '../../shared/empty.util'; +import { hasNoValue, hasValue, isNotUndefined } from '../../shared/empty.util'; import { CacheableObject } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; @@ -30,6 +31,7 @@ import { RequestEntry } from './request.reducer'; import { CommitSSBAction } from '../cache/server-sync-buffer.actions'; import { RestRequestMethod } from './rest-request-method'; import { getResponseFromEntry } from '../shared/operators'; +import { AddToIndexAction } from '../index/index.actions'; @Injectable() export class RequestService { @@ -48,6 +50,10 @@ export class RequestService { return pathSelector(coreSelector, 'index', IndexName.REQUEST, href); } + private originalUUIDFromUUIDSelector(uuid: string): MemoizedSelector { + return pathSelector(coreSelector, 'index', IndexName.UUID_MAPPING, uuid); + } + generateRequestId(): string { return `client/${this.uuidService.generate()}`; } @@ -70,7 +76,15 @@ export class RequestService { } getByUUID(uuid: string): Observable { - return this.store.pipe(select(this.entryFromUUIDSelector(uuid))); + return observableRace( + this.store.pipe(select(this.entryFromUUIDSelector(uuid))), + this.store.pipe( + select(this.originalUUIDFromUUIDSelector(uuid)), + switchMap((originalUUID) => { + return this.store.pipe(select(this.entryFromUUIDSelector(originalUUID))) + }, + )) + ); } getByHref(href: string): Observable { @@ -88,32 +102,42 @@ export class RequestService { if (isGetRequest && !forceBypassCache) { this.trackRequestsOnTheirWayToTheStore(request); } + } else { + this.getByHref(request.href).pipe( + filter((entry) => hasValue(entry)), + take(1) + ).subscribe((entry) => { + return this.store.dispatch(new AddToIndexAction(IndexName.UUID_MAPPING, request.uuid, entry.request.uuid)) + } + ) } } private isCachedOrPending(request: GetRequest) { let isCached = this.objectCache.hasBySelfLink(request.href); - const responses: Observable = this.isReusable(request.uuid).pipe( - filter((reusable: boolean) => !isCached && reusable), - switchMap(() => { - return this.getByHref(request.href).pipe( - getResponseFromEntry(), - take(1) - ); - } - )); + if (isCached) { + const responses: Observable = this.isReusable(request.uuid).pipe( + filter((reusable: boolean) => 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 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 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; } diff --git a/src/app/core/index/index.reducer.spec.ts b/src/app/core/index/index.reducer.spec.ts index a1cf92aeb3..ffc2c9fadc 100644 --- a/src/app/core/index/index.reducer.spec.ts +++ b/src/app/core/index/index.reducer.spec.ts @@ -20,6 +20,10 @@ describe('requestReducer', () => { const testState: IndexState = { [IndexName.OBJECT]: { [key1]: val1 + },[IndexName.REQUEST]: { + [key1]: val1 + },[IndexName.UUID_MAPPING]: { + [key1]: val1 } }; deepFreeze(testState); diff --git a/src/app/core/index/index.reducer.ts b/src/app/core/index/index.reducer.ts index 869dee9e51..c179182509 100644 --- a/src/app/core/index/index.reducer.ts +++ b/src/app/core/index/index.reducer.ts @@ -7,13 +7,12 @@ import { export enum IndexName { OBJECT = 'object/uuid-to-self-link', - REQUEST = 'get-request/href-to-uuid' + REQUEST = 'get-request/href-to-uuid', + UUID_MAPPING = 'get-request/configured-to-cache-uuid' } -export interface IndexState { - // TODO this should be `[name in IndexName]: {` but that's currently broken, - // see https://github.com/Microsoft/TypeScript/issues/13042 - [name: string]: { +export type IndexState = { + [name in IndexName]: { [key: string]: string } } @@ -43,9 +42,10 @@ function addToIndex(state: IndexState, action: AddToIndexAction): IndexState { const newSubState = Object.assign({}, subState, { [action.payload.key]: action.payload.value }); - return Object.assign({}, state, { + const obs = Object.assign({}, state, { [action.payload.name]: newSubState - }) + }); + return obs; } function removeFromIndexByValue(state: IndexState, action: RemoveFromIndexByValueAction): IndexState { diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index 3a5da84e13..a0bf7aabd5 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -1,6 +1,6 @@ import { Observable, of as observableOf, combineLatest as observableCombineLatest } from 'rxjs'; import { - distinctUntilChanged, + distinctUntilChanged, first, map, mergeMap, startWith, @@ -40,7 +40,7 @@ export class HALEndpointService { return this.requestService.getByHref(request.href).pipe( getResponseFromEntry(), map((response: EndpointMapSuccessResponse) => response.endpointMap), - distinctUntilChanged()); + ); } public getEndpoint(linkPath: string): Observable {