mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
fix timestamps on rehydrate
This commit is contained in:
13
src/app/core/cache/object-cache.actions.ts
vendored
13
src/app/core/cache/object-cache.actions.ts
vendored
@@ -4,7 +4,8 @@ import { CacheableObject } from "./object-cache.reducer";
|
|||||||
|
|
||||||
export const ObjectCacheActionTypes = {
|
export const ObjectCacheActionTypes = {
|
||||||
ADD: type('dspace/core/cache/object/ADD'),
|
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 {
|
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
|
export type ObjectCacheAction
|
||||||
= AddToObjectCacheAction
|
= AddToObjectCacheAction
|
||||||
| RemoveFromObjectCacheAction
|
| RemoveFromObjectCacheAction
|
||||||
|
| ResetObjectCacheTimestampsAction;
|
||||||
|
56
src/app/core/cache/object-cache.reducer.spec.ts
vendored
56
src/app/core/cache/object-cache.reducer.spec.ts
vendored
@@ -2,7 +2,7 @@ import * as deepFreeze from "deep-freeze";
|
|||||||
import { objectCacheReducer } from "./object-cache.reducer";
|
import { objectCacheReducer } from "./object-cache.reducer";
|
||||||
import {
|
import {
|
||||||
AddToObjectCacheAction,
|
AddToObjectCacheAction,
|
||||||
RemoveFromObjectCacheAction
|
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
|
||||||
} from "./object-cache.actions";
|
} from "./object-cache.actions";
|
||||||
|
|
||||||
class NullAction extends RemoveFromObjectCacheAction {
|
class NullAction extends RemoveFromObjectCacheAction {
|
||||||
@@ -15,15 +15,24 @@ class NullAction extends RemoveFromObjectCacheAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("objectCacheReducer", () => {
|
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 = {
|
const testState = {
|
||||||
[uuid]: {
|
[uuid1]: {
|
||||||
data: {
|
data: {
|
||||||
uuid: uuid,
|
uuid: uuid1,
|
||||||
foo: "bar"
|
foo: "bar"
|
||||||
},
|
},
|
||||||
timeAdded: new Date().getTime(),
|
timeAdded: new Date().getTime(),
|
||||||
msToLive: 900000
|
msToLive: 900000
|
||||||
|
},
|
||||||
|
[uuid2]: {
|
||||||
|
data: {
|
||||||
|
uuid: uuid2,
|
||||||
|
foo: "baz"
|
||||||
|
},
|
||||||
|
timeAdded: new Date().getTime(),
|
||||||
|
msToLive: 900000
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
deepFreeze(testState);
|
deepFreeze(testState);
|
||||||
@@ -44,31 +53,31 @@ describe("objectCacheReducer", () => {
|
|||||||
|
|
||||||
it("should add the payload to the cache in response to an ADD action", () => {
|
it("should add the payload to the cache in response to an ADD action", () => {
|
||||||
const state = Object.create(null);
|
const state = Object.create(null);
|
||||||
const objectToCache = {uuid: uuid};
|
const objectToCache = {uuid: uuid1};
|
||||||
const timeAdded = new Date().getTime();
|
const timeAdded = new Date().getTime();
|
||||||
const msToLive = 900000;
|
const msToLive = 900000;
|
||||||
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
|
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
|
||||||
const newState = objectCacheReducer(state, action);
|
const newState = objectCacheReducer(state, action);
|
||||||
|
|
||||||
expect(newState[uuid].data).toEqual(objectToCache);
|
expect(newState[uuid1].data).toEqual(objectToCache);
|
||||||
expect(newState[uuid].timeAdded).toEqual(timeAdded);
|
expect(newState[uuid1].timeAdded).toEqual(timeAdded);
|
||||||
expect(newState[uuid].msToLive).toEqual(msToLive);
|
expect(newState[uuid1].msToLive).toEqual(msToLive);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should overwrite an object in the cache in response to an ADD action if it already exists", () => {
|
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 timeAdded = new Date().getTime();
|
||||||
const msToLive = 900000;
|
const msToLive = 900000;
|
||||||
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
|
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
|
||||||
const newState = objectCacheReducer(testState, action);
|
const newState = objectCacheReducer(testState, action);
|
||||||
|
|
||||||
expect(newState[uuid].data['foo']).toBe("baz");
|
expect(newState[uuid1].data['foo']).toBe("baz");
|
||||||
expect(newState[uuid].data['somethingElse']).toBe(true);
|
expect(newState[uuid1].data['somethingElse']).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should perform the ADD action without affecting the previous state", () => {
|
it("should perform the ADD action without affecting the previous state", () => {
|
||||||
const state = Object.create(null);
|
const state = Object.create(null);
|
||||||
const objectToCache = {uuid: uuid};
|
const objectToCache = {uuid: uuid1};
|
||||||
const timeAdded = new Date().getTime();
|
const timeAdded = new Date().getTime();
|
||||||
const msToLive = 900000;
|
const msToLive = 900000;
|
||||||
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
|
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", () => {
|
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);
|
const newState = objectCacheReducer(testState, action);
|
||||||
|
|
||||||
expect(testState[uuid]).not.toBeUndefined();
|
expect(testState[uuid1]).not.toBeUndefined();
|
||||||
expect(newState[uuid]).toBeUndefined();
|
expect(newState[uuid1]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shouldn't do anything in response to the REMOVE action for an object that isn't cached", () => {
|
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", () => {
|
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
|
//testState has already been frozen above
|
||||||
objectCacheReducer(testState, action);
|
objectCacheReducer(testState, action);
|
||||||
});
|
});
|
||||||
|
29
src/app/core/cache/object-cache.reducer.ts
vendored
29
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -1,4 +1,7 @@
|
|||||||
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 { hasValue } from "../../shared/empty.util";
|
||||||
import { CacheEntry } from "./cache-entry";
|
import { CacheEntry } from "./cache-entry";
|
||||||
|
|
||||||
@@ -54,6 +57,10 @@ export const objectCacheReducer = (state = initialState, action: ObjectCacheActi
|
|||||||
return removeFromObjectCache(state, <RemoveFromObjectCacheAction>action)
|
return removeFromObjectCache(state, <RemoveFromObjectCacheAction>action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ObjectCacheActionTypes.RESET_TIMESTAMPS: {
|
||||||
|
return resetObjectCacheTimestamps(state, <ResetObjectCacheTimestampsAction>action)
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -101,3 +108,23 @@ function removeFromObjectCache(state: ObjectCacheState, action: RemoveFromObject
|
|||||||
return state;
|
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;
|
||||||
|
}
|
||||||
|
15
src/app/core/cache/request-cache.actions.ts
vendored
15
src/app/core/cache/request-cache.actions.ts
vendored
@@ -9,7 +9,8 @@ export const RequestCacheActionTypes = {
|
|||||||
FIND_ALL: type('dspace/core/cache/request/FIND_ALL'),
|
FIND_ALL: type('dspace/core/cache/request/FIND_ALL'),
|
||||||
SUCCESS: type('dspace/core/cache/request/SUCCESS'),
|
SUCCESS: type('dspace/core/cache/request/SUCCESS'),
|
||||||
ERROR: type('dspace/core/cache/request/ERROR'),
|
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 {
|
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
|
export type RequestCacheAction
|
||||||
= RequestCacheFindAllAction
|
= RequestCacheFindAllAction
|
||||||
| RequestCacheFindByIDAction
|
| RequestCacheFindByIDAction
|
||||||
| RequestCacheSuccessAction
|
| RequestCacheSuccessAction
|
||||||
| RequestCacheErrorAction
|
| RequestCacheErrorAction
|
||||||
| RequestCacheRemoveAction;
|
| RequestCacheRemoveAction
|
||||||
|
| ResetRequestCacheTimestampsAction;
|
||||||
|
17
src/app/core/cache/request-cache.reducer.ts
vendored
17
src/app/core/cache/request-cache.reducer.ts
vendored
@@ -3,7 +3,7 @@ import { SortOptions } from "../shared/sort-options.model";
|
|||||||
import {
|
import {
|
||||||
RequestCacheAction, RequestCacheActionTypes, RequestCacheFindAllAction,
|
RequestCacheAction, RequestCacheActionTypes, RequestCacheFindAllAction,
|
||||||
RequestCacheSuccessAction, RequestCacheErrorAction, RequestCacheFindByIDAction,
|
RequestCacheSuccessAction, RequestCacheErrorAction, RequestCacheFindByIDAction,
|
||||||
RequestCacheRemoveAction
|
RequestCacheRemoveAction, ResetRequestCacheTimestampsAction
|
||||||
} from "./request-cache.actions";
|
} from "./request-cache.actions";
|
||||||
import { OpaqueToken } from "@angular/core";
|
import { OpaqueToken } from "@angular/core";
|
||||||
import { CacheEntry } from "./cache-entry";
|
import { CacheEntry } from "./cache-entry";
|
||||||
@@ -54,6 +54,10 @@ export const requestCacheReducer = (state = initialState, action: RequestCacheAc
|
|||||||
return removeFromCache(state, <RequestCacheRemoveAction> action);
|
return removeFromCache(state, <RequestCacheRemoveAction> action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case RequestCacheActionTypes.RESET_TIMESTAMPS: {
|
||||||
|
return resetRequestCacheTimestamps(state, <ResetRequestCacheTimestampsAction>action)
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return state;
|
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;
|
||||||
|
}
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
import { EffectsModule } from "@ngrx/effects";
|
import { EffectsModule } from "@ngrx/effects";
|
||||||
import { CollectionDataEffects } from "./data-services/collection-data.effects";
|
import { CollectionDataEffects } from "./data-services/collection-data.effects";
|
||||||
import { ItemDataEffects } from "./data-services/item-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 = [
|
export const coreEffects = [
|
||||||
EffectsModule.run(CollectionDataEffects),
|
EffectsModule.run(CollectionDataEffects),
|
||||||
EffectsModule.run(ItemDataEffects)
|
EffectsModule.run(ItemDataEffects),
|
||||||
|
EffectsModule.run(RequestCacheEffects),
|
||||||
|
EffectsModule.run(ObjectCacheEffects),
|
||||||
];
|
];
|
||||||
|
28
src/app/core/data-services/object-cache.effects.ts
Normal file
28
src/app/core/data-services/object-cache.effects.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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()));
|
||||||
|
|
||||||
|
}
|
36
src/app/core/data-services/request-cache.effects.ts
Normal file
36
src/app/core/data-services/request-cache.effects.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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()));
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user