added indexing for different UUIDs

This commit is contained in:
lotte
2018-10-19 16:07:07 +02:00
parent ec5f977dd2
commit 5c12e2d995
12 changed files with 101 additions and 43 deletions

View File

@@ -24,9 +24,12 @@ export class AuthRequestService {
protected fetchRequest(request: RestRequest): Observable<any> { protected fetchRequest(request: RestRequest): Observable<any> {
return this.requestService.getByHref(request.href).pipe( return this.requestService.getByHref(request.href).pipe(
tap((t) => console.log(t)),
getResponseFromEntry(), getResponseFromEntry(),
// TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed // 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)),
tap((t) => console.log(t)),
mergeMap((response) => { mergeMap((response) => {
if (response.isSuccessful && isNotEmpty(response)) { if (response.isSuccessful && isNotEmpty(response)) {
return observableOf((response as AuthStatusResponse).response); return observableOf((response as AuthStatusResponse).response);

View File

@@ -116,6 +116,7 @@ export class AuthService {
options.headers = headers; options.headers = headers;
return this.authRequestService.postToEndpoint('login', body, options).pipe( return this.authRequestService.postToEndpoint('login', body, options).pipe(
map((status: AuthStatus) => { map((status: AuthStatus) => {
console.log('yey response');
if (status.authenticated) { if (status.authenticated) {
return status; return status;
} else { } else {

View File

@@ -5,7 +5,15 @@ import {
race as observableRace race as observableRace
} from 'rxjs'; } from 'rxjs';
import { Injectable } from '@angular/core'; 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 { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util';
import { PaginatedList } from '../../data/paginated-list'; import { PaginatedList } from '../../data/paginated-list';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
@@ -32,8 +40,6 @@ export class RemoteDataBuildService {
} }
buildSingle<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<TDomain>> { buildSingle<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<TDomain>> {
console.log('call buildSingle', href$);
if (typeof href$ === 'string') { if (typeof href$ === 'string') {
href$ = observableOf(href$); href$ = observableOf(href$);
} }
@@ -45,7 +51,9 @@ export class RemoteDataBuildService {
const requestEntry$ = observableRace( const requestEntry$ = observableRace(
href$.pipe(getRequestFromSelflink(this.requestService)), href$.pipe(getRequestFromSelflink(this.requestService)),
requestHref$.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. // always use self link if that is cached, only if it isn't, get it via the response.
const payload$ = const payload$ =

View File

@@ -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 { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs';
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { NormalizedCommunity } from '../cache/models/normalized-community.model';
@@ -59,7 +68,8 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
// ); // );
const responses = scopeCommunityHrefObs.pipe( const responses = scopeCommunityHrefObs.pipe(
mergeMap((href: string) => this.requestService.getByHref(href)), mergeMap((href: string) => this.requestService.getByHref(href)),
getResponseFromEntry()); getResponseFromEntry()
);
const errorResponses = responses.pipe( const errorResponses = responses.pipe(
filter((response) => !response.isSuccessful), filter((response) => !response.isSuccessful),
mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`))) mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`)))

View File

@@ -47,8 +47,6 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
if (isNotEmpty(args)) { if (isNotEmpty(args)) {
return result.pipe(map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString())); return result.pipe(map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString()));
} else { } else {
result.subscribe((t) => console.log(t));
return result; return result;
} }
} }

View File

@@ -3,7 +3,7 @@ import { Inject, Injectable, Injector } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; 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 { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service'; 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 { RequestService } from './request.service';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; 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 { ErrorResponse, RestResponse } from '../cache/response.models';
import { StoreActionTypes } from '../../store.actions'; import { StoreActionTypes } from '../../store.actions';
@@ -40,7 +40,9 @@ export class RequestEffects {
take(1) take(1)
); );
}), }),
filter((entry: RequestEntry) => hasValue(entry)),
map((entry: RequestEntry) => entry.request), map((entry: RequestEntry) => entry.request),
tap((entry: RequestEntry) => console.log(entry)),
flatMap((request: RestRequest) => { flatMap((request: RestRequest) => {
let body; let body;
if (isNotEmpty(request.body)) { if (isNotEmpty(request.body)) {

View File

@@ -53,6 +53,14 @@ function configureRequest(state: RequestState, action: RequestConfigureAction):
completed: false, 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 { function executeRequest(state: RequestState, action: RequestExecuteAction): RequestState {

View File

@@ -170,11 +170,11 @@ describe('RequestService', () => {
it('should return an Observable of undefined', () => { it('should return an Observable of undefined', () => {
const result = service.getByUUID(testUUID); const result = service.getByUUID(testUUID);
const expected = cold('b', { // const expected = cold('b', {
b: undefined // b: undefined
}); // });
expect(result).toBeObservable(expected); scheduler.expectObservable(result).toBe('b', {b: undefined});
}); });
}); });

View File

@@ -12,10 +12,11 @@ import {
take, take,
tap tap
} from 'rxjs/operators'; } from 'rxjs/operators';
import { race as observableRace } from 'rxjs';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MemoizedSelector, select, Store } from '@ngrx/store'; 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 { CacheableObject } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; 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 { CommitSSBAction } from '../cache/server-sync-buffer.actions';
import { RestRequestMethod } from './rest-request-method'; import { RestRequestMethod } from './rest-request-method';
import { getResponseFromEntry } from '../shared/operators'; import { getResponseFromEntry } from '../shared/operators';
import { AddToIndexAction } from '../index/index.actions';
@Injectable() @Injectable()
export class RequestService { export class RequestService {
@@ -48,6 +50,10 @@ export class RequestService {
return pathSelector<CoreState, string>(coreSelector, 'index', IndexName.REQUEST, href); return pathSelector<CoreState, string>(coreSelector, 'index', IndexName.REQUEST, href);
} }
private originalUUIDFromUUIDSelector(uuid: string): MemoizedSelector<CoreState, string> {
return pathSelector<CoreState, string>(coreSelector, 'index', IndexName.UUID_MAPPING, uuid);
}
generateRequestId(): string { generateRequestId(): string {
return `client/${this.uuidService.generate()}`; return `client/${this.uuidService.generate()}`;
} }
@@ -70,7 +76,15 @@ export class RequestService {
} }
getByUUID(uuid: string): Observable<RequestEntry> { getByUUID(uuid: string): Observable<RequestEntry> {
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<RequestEntry> { getByHref(href: string): Observable<RequestEntry> {
@@ -88,32 +102,42 @@ export class RequestService {
if (isGetRequest && !forceBypassCache) { if (isGetRequest && !forceBypassCache) {
this.trackRequestsOnTheirWayToTheStore(request); 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) { private isCachedOrPending(request: GetRequest) {
let isCached = this.objectCache.hasBySelfLink(request.href); let isCached = this.objectCache.hasBySelfLink(request.href);
const responses: Observable<RestResponse> = this.isReusable(request.uuid).pipe( if (isCached) {
filter((reusable: boolean) => !isCached && reusable), const responses: Observable<RestResponse> = this.isReusable(request.uuid).pipe(
switchMap(() => { filter((reusable: boolean) => reusable),
return this.getByHref(request.href).pipe( switchMap(() => {
getResponseFromEntry(), return this.getByHref(request.href).pipe(
take(1) 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 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( const dsoSuccessResponses = responses.pipe(
filter((response) => response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)), filter((response) => response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)),
map((response: DSOSuccessResponse) => response.resourceSelfLinks), map((response: DSOSuccessResponse) => response.resourceSelfLinks),
map((resourceSelfLinks: string[]) => resourceSelfLinks map((resourceSelfLinks: string[]) => resourceSelfLinks
.every((selfLink) => this.objectCache.hasBySelfLink(selfLink)) .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); const isPending = this.isPending(request);
return isCached || isPending; return isCached || isPending;
} }

View File

@@ -20,6 +20,10 @@ describe('requestReducer', () => {
const testState: IndexState = { const testState: IndexState = {
[IndexName.OBJECT]: { [IndexName.OBJECT]: {
[key1]: val1 [key1]: val1
},[IndexName.REQUEST]: {
[key1]: val1
},[IndexName.UUID_MAPPING]: {
[key1]: val1
} }
}; };
deepFreeze(testState); deepFreeze(testState);

View File

@@ -7,13 +7,12 @@ import {
export enum IndexName { export enum IndexName {
OBJECT = 'object/uuid-to-self-link', 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 { export type IndexState = {
// TODO this should be `[name in IndexName]: {` but that's currently broken, [name in IndexName]: {
// see https://github.com/Microsoft/TypeScript/issues/13042
[name: string]: {
[key: string]: string [key: string]: string
} }
} }
@@ -43,9 +42,10 @@ function addToIndex(state: IndexState, action: AddToIndexAction): IndexState {
const newSubState = Object.assign({}, subState, { const newSubState = Object.assign({}, subState, {
[action.payload.key]: action.payload.value [action.payload.key]: action.payload.value
}); });
return Object.assign({}, state, { const obs = Object.assign({}, state, {
[action.payload.name]: newSubState [action.payload.name]: newSubState
}) });
return obs;
} }
function removeFromIndexByValue(state: IndexState, action: RemoveFromIndexByValueAction): IndexState { function removeFromIndexByValue(state: IndexState, action: RemoveFromIndexByValueAction): IndexState {

View File

@@ -1,6 +1,6 @@
import { Observable, of as observableOf, combineLatest as observableCombineLatest } from 'rxjs'; import { Observable, of as observableOf, combineLatest as observableCombineLatest } from 'rxjs';
import { import {
distinctUntilChanged, distinctUntilChanged, first,
map, map,
mergeMap, mergeMap,
startWith, startWith,
@@ -40,7 +40,7 @@ export class HALEndpointService {
return this.requestService.getByHref(request.href).pipe( return this.requestService.getByHref(request.href).pipe(
getResponseFromEntry(), getResponseFromEntry(),
map((response: EndpointMapSuccessResponse) => response.endpointMap), map((response: EndpointMapSuccessResponse) => response.endpointMap),
distinctUntilChanged()); );
} }
public getEndpoint(linkPath: string): Observable<string> { public getEndpoint(linkPath: string): Observable<string> {