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> {
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);

View File

@@ -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 {

View File

@@ -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<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<TDomain>> {
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$ =

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 { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
@@ -59,7 +68,8 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
// );
const responses = scopeCommunityHrefObs.pipe(
mergeMap((href: string) => 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`)))

View File

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

View File

@@ -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)) {

View File

@@ -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 {

View File

@@ -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});
});
});

View File

@@ -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<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 {
return `client/${this.uuidService.generate()}`;
}
@@ -70,7 +76,15 @@ export class RequestService {
}
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> {
@@ -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<RestResponse> = 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<RestResponse> = 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;
}

View File

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

View File

@@ -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 {

View File

@@ -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<string> {