fixed issue where resources that were fetched from a list where not able to be rendered separately

This commit is contained in:
Art Lowel
2017-06-19 15:20:48 +02:00
parent e37ea12e2d
commit f6550e2628
8 changed files with 100 additions and 57 deletions

View File

@@ -30,37 +30,33 @@ export class RemoteDataBuildService {
href: string,
normalizedType: GenericConstructor<TNormalized>
): RemoteData<TDomain> {
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href);
const responseCacheObs = this.responseCache.get(href);
const requestHrefObs = this.objectCache.getRequestHrefBySelfLink(href);
const requestPending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.requestPending).distinctUntilChanged();
const requestObs = Observable.race(
this.store.select<RequestEntry>('core', 'data', 'request', href).filter(entry => hasValue(entry)),
requestHrefObs.flatMap(requestHref =>
this.store.select<RequestEntry>('core', 'data', 'request', requestHref)).filter(entry => hasValue(entry))
);
const responsePending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.responsePending).distinctUntilChanged();
const responseCacheObs = Observable.race(
this.responseCache.get(href).filter(entry => hasValue(entry)),
requestHrefObs.flatMap(requestHref => this.responseCache.get(requestHref)).filter(entry => hasValue(entry))
);
const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged();
const responsePending = requestObs.map((entry: RequestEntry) => entry.responsePending).distinctUntilChanged();
const isSuccessFul = responseCacheObs
.map((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful).distinctUntilChanged();
.map((entry: ResponseCacheEntry) => entry.response.isSuccessful).distinctUntilChanged();
const errorMessage = responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && !entry.response.isSuccessful)
.filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<ErrorResponse> entry.response).errorMessage)
.distinctUntilChanged();
const payload =
Observable.race(
this.objectCache.getBySelfLink<TNormalized>(href, normalizedType),
responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.flatMap((resourceUUIDs: Array<string>) => {
if (isNotEmpty(resourceUUIDs)) {
return this.objectCache.get(resourceUUIDs[0], normalizedType);
}
else {
return Observable.of(undefined);
}
})
.distinctUntilChanged()
).map((normalized: TNormalized) => {
const payload = this.objectCache.getBySelfLink<TNormalized>(href, normalizedType)
.map((normalized: TNormalized) => {
return this.build<TNormalized, TDomain>(normalized);
});
@@ -78,23 +74,24 @@ export class RemoteDataBuildService {
href: string,
normalizedType: GenericConstructor<TNormalized>
): RemoteData<TDomain[]> {
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href);
const responseCacheObs = this.responseCache.get(href);
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href)
.filter(entry => hasValue(entry));
const responseCacheObs = this.responseCache.get(href).filter(entry => hasValue(entry));
const requestPending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.requestPending).distinctUntilChanged();
const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged();
const responsePending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.responsePending).distinctUntilChanged();
const responsePending = requestObs.map((entry: RequestEntry) => entry.responsePending).distinctUntilChanged();
const isSuccessFul = responseCacheObs
.map((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful).distinctUntilChanged();
.map((entry: ResponseCacheEntry) => entry.response.isSuccessful).distinctUntilChanged();
const errorMessage = responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && !entry.response.isSuccessful)
.filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<ErrorResponse> entry.response).errorMessage)
.distinctUntilChanged();
const payload = responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful)
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.flatMap((resourceUUIDs: Array<string>) => {
return this.objectCache.getList(resourceUUIDs, normalizedType)

View File

@@ -20,6 +20,7 @@ export class AddToObjectCacheAction implements Action {
objectToCache: CacheableObject;
timeAdded: number;
msToLive: number;
requestHref: string;
};
/**
@@ -31,9 +32,13 @@ export class AddToObjectCacheAction implements Action {
* the time it was added
* @param msToLive
* the amount of milliseconds before it should expire
* @param requestHref
* The href of the request that resulted in this object
* This isn't necessarily the same as the object's self
* link, it could have been part of a list for example
*/
constructor(objectToCache: CacheableObject, timeAdded: number, msToLive: number) {
this.payload = { objectToCache, timeAdded, msToLive };
constructor(objectToCache: CacheableObject, timeAdded: number, msToLive: number, requestHref: string) {
this.payload = { objectToCache, timeAdded, msToLive, requestHref };
}
}

View File

@@ -24,7 +24,8 @@ describe("objectCacheReducer", () => {
foo: "bar"
},
timeAdded: new Date().getTime(),
msToLive: 900000
msToLive: 900000,
requestHref: "https://rest.api/endpoint/uuid1"
},
[uuid2]: {
data: {
@@ -32,7 +33,8 @@ describe("objectCacheReducer", () => {
foo: "baz"
},
timeAdded: new Date().getTime(),
msToLive: 900000
msToLive: 900000,
requestHref: "https://rest.api/endpoint/uuid2"
}
};
deepFreeze(testState);
@@ -56,7 +58,8 @@ describe("objectCacheReducer", () => {
const objectToCache = {uuid: uuid1};
const timeAdded = new Date().getTime();
const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
const requestHref = "https://rest.api/endpoint/uuid1";
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref);
const newState = objectCacheReducer(state, action);
expect(newState[uuid1].data).toEqual(objectToCache);
@@ -68,7 +71,8 @@ describe("objectCacheReducer", () => {
const objectToCache = {uuid: uuid1, foo: "baz", somethingElse: true};
const timeAdded = new Date().getTime();
const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
const requestHref = "https://rest.api/endpoint/uuid1";
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref);
const newState = objectCacheReducer(testState, action);
expect(newState[uuid1].data['foo']).toBe("baz");
@@ -80,7 +84,8 @@ describe("objectCacheReducer", () => {
const objectToCache = {uuid: uuid1};
const timeAdded = new Date().getTime();
const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
const requestHref = "https://rest.api/endpoint/uuid1";
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref);
deepFreeze(state);
objectCacheReducer(state, action);

View File

@@ -22,6 +22,7 @@ export class ObjectCacheEntry implements CacheEntry {
data: CacheableObject;
timeAdded: number;
msToLive: number;
requestHref: string;
}
/**
@@ -83,7 +84,8 @@ function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheActio
[action.payload.objectToCache.uuid]: {
data: action.payload.objectToCache,
timeAdded: action.payload.timeAdded,
msToLive: action.payload.msToLive
msToLive: action.payload.msToLive,
requestHref: action.payload.requestHref
}
});
}

View File

@@ -22,9 +22,13 @@ export class ObjectCacheService {
* The object to add
* @param msToLive
* The number of milliseconds it should be cached for
* @param requestHref
* The href of the request that resulted in this object
* This isn't necessarily the same as the object's self
* link, it could have been part of a list for example
*/
add(objectToCache: CacheableObject, msToLive: number): void {
this.store.dispatch(new AddToObjectCacheAction(objectToCache, new Date().getTime(), msToLive));
add(objectToCache: CacheableObject, msToLive: number, requestHref: string): void {
this.store.dispatch(new AddToObjectCacheAction(objectToCache, new Date().getTime(), msToLive, requestHref));
}
/**
@@ -54,9 +58,7 @@ export class ObjectCacheService {
* An observable of the requested object
*/
get<T extends CacheableObject>(uuid: string, type: GenericConstructor<T>): Observable<T> {
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
.filter(entry => this.isValid(entry))
.distinctUntilChanged()
return this.getEntry(uuid)
.map((entry: ObjectCacheEntry) => <T> Object.assign(new type(), entry.data));
}
@@ -65,6 +67,23 @@ export class ObjectCacheService {
.flatMap((uuid: string) => this.get(uuid, type))
}
private getEntry(uuid: string): Observable<ObjectCacheEntry> {
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
.filter(entry => this.isValid(entry))
.distinctUntilChanged();
}
getRequestHref(uuid: string): Observable<string> {
return this.getEntry(uuid)
.map((entry: ObjectCacheEntry) => entry.requestHref)
.distinctUntilChanged();
}
getRequestHrefBySelfLink(self: string): Observable<string> {
return this.store.select<string>('core', 'index', 'href', self)
.flatMap((uuid: string) => this.getRequestHref(uuid));
}
/**
* Get an observable for an array of objects of the same type
* with the specified UUIDs

View File

@@ -45,7 +45,7 @@ export class RequestEffects {
})
.flatMap((entry: RequestEntry) => {
return this.restApi.get(entry.request.href)
.map((data: DSpaceRESTV2Response) => this.processEmbedded(data._embedded))
.map((data: DSpaceRESTV2Response) => this.processEmbedded(data._embedded, entry.request.href))
.map((ids: Array<string>) => new SuccessResponse(ids))
.do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
.map((response: Response) => new RequestCompleteAction(entry.request.href))
@@ -54,25 +54,25 @@ export class RequestEffects {
.map((response: Response) => new RequestCompleteAction(entry.request.href)));
});
protected processEmbedded(_embedded: any): Array<string> {
protected processEmbedded(_embedded: any, requestHref): Array<string> {
if (isNotEmpty(_embedded)) {
if (isObjectLevel(_embedded)) {
return this.deserializeAndCache(_embedded);
return this.deserializeAndCache(_embedded, requestHref);
}
else {
let uuids = [];
Object.keys(_embedded)
.filter(property => _embedded.hasOwnProperty(property))
.forEach(property => {
uuids = [...uuids, ...this.deserializeAndCache(_embedded[property])];
uuids = [...uuids, ...this.deserializeAndCache(_embedded[property], requestHref)];
});
return uuids;
}
}
}
protected deserializeAndCache(obj): Array<string> {
protected deserializeAndCache(obj, requestHref): Array<string> {
let type: ResourceType;
const isArray = Array.isArray(obj);
@@ -96,19 +96,19 @@ export class RequestEffects {
if (isArray) {
obj.forEach(o => {
if (isNotEmpty(o._embedded)) {
this.processEmbedded(o._embedded);
this.processEmbedded(o._embedded, requestHref);
}
});
const normalizedObjArr = serializer.deserializeArray(obj);
normalizedObjArr.forEach(t => this.addToObjectCache(t));
normalizedObjArr.forEach(t => this.addToObjectCache(t, requestHref));
return normalizedObjArr.map(t => t.uuid);
}
else {
if (isNotEmpty(obj._embedded)) {
this.processEmbedded(obj._embedded);
this.processEmbedded(obj._embedded, requestHref);
}
const normalizedObj = serializer.deserialize(obj);
this.addToObjectCache(normalizedObj);
this.addToObjectCache(normalizedObj, requestHref);
return [normalizedObj.uuid];
}
@@ -125,10 +125,10 @@ export class RequestEffects {
}
}
protected addToObjectCache(co: CacheableObject): void {
protected addToObjectCache(co: CacheableObject, requestHref: string): void {
if (hasNoValue(co) || hasNoValue(co.uuid)) {
throw new Error('The server returned an invalid object');
}
this.objectCache.add(co, this.EnvConfig.cache.msToLive);
this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref);
}
}

View File

@@ -8,6 +8,9 @@ import { RequestConfigureAction, RequestExecuteAction } from "./request.actions"
import { ResponseCacheService } from "../cache/response-cache.service";
import { ObjectCacheService } from "../cache/object-cache.service";
import { CacheableObject } from "../cache/object-cache.reducer";
import { ResponseCacheEntry } from "../cache/response-cache.reducer";
import { request } from "http";
import { SuccessResponse } from "../cache/response-cache.models";
@Injectable()
export class RequestService {
@@ -35,7 +38,19 @@ export class RequestService {
}
configure<T extends CacheableObject>(request: Request<T>): void {
const isCached = this.objectCache.hasBySelfLink(request.href);
let isCached = this.objectCache.hasBySelfLink(request.href);
if (!isCached && this.responseCache.has(request.href)) {
//if it isn't cached it may be a list endpoint, if so verify
//every object included in the response is still cached
this.responseCache.get(request.href)
.take(1)
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.map((resourceUUIDs: Array<string>) => resourceUUIDs.every(uuid => this.objectCache.has(uuid)))
.subscribe(c => isCached = c);
}
const isPending = this.isPending(request.href);
if (!(isCached || isPending)) {

View File

@@ -35,7 +35,7 @@ export const COMMUNITIES = {
],
"_links": {
"self": {
"href": "http://dspace7.4science.it/dspace-spring-rest/api/core/community/9076bd16-e69a-48d6-9e41-0238cb40d863"
"href": "/communities/6631"
},
"collections": [
{ "href": "/collections/5179" }
@@ -78,7 +78,7 @@ export const COMMUNITIES = {
],
"_links": {
"self": {
"href": "http://dspace7.4science.it/dspace-spring-rest/api/core/community/9076bd16-e69a-48d6-9e41-0238cb40d863"
"href": "/communities/2365"
},
"collections": [
{ "href": "/collections/6547" }