Skip to content
Snippets Groups Projects
Commit a535b86e authored by Art Lowel's avatar Art Lowel
Browse files

fix timestamps on rehydrate

parent 0e516bea
Branches
Tags
No related merge requests found
......@@ -4,7 +4,8 @@ import { CacheableObject } from "./object-cache.reducer";
export const ObjectCacheActionTypes = {
ADD: type('dspace/core/cache/object/ADD'),
REMOVE: type('dspace/core/cache/object/REMOVE')
REMOVE: type('dspace/core/cache/object/REMOVE'),
RESET_TIMESTAMPS: type('dspace/core/cache/object/RESET_TIMESTAMPS')
};
export class AddToObjectCacheAction implements Action {
......@@ -29,6 +30,16 @@ export class RemoveFromObjectCacheAction implements Action {
}
}
export class ResetObjectCacheTimestampsAction implements Action {
type = ObjectCacheActionTypes.RESET_TIMESTAMPS;
payload: number;
constructor(newTimestamp: number) {
this.payload = newTimestamp;
}
}
export type ObjectCacheAction
= AddToObjectCacheAction
| RemoveFromObjectCacheAction
| ResetObjectCacheTimestampsAction;
......@@ -2,7 +2,7 @@ import * as deepFreeze from "deep-freeze";
import { objectCacheReducer } from "./object-cache.reducer";
import {
AddToObjectCacheAction,
RemoveFromObjectCacheAction
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
} from "./object-cache.actions";
class NullAction extends RemoveFromObjectCacheAction {
......@@ -15,15 +15,24 @@ class NullAction extends RemoveFromObjectCacheAction {
}
describe("objectCacheReducer", () => {
const uuid = '1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
const uuid1 = '1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
const uuid2 = '28b04544-1766-4e82-9728-c4e93544ecd3';
const testState = {
[uuid]: {
[uuid1]: {
data: {
uuid: uuid,
uuid: uuid1,
foo: "bar"
},
timeAdded: new Date().getTime(),
msToLive: 900000
},
[uuid2]: {
data: {
uuid: uuid2,
foo: "baz"
},
timeAdded: new Date().getTime(),
msToLive: 900000
}
};
deepFreeze(testState);
......@@ -44,31 +53,31 @@ describe("objectCacheReducer", () => {
it("should add the payload to the cache in response to an ADD action", () => {
const state = Object.create(null);
const objectToCache = {uuid: uuid};
const objectToCache = {uuid: uuid1};
const timeAdded = new Date().getTime();
const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
const newState = objectCacheReducer(state, action);
expect(newState[uuid].data).toEqual(objectToCache);
expect(newState[uuid].timeAdded).toEqual(timeAdded);
expect(newState[uuid].msToLive).toEqual(msToLive);
expect(newState[uuid1].data).toEqual(objectToCache);
expect(newState[uuid1].timeAdded).toEqual(timeAdded);
expect(newState[uuid1].msToLive).toEqual(msToLive);
});
it("should overwrite an object in the cache in response to an ADD action if it already exists", () => {
const objectToCache = {uuid: uuid, foo: "baz", somethingElse: true};
const objectToCache = {uuid: uuid1, foo: "baz", somethingElse: true};
const timeAdded = new Date().getTime();
const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
const newState = objectCacheReducer(testState, action);
expect(newState[uuid].data['foo']).toBe("baz");
expect(newState[uuid].data['somethingElse']).toBe(true);
expect(newState[uuid1].data['foo']).toBe("baz");
expect(newState[uuid1].data['somethingElse']).toBe(true);
});
it("should perform the ADD action without affecting the previous state", () => {
const state = Object.create(null);
const objectToCache = {uuid: uuid};
const objectToCache = {uuid: uuid1};
const timeAdded = new Date().getTime();
const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
......@@ -78,11 +87,11 @@ describe("objectCacheReducer", () => {
});
it("should remove the specified object from the cache in response to the REMOVE action", () => {
const action = new RemoveFromObjectCacheAction(uuid);
const action = new RemoveFromObjectCacheAction(uuid1);
const newState = objectCacheReducer(testState, action);
expect(testState[uuid]).not.toBeUndefined();
expect(newState[uuid]).toBeUndefined();
expect(testState[uuid1]).not.toBeUndefined();
expect(newState[uuid1]).toBeUndefined();
});
it("shouldn't do anything in response to the REMOVE action for an object that isn't cached", () => {
......@@ -93,7 +102,22 @@ describe("objectCacheReducer", () => {
});
it("should perform the REMOVE action without affecting the previous state", () => {
const action = new RemoveFromObjectCacheAction(uuid);
const action = new RemoveFromObjectCacheAction(uuid1);
//testState has already been frozen above
objectCacheReducer(testState, action);
});
it("should set the timestamp of all objects in the cache in response to a RESET_TIMESTAMPS action", () => {
const newTimestamp = new Date().getTime();
const action = new ResetObjectCacheTimestampsAction(newTimestamp);
const newState = objectCacheReducer(testState, action);
Object.keys(newState).forEach((key) => {
expect(newState[key].timeAdded).toEqual(newTimestamp);
});
});
it("should perform the RESET_TIMESTAMPS action without affecting the previous state", () => {
const action = new ResetObjectCacheTimestampsAction(new Date().getTime());
//testState has already been frozen above
objectCacheReducer(testState, action);
});
......
import { ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
import {
ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction,
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
} from "./object-cache.actions";
import { hasValue } from "../../shared/empty.util";
import { CacheEntry } from "./cache-entry";
......@@ -54,6 +57,10 @@ export const objectCacheReducer = (state = initialState, action: ObjectCacheActi
return removeFromObjectCache(state, <RemoveFromObjectCacheAction>action)
}
case ObjectCacheActionTypes.RESET_TIMESTAMPS: {
return resetObjectCacheTimestamps(state, <ResetObjectCacheTimestampsAction>action)
}
default: {
return state;
}
......@@ -101,3 +108,23 @@ function removeFromObjectCache(state: ObjectCacheState, action: RemoveFromObject
return state;
}
}
/**
* Set the timeAdded timestamp of every cached object to the specified value
*
* @param state
* the current state
* @param action
* a ResetObjectCacheTimestampsAction
* @return ObjectCacheState
* the new state, with all timeAdded timestamps set to the specified value
*/
function resetObjectCacheTimestamps(state: ObjectCacheState, action: ResetObjectCacheTimestampsAction): ObjectCacheState {
let newState = Object.create(null);
Object.keys(state).forEach(key => {
newState[key] = Object.assign({}, state[key], {
timeAdded: action.payload
});
});
return newState;
}
......@@ -9,7 +9,8 @@ export const RequestCacheActionTypes = {
FIND_ALL: type('dspace/core/cache/request/FIND_ALL'),
SUCCESS: type('dspace/core/cache/request/SUCCESS'),
ERROR: type('dspace/core/cache/request/ERROR'),
REMOVE: type('dspace/core/cache/request/REMOVE')
REMOVE: type('dspace/core/cache/request/REMOVE'),
RESET_TIMESTAMPS: type('dspace/core/cache/request/RESET_TIMESTAMPS')
};
export class RequestCacheFindAllAction implements Action {
......@@ -103,9 +104,19 @@ export class RequestCacheRemoveAction implements Action {
}
}
export class ResetRequestCacheTimestampsAction implements Action {
type = RequestCacheActionTypes.RESET_TIMESTAMPS;
payload: number;
constructor(newTimestamp: number) {
this.payload = newTimestamp;
}
}
export type RequestCacheAction
= RequestCacheFindAllAction
| RequestCacheFindByIDAction
| RequestCacheSuccessAction
| RequestCacheErrorAction
| RequestCacheRemoveAction;
| RequestCacheRemoveAction
| ResetRequestCacheTimestampsAction;
......@@ -3,7 +3,7 @@ import { SortOptions } from "../shared/sort-options.model";
import {
RequestCacheAction, RequestCacheActionTypes, RequestCacheFindAllAction,
RequestCacheSuccessAction, RequestCacheErrorAction, RequestCacheFindByIDAction,
RequestCacheRemoveAction
RequestCacheRemoveAction, ResetRequestCacheTimestampsAction
} from "./request-cache.actions";
import { OpaqueToken } from "@angular/core";
import { CacheEntry } from "./cache-entry";
......@@ -54,6 +54,10 @@ export const requestCacheReducer = (state = initialState, action: RequestCacheAc
return removeFromCache(state, <RequestCacheRemoveAction> action);
}
case RequestCacheActionTypes.RESET_TIMESTAMPS: {
return resetRequestCacheTimestamps(state, <ResetRequestCacheTimestampsAction>action)
}
default: {
return state;
}
......@@ -121,5 +125,12 @@ function removeFromCache(state: RequestCacheState, action: RequestCacheRemoveAct
}
}
function resetRequestCacheTimestamps(state: RequestCacheState, action: ResetRequestCacheTimestampsAction): RequestCacheState {
let newState = Object.create(null);
Object.keys(state).forEach(key => {
newState[key] = Object.assign({}, state[key], {
timeAdded: action.payload
});
});
return newState;
}
import { EffectsModule } from "@ngrx/effects";
import { CollectionDataEffects } from "./data-services/collection-data.effects";
import { ItemDataEffects } from "./data-services/item-data.effects";
import { ObjectCacheEffects } from "./data-services/object-cache.effects";
import { RequestCacheEffects } from "./data-services/request-cache.effects";
export const coreEffects = [
EffectsModule.run(CollectionDataEffects),
EffectsModule.run(ItemDataEffects)
EffectsModule.run(ItemDataEffects),
EffectsModule.run(RequestCacheEffects),
EffectsModule.run(ObjectCacheEffects),
];
import { Injectable } from "@angular/core";
import { Actions, Effect } from "@ngrx/effects";
import { StoreActionTypes } from "../../store.actions";
import { ResetObjectCacheTimestampsAction } from "../cache/object-cache.actions";
import { Store } from "@ngrx/store";
import { ObjectCacheState } from "../cache/object-cache.reducer";
@Injectable()
export class ObjectCacheEffects {
constructor(
private actions$: Actions,
private store: Store<ObjectCacheState>
) { }
/**
* 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$
.ofType(StoreActionTypes.REHYDRATE)
.map(() => new ResetObjectCacheTimestampsAction(new Date().getTime()));
}
import { Injectable } from "@angular/core";
import { Actions, Effect } from "@ngrx/effects";
import { ResetRequestCacheTimestampsAction } from "../cache/request-cache.actions";
import { Store } from "@ngrx/store";
import { RequestCacheState } from "../cache/request-cache.reducer";
import { ObjectCacheActionTypes } from "../cache/object-cache.actions";
@Injectable()
export class RequestCacheEffects {
constructor(
private actions$: Actions,
private store: Store<RequestCacheState>
) { }
/**
* 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
*
* This effect should listen for StoreActionTypes.REHYDRATE,
* but can't because you can only have one effect listen to
* an action atm. Github issue:
* https://github.com/ngrx/effects/issues/87
*
* It's listening for ObjectCacheActionTypes.RESET_TIMESTAMPS
* instead, until there's a solution.
*/
@Effect() fixTimestampsOnRehydrate = this.actions$
.ofType(ObjectCacheActionTypes.RESET_TIMESTAMPS)
.map(() => new ResetRequestCacheTimestampsAction(new Date().getTime()));
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment