diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts index b2508bc25a..3c8dc9735f 100644 --- a/src/app/core/core.effects.ts +++ b/src/app/core/core.effects.ts @@ -1,6 +1,8 @@ import { EffectsModule } from "@ngrx/effects"; import { CollectionDataEffects } from "./data-services/collection/collection-data.effects"; +import { ItemDataEffects } from "./data-services/item/item-data.effects"; export const coreEffects = [ - EffectsModule.run(CollectionDataEffects) + EffectsModule.run(CollectionDataEffects), + EffectsModule.run(ItemDataEffects) ]; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index e7fb604c34..af0543c530 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -5,6 +5,7 @@ import { isNotEmpty } from "../shared/empty.util"; import { DSpaceRESTv2Service } from "./dspace-rest-v2/dspace-rest-v2.service"; import { CollectionDataService } from "./data-services/collection/collection-data.service"; import { CacheService } from "./data-services/cache/cache.service"; +import { ItemDataService } from "./data-services/item/item-data.service"; const IMPORTS = [ CommonModule, @@ -19,6 +20,7 @@ const EXPORTS = [ const PROVIDERS = [ CollectionDataService, + ItemDataService, DSpaceRESTv2Service, CacheService ]; diff --git a/src/app/core/core.reducers.ts b/src/app/core/core.reducers.ts index 9adea94095..b872b48af7 100644 --- a/src/app/core/core.reducers.ts +++ b/src/app/core/core.reducers.ts @@ -4,14 +4,17 @@ import { collectionDataReducer } from "./data-services/collection/collection-data.reducer"; import { CacheState, cacheReducer } from "./data-services/cache/cache.reducer"; +import { ItemDataState, itemDataReducer } from "./data-services/item/item-data.reducer"; export interface CoreState { collectionData: CollectionDataState, + itemData: ItemDataState, cache: CacheState } export const reducers = { collectionData: collectionDataReducer, + itemData: itemDataReducer, cache: cacheReducer }; diff --git a/src/app/core/data-services/collection/collection-data.effects.ts b/src/app/core/data-services/collection/collection-data.effects.ts index 192b2e48b6..a9bb37c231 100644 --- a/src/app/core/data-services/collection/collection-data.effects.ts +++ b/src/app/core/data-services/collection/collection-data.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { Actions, Effect, toPayload } from "@ngrx/effects"; +import { Actions, Effect } from "@ngrx/effects"; import { Collection } from "../../shared/collection.model"; import { Observable } from "rxjs"; import { diff --git a/src/app/core/data-services/collection/collection-data.service.ts b/src/app/core/data-services/collection/collection-data.service.ts index 2468f8ad09..7f191eb603 100644 --- a/src/app/core/data-services/collection/collection-data.service.ts +++ b/src/app/core/data-services/collection/collection-data.service.ts @@ -15,8 +15,8 @@ export class CollectionDataService { private cache: CacheService ) { } - findAll(scope?: Collection): Observable { - this.store.dispatch(new CollectionFindMultipleRequestAction(scope)); + findAll(scopeID?: string): Observable { + this.store.dispatch(new CollectionFindMultipleRequestAction(scopeID)); //get an observable of the IDs from the collectionData store return this.store.select>('core', 'collectionData', 'findMultiple', 'collectionsIDs') .flatMap((collectionIds: Array) => { diff --git a/src/app/core/data-services/collection/collection-find-multiple.actions.ts b/src/app/core/data-services/collection/collection-find-multiple.actions.ts index a68f7e2478..bdd657dc0f 100644 --- a/src/app/core/data-services/collection/collection-find-multiple.actions.ts +++ b/src/app/core/data-services/collection/collection-find-multiple.actions.ts @@ -1,6 +1,5 @@ import { Action } from "@ngrx/store"; import { type } from "../../../shared/ngrx/type"; -import { Collection } from "../../shared/collection.model"; import { PaginationOptions } from "../../shared/pagination-options.model"; import { SortOptions } from "../../shared/sort-options.model"; @@ -13,18 +12,18 @@ export const CollectionFindMultipleActionTypes = { export class CollectionFindMultipleRequestAction implements Action { type = CollectionFindMultipleActionTypes.FIND_MULTI_REQUEST; payload: { - scope: Collection, + scopeID: string, paginationOptions: PaginationOptions, sortOptions: SortOptions }; constructor( - scope?: Collection, + scopeID?: string, paginationOptions: PaginationOptions = new PaginationOptions(), sortOptions: SortOptions = new SortOptions() ) { this.payload = { - scope, + scopeID, paginationOptions, sortOptions } diff --git a/src/app/core/data-services/collection/collection-find-multiple.reducer.ts b/src/app/core/data-services/collection/collection-find-multiple.reducer.ts index 463155d0f5..2a3cf6776d 100644 --- a/src/app/core/data-services/collection/collection-find-multiple.reducer.ts +++ b/src/app/core/data-services/collection/collection-find-multiple.reducer.ts @@ -1,4 +1,3 @@ -import { Collection } from "../../shared/collection.model"; import { PaginationOptions } from "../../shared/pagination-options.model"; import { SortOptions } from "../../shared/sort-options.model"; import { @@ -7,7 +6,7 @@ import { } from "./collection-find-multiple.actions"; export interface CollectionFindMultipleState { - scope: Collection; + scopeID: string; collectionsIDs: Array; isLoading: boolean; errorMessage: string; @@ -16,7 +15,7 @@ export interface CollectionFindMultipleState { } const initialState: CollectionFindMultipleState = { - scope: undefined, + scopeID: undefined, collectionsIDs: [], isLoading: false, errorMessage: undefined, @@ -29,7 +28,7 @@ export const findMultipleReducer = (state = initialState, action: CollectionFind case CollectionFindMultipleActionTypes.FIND_MULTI_REQUEST: { return Object.assign({}, state, { - scope: action.payload.scope, + scopeID: action.payload.scopeID, collectionsIDs: [], isLoading: true, errorMessage: undefined, diff --git a/src/app/core/data-services/item/item-data.effects.ts b/src/app/core/data-services/item/item-data.effects.ts new file mode 100644 index 0000000000..41554453d3 --- /dev/null +++ b/src/app/core/data-services/item/item-data.effects.ts @@ -0,0 +1,66 @@ +import { Injectable } from "@angular/core"; +import { Actions, Effect } from "@ngrx/effects"; +import { Item } from "../../shared/item.model"; +import { Observable } from "rxjs"; +import { + ItemFindMultipleActionTypes, + ItemFindMultipleSuccessAction, + ItemFindMultipleErrorAction +} from "./item-find-multiple.actions"; +import { + ItemFindSingleActionTypes, + ItemFindByIdSuccessAction, + ItemFindByIdErrorAction +} from "./item-find-single.actions"; +import { DSpaceRESTV2Response } from "../../dspace-rest-v2/dspace-rest-v2-response.model"; +import { DSpaceRESTv2Serializer } from "../../dspace-rest-v2/dspace-rest-v2.serializer"; +import { DSpaceRESTv2Service } from "../../dspace-rest-v2/dspace-rest-v2.service"; +import { CacheService } from "../cache/cache.service"; +import { GlobalConfig } from "../../../../config"; + + +@Injectable() +export class ItemDataEffects { + constructor( + private actions$: Actions, + private restApi: DSpaceRESTv2Service, + private cache: CacheService + ) {} + + // TODO, results of a findall aren't retrieved from cache for now, + // because currently the cache is more of an object store. We need to move + // more towards memoization for things like this. + @Effect() findAll$ = this.actions$ + .ofType(ItemFindMultipleActionTypes.FIND_MULTI_REQUEST) + .switchMap(() => { + return this.restApi.get('/items') + .map((data: DSpaceRESTV2Response) => new DSpaceRESTv2Serializer(Item).deserializeArray(data)) + .do((items: Item[]) => { + items.forEach((item) => { + this.cache.add(item, GlobalConfig.cache.msToLive); + }); + }) + .map((items: Array) => items.map(item => item.id)) + .map((ids: Array) => new ItemFindMultipleSuccessAction(ids)) + .catch((errorMsg: string) => Observable.of(new ItemFindMultipleErrorAction(errorMsg))); + }); + + @Effect() findById$ = this.actions$ + .ofType(ItemFindSingleActionTypes.FIND_BY_ID_REQUEST) + .switchMap(action => { + if (this.cache.has(action.payload)) { + return this.cache.get(action.payload) + .map(item => new ItemFindByIdSuccessAction(item.id)); + } + else { + return this.restApi.get(`/items/${action.payload}`) + .map((data: DSpaceRESTV2Response) => new DSpaceRESTv2Serializer(Item).deserialize(data)) + .do((item: Item) => { + this.cache.add(item, GlobalConfig.cache.msToLive); + }) + .map((item: Item) => new ItemFindByIdSuccessAction(item.id)) + .catch((errorMsg: string) => Observable.of(new ItemFindByIdErrorAction(errorMsg))); + } + }); + +} diff --git a/src/app/core/data-services/item/item-data.reducer.ts b/src/app/core/data-services/item/item-data.reducer.ts new file mode 100644 index 0000000000..20c0d805e5 --- /dev/null +++ b/src/app/core/data-services/item/item-data.reducer.ts @@ -0,0 +1,17 @@ +import { combineReducers } from "@ngrx/store"; +import { ItemFindMultipleState, findMultipleReducer } from "./item-find-multiple.reducer"; +import { ItemFindSingleState, findSingleReducer } from "./item-find-single.reducer"; + +export interface ItemDataState { + findMultiple: ItemFindMultipleState, + findSingle: ItemFindSingleState +} + +const reducers = { + findMultiple: findMultipleReducer, + findSingle: findSingleReducer +}; + +export function itemDataReducer(state: any, action: any) { + return combineReducers(reducers)(state, action); +} diff --git a/src/app/core/data-services/item/item-data.service.ts b/src/app/core/data-services/item/item-data.service.ts new file mode 100644 index 0000000000..a3701d9f7b --- /dev/null +++ b/src/app/core/data-services/item/item-data.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { ItemDataState } from "./item-data.reducer"; +import { Store } from "@ngrx/store"; +import { Item } from "../../shared/item.model"; +import { ItemFindMultipleRequestAction } from "./item-find-multiple.actions"; +import { ItemFindByIdRequestAction } from "./item-find-single.actions"; +import { CacheService } from "../cache/cache.service"; +import 'rxjs/add/observable/forkJoin'; + +@Injectable() +export class ItemDataService { + constructor( + private store: Store, + private cache: CacheService + ) { } + + findAll(scopeID?: string): Observable { + this.store.dispatch(new ItemFindMultipleRequestAction(scopeID)); + //get an observable of the IDs from the itemData store + return this.store.select>('core', 'itemData', 'findMultiple', 'itemsIDs') + .flatMap((itemIds: Array) => { + // use those IDs to fetch the actual item objects from the cache + return this.cache.getList(itemIds); + }); + } + + findById(id: string): Observable { + this.store.dispatch(new ItemFindByIdRequestAction(id)); + return this.cache.get(id); + } + +} diff --git a/src/app/core/data-services/item/item-find-multiple.actions.ts b/src/app/core/data-services/item/item-find-multiple.actions.ts new file mode 100644 index 0000000000..6d90a1f604 --- /dev/null +++ b/src/app/core/data-services/item/item-find-multiple.actions.ts @@ -0,0 +1,54 @@ +import { Action } from "@ngrx/store"; +import { type } from "../../../shared/ngrx/type"; +import { PaginationOptions } from "../../shared/pagination-options.model"; +import { SortOptions } from "../../shared/sort-options.model"; + +export const ItemFindMultipleActionTypes = { + FIND_MULTI_REQUEST: type('dspace/core/data/item/FIND_MULTI_REQUEST'), + FIND_MULTI_SUCCESS: type('dspace/core/data/item/FIND_MULTI_SUCCESS'), + FIND_MULTI_ERROR: type('dspace/core/data/item/FIND_MULTI_ERROR') +}; + +export class ItemFindMultipleRequestAction implements Action { + type = ItemFindMultipleActionTypes.FIND_MULTI_REQUEST; + payload: { + scopeID: string, + paginationOptions: PaginationOptions, + sortOptions: SortOptions + }; + + constructor( + scopeID?: string, + paginationOptions: PaginationOptions = new PaginationOptions(), + sortOptions: SortOptions = new SortOptions() + ) { + this.payload = { + scopeID, + paginationOptions, + sortOptions + } + } +} + +export class ItemFindMultipleSuccessAction implements Action { + type = ItemFindMultipleActionTypes.FIND_MULTI_SUCCESS; + payload: Array; + + constructor(itemIDs: Array) { + this.payload = itemIDs; + } +} + +export class ItemFindMultipleErrorAction implements Action { + type = ItemFindMultipleActionTypes.FIND_MULTI_ERROR; + payload: string; + + constructor(errorMessage: string) { + this.payload = errorMessage; + } +} + +export type ItemFindMultipleAction + = ItemFindMultipleRequestAction + | ItemFindMultipleSuccessAction + | ItemFindMultipleErrorAction; diff --git a/src/app/core/data-services/item/item-find-multiple.reducer.ts b/src/app/core/data-services/item/item-find-multiple.reducer.ts new file mode 100644 index 0000000000..1621a63cd8 --- /dev/null +++ b/src/app/core/data-services/item/item-find-multiple.reducer.ts @@ -0,0 +1,59 @@ +import { PaginationOptions } from "../../shared/pagination-options.model"; +import { SortOptions } from "../../shared/sort-options.model"; +import { + ItemFindMultipleAction, + ItemFindMultipleActionTypes +} from "./item-find-multiple.actions"; + +export interface ItemFindMultipleState { + scopeID: string; + itemsIDs: Array; + isLoading: boolean; + errorMessage: string; + paginationOptions: PaginationOptions; + sortOptions: SortOptions; +} + +const initialState: ItemFindMultipleState = { + scopeID: undefined, + itemsIDs: [], + isLoading: false, + errorMessage: undefined, + paginationOptions: undefined, + sortOptions: undefined +}; + +export const findMultipleReducer = (state = initialState, action: ItemFindMultipleAction): ItemFindMultipleState => { + switch (action.type) { + + case ItemFindMultipleActionTypes.FIND_MULTI_REQUEST: { + return Object.assign({}, state, { + scopeID: action.payload.scopeID, + itemsIDs: [], + isLoading: true, + errorMessage: undefined, + paginationOptions: action.payload.paginationOptions, + sortOptions: action.payload.sortOptions + }); + } + + case ItemFindMultipleActionTypes.FIND_MULTI_SUCCESS: { + return Object.assign({}, state, { + isLoading: false, + itemsIDs: action.payload, + errorMessage: undefined + }); + } + + case ItemFindMultipleActionTypes.FIND_MULTI_ERROR: { + return Object.assign({}, state, { + isLoading: false, + errorMessage: action.payload + }); + } + + default: { + return state; + } + } +}; diff --git a/src/app/core/data-services/item/item-find-single.actions.ts b/src/app/core/data-services/item/item-find-single.actions.ts new file mode 100644 index 0000000000..1e00fdb6f1 --- /dev/null +++ b/src/app/core/data-services/item/item-find-single.actions.ts @@ -0,0 +1,42 @@ +import { Action } from "@ngrx/store"; +import { type } from "../../../shared/ngrx/type"; +import { Item } from "../../shared/item.model"; + +export const ItemFindSingleActionTypes = { + FIND_BY_ID_REQUEST: type('dspace/core/data/item/FIND_BY_ID_REQUEST'), + FIND_BY_ID_SUCCESS: type('dspace/core/data/item/FIND_BY_ID_SUCCESS'), + FIND_BY_ID_ERROR: type('dspace/core/data/item/FIND_BY_ID_ERROR') +}; + +export class ItemFindByIdRequestAction implements Action { + type = ItemFindSingleActionTypes.FIND_BY_ID_REQUEST; + payload: string; + + constructor(id: string) { + this.payload = id; + } +} + +export class ItemFindByIdSuccessAction implements Action { + type = ItemFindSingleActionTypes.FIND_BY_ID_SUCCESS; + payload: string; + + constructor(itemID: string) { + this.payload = itemID; + } +} + +export class ItemFindByIdErrorAction implements Action { + type = ItemFindSingleActionTypes.FIND_BY_ID_ERROR; + payload: string; + + constructor(errorMessage: string) { + this.payload = errorMessage; + } +} + +export type ItemFindSingleAction + = ItemFindByIdRequestAction + | ItemFindByIdSuccessAction + | ItemFindByIdErrorAction; + diff --git a/src/app/core/data-services/item/item-find-single.reducer.ts b/src/app/core/data-services/item/item-find-single.reducer.ts new file mode 100644 index 0000000000..5c11162cb0 --- /dev/null +++ b/src/app/core/data-services/item/item-find-single.reducer.ts @@ -0,0 +1,48 @@ +import { Item } from "../../shared/item.model"; +import { + ItemFindSingleAction, + ItemFindSingleActionTypes +} from "./item-find-single.actions"; + +export interface ItemFindSingleState { + isLoading: boolean; + errorMessage: string; + itemID: string; +} + +const initialState: ItemFindSingleState = { + isLoading: false, + errorMessage: undefined, + itemID: undefined +}; + +export const findSingleReducer = (state = initialState, action: ItemFindSingleAction): ItemFindSingleState => { + switch (action.type) { + + case ItemFindSingleActionTypes.FIND_BY_ID_REQUEST: { + return Object.assign({}, state, { + isLoading: true, + errorMessage: undefined, + itemID: action.payload + }); + } + + case ItemFindSingleActionTypes.FIND_BY_ID_SUCCESS: { + return Object.assign({}, state, { + isLoading: false, + errorMessage: undefined, + }); + } + + case ItemFindSingleActionTypes.FIND_BY_ID_ERROR: { + return Object.assign({}, state, { + isLoading: false, + errorMessage: action.payload + }); + } + + default: { + return state; + } + } +};