mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
fixed lint errors
This commit is contained in:
@@ -9,21 +9,21 @@ import {
|
||||
import {
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
DebugElement
|
||||
} from "@angular/core";
|
||||
} from '@angular/core';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateModule, TranslateLoader } from "@ngx-translate/core";
|
||||
import { Store, StoreModule } from "@ngrx/store";
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
|
||||
// Load the implementations that should be tested
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
import { HostWindowState } from "./shared/host-window.reducer";
|
||||
import { HostWindowResizeAction } from "./shared/host-window.actions";
|
||||
import { MockTranslateLoader } from "./shared/testing/mock-translate-loader";
|
||||
import { HostWindowState } from './shared/host-window.reducer';
|
||||
import { HostWindowResizeAction } from './shared/host-window.actions';
|
||||
import { MockTranslateLoader } from './shared/testing/mock-translate-loader';
|
||||
|
||||
import { BrowserCookiesModule } from '../modules/cookies/browser-cookies.module';
|
||||
import { BrowserDataLoaderModule } from '../modules/data-loader/browser-data-loader.module';
|
||||
@@ -31,7 +31,7 @@ import { BrowserTransferStateModule } from '../modules/transfer-state/browser-tr
|
||||
import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module';
|
||||
|
||||
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
|
||||
import { NativeWindowRef, NativeWindowService } from "./shared/window.service";
|
||||
import { NativeWindowRef, NativeWindowService } from './shared/window.service';
|
||||
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
@@ -73,7 +73,7 @@ describe('App component', () => {
|
||||
|
||||
comp = fixture.componentInstance; // component test instance
|
||||
|
||||
// query for the <div class="outer-wrapper"> by CSS element selector
|
||||
// query for the <div class='outer-wrapper'> by CSS element selector
|
||||
de = fixture.debugElement.query(By.css('div.outer-wrapper'));
|
||||
el = de.nativeElement;
|
||||
});
|
||||
@@ -83,7 +83,7 @@ describe('App component', () => {
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
describe("when the window is resized", () => {
|
||||
describe('when the window is resized', () => {
|
||||
let width: number;
|
||||
let height: number;
|
||||
let store: Store<HostWindowState>;
|
||||
@@ -97,7 +97,7 @@ describe('App component', () => {
|
||||
height = window.innerHeight;
|
||||
});
|
||||
|
||||
it("should dispatch a HostWindowResizeAction with the width and height of the window as its payload", () => {
|
||||
it('should dispatch a HostWindowResizeAction with the width and height of the window as its payload', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new HostWindowResizeAction(width, height));
|
||||
});
|
||||
|
||||
|
@@ -5,19 +5,19 @@ import {
|
||||
ViewEncapsulation,
|
||||
OnInit,
|
||||
HostListener
|
||||
} from "@angular/core";
|
||||
} from '@angular/core';
|
||||
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Store } from "@ngrx/store";
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { TransferState } from '../modules/transfer-state/transfer-state';
|
||||
|
||||
import { HostWindowState } from "./shared/host-window.reducer";
|
||||
import { HostWindowState } from './shared/host-window.reducer';
|
||||
|
||||
import { HostWindowResizeAction } from "./shared/host-window.actions";
|
||||
import { HostWindowResizeAction } from './shared/host-window.actions';
|
||||
|
||||
import { NativeWindowRef, NativeWindowService } from "./shared/window.service";
|
||||
import { NativeWindowRef, NativeWindowService } from './shared/window.service';
|
||||
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../config';
|
||||
|
||||
@@ -54,8 +54,8 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const env: string = this.config.production ? "Production" : "Development";
|
||||
const color: string = this.config.production ? "red" : "green";
|
||||
const env: string = this.config.production ? 'Production' : 'Development';
|
||||
const color: string = this.config.production ? 'red' : 'green';
|
||||
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
||||
}
|
||||
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import { EffectsModule } from "@ngrx/effects";
|
||||
import { HeaderEffects } from "./header/header.effects";
|
||||
import { StoreEffects } from "./store.effects";
|
||||
import { coreEffects } from "./core/core.effects";
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { HeaderEffects } from './header/header.effects';
|
||||
import { StoreEffects } from './store.effects';
|
||||
import { coreEffects } from './core/core.effects';
|
||||
|
||||
export const effects = [
|
||||
...coreEffects, //TODO should probably be imported in coreModule
|
||||
...coreEffects, // TODO: should probably be imported in coreModule
|
||||
EffectsModule.run(StoreEffects),
|
||||
EffectsModule.run(HeaderEffects)
|
||||
];
|
||||
|
@@ -2,9 +2,9 @@ import { NgModule } from '@angular/core';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { StoreModule, Store } from "@ngrx/store";
|
||||
import { RouterStoreModule } from "@ngrx/router-store";
|
||||
import { StoreDevtoolsModule } from "@ngrx/store-devtools";
|
||||
import { StoreModule, Store } from '@ngrx/store';
|
||||
import { RouterStoreModule } from '@ngrx/router-store';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
|
||||
import { rootReducer, AppState } from './app.reducer';
|
||||
import { effects } from './app.effects';
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { combineReducers, ActionReducer } from "@ngrx/store";
|
||||
import { routerReducer, RouterState } from "@ngrx/router-store";
|
||||
import { headerReducer, HeaderState } from './header/header.reducer';
|
||||
import { hostWindowReducer, HostWindowState } from "./shared/host-window.reducer";
|
||||
import { CoreState, coreReducer } from "./core/core.reducers";
|
||||
import { combineReducers, ActionReducer } from '@ngrx/store';
|
||||
import { routerReducer, RouterState } from '@ngrx/router-store';
|
||||
import { storeFreeze } from 'ngrx-store-freeze';
|
||||
import { compose } from "@ngrx/core";
|
||||
import { StoreActionTypes } from "./store.actions";
|
||||
import { compose } from '@ngrx/core';
|
||||
|
||||
import { headerReducer, HeaderState } from './header/header.reducer';
|
||||
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
||||
import { CoreState, coreReducer } from './core/core.reducers';
|
||||
|
||||
import { StoreActionTypes } from './store.actions';
|
||||
|
||||
import { ENV_CONFIG } from '../config';
|
||||
|
||||
|
@@ -4,17 +4,18 @@ import {
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import { Collection } from "../core/shared/collection.model";
|
||||
import { Bitstream } from "../core/shared/bitstream.model";
|
||||
import { RemoteData } from "../core/data/remote-data";
|
||||
import { CollectionDataService } from "../core/data/collection-data.service";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { ItemDataService } from "../core/data/item-data.service";
|
||||
import { Item } from "../core/shared/item.model";
|
||||
import { SortOptions, SortDirection } from "../core/cache/models/sort-options.model";
|
||||
import { PaginationComponentOptions } from "../shared/pagination/pagination-component-options.model";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import { hasValue } from "../shared/empty.util";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Collection } from '../core/shared/collection.model';
|
||||
import { Bitstream } from '../core/shared/bitstream.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
import { SortOptions, SortDirection } from '../core/cache/models/sort-options.model';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page',
|
||||
@@ -37,19 +38,18 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
private ref: ChangeDetectorRef,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
this.universalInit();
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subs.push(this.route.params.map((params: Params) => params['id'])
|
||||
this.subs.push(this.route.params.map((params: Params) => params.id)
|
||||
.subscribe((id: string) => {
|
||||
this.collectionId = id;
|
||||
this.collectionData = this.collectionDataService.findById(this.collectionId);
|
||||
this.subs.push(this.collectionData.payload
|
||||
.subscribe(collection => this.logoData = collection.logo));
|
||||
this.subs.push(this.collectionData.payload.subscribe((collection) => this.logoData = collection.logo));
|
||||
|
||||
this.config = new PaginationComponentOptions();
|
||||
this.config.id = "collection-browse";
|
||||
this.config.id = 'collection-browse';
|
||||
this.config.pageSizeOptions = [4];
|
||||
this.config.pageSize = 4;
|
||||
this.sortConfig = new SortOptions();
|
||||
@@ -60,12 +60,7 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter(sub => hasValue(sub))
|
||||
.forEach(sub => sub.unsubscribe());
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
onPageChange(currentPage: number): void {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
import { Community } from "../core/shared/community.model";
|
||||
import { Bitstream } from "../core/shared/bitstream.model";
|
||||
import { RemoteData } from "../core/data/remote-data";
|
||||
import { CommunityDataService } from "../core/data/community-data.service";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { hasValue } from "../shared/empty.util";
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Community } from '../core/shared/community.model';
|
||||
import { Bitstream } from '../core/shared/bitstream.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page',
|
||||
@@ -22,23 +23,21 @@ export class CommunityPageComponent implements OnInit, OnDestroy {
|
||||
private communityDataService: CommunityDataService,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
this.universalInit();
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.params.subscribe((params: Params) => {
|
||||
this.communityData = this.communityDataService.findById(params['id']);
|
||||
this.communityData = this.communityDataService.findById(params.id);
|
||||
this.subs.push(this.communityData.payload
|
||||
.subscribe(community => this.logoData = community.logo));
|
||||
.subscribe((community) => this.logoData = community.logo));
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter(sub => hasValue(sub))
|
||||
.forEach(sub => sub.unsubscribe());
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { CommunityPageComponent } from './community-page.component';
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CollectionDataService } from "../../core/data/collection-data.service";
|
||||
import { RemoteData } from "../../core/data/remote-data";
|
||||
import { Collection } from "../../core/shared/collection.model";
|
||||
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page-sub-collection-list',
|
||||
@@ -12,13 +12,7 @@ import { Collection } from "../../core/shared/collection.model";
|
||||
export class CommunityPageSubCollectionListComponent implements OnInit {
|
||||
subCollections: RemoteData<Collection[]>;
|
||||
|
||||
constructor(
|
||||
private cds: CollectionDataService
|
||||
) {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
constructor(private cds: CollectionDataService) {
|
||||
|
||||
}
|
||||
|
||||
|
27
src/app/core/cache/builders/build-decorators.ts
vendored
27
src/app/core/cache/builders/build-decorators.ts
vendored
@@ -1,28 +1,29 @@
|
||||
import "reflect-metadata";
|
||||
import { GenericConstructor } from "../../shared/generic-constructor";
|
||||
import { CacheableObject } from "../object-cache.reducer";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import 'reflect-metadata';
|
||||
|
||||
const mapsToMetadataKey = Symbol("mapsTo");
|
||||
const relationshipKey = Symbol("relationship");
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { CacheableObject } from '../object-cache.reducer';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
const mapsToMetadataKey = Symbol('mapsTo');
|
||||
const relationshipKey = Symbol('relationship');
|
||||
|
||||
const relationshipMap = new Map();
|
||||
|
||||
export function mapsTo(value: GenericConstructor<CacheableObject>) {
|
||||
return Reflect.metadata(mapsToMetadataKey, value);
|
||||
};
|
||||
}
|
||||
|
||||
export function getMapsTo(target: any) {
|
||||
return Reflect.getOwnMetadata(mapsToMetadataKey, target);
|
||||
};
|
||||
}
|
||||
|
||||
export function relationship(value: ResourceType, isList: boolean = false): any {
|
||||
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
return function r(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (!target || !propertyKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let metaDataList: Array<string> = relationshipMap.get(target.constructor) || [];
|
||||
const metaDataList: string[] = relationshipMap.get(target.constructor) || [];
|
||||
if (metaDataList.indexOf(propertyKey) === -1) {
|
||||
metaDataList.push(propertyKey);
|
||||
}
|
||||
@@ -30,12 +31,12 @@ export function relationship(value: ResourceType, isList: boolean = false): any
|
||||
|
||||
return Reflect.metadata(relationshipKey, { resourceType: value, isList }).apply(this, arguments);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function getRelationMetadata(target: any, propertyKey: string) {
|
||||
return Reflect.getMetadata(relationshipKey, target, propertyKey);
|
||||
};
|
||||
}
|
||||
|
||||
export function getRelationships(target: any) {
|
||||
return relationshipMap.get(target);
|
||||
};
|
||||
}
|
||||
|
@@ -1,20 +1,21 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { CacheableObject } from "../object-cache.reducer";
|
||||
import { ObjectCacheService } from "../object-cache.service";
|
||||
import { RequestService } from "../../data/request.service";
|
||||
import { ResponseCacheService } from "../response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { CoreState } from "../../core.reducers";
|
||||
import { RequestEntry } from "../../data/request.reducer";
|
||||
import { hasValue, isNotEmpty } from "../../../shared/empty.util";
|
||||
import { ResponseCacheEntry } from "../response-cache.reducer";
|
||||
import { ErrorResponse, SuccessResponse } from "../response-cache.models";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import { RemoteData } from "../../data/remote-data";
|
||||
import { GenericConstructor } from "../../shared/generic-constructor";
|
||||
import { getMapsTo, getRelationMetadata, getRelationships } from "./build-decorators";
|
||||
import { NormalizedObjectFactory } from "../models/normalized-object-factory";
|
||||
import { Request } from "../../data/request.models";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { CacheableObject } from '../object-cache.reducer';
|
||||
import { ObjectCacheService } from '../object-cache.service';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { ResponseCacheService } from '../response-cache.service';
|
||||
import { CoreState } from '../../core.reducers';
|
||||
import { RequestEntry } from '../../data/request.reducer';
|
||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { ResponseCacheEntry } from '../response-cache.reducer';
|
||||
import { ErrorResponse, SuccessResponse } from '../response-cache.models';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators';
|
||||
import { NormalizedObjectFactory } from '../models/normalized-object-factory';
|
||||
import { Request } from '../../data/request.models';
|
||||
|
||||
@Injectable()
|
||||
export class RemoteDataBuildService {
|
||||
@@ -33,14 +34,14 @@ export class RemoteDataBuildService {
|
||||
const requestHrefObs = this.objectCache.getRequestHrefBySelfLink(href);
|
||||
|
||||
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))
|
||||
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 responseCacheObs = Observable.race(
|
||||
this.responseCache.get(href).filter(entry => hasValue(entry)),
|
||||
requestHrefObs.flatMap(requestHref => this.responseCache.get(requestHref)).filter(entry => hasValue(entry))
|
||||
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();
|
||||
@@ -52,17 +53,19 @@ export class RemoteDataBuildService {
|
||||
|
||||
const errorMessage = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<ErrorResponse>entry.response).errorMessage)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as ErrorResponse).errorMessage)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const statusCode = responseCacheObs
|
||||
.map((entry: ResponseCacheEntry) => entry.response.statusCode)
|
||||
.distinctUntilChanged();
|
||||
|
||||
/* tslint:disable:no-string-literal */
|
||||
const pageInfo = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => hasValue(entry.response) && hasValue(entry.response['pageInfo']))
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse>entry.response).pageInfo)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).pageInfo)
|
||||
.distinctUntilChanged();
|
||||
/* tslint:enable:no-string-literal */
|
||||
|
||||
// always use self link if that is cached, only if it isn't, get it via the response.
|
||||
const payload =
|
||||
@@ -70,12 +73,11 @@ export class RemoteDataBuildService {
|
||||
this.objectCache.getBySelfLink<TNormalized>(href, normalizedType).startWith(undefined),
|
||||
responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse>entry.response).resourceUUIDs)
|
||||
.flatMap((resourceUUIDs: Array<string>) => {
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceUUIDs)
|
||||
.flatMap((resourceUUIDs: string[]) => {
|
||||
if (isNotEmpty(resourceUUIDs)) {
|
||||
return this.objectCache.get(resourceUUIDs[0], normalizedType);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return Observable.of(undefined);
|
||||
}
|
||||
})
|
||||
@@ -84,17 +86,15 @@ export class RemoteDataBuildService {
|
||||
(fromSelfLink, fromResponse) => {
|
||||
if (hasValue(fromSelfLink)) {
|
||||
return fromSelfLink;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return fromResponse;
|
||||
}
|
||||
}
|
||||
).filter(normalized => hasValue(normalized))
|
||||
).filter((normalized) => hasValue(normalized))
|
||||
.map((normalized: TNormalized) => {
|
||||
return this.build<TNormalized, TDomain>(normalized);
|
||||
});
|
||||
|
||||
|
||||
return new RemoteData(
|
||||
href,
|
||||
requestPending,
|
||||
@@ -112,8 +112,8 @@ export class RemoteDataBuildService {
|
||||
normalizedType: GenericConstructor<TNormalized>
|
||||
): RemoteData<TDomain[]> {
|
||||
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href)
|
||||
.filter(entry => hasValue(entry));
|
||||
const responseCacheObs = this.responseCache.get(href).filter(entry => hasValue(entry));
|
||||
.filter((entry) => hasValue(entry));
|
||||
const responseCacheObs = this.responseCache.get(href).filter((entry) => hasValue(entry));
|
||||
|
||||
const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged();
|
||||
|
||||
@@ -124,22 +124,24 @@ export class RemoteDataBuildService {
|
||||
|
||||
const errorMessage = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<ErrorResponse>entry.response).errorMessage)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as ErrorResponse).errorMessage)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const statusCode = responseCacheObs
|
||||
.map((entry: ResponseCacheEntry) => entry.response.statusCode)
|
||||
.distinctUntilChanged();
|
||||
|
||||
/* tslint:disable:no-string-literal */
|
||||
const pageInfo = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => hasValue(entry.response) && hasValue(entry.response['pageInfo']))
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse>entry.response).pageInfo)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).pageInfo)
|
||||
.distinctUntilChanged();
|
||||
/* tslint:enable:no-string-literal */
|
||||
|
||||
const payload = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse>entry.response).resourceUUIDs)
|
||||
.flatMap((resourceUUIDs: Array<string>) => {
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceUUIDs)
|
||||
.flatMap((resourceUUIDs: string[]) => {
|
||||
return this.objectCache.getList(resourceUUIDs, normalizedType)
|
||||
.map((normList: TNormalized[]) => {
|
||||
return normList.map((normalized: TNormalized) => {
|
||||
@@ -161,9 +163,8 @@ export class RemoteDataBuildService {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
build<TNormalized extends CacheableObject, TDomain>(normalized: TNormalized): TDomain {
|
||||
let links: any = {};
|
||||
const links: any = {};
|
||||
|
||||
const relationships = getRelationships(normalized.constructor) || [];
|
||||
|
||||
@@ -180,19 +181,17 @@ export class RemoteDataBuildService {
|
||||
});
|
||||
}, 0);
|
||||
|
||||
let rdArr = [];
|
||||
const rdArr = [];
|
||||
normalized[relationship].forEach((href: string) => {
|
||||
rdArr.push(this.buildSingle(href, resourceConstructor));
|
||||
});
|
||||
|
||||
if (rdArr.length === 1) {
|
||||
links[relationship] = rdArr[0];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
links[relationship] = this.aggregate(rdArr);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// without the setTimeout, the actions inside requestService.configure
|
||||
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
|
||||
setTimeout(() => {
|
||||
@@ -215,51 +214,49 @@ export class RemoteDataBuildService {
|
||||
return Object.assign(new domainModel(), normalized, links);
|
||||
}
|
||||
|
||||
aggregate<T>(input: RemoteData<T>[]): RemoteData<T[]> {
|
||||
aggregate<T>(input: Array<RemoteData<T>>): RemoteData<T[]> {
|
||||
const requestPending = Observable.combineLatest(
|
||||
...input.map(rd => rd.isRequestPending),
|
||||
).map((...pendingArray) => pendingArray.every(e => e === true))
|
||||
...input.map((rd) => rd.isRequestPending),
|
||||
).map((...pendingArray) => pendingArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const responsePending = Observable.combineLatest(
|
||||
...input.map(rd => rd.isResponsePending),
|
||||
).map((...pendingArray) => pendingArray.every(e => e === true))
|
||||
...input.map((rd) => rd.isResponsePending),
|
||||
).map((...pendingArray) => pendingArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const isSuccessFul = Observable.combineLatest(
|
||||
...input.map(rd => rd.hasSucceeded),
|
||||
).map((...successArray) => successArray.every(e => e === true))
|
||||
...input.map((rd) => rd.hasSucceeded),
|
||||
).map((...successArray) => successArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
|
||||
const errorMessage = Observable.combineLatest(
|
||||
...input.map(rd => rd.errorMessage),
|
||||
...input.map((rd) => rd.errorMessage),
|
||||
).map((...errors) => errors
|
||||
.map((e, idx) => {
|
||||
if (hasValue(e)) {
|
||||
return `[${idx}]: ${e}`;
|
||||
}
|
||||
})
|
||||
.filter(e => hasValue(e))
|
||||
.join(", ")
|
||||
.filter((e) => hasValue(e))
|
||||
.join(', ')
|
||||
);
|
||||
|
||||
const statusCode = Observable.combineLatest(
|
||||
...input.map(rd => rd.statusCode),
|
||||
...input.map((rd) => rd.statusCode),
|
||||
).map((...statusCodes) => statusCodes
|
||||
.map((code, idx) => {
|
||||
if (hasValue(code)) {
|
||||
return `[${idx}]: ${code}`;
|
||||
}
|
||||
})
|
||||
.filter(c => hasValue(c))
|
||||
.join(", ")
|
||||
.filter((c) => hasValue(c))
|
||||
.join(', ')
|
||||
);
|
||||
|
||||
const pageInfo = Observable.of(undefined);
|
||||
|
||||
const payload = <Observable<T[]>>Observable.combineLatest(
|
||||
...input.map(rd => rd.payload)
|
||||
);
|
||||
const payload = Observable.combineLatest(...input.map((rd) => rd.payload)) as Observable<T[]>;
|
||||
|
||||
return new RemoteData(
|
||||
// This is an aggregated object, it doesn't necessarily correspond
|
||||
@@ -275,4 +272,5 @@ export class RemoteDataBuildService {
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
7
src/app/core/cache/cache.reducers.ts
vendored
7
src/app/core/cache/cache.reducers.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { ResponseCacheState, responseCacheReducer } from "./response-cache.reducer";
|
||||
import { ObjectCacheState, objectCacheReducer } from "./object-cache.reducer";
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
|
||||
import { ResponseCacheState, responseCacheReducer } from './response-cache.reducer';
|
||||
import { ObjectCacheState, objectCacheReducer } from './object-cache.reducer';
|
||||
|
||||
export interface CacheState {
|
||||
response: ResponseCacheState,
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { NormalizedObject } from "./normalized-object.model";
|
||||
import { inheritSerialization } from "cerialize";
|
||||
import { inheritSerialization } from 'cerialize';
|
||||
|
||||
import { NormalizedObject } from './normalized-object.model';
|
||||
|
||||
@inheritSerialization(NormalizedObject)
|
||||
export class NormalizedBitstreamFormat extends NormalizedObject {
|
||||
//TODO this class was created as a placeholder when we connected to the live rest api
|
||||
// TODO: this class was created as a placeholder when we connected to the live rest api
|
||||
|
||||
get uuid(): string {
|
||||
return this.self;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { inheritSerialization, autoserialize } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Bitstream } from "../../shared/bitstream.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { inheritSerialization, autoserialize } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Bitstream } from '../../shared/bitstream.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Bitstream)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -43,7 +44,7 @@ export class NormalizedBitstream extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Item, true)
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Bundle that owns this Bitstream
|
||||
@@ -57,4 +58,5 @@ export class NormalizedBitstream extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
bundleName: string;
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { autoserialize, inheritSerialization } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Bundle } from "../../shared/bundle.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Bundle } from '../../shared/bundle.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Bundle)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -17,7 +18,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
|
||||
/**
|
||||
* An array of Items that are direct parents of this Bundle
|
||||
*/
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Item that owns this Bundle
|
||||
@@ -26,5 +27,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Bitstream, true)
|
||||
bitstreams: Array<string>;
|
||||
bitstreams: string[];
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Collection } from "../../shared/collection.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Collection } from '../../shared/collection.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Collection)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -26,7 +27,7 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Community, true)
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Community that owns this Collection
|
||||
@@ -37,6 +38,6 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Item, true)
|
||||
items: Array<string>;
|
||||
items: string[];
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Community } from "../../shared/community.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { autoserialize, inheritSerialization, autoserializeAs } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Community } from '../../shared/community.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Community)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -26,7 +27,7 @@ export class NormalizedCommunity extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Community, true)
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Community that owns this Community
|
||||
@@ -37,6 +38,6 @@ export class NormalizedCommunity extends NormalizedDSpaceObject {
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Collection, true)
|
||||
collections: Array<string>;
|
||||
collections: string[];
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from "cerialize";
|
||||
import { Metadatum } from "../../shared/metadatum.model";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { NormalizedObject } from "./normalized-object.model";
|
||||
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
|
||||
|
||||
import { Metadatum } from '../../shared/metadatum.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { NormalizedObject } from './normalized-object.model';
|
||||
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
@@ -51,17 +52,18 @@ export abstract class NormalizedDSpaceObject extends NormalizedObject {
|
||||
* An array containing all metadata of this DSpaceObject
|
||||
*/
|
||||
@autoserializeAs(Metadatum)
|
||||
metadata: Array<Metadatum>;
|
||||
metadata: Metadatum[];
|
||||
|
||||
/**
|
||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||
*/
|
||||
@autoserialize
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The DSpaceObject that owns this DSpaceObject
|
||||
*/
|
||||
@autoserialize
|
||||
owner: string;
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { inheritSerialization, autoserialize, autoserializeAs } from "cerialize";
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { Item } from "../../shared/item.model";
|
||||
import { mapsTo, relationship } from "../builders/build-decorators";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { inheritSerialization, autoserialize, autoserializeAs } from 'cerialize';
|
||||
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { Item } from '../../shared/item.model';
|
||||
import { mapsTo, relationship } from '../builders/build-decorators';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
@mapsTo(Item)
|
||||
@inheritSerialization(NormalizedDSpaceObject)
|
||||
@@ -43,7 +44,7 @@ export class NormalizedItem extends NormalizedDSpaceObject {
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Collection, true)
|
||||
parents: Array<string>;
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Collection that owns this Item
|
||||
@@ -53,5 +54,6 @@ export class NormalizedItem extends NormalizedDSpaceObject {
|
||||
|
||||
@autoserialize
|
||||
@relationship(ResourceType.Bitstream, true)
|
||||
bitstreams: Array<string>;
|
||||
bitstreams: string[];
|
||||
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
|
||||
import { NormalizedBitstream } from "./normalized-bitstream.model";
|
||||
import { NormalizedBundle } from "./normalized-bundle.model";
|
||||
import { NormalizedItem } from "./normalized-item.model";
|
||||
import { NormalizedCollection } from "./normalized-collection.model";
|
||||
import { GenericConstructor } from "../../shared/generic-constructor";
|
||||
import { NormalizedCommunity } from "./normalized-community.model";
|
||||
import { ResourceType } from "../../shared/resource-type";
|
||||
import { NormalizedObject } from "./normalized-object.model";
|
||||
import { NormalizedBitstreamFormat } from "./normalized-bitstream-format.model";
|
||||
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
|
||||
import { NormalizedBitstream } from './normalized-bitstream.model';
|
||||
import { NormalizedBundle } from './normalized-bundle.model';
|
||||
import { NormalizedItem } from './normalized-item.model';
|
||||
import { NormalizedCollection } from './normalized-collection.model';
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { NormalizedCommunity } from './normalized-community.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { NormalizedObject } from './normalized-object.model';
|
||||
import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
|
||||
|
||||
export class NormalizedObjectFactory {
|
||||
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { CacheableObject } from "../object-cache.reducer";
|
||||
import { autoserialize } from "cerialize";
|
||||
import { CacheableObject } from '../object-cache.reducer';
|
||||
import { autoserialize } from 'cerialize';
|
||||
/**
|
||||
* An abstract model class for a NormalizedObject.
|
||||
*/
|
||||
|
3
src/app/core/cache/models/self-link.model.ts
vendored
3
src/app/core/cache/models/self-link.model.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { autoserialize } from "cerialize";
|
||||
import { autoserialize } from 'cerialize';
|
||||
|
||||
export class SelfLink {
|
||||
|
||||
@@ -7,4 +7,5 @@ export class SelfLink {
|
||||
|
||||
@autoserialize
|
||||
uuid: string;
|
||||
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ export enum SortDirection {
|
||||
|
||||
export class SortOptions {
|
||||
|
||||
constructor(public field: string = "name", public direction: SortDirection = SortDirection.Ascending) {
|
||||
constructor(public field: string = 'name', public direction: SortDirection = SortDirection.Ascending) {
|
||||
|
||||
}
|
||||
}
|
||||
|
9
src/app/core/cache/object-cache.actions.ts
vendored
9
src/app/core/cache/object-cache.actions.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { CacheableObject } from "./object-cache.reducer";
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { CacheableObject } from './object-cache.reducer';
|
||||
|
||||
/**
|
||||
* The list of ObjectCacheAction type definitions
|
||||
@@ -11,6 +12,7 @@ export const ObjectCacheActionTypes = {
|
||||
RESET_TIMESTAMPS: type('dspace/core/cache/object/RESET_TIMESTAMPS')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
/**
|
||||
* An ngrx action to add an object to the cache
|
||||
*/
|
||||
@@ -77,6 +79,7 @@ export class ResetObjectCacheTimestampsAction implements Action {
|
||||
this.payload = newTimestamp;
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A type to encompass all ObjectCacheActions
|
||||
|
47
src/app/core/cache/object-cache.reducer.spec.ts
vendored
47
src/app/core/cache/object-cache.reducer.spec.ts
vendored
@@ -1,9 +1,10 @@
|
||||
import * as deepFreeze from "deep-freeze";
|
||||
import { objectCacheReducer } from "./object-cache.reducer";
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
|
||||
import { objectCacheReducer } from './object-cache.reducer';
|
||||
import {
|
||||
AddToObjectCacheAction,
|
||||
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
|
||||
} from "./object-cache.actions";
|
||||
} from './object-cache.actions';
|
||||
|
||||
class NullAction extends RemoveFromObjectCacheAction {
|
||||
type = null;
|
||||
@@ -14,51 +15,51 @@ class NullAction extends RemoveFromObjectCacheAction {
|
||||
}
|
||||
}
|
||||
|
||||
describe("objectCacheReducer", () => {
|
||||
describe('objectCacheReducer', () => {
|
||||
const uuid1 = '1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
||||
const uuid2 = '28b04544-1766-4e82-9728-c4e93544ecd3';
|
||||
const testState = {
|
||||
[uuid1]: {
|
||||
data: {
|
||||
uuid: uuid1,
|
||||
foo: "bar"
|
||||
foo: 'bar'
|
||||
},
|
||||
timeAdded: new Date().getTime(),
|
||||
msToLive: 900000,
|
||||
requestHref: "https://rest.api/endpoint/uuid1"
|
||||
requestHref: 'https://rest.api/endpoint/uuid1'
|
||||
},
|
||||
[uuid2]: {
|
||||
data: {
|
||||
uuid: uuid2,
|
||||
foo: "baz"
|
||||
foo: 'baz'
|
||||
},
|
||||
timeAdded: new Date().getTime(),
|
||||
msToLive: 900000,
|
||||
requestHref: "https://rest.api/endpoint/uuid2"
|
||||
requestHref: 'https://rest.api/endpoint/uuid2'
|
||||
}
|
||||
};
|
||||
deepFreeze(testState);
|
||||
|
||||
it("should return the current state when no valid actions have been made", () => {
|
||||
it('should return the current state when no valid actions have been made', () => {
|
||||
const action = new NullAction();
|
||||
const newState = objectCacheReducer(testState, action);
|
||||
|
||||
expect(newState).toEqual(testState);
|
||||
});
|
||||
|
||||
it("should start with an empty cache", () => {
|
||||
it('should start with an empty cache', () => {
|
||||
const action = new NullAction();
|
||||
const initialState = objectCacheReducer(undefined, action);
|
||||
|
||||
expect(initialState).toEqual(Object.create(null));
|
||||
});
|
||||
|
||||
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 objectToCache = { uuid: uuid1 };
|
||||
const timeAdded = new Date().getTime();
|
||||
const msToLive = 900000;
|
||||
const requestHref = "https://rest.api/endpoint/uuid1";
|
||||
const requestHref = 'https://rest.api/endpoint/uuid1';
|
||||
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref);
|
||||
const newState = objectCacheReducer(state, action);
|
||||
|
||||
@@ -67,31 +68,33 @@ describe("objectCacheReducer", () => {
|
||||
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: uuid1, foo: "baz", somethingElse: true };
|
||||
it('should overwrite an object in the cache in response to an ADD action if it already exists', () => {
|
||||
const objectToCache = { uuid: uuid1, foo: 'baz', somethingElse: true };
|
||||
const timeAdded = new Date().getTime();
|
||||
const msToLive = 900000;
|
||||
const requestHref = "https://rest.api/endpoint/uuid1";
|
||||
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");
|
||||
/* tslint:disable:no-string-literal */
|
||||
expect(newState[uuid1].data['foo']).toBe('baz');
|
||||
expect(newState[uuid1].data['somethingElse']).toBe(true);
|
||||
/* tslint:enable:no-string-literal */
|
||||
});
|
||||
|
||||
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 objectToCache = { uuid: uuid1 };
|
||||
const timeAdded = new Date().getTime();
|
||||
const msToLive = 900000;
|
||||
const requestHref = "https://rest.api/endpoint/uuid1";
|
||||
const requestHref = 'https://rest.api/endpoint/uuid1';
|
||||
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref);
|
||||
deepFreeze(state);
|
||||
|
||||
objectCacheReducer(state, action);
|
||||
});
|
||||
|
||||
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(uuid1);
|
||||
const newState = objectCacheReducer(testState, action);
|
||||
|
||||
@@ -108,13 +111,13 @@ describe("objectCacheReducer", () => {
|
||||
expect(newState).toEqual(testState);
|
||||
});
|
||||
|
||||
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(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", () => {
|
||||
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);
|
||||
@@ -123,7 +126,7 @@ describe("objectCacheReducer", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should perform the RESET_TIMESTAMPS action without affecting the previous state", () => {
|
||||
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);
|
||||
|
21
src/app/core/cache/object-cache.reducer.ts
vendored
21
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction,
|
||||
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
|
||||
} from "./object-cache.actions";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
import { CacheEntry } from "./cache-entry";
|
||||
} from './object-cache.actions';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { CacheEntry } from './cache-entry';
|
||||
|
||||
/**
|
||||
* An interface to represent objects that can be cached
|
||||
@@ -52,15 +52,15 @@ export const objectCacheReducer = (state = initialState, action: ObjectCacheActi
|
||||
switch (action.type) {
|
||||
|
||||
case ObjectCacheActionTypes.ADD: {
|
||||
return addToObjectCache(state, <AddToObjectCacheAction>action);
|
||||
return addToObjectCache(state, action as AddToObjectCacheAction);
|
||||
}
|
||||
|
||||
case ObjectCacheActionTypes.REMOVE: {
|
||||
return removeFromObjectCache(state, <RemoveFromObjectCacheAction>action)
|
||||
return removeFromObjectCache(state, action as RemoveFromObjectCacheAction)
|
||||
}
|
||||
|
||||
case ObjectCacheActionTypes.RESET_TIMESTAMPS: {
|
||||
return resetObjectCacheTimestamps(state, <ResetObjectCacheTimestampsAction>action)
|
||||
return resetObjectCacheTimestamps(state, action as ResetObjectCacheTimestampsAction)
|
||||
}
|
||||
|
||||
default: {
|
||||
@@ -102,12 +102,11 @@ function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheActio
|
||||
*/
|
||||
function removeFromObjectCache(state: ObjectCacheState, action: RemoveFromObjectCacheAction): ObjectCacheState {
|
||||
if (hasValue(state[action.payload])) {
|
||||
let newObjectCache = Object.assign({}, state);
|
||||
const newObjectCache = Object.assign({}, state);
|
||||
delete newObjectCache[action.payload];
|
||||
|
||||
return newObjectCache;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -123,8 +122,8 @@ function removeFromObjectCache(state: ObjectCacheState, action: RemoveFromObject
|
||||
* 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 => {
|
||||
const newState = Object.create(null);
|
||||
Object.keys(state).forEach((key) => {
|
||||
newState[key] = Object.assign({}, state[key], {
|
||||
timeAdded: action.payload
|
||||
});
|
||||
|
61
src/app/core/cache/object-cache.service.spec.ts
vendored
61
src/app/core/cache/object-cache.service.spec.ts
vendored
@@ -1,9 +1,9 @@
|
||||
import { Store } from "@ngrx/store";
|
||||
import { Observable } from "rxjs";
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ObjectCacheService } from "./object-cache.service";
|
||||
import { ObjectCacheState, CacheableObject } from "./object-cache.reducer";
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
|
||||
import { ObjectCacheService } from './object-cache.service';
|
||||
import { ObjectCacheState, CacheableObject } from './object-cache.reducer';
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
|
||||
|
||||
class TestClass implements CacheableObject {
|
||||
constructor(
|
||||
@@ -16,7 +16,7 @@ class TestClass implements CacheableObject {
|
||||
}
|
||||
}
|
||||
|
||||
describe("ObjectCacheService", () => {
|
||||
describe('ObjectCacheService', () => {
|
||||
let service: ObjectCacheService;
|
||||
let store: Store<ObjectCacheState>;
|
||||
|
||||
@@ -40,65 +40,65 @@ describe("ObjectCacheService", () => {
|
||||
spyOn(store, 'dispatch');
|
||||
service = new ObjectCacheService(store);
|
||||
|
||||
spyOn(Date.prototype, 'getTime').and.callFake(function() {
|
||||
spyOn(Date.prototype, 'getTime').and.callFake(() => {
|
||||
return timestamp;
|
||||
});
|
||||
});
|
||||
|
||||
describe("add", () => {
|
||||
it("should dispatch an ADD action with the object to add, the time to live, and the current timestamp", () => {
|
||||
describe('add', () => {
|
||||
it('should dispatch an ADD action with the object to add, the time to live, and the current timestamp', () => {
|
||||
service.add(objectToCache, msToLive, requestHref);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new AddToObjectCacheAction(objectToCache, timestamp, msToLive, requestHref));
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", () => {
|
||||
it("should dispatch a REMOVE action with the UUID of the object to remove", () => {
|
||||
describe('remove', () => {
|
||||
it('should dispatch a REMOVE action with the UUID of the object to remove', () => {
|
||||
service.remove(uuid);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new RemoveFromObjectCacheAction(uuid));
|
||||
});
|
||||
});
|
||||
|
||||
describe("get", () => {
|
||||
it("should return an observable of the cached object with the specified UUID and type", () => {
|
||||
describe('get', () => {
|
||||
it('should return an observable of the cached object with the specified UUID and type', () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(cacheEntry));
|
||||
|
||||
let testObj: any;
|
||||
// due to the implementation of spyOn above, this subscribe will be synchronous
|
||||
service.get(uuid, TestClass).take(1).subscribe(o => testObj = o);
|
||||
service.get(uuid, TestClass).take(1).subscribe((o) => testObj = o);
|
||||
expect(testObj.uuid).toBe(uuid);
|
||||
expect(testObj.foo).toBe("bar");
|
||||
expect(testObj.foo).toBe('bar');
|
||||
// this only works if testObj is an instance of TestClass
|
||||
expect(testObj.test()).toBe("bar" + uuid);
|
||||
expect(testObj.test()).toBe('bar' + uuid);
|
||||
});
|
||||
|
||||
it("should not return a cached object that has exceeded its time to live", () => {
|
||||
it('should not return a cached object that has exceeded its time to live', () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
|
||||
|
||||
let getObsHasFired = false;
|
||||
const subscription = service.get(uuid, TestClass).subscribe(o => getObsHasFired = true);
|
||||
const subscription = service.get(uuid, TestClass).subscribe((o) => getObsHasFired = true);
|
||||
expect(getObsHasFired).toBe(false);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getList", () => {
|
||||
it("should return an observable of the array of cached objects with the specified UUID and type", () => {
|
||||
spyOn(service, 'get').and.returnValue(Observable.of(new TestClass(uuid, "bar")));
|
||||
describe('getList', () => {
|
||||
it('should return an observable of the array of cached objects with the specified UUID and type', () => {
|
||||
spyOn(service, 'get').and.returnValue(Observable.of(new TestClass(uuid, 'bar')));
|
||||
|
||||
let testObjs: Array<any>;
|
||||
service.getList([uuid, uuid], TestClass).take(1).subscribe(arr => testObjs = arr);
|
||||
let testObjs: any[];
|
||||
service.getList([uuid, uuid], TestClass).take(1).subscribe((arr) => testObjs = arr);
|
||||
expect(testObjs[0].uuid).toBe(uuid);
|
||||
expect(testObjs[0].foo).toBe("bar");
|
||||
expect(testObjs[0].test()).toBe("bar" + uuid);
|
||||
expect(testObjs[0].foo).toBe('bar');
|
||||
expect(testObjs[0].test()).toBe('bar' + uuid);
|
||||
expect(testObjs[1].uuid).toBe(uuid);
|
||||
expect(testObjs[1].foo).toBe("bar");
|
||||
expect(testObjs[1].test()).toBe("bar" + uuid);
|
||||
expect(testObjs[1].foo).toBe('bar');
|
||||
expect(testObjs[1].test()).toBe('bar' + uuid);
|
||||
});
|
||||
});
|
||||
|
||||
describe("has", () => {
|
||||
it("should return true if the object with the supplied UUID is cached and still valid", () => {
|
||||
describe('has', () => {
|
||||
it('should return true if the object with the supplied UUID is cached and still valid', () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(cacheEntry));
|
||||
|
||||
expect(service.has(uuid)).toBe(true);
|
||||
@@ -110,10 +110,11 @@ describe("ObjectCacheService", () => {
|
||||
expect(service.has(uuid)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false if the object with the supplied UUID is cached but has exceeded its time to live", () => {
|
||||
it('should return false if the object with the supplied UUID is cached but has exceeded its time to live', () => {
|
||||
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
|
||||
|
||||
expect(service.has(uuid)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
29
src/app/core/cache/object-cache.service.ts
vendored
29
src/app/core/cache/object-cache.service.ts
vendored
@@ -1,10 +1,12 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { ObjectCacheState, ObjectCacheEntry, CacheableObject } from "./object-cache.reducer";
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
|
||||
import { Observable } from "rxjs";
|
||||
import { hasNoValue } from "../../shared/empty.util";
|
||||
import { GenericConstructor } from "../shared/generic-constructor";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ObjectCacheState, ObjectCacheEntry, CacheableObject } from './object-cache.reducer';
|
||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
|
||||
import { hasNoValue } from '../../shared/empty.util';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
|
||||
/**
|
||||
* A service to interact with the object cache
|
||||
@@ -59,7 +61,7 @@ export class ObjectCacheService {
|
||||
*/
|
||||
get<T extends CacheableObject>(uuid: string, type: GenericConstructor<T>): Observable<T> {
|
||||
return this.getEntry(uuid)
|
||||
.map((entry: ObjectCacheEntry) => <T>Object.assign(new type(), entry.data));
|
||||
.map((entry: ObjectCacheEntry) => Object.assign(new type(), entry.data) as T);
|
||||
}
|
||||
|
||||
getBySelfLink<T extends CacheableObject>(href: string, type: GenericConstructor<T>): Observable<T> {
|
||||
@@ -69,7 +71,7 @@ export class ObjectCacheService {
|
||||
|
||||
private getEntry(uuid: string): Observable<ObjectCacheEntry> {
|
||||
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
||||
.filter(entry => this.isValid(entry))
|
||||
.filter((entry) => this.isValid(entry))
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
@@ -103,7 +105,7 @@ export class ObjectCacheService {
|
||||
* The type of the objects to get
|
||||
* @return Observable<Array<T>>
|
||||
*/
|
||||
getList<T extends CacheableObject>(uuids: Array<string>, type: GenericConstructor<T>): Observable<Array<T>> {
|
||||
getList<T extends CacheableObject>(uuids: string[], type: GenericConstructor<T>): Observable<T[]> {
|
||||
return Observable.combineLatest(
|
||||
uuids.map((id: string) => this.get<T>(id, type))
|
||||
);
|
||||
@@ -123,7 +125,7 @@ export class ObjectCacheService {
|
||||
|
||||
this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
|
||||
.take(1)
|
||||
.subscribe(entry => result = this.isValid(entry));
|
||||
.subscribe((entry) => result = this.isValid(entry));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -138,7 +140,7 @@ export class ObjectCacheService {
|
||||
* false otherwise
|
||||
*/
|
||||
hasBySelfLink(href: string): boolean {
|
||||
let result: boolean = false;
|
||||
let result = false;
|
||||
|
||||
this.store.select<string>('core', 'index', 'href', href)
|
||||
.take(1)
|
||||
@@ -159,8 +161,7 @@ export class ObjectCacheService {
|
||||
private isValid(entry: ObjectCacheEntry): boolean {
|
||||
if (hasNoValue(entry)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const timeOutdated = entry.timeAdded + entry.msToLive;
|
||||
const isOutDated = new Date().getTime() > timeOutdated;
|
||||
if (isOutDated) {
|
||||
|
9
src/app/core/cache/response-cache.actions.ts
vendored
9
src/app/core/cache/response-cache.actions.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { Response } from "./response-cache.models";
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { Response } from './response-cache.models';
|
||||
|
||||
/**
|
||||
* The list of ResponseCacheAction type definitions
|
||||
@@ -11,6 +12,7 @@ export const ResponseCacheActionTypes = {
|
||||
RESET_TIMESTAMPS: type('dspace/core/cache/response/RESET_TIMESTAMPS')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class ResponseCacheAddAction implements Action {
|
||||
type = ResponseCacheActionTypes.ADD;
|
||||
payload: {
|
||||
@@ -59,6 +61,7 @@ export class ResetResponseCacheTimestampsAction implements Action {
|
||||
this.payload = newTimestamp;
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A type to encompass all ResponseCacheActions
|
||||
|
8
src/app/core/cache/response-cache.models.ts
vendored
8
src/app/core/cache/response-cache.models.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { RequestError } from "../data/request.models";
|
||||
import { PageInfo } from "../shared/page-info.model";
|
||||
import { RequestError } from '../data/request.models';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class Response {
|
||||
constructor(
|
||||
public isSuccessful: boolean,
|
||||
@@ -10,7 +11,7 @@ export class Response {
|
||||
|
||||
export class SuccessResponse extends Response {
|
||||
constructor(
|
||||
public resourceUUIDs: Array<String>,
|
||||
public resourceUUIDs: string[],
|
||||
public statusCode: string,
|
||||
public pageInfo?: PageInfo
|
||||
) {
|
||||
@@ -27,3 +28,4 @@ export class ErrorResponse extends Response {
|
||||
this.errorMessage = error.message;
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
110
src/app/core/cache/response-cache.reducer.spec.ts
vendored
110
src/app/core/cache/response-cache.reducer.spec.ts
vendored
@@ -1,11 +1,11 @@
|
||||
import * as deepFreeze from "deep-freeze";
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
|
||||
import { responseCacheReducer, ResponseCacheState } from "./response-cache.reducer";
|
||||
import { responseCacheReducer, ResponseCacheState } from './response-cache.reducer';
|
||||
|
||||
import {
|
||||
ResponseCacheRemoveAction,
|
||||
ResetResponseCacheTimestampsAction
|
||||
} from "./response-cache.actions";
|
||||
} from './response-cache.actions';
|
||||
|
||||
class NullAction extends ResponseCacheRemoveAction {
|
||||
type = null;
|
||||
@@ -16,37 +16,37 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
}
|
||||
}
|
||||
|
||||
// describe("responseCacheReducer", () => {
|
||||
// const keys = ["125c17f89046283c5f0640722aac9feb", "a06c3006a41caec5d635af099b0c780c"];
|
||||
// describe('responseCacheReducer', () => {
|
||||
// const keys = ['125c17f89046283c5f0640722aac9feb', 'a06c3006a41caec5d635af099b0c780c'];
|
||||
// const services = [new OpaqueToken('service1'), new OpaqueToken('service2')];
|
||||
// const msToLive = 900000;
|
||||
// const uuids = [
|
||||
// "9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
// "598ce822-c357-46f3-ab70-63724d02d6ad",
|
||||
// "be8325f7-243b-49f4-8a4b-df2b793ff3b5"
|
||||
// '9e32a2e2-6b91-4236-a361-995ccdc14c60',
|
||||
// '598ce822-c357-46f3-ab70-63724d02d6ad',
|
||||
// 'be8325f7-243b-49f4-8a4b-df2b793ff3b5'
|
||||
// ];
|
||||
// const resourceID = "9978";
|
||||
// const paginationOptions = { "resultsPerPage": 10, "currentPage": 1 };
|
||||
// const sortOptions = { "field": "id", "direction": 0 };
|
||||
// const resourceID = '9978';
|
||||
// const paginationOptions = { 'resultsPerPage': 10, 'currentPage': 1 };
|
||||
// const sortOptions = { 'field': 'id', 'direction': 0 };
|
||||
// const testState = {
|
||||
// [keys[0]]: {
|
||||
// "key": keys[0],
|
||||
// "service": services[0],
|
||||
// "resourceUUIDs": [uuids[0], uuids[1]],
|
||||
// "isLoading": false,
|
||||
// "paginationOptions": paginationOptions,
|
||||
// "sortOptions": sortOptions,
|
||||
// "timeAdded": new Date().getTime(),
|
||||
// "msToLive": msToLive
|
||||
// 'key': keys[0],
|
||||
// 'service': services[0],
|
||||
// 'resourceUUIDs': [uuids[0], uuids[1]],
|
||||
// 'isLoading': false,
|
||||
// 'paginationOptions': paginationOptions,
|
||||
// 'sortOptions': sortOptions,
|
||||
// 'timeAdded': new Date().getTime(),
|
||||
// 'msToLive': msToLive
|
||||
// },
|
||||
// [keys[1]]: {
|
||||
// "key": keys[1],
|
||||
// "service": services[1],
|
||||
// "resourceID": resourceID,
|
||||
// "resourceUUIDs": [uuids[2]],
|
||||
// "isLoading": false,
|
||||
// "timeAdded": new Date().getTime(),
|
||||
// "msToLive": msToLive
|
||||
// 'key': keys[1],
|
||||
// 'service': services[1],
|
||||
// 'resourceID': resourceID,
|
||||
// 'resourceUUIDs': [uuids[2]],
|
||||
// 'isLoading': false,
|
||||
// 'timeAdded': new Date().getTime(),
|
||||
// 'msToLive': msToLive
|
||||
// }
|
||||
// };
|
||||
// deepFreeze(testState);
|
||||
@@ -59,29 +59,29 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
// deepFreeze(errorState);
|
||||
//
|
||||
//
|
||||
// it("should return the current state when no valid actions have been made", () => {
|
||||
// it('should return the current state when no valid actions have been made', () => {
|
||||
// const action = new NullAction();
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
//
|
||||
// expect(newState).toEqual(testState);
|
||||
// });
|
||||
//
|
||||
// it("should start with an empty cache", () => {
|
||||
// it('should start with an empty cache', () => {
|
||||
// const action = new NullAction();
|
||||
// const initialState = responseCacheReducer(undefined, action);
|
||||
//
|
||||
// expect(initialState).toEqual(Object.create(null));
|
||||
// });
|
||||
//
|
||||
// describe("FIND_BY_ID", () => {
|
||||
// describe('FIND_BY_ID', () => {
|
||||
// const action = new ResponseCacheFindByIDAction(keys[0], services[0], resourceID);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should add the request to the cache", () => {
|
||||
// it('should add the request to the cache', () => {
|
||||
// const state = Object.create(null);
|
||||
// const newState = responseCacheReducer(state, action);
|
||||
// expect(newState[keys[0]].key).toBe(keys[0]);
|
||||
@@ -89,28 +89,28 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
// expect(newState[keys[0]].resourceID).toBe(resourceID);
|
||||
// });
|
||||
//
|
||||
// it("should set responsePending to true", () => {
|
||||
// it('should set responsePending to true', () => {
|
||||
// const state = Object.create(null);
|
||||
// const newState = responseCacheReducer(state, action);
|
||||
// expect(newState[keys[0]].responsePending).toBe(true);
|
||||
// });
|
||||
//
|
||||
// it("should remove any previous error message or resourceUUID for the request", () => {
|
||||
// it('should remove any previous error message or resourceUUID for the request', () => {
|
||||
// const newState = responseCacheReducer(errorState, action);
|
||||
// expect(newState[keys[0]].resourceUUIDs.length).toBe(0);
|
||||
// expect(newState[keys[0]].errorMessage).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("FIND_ALL", () => {
|
||||
// describe('FIND_ALL', () => {
|
||||
// const action = new ResponseCacheFindAllAction(keys[0], services[0], resourceID, paginationOptions, sortOptions);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should add the request to the cache", () => {
|
||||
// it('should add the request to the cache', () => {
|
||||
// const state = Object.create(null);
|
||||
// const newState = responseCacheReducer(state, action);
|
||||
// expect(newState[keys[0]].key).toBe(keys[0]);
|
||||
@@ -120,84 +120,84 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
// expect(newState[keys[0]].sortOptions).toEqual(sortOptions);
|
||||
// });
|
||||
//
|
||||
// it("should set responsePending to true", () => {
|
||||
// it('should set responsePending to true', () => {
|
||||
// const state = Object.create(null);
|
||||
// const newState = responseCacheReducer(state, action);
|
||||
// expect(newState[keys[0]].responsePending).toBe(true);
|
||||
// });
|
||||
//
|
||||
// it("should remove any previous error message or resourceUUIDs for the request", () => {
|
||||
// it('should remove any previous error message or resourceUUIDs for the request', () => {
|
||||
// const newState = responseCacheReducer(errorState, action);
|
||||
// expect(newState[keys[0]].resourceUUIDs.length).toBe(0);
|
||||
// expect(newState[keys[0]].errorMessage).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("SUCCESS", () => {
|
||||
// describe('SUCCESS', () => {
|
||||
// const successUUIDs = [uuids[0], uuids[2]];
|
||||
// const successTimeAdded = new Date().getTime();
|
||||
// const successMsToLive = 5;
|
||||
// const action = new ResponseCacheSuccessAction(keys[0], successUUIDs, successTimeAdded, successMsToLive);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should add the response to the cached request", () => {
|
||||
// it('should add the response to the cached request', () => {
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(newState[keys[0]].resourceUUIDs).toBe(successUUIDs);
|
||||
// expect(newState[keys[0]].timeAdded).toBe(successTimeAdded);
|
||||
// expect(newState[keys[0]].msToLive).toBe(successMsToLive);
|
||||
// });
|
||||
//
|
||||
// it("should set responsePending to false", () => {
|
||||
// it('should set responsePending to false', () => {
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(newState[keys[0]].responsePending).toBe(false);
|
||||
// });
|
||||
//
|
||||
// it("should remove any previous error message for the request", () => {
|
||||
// it('should remove any previous error message for the request', () => {
|
||||
// const newState = responseCacheReducer(errorState, action);
|
||||
// expect(newState[keys[0]].errorMessage).toBeUndefined();
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("ERROR", () => {
|
||||
// describe('ERROR', () => {
|
||||
// const errorMsg = 'errorMsg';
|
||||
// const action = new ResponseCacheErrorAction(keys[0], errorMsg);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should set an error message for the request", () => {
|
||||
// it('should set an error message for the request', () => {
|
||||
// const newState = responseCacheReducer(errorState, action);
|
||||
// expect(newState[keys[0]].errorMessage).toBe(errorMsg);
|
||||
// });
|
||||
//
|
||||
// it("should set responsePending to false", () => {
|
||||
// it('should set responsePending to false', () => {
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(newState[keys[0]].responsePending).toBe(false);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("REMOVE", () => {
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// describe('REMOVE', () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// const action = new ResponseCacheRemoveAction(keys[0]);
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should remove the specified request from the cache", () => {
|
||||
// it('should remove the specified request from the cache', () => {
|
||||
// const action = new ResponseCacheRemoveAction(keys[0]);
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(testState[keys[0]]).not.toBeUndefined();
|
||||
// expect(newState[keys[0]]).toBeUndefined();
|
||||
// });
|
||||
//
|
||||
// it("shouldn't do anything when the specified key isn't cached", () => {
|
||||
// const wrongKey = "this isn't cached";
|
||||
// it('shouldn't do anything when the specified key isn't cached', () => {
|
||||
// const wrongKey = 'this isn't cached';
|
||||
// const action = new ResponseCacheRemoveAction(wrongKey);
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// expect(testState[wrongKey]).toBeUndefined();
|
||||
@@ -205,16 +205,16 @@ class NullAction extends ResponseCacheRemoveAction {
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("RESET_TIMESTAMPS", () => {
|
||||
// describe('RESET_TIMESTAMPS', () => {
|
||||
// const newTimeStamp = new Date().getTime();
|
||||
// const action = new ResetResponseCacheTimestampsAction(newTimeStamp);
|
||||
//
|
||||
// it("should perform the action without affecting the previous state", () => {
|
||||
// it('should perform the action without affecting the previous state', () => {
|
||||
// //testState has already been frozen above
|
||||
// responseCacheReducer(testState, action);
|
||||
// });
|
||||
//
|
||||
// it("should set the timestamp of all requests in the cache", () => {
|
||||
// it('should set the timestamp of all requests in the cache', () => {
|
||||
// const newState = responseCacheReducer(testState, action);
|
||||
// Object.keys(newState).forEach((key) => {
|
||||
// expect(newState[key].timeAdded).toEqual(newTimeStamp);
|
||||
|
23
src/app/core/cache/response-cache.reducer.ts
vendored
23
src/app/core/cache/response-cache.reducer.ts
vendored
@@ -2,10 +2,10 @@ import {
|
||||
ResponseCacheAction, ResponseCacheActionTypes,
|
||||
ResponseCacheRemoveAction, ResetResponseCacheTimestampsAction,
|
||||
ResponseCacheAddAction
|
||||
} from "./response-cache.actions";
|
||||
import { CacheEntry } from "./cache-entry";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
import { Response } from "./response-cache.models";
|
||||
} from './response-cache.actions';
|
||||
import { CacheEntry } from './cache-entry';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { Response } from './response-cache.models';
|
||||
|
||||
/**
|
||||
* An entry in the ResponseCache
|
||||
@@ -41,15 +41,15 @@ export const responseCacheReducer = (state = initialState, action: ResponseCache
|
||||
switch (action.type) {
|
||||
|
||||
case ResponseCacheActionTypes.ADD: {
|
||||
return addToCache(state, <ResponseCacheAddAction>action);
|
||||
return addToCache(state, action as ResponseCacheAddAction);
|
||||
}
|
||||
|
||||
case ResponseCacheActionTypes.REMOVE: {
|
||||
return removeFromCache(state, <ResponseCacheRemoveAction>action);
|
||||
return removeFromCache(state, action as ResponseCacheRemoveAction);
|
||||
}
|
||||
|
||||
case ResponseCacheActionTypes.RESET_TIMESTAMPS: {
|
||||
return resetResponseCacheTimestamps(state, <ResetResponseCacheTimestampsAction>action)
|
||||
return resetResponseCacheTimestamps(state, action as ResetResponseCacheTimestampsAction)
|
||||
}
|
||||
|
||||
default: {
|
||||
@@ -81,12 +81,11 @@ function addToCache(state: ResponseCacheState, action: ResponseCacheAddAction):
|
||||
*/
|
||||
function removeFromCache(state: ResponseCacheState, action: ResponseCacheRemoveAction): ResponseCacheState {
|
||||
if (hasValue(state[action.payload])) {
|
||||
let newCache = Object.assign({}, state);
|
||||
const newCache = Object.assign({}, state);
|
||||
delete newCache[action.payload];
|
||||
|
||||
return newCache;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -102,8 +101,8 @@ function removeFromCache(state: ResponseCacheState, action: ResponseCacheRemoveA
|
||||
* the new state, with all timeAdded timestamps set to the specified value
|
||||
*/
|
||||
function resetResponseCacheTimestamps(state: ResponseCacheState, action: ResetResponseCacheTimestampsAction): ResponseCacheState {
|
||||
let newState = Object.create(null);
|
||||
Object.keys(state).forEach(key => {
|
||||
const newState = Object.create(null);
|
||||
Object.keys(state).forEach((key) => {
|
||||
newState[key] = Object.assign({}, state[key], {
|
||||
timeAdded: action.payload
|
||||
});
|
||||
|
@@ -1,18 +1,19 @@
|
||||
import { ResponseCacheService } from "./response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { ResponseCacheState, ResponseCacheEntry } from "./response-cache.reducer";
|
||||
import { OpaqueToken } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { OpaqueToken } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
// describe("ResponseCacheService", () => {
|
||||
import { ResponseCacheService } from './response-cache.service';
|
||||
import { ResponseCacheState, ResponseCacheEntry } from './response-cache.reducer';
|
||||
|
||||
// describe('ResponseCacheService', () => {
|
||||
// let service: ResponseCacheService;
|
||||
// let store: Store<ResponseCacheState>;
|
||||
//
|
||||
// const keys = ["125c17f89046283c5f0640722aac9feb", "a06c3006a41caec5d635af099b0c780c"];
|
||||
// const keys = ['125c17f89046283c5f0640722aac9feb', 'a06c3006a41caec5d635af099b0c780c'];
|
||||
// const serviceTokens = [new OpaqueToken('service1'), new OpaqueToken('service2')];
|
||||
// const resourceID = "9978";
|
||||
// const paginationOptions = { "resultsPerPage": 10, "currentPage": 1 };
|
||||
// const sortOptions = { "field": "id", "direction": 0 };
|
||||
// const resourceID = '9978';
|
||||
// const paginationOptions = { 'resultsPerPage': 10, 'currentPage': 1 };
|
||||
// const sortOptions = { 'field': 'id', 'direction': 0 };
|
||||
// const timestamp = new Date().getTime();
|
||||
// const validCacheEntry = (key) => {
|
||||
// return {
|
||||
@@ -36,33 +37,33 @@ import { Observable } from "rxjs";
|
||||
// spyOn(window, 'Date').and.returnValue({ getTime: () => timestamp });
|
||||
// });
|
||||
//
|
||||
// describe("findAll", () => {
|
||||
// describe('findAll', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "get").and.callFake((key) => Observable.of({key: key}));
|
||||
// spyOn(service, 'get').and.callFake((key) => Observable.of({key: key}));
|
||||
// });
|
||||
// describe("if the key isn't cached", () => {
|
||||
// describe('if the key isn't cached', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "has").and.returnValue(false);
|
||||
// spyOn(service, 'has').and.returnValue(false);
|
||||
// });
|
||||
// it("should dispatch a FIND_ALL action with the key, service, scopeID, paginationOptions and sortOptions", () => {
|
||||
// it('should dispatch a FIND_ALL action with the key, service, scopeID, paginationOptions and sortOptions', () => {
|
||||
// service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions);
|
||||
// expect(store.dispatch).toHaveBeenCalledWith(new ResponseCacheFindAllAction(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions))
|
||||
// });
|
||||
// it("should return an observable of the newly cached request with the specified key", () => {
|
||||
// it('should return an observable of the newly cached request with the specified key', () => {
|
||||
// let result: ResponseCacheEntry;
|
||||
// service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions).take(1).subscribe(entry => result = entry);
|
||||
// expect(result.key).toEqual(keys[0]);
|
||||
// });
|
||||
// });
|
||||
// describe("if the key is already cached", () => {
|
||||
// describe('if the key is already cached', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "has").and.returnValue(true);
|
||||
// spyOn(service, 'has').and.returnValue(true);
|
||||
// });
|
||||
// it("shouldn't dispatch anything", () => {
|
||||
// it('shouldn't dispatch anything', () => {
|
||||
// service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions);
|
||||
// expect(store.dispatch).not.toHaveBeenCalled();
|
||||
// });
|
||||
// it("should return an observable of the existing cached request with the specified key", () => {
|
||||
// it('should return an observable of the existing cached request with the specified key', () => {
|
||||
// let result: ResponseCacheEntry;
|
||||
// service.findAll(keys[0], serviceTokens[0], resourceID, paginationOptions, sortOptions).take(1).subscribe(entry => result = entry);
|
||||
// expect(result.key).toEqual(keys[0]);
|
||||
@@ -70,33 +71,33 @@ import { Observable } from "rxjs";
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("findById", () => {
|
||||
// describe('findById', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "get").and.callFake((key) => Observable.of({key: key}));
|
||||
// spyOn(service, 'get').and.callFake((key) => Observable.of({key: key}));
|
||||
// });
|
||||
// describe("if the key isn't cached", () => {
|
||||
// describe('if the key isn't cached', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "has").and.returnValue(false);
|
||||
// spyOn(service, 'has').and.returnValue(false);
|
||||
// });
|
||||
// it("should dispatch a FIND_BY_ID action with the key, service, and resourceID", () => {
|
||||
// it('should dispatch a FIND_BY_ID action with the key, service, and resourceID', () => {
|
||||
// service.findById(keys[0], serviceTokens[0], resourceID);
|
||||
// expect(store.dispatch).toHaveBeenCalledWith(new ResponseCacheFindByIDAction(keys[0], serviceTokens[0], resourceID))
|
||||
// });
|
||||
// it("should return an observable of the newly cached request with the specified key", () => {
|
||||
// it('should return an observable of the newly cached request with the specified key', () => {
|
||||
// let result: ResponseCacheEntry;
|
||||
// service.findById(keys[0], serviceTokens[0], resourceID).take(1).subscribe(entry => result = entry);
|
||||
// expect(result.key).toEqual(keys[0]);
|
||||
// });
|
||||
// });
|
||||
// describe("if the key is already cached", () => {
|
||||
// describe('if the key is already cached', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(service, "has").and.returnValue(true);
|
||||
// spyOn(service, 'has').and.returnValue(true);
|
||||
// });
|
||||
// it("shouldn't dispatch anything", () => {
|
||||
// it('shouldn't dispatch anything', () => {
|
||||
// service.findById(keys[0], serviceTokens[0], resourceID);
|
||||
// expect(store.dispatch).not.toHaveBeenCalled();
|
||||
// });
|
||||
// it("should return an observable of the existing cached request with the specified key", () => {
|
||||
// it('should return an observable of the existing cached request with the specified key', () => {
|
||||
// let result: ResponseCacheEntry;
|
||||
// service.findById(keys[0], serviceTokens[0], resourceID).take(1).subscribe(entry => result = entry);
|
||||
// expect(result.key).toEqual(keys[0]);
|
||||
@@ -104,9 +105,9 @@ import { Observable } from "rxjs";
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("get", () => {
|
||||
// it("should return an observable of the cached request with the specified key", () => {
|
||||
// spyOn(store, "select").and.callFake((...args:Array<any>) => {
|
||||
// describe('get', () => {
|
||||
// it('should return an observable of the cached request with the specified key', () => {
|
||||
// spyOn(store, 'select').and.callFake((...args:Array<any>) => {
|
||||
// return Observable.of(validCacheEntry(args[args.length - 1]));
|
||||
// });
|
||||
//
|
||||
@@ -115,8 +116,8 @@ import { Observable } from "rxjs";
|
||||
// expect(testObj.key).toEqual(keys[1]);
|
||||
// });
|
||||
//
|
||||
// it("should not return a cached request that has exceeded its time to live", () => {
|
||||
// spyOn(store, "select").and.callFake((...args:Array<any>) => {
|
||||
// it('should not return a cached request that has exceeded its time to live', () => {
|
||||
// spyOn(store, 'select').and.callFake((...args:Array<any>) => {
|
||||
// return Observable.of(invalidCacheEntry(args[args.length - 1]));
|
||||
// });
|
||||
//
|
||||
@@ -127,18 +128,18 @@ import { Observable } from "rxjs";
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe("has", () => {
|
||||
// it("should return true if the request with the supplied key is cached and still valid", () => {
|
||||
// describe('has', () => {
|
||||
// it('should return true if the request with the supplied key is cached and still valid', () => {
|
||||
// spyOn(store, 'select').and.returnValue(Observable.of(validCacheEntry(keys[1])));
|
||||
// expect(service.has(keys[1])).toBe(true);
|
||||
// });
|
||||
//
|
||||
// it("should return false if the request with the supplied key isn't cached", () => {
|
||||
// it('should return false if the request with the supplied key isn't cached', () => {
|
||||
// spyOn(store, 'select').and.returnValue(Observable.of(undefined));
|
||||
// expect(service.has(keys[1])).toBe(false);
|
||||
// });
|
||||
//
|
||||
// it("should return false if the request with the supplied key is cached but has exceeded its time to live", () => {
|
||||
// it('should return false if the request with the supplied key is cached but has exceeded its time to live', () => {
|
||||
// spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry(keys[1])));
|
||||
// expect(service.has(keys[1])).toBe(false);
|
||||
// });
|
||||
|
28
src/app/core/cache/response-cache.service.ts
vendored
28
src/app/core/cache/response-cache.service.ts
vendored
@@ -1,15 +1,12 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
import {
|
||||
ResponseCacheState, ResponseCacheEntry
|
||||
} from "./response-cache.reducer";
|
||||
import { Observable } from "rxjs";
|
||||
import { hasNoValue } from "../../shared/empty.util";
|
||||
import {
|
||||
ResponseCacheRemoveAction,
|
||||
ResponseCacheAddAction
|
||||
} from "./response-cache.actions";
|
||||
import { Response } from "./response-cache.models";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ResponseCacheState, ResponseCacheEntry } from './response-cache.reducer';
|
||||
import { hasNoValue } from '../../shared/empty.util';
|
||||
import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions';
|
||||
import { Response } from './response-cache.models';
|
||||
|
||||
/**
|
||||
* A service to interact with the response cache
|
||||
@@ -38,7 +35,7 @@ export class ResponseCacheService {
|
||||
*/
|
||||
get(key: string): Observable<ResponseCacheEntry> {
|
||||
return this.store.select<ResponseCacheEntry>('core', 'cache', 'response', key)
|
||||
.filter(entry => this.isValid(entry))
|
||||
.filter((entry) => this.isValid(entry))
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
@@ -56,7 +53,7 @@ export class ResponseCacheService {
|
||||
|
||||
this.store.select<ResponseCacheEntry>('core', 'cache', 'response', key)
|
||||
.take(1)
|
||||
.subscribe(entry => {
|
||||
.subscribe((entry) => {
|
||||
result = this.isValid(entry);
|
||||
});
|
||||
|
||||
@@ -75,8 +72,7 @@ export class ResponseCacheService {
|
||||
private isValid(entry: ResponseCacheEntry): boolean {
|
||||
if (hasNoValue(entry)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const timeOutdated = entry.timeAdded + entry.msToLive;
|
||||
const isOutDated = new Date().getTime() > timeOutdated;
|
||||
if (isOutDated) {
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { EffectsModule } from "@ngrx/effects";
|
||||
import { ObjectCacheEffects } from "./data/object-cache.effects";
|
||||
import { RequestCacheEffects } from "./data/request-cache.effects";
|
||||
import { HrefIndexEffects } from "./index/href-index.effects";
|
||||
import { RequestEffects } from "./data/request.effects";
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { ObjectCacheEffects } from './data/object-cache.effects';
|
||||
import { RequestCacheEffects } from './data/request-cache.effects';
|
||||
import { HrefIndexEffects } from './index/href-index.effects';
|
||||
import { RequestEffects } from './data/request.effects';
|
||||
|
||||
export const coreEffects = [
|
||||
EffectsModule.run(RequestEffects),
|
||||
|
@@ -1,18 +1,19 @@
|
||||
import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
import { isNotEmpty } from "../shared/empty.util";
|
||||
import { FooterComponent } from "./footer/footer.component";
|
||||
import { DSpaceRESTv2Service } from "./dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { ObjectCacheService } from "./cache/object-cache.service";
|
||||
import { ResponseCacheService } from "./cache/response-cache.service";
|
||||
import { CollectionDataService } from "./data/collection-data.service";
|
||||
import { ItemDataService } from "./data/item-data.service";
|
||||
import { RequestService } from "./data/request.service";
|
||||
import { RemoteDataBuildService } from "./cache/builders/remote-data-build.service";
|
||||
import { CommunityDataService } from "./data/community-data.service";
|
||||
import { PaginationComponentOptions } from "../shared/pagination/pagination-component-options.model";
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { isNotEmpty } from '../shared/empty.util';
|
||||
import { FooterComponent } from './footer/footer.component';
|
||||
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { ObjectCacheService } from './cache/object-cache.service';
|
||||
import { ResponseCacheService } from './cache/response-cache.service';
|
||||
import { CollectionDataService } from './data/collection-data.service';
|
||||
import { ItemDataService } from './data/item-data.service';
|
||||
import { RequestService } from './data/request.service';
|
||||
import { RemoteDataBuildService } from './cache/builders/remote-data-build.service';
|
||||
import { CommunityDataService } from './data/community-data.service';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
|
||||
const IMPORTS = [
|
||||
CommonModule,
|
||||
@@ -46,12 +47,6 @@ const PROVIDERS = [
|
||||
providers: [...PROVIDERS]
|
||||
})
|
||||
export class CoreModule {
|
||||
constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (isNotEmpty(parentModule)) {
|
||||
throw new Error(
|
||||
'CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
@@ -61,4 +56,11 @@ export class CoreModule {
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (isNotEmpty(parentModule)) {
|
||||
throw new Error('CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { CacheState, cacheReducer } from "./cache/cache.reducers";
|
||||
import { IndexState, indexReducer } from "./index/index.reducers";
|
||||
import { DataState, dataReducer } from "./data/data.reducers";
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
|
||||
import { CacheState, cacheReducer } from './cache/cache.reducers';
|
||||
import { IndexState, indexReducer } from './index/index.reducers';
|
||||
import { DataState, dataReducer } from './data/data.reducers';
|
||||
|
||||
export interface CoreState {
|
||||
cache: CacheState,
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { DataService } from "./data.service";
|
||||
import { Collection } from "../shared/collection.model";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { NormalizedCollection } from "../cache/models/normalized-collection.model";
|
||||
import { CoreState } from "../core.reducers";
|
||||
import { RequestService } from "./request.service";
|
||||
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from "../../../config";
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class CollectionDataService extends DataService<NormalizedCollection, Collection> {
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { DataService } from "./data.service";
|
||||
import { Community } from "../shared/community.model";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { NormalizedCommunity } from "../cache/models/normalized-community.model";
|
||||
import { CoreState } from "../core.reducers";
|
||||
import { RequestService } from "./request.service";
|
||||
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from "../../../config";
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Community } from '../shared/community.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityDataService extends DataService<NormalizedCommunity, Community> {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { RequestState, requestReducer } from "./request.reducer";
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
|
||||
import { RequestState, requestReducer } from './request.reducer';
|
||||
|
||||
export interface DataState {
|
||||
request: RequestState
|
||||
|
@@ -1,18 +1,18 @@
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { hasValue, isNotEmpty } from "../../shared/empty.util";
|
||||
import { RemoteData } from "./remote-data";
|
||||
import { FindAllOptions, FindAllRequest, FindByIDRequest, Request } from "./request.models";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { RequestConfigureAction, RequestExecuteAction } from "./request.actions";
|
||||
import { CoreState } from "../core.reducers";
|
||||
import { RequestService } from "./request.service";
|
||||
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
|
||||
import { GenericConstructor } from "../shared/generic-constructor";
|
||||
import { Inject } from "@angular/core";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from "../../../config";
|
||||
import { RESTURLCombiner } from "../url-combiner/rest-url-combiner";
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindAllOptions, FindAllRequest, FindByIDRequest, Request } from './request.models';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { Inject } from '@angular/core';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
|
||||
export abstract class DataService<TNormalized extends CacheableObject, TDomain> {
|
||||
protected abstract objectCache: ObjectCacheService;
|
||||
@@ -32,17 +32,16 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
|
||||
protected getFindAllHref(options: FindAllOptions = {}): string {
|
||||
let result;
|
||||
let args = [];
|
||||
const args = [];
|
||||
|
||||
if (hasValue(options.scopeID)) {
|
||||
result = this.browseEndpoint;
|
||||
args.push(`scope=${options.scopeID}`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result = this.resourceEndpoint;
|
||||
}
|
||||
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === "number") {
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
||||
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
|
||||
args.push(`page=${options.currentPage - 1}`);
|
||||
}
|
||||
@@ -65,7 +64,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
return new RESTURLCombiner(this.EnvConfig, result).toString();
|
||||
}
|
||||
|
||||
findAll(options: FindAllOptions = {}): RemoteData<Array<TDomain>> {
|
||||
findAll(options: FindAllOptions = {}): RemoteData<TDomain[]> {
|
||||
const href = this.getFindAllHref(options);
|
||||
const request = new FindAllRequest(href, options);
|
||||
this.requestService.configure(request);
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { DataService } from "./data.service";
|
||||
import { Item } from "../shared/item.model";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { CoreState } from "../core.reducers";
|
||||
import { NormalizedItem } from "../cache/models/normalized-item.model";
|
||||
import { RequestService } from "./request.service";
|
||||
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from "../../../config";
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { NormalizedItem } from '../cache/models/normalized-item.model';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
|
@@ -1,18 +1,15 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { StoreActionTypes } from "../../store.actions";
|
||||
import { ResetObjectCacheTimestampsAction } from "../cache/object-cache.actions";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
|
||||
import { StoreActionTypes } from '../../store.actions';
|
||||
import { ResetObjectCacheTimestampsAction } from '../cache/object-cache.actions';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectCacheEffects {
|
||||
|
||||
constructor(
|
||||
private actions$: Actions
|
||||
) { }
|
||||
|
||||
/**
|
||||
* When the store is rehydrated in the browser, set all cache
|
||||
* timestamps to "now", because the time zone of the server can
|
||||
* timestamps to 'now', because the time zone of the server can
|
||||
* differ from the client.
|
||||
*
|
||||
* This assumes that the server cached everything a negligible
|
||||
@@ -22,4 +19,6 @@ export class ObjectCacheEffects {
|
||||
.ofType(StoreActionTypes.REHYDRATE)
|
||||
.map(() => new ResetObjectCacheTimestampsAction(new Date().getTime()));
|
||||
|
||||
constructor(private actions$: Actions) { }
|
||||
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { Observable } from "rxjs";
|
||||
import { PageInfo } from "../shared/page-info.model";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
|
||||
export enum RemoteDataState {
|
||||
RequestPending = <any>"RequestPending",
|
||||
ResponsePending = <any>"ResponsePending",
|
||||
Failed = <any>"Failed",
|
||||
Success = <any>"Success"
|
||||
RequestPending = 'RequestPending' as any,
|
||||
ResponsePending = 'ResponsePending' as any,
|
||||
Failed = 'Failed' as any,
|
||||
Success = 'Success' as any
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,14 +33,11 @@ export class RemoteData<T> {
|
||||
(requestPending, responsePending, isSuccessFul) => {
|
||||
if (requestPending) {
|
||||
return RemoteDataState.RequestPending
|
||||
}
|
||||
else if (responsePending) {
|
||||
} else if (responsePending) {
|
||||
return RemoteDataState.ResponsePending
|
||||
}
|
||||
else if (!isSuccessFul) {
|
||||
} else if (!isSuccessFul) {
|
||||
return RemoteDataState.Failed
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return RemoteDataState.Success
|
||||
}
|
||||
}
|
||||
@@ -47,36 +45,26 @@ export class RemoteData<T> {
|
||||
}
|
||||
|
||||
get isRequestPending(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => state == RemoteDataState.RequestPending)
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => state === RemoteDataState.RequestPending).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get isResponsePending(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => state == RemoteDataState.ResponsePending)
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => state === RemoteDataState.ResponsePending).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get isLoading(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => {
|
||||
return state == RemoteDataState.RequestPending
|
||||
return this.state.map((state) => {
|
||||
return state === RemoteDataState.RequestPending
|
||||
|| state === RemoteDataState.ResponsePending
|
||||
})
|
||||
.distinctUntilChanged();
|
||||
}).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get hasFailed(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => state == RemoteDataState.Failed)
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => state === RemoteDataState.Failed).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get hasSucceeded(): Observable<boolean> {
|
||||
return this.state
|
||||
.map(state => state == RemoteDataState.Success)
|
||||
.distinctUntilChanged();
|
||||
return this.state.map((state) => state === RemoteDataState.Success).distinctUntilChanged();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,18 +1,15 @@
|
||||
import { Injectable, Inject } from "@angular/core";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { ObjectCacheActionTypes } from "../cache/object-cache.actions";
|
||||
import { ResetResponseCacheTimestampsAction } from "../cache/response-cache.actions";
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
|
||||
import { ObjectCacheActionTypes } from '../cache/object-cache.actions';
|
||||
import { ResetResponseCacheTimestampsAction } from '../cache/response-cache.actions';
|
||||
|
||||
@Injectable()
|
||||
export class RequestCacheEffects {
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* When the store is rehydrated in the browser, set all cache
|
||||
* timestamps to "now", because the time zone of the server can
|
||||
* timestamps to 'now', because the time zone of the server can
|
||||
* differ from the client.
|
||||
*
|
||||
* This assumes that the server cached everything a negligible
|
||||
@@ -29,4 +26,7 @@ export class RequestCacheEffects {
|
||||
@Effect() fixTimestampsOnRehydrate = this.actions$
|
||||
.ofType(ObjectCacheActionTypes.RESET_TIMESTAMPS)
|
||||
.map(() => new ResetResponseCacheTimestampsAction(new Date().getTime()));
|
||||
|
||||
constructor(private actions$: Actions, ) { }
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { Request } from "./request.models";
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { Request } from './request.models';
|
||||
|
||||
/**
|
||||
* The list of RequestAction type definitions
|
||||
@@ -12,6 +12,7 @@ export const RequestActionTypes = {
|
||||
COMPLETE: type('dspace/core/data/request/COMPLETE')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class RequestConfigureAction implements Action {
|
||||
type = RequestActionTypes.CONFIGURE;
|
||||
payload: Request<CacheableObject>;
|
||||
@@ -49,6 +50,7 @@ export class RequestCompleteAction implements Action {
|
||||
this.payload = key;
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A type to encompass all RequestActions
|
||||
|
@@ -1,26 +1,27 @@
|
||||
import { Injectable, Inject } from "@angular/core";
|
||||
import { Actions, Effect } from "@ngrx/effects";
|
||||
import { DSpaceRESTv2Service } from "../dspace-rest-v2/dspace-rest-v2.service";
|
||||
import { ObjectCacheService } from "../cache/object-cache.service";
|
||||
import { DSpaceRESTV2Response } from "../dspace-rest-v2/dspace-rest-v2-response.model";
|
||||
import { DSpaceRESTv2Serializer } from "../dspace-rest-v2/dspace-rest-v2.serializer";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { Observable } from "rxjs";
|
||||
import { Response, SuccessResponse, ErrorResponse } from "../cache/response-cache.models";
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from "../../shared/empty.util";
|
||||
import { GlobalConfig, GLOBAL_CONFIG } from "../../../config";
|
||||
import { RequestEntry } from "./request.reducer";
|
||||
import {
|
||||
RequestActionTypes, RequestExecuteAction,
|
||||
RequestCompleteAction
|
||||
} from "./request.actions";
|
||||
import { ResponseCacheService } from "../cache/response-cache.service";
|
||||
import { RequestService } from "./request.service";
|
||||
import { NormalizedObjectFactory } from "../cache/models/normalized-object-factory";
|
||||
import { ResourceType } from "../shared/resource-type";
|
||||
import { RequestError } from "./request.models";
|
||||
import { PageInfo } from "../shared/page-info.model";
|
||||
import { NormalizedObject } from "../cache/models/normalized-object.model";
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
|
||||
// tslint:disable-next-line:import-blacklist
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { Response, SuccessResponse, ErrorResponse } from '../cache/response-cache.models';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { RequestActionTypes, RequestExecuteAction, RequestCompleteAction } from './request.actions';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
|
||||
import { ResourceType } from '../shared/resource-type';
|
||||
import { RequestError } from './request.models';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
|
||||
import { GlobalConfig, GLOBAL_CONFIG } from '../../../config';
|
||||
|
||||
function isObjectLevel(halObj: any) {
|
||||
return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
|
||||
@@ -38,23 +39,14 @@ function flattenSingleKeyObject(obj: any): any {
|
||||
return obj[keys[0]];
|
||||
}
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
class ProcessRequestDTO {
|
||||
[key: string]: NormalizedObject[]
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class RequestEffects {
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private config: GlobalConfig,
|
||||
private actions$: Actions,
|
||||
private restApi: DSpaceRESTv2Service,
|
||||
private objectCache: ObjectCacheService,
|
||||
private responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService
|
||||
) { }
|
||||
|
||||
@Effect() execute = this.actions$
|
||||
.ofType(RequestActionTypes.EXECUTE)
|
||||
.flatMap((action: RequestExecuteAction) => {
|
||||
@@ -65,48 +57,52 @@ export class RequestEffects {
|
||||
return this.restApi.get(entry.request.href)
|
||||
.map((data: DSpaceRESTV2Response) => {
|
||||
const processRequestDTO = this.process(data.payload, entry.request.href);
|
||||
const uuids = flattenSingleKeyObject(processRequestDTO).map(no => no.uuid);
|
||||
const uuids = flattenSingleKeyObject(processRequestDTO).map((no) => no.uuid);
|
||||
return new SuccessResponse(uuids, data.statusCode, this.processPageInfo(data.payload.page))
|
||||
}).do((response: Response) => this.responseCache.add(entry.request.href, response, this.config.cache.msToLive))
|
||||
}).do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
.map((response: Response) => new RequestCompleteAction(entry.request.href))
|
||||
.catch((error: RequestError) => Observable.of(new ErrorResponse(error))
|
||||
.do((response: Response) => this.responseCache.add(entry.request.href, response, this.config.cache.msToLive))
|
||||
.do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
.map((response: Response) => new RequestCompleteAction(entry.request.href)));
|
||||
});
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||
private actions$: Actions,
|
||||
private restApi: DSpaceRESTv2Service,
|
||||
private objectCache: ObjectCacheService,
|
||||
private responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService
|
||||
) { }
|
||||
|
||||
protected process(data: any, requestHref: string): ProcessRequestDTO {
|
||||
|
||||
if (isNotEmpty(data)) {
|
||||
if (isPaginatedResponse(data)) {
|
||||
return this.process(data._embedded, requestHref);
|
||||
}
|
||||
else if (isObjectLevel(data)) {
|
||||
return { "topLevel": this.deserializeAndCache(data, requestHref) };
|
||||
}
|
||||
else {
|
||||
let result = new ProcessRequestDTO();
|
||||
} else if (isObjectLevel(data)) {
|
||||
return { topLevel: this.deserializeAndCache(data, requestHref) };
|
||||
} else {
|
||||
const result = new ProcessRequestDTO();
|
||||
if (Array.isArray(data)) {
|
||||
result['topLevel'] = [];
|
||||
data.forEach(datum => {
|
||||
result.topLevel = [];
|
||||
data.forEach((datum) => {
|
||||
if (isPaginatedResponse(datum)) {
|
||||
const obj = this.process(datum, requestHref);
|
||||
result['topLevel'] = [...result['topLevel'], ...flattenSingleKeyObject(obj)];
|
||||
}
|
||||
else {
|
||||
result['topLevel'] = [...result['topLevel'], ...this.deserializeAndCache(datum, requestHref)];
|
||||
result.topLevel = [...result.topLevel, ...flattenSingleKeyObject(obj)];
|
||||
} else {
|
||||
result.topLevel = [...result.topLevel, ...this.deserializeAndCache(datum, requestHref)];
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Object.keys(data)
|
||||
.filter(property => data.hasOwnProperty(property))
|
||||
.filter(property => hasValue(data[property]))
|
||||
.forEach(property => {
|
||||
.filter((property) => data.hasOwnProperty(property))
|
||||
.filter((property) => hasValue(data[property]))
|
||||
.forEach((property) => {
|
||||
if (isPaginatedResponse(data[property])) {
|
||||
const obj = this.process(data[property], requestHref);
|
||||
result[property] = flattenSingleKeyObject(obj);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result[property] = this.deserializeAndCache(data[property], requestHref);
|
||||
}
|
||||
});
|
||||
@@ -119,11 +115,11 @@ export class RequestEffects {
|
||||
protected deserializeAndCache(obj, requestHref: string): NormalizedObject[] {
|
||||
if (Array.isArray(obj)) {
|
||||
let result = [];
|
||||
obj.forEach(o => result = [...result, ...this.deserializeAndCache(o, requestHref)])
|
||||
obj.forEach((o) => result = [...result, ...this.deserializeAndCache(o, requestHref)])
|
||||
return result;
|
||||
}
|
||||
|
||||
let type: ResourceType = obj["type"];
|
||||
const type: ResourceType = obj.type;
|
||||
if (hasValue(type)) {
|
||||
const normObjConstructor = NormalizedObjectFactory.getConstructor(type);
|
||||
|
||||
@@ -134,11 +130,11 @@ export class RequestEffects {
|
||||
if (isNotEmpty(obj._embedded)) {
|
||||
processed = this.process(obj._embedded, requestHref);
|
||||
}
|
||||
let normalizedObj = serializer.deserialize(obj);
|
||||
const normalizedObj = serializer.deserialize(obj);
|
||||
|
||||
if (isNotEmpty(processed)) {
|
||||
let linksOnly = {};
|
||||
Object.keys(processed).forEach(key => {
|
||||
const linksOnly = {};
|
||||
Object.keys(processed).forEach((key) => {
|
||||
linksOnly[key] = processed[key].map((no: NormalizedObject) => no.self);
|
||||
});
|
||||
Object.assign(normalizedObj, linksOnly);
|
||||
@@ -147,16 +143,14 @@ export class RequestEffects {
|
||||
this.addToObjectCache(normalizedObj, requestHref);
|
||||
return [normalizedObj];
|
||||
|
||||
}
|
||||
else {
|
||||
//TODO move check to Validator?
|
||||
} else {
|
||||
// TODO: move check to Validator?
|
||||
// throw new Error(`The server returned an object with an unknown a known type: ${type}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
//TODO move check to Validator
|
||||
} else {
|
||||
// TODO: move check to Validator
|
||||
// throw new Error(`The server returned an object without a type: ${JSON.stringify(obj)}`);
|
||||
return [];
|
||||
}
|
||||
@@ -166,16 +160,16 @@ export class RequestEffects {
|
||||
if (hasNoValue(co) || hasNoValue(co.uuid)) {
|
||||
throw new Error('The server returned an invalid object');
|
||||
}
|
||||
this.objectCache.add(co, this.config.cache.msToLive, requestHref);
|
||||
this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref);
|
||||
}
|
||||
|
||||
protected processPageInfo(pageObj: any): PageInfo {
|
||||
if (isNotEmpty(pageObj)) {
|
||||
return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { SortOptions } from "../cache/models/sort-options.model";
|
||||
import { PaginationComponentOptions } from "../../shared/pagination/pagination-component-options.model";
|
||||
import { GenericConstructor } from "../shared/generic-constructor";
|
||||
import { SortOptions } from '../cache/models/sort-options.model';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class Request<T> {
|
||||
constructor(
|
||||
public href: string,
|
||||
@@ -36,3 +37,4 @@ export class FindAllRequest<T> extends Request<T> {
|
||||
export class RequestError extends Error {
|
||||
statusText: string;
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import {
|
||||
RequestActionTypes, RequestAction, RequestConfigureAction,
|
||||
RequestExecuteAction, RequestCompleteAction
|
||||
} from "./request.actions";
|
||||
import { Request } from "./request.models";
|
||||
} from './request.actions';
|
||||
import { Request } from './request.models';
|
||||
|
||||
export class RequestEntry {
|
||||
request: Request<CacheableObject>;
|
||||
@@ -12,7 +12,6 @@ export class RequestEntry {
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface RequestState {
|
||||
[key: string]: RequestEntry
|
||||
}
|
||||
@@ -24,15 +23,15 @@ export const requestReducer = (state = initialState, action: RequestAction): Req
|
||||
switch (action.type) {
|
||||
|
||||
case RequestActionTypes.CONFIGURE: {
|
||||
return configureRequest(state, <RequestConfigureAction>action);
|
||||
return configureRequest(state, action as RequestConfigureAction);
|
||||
}
|
||||
|
||||
case RequestActionTypes.EXECUTE: {
|
||||
return executeRequest(state, <RequestExecuteAction>action);
|
||||
return executeRequest(state, action as RequestExecuteAction);
|
||||
}
|
||||
|
||||
case RequestActionTypes.COMPLETE: {
|
||||
return completeRequest(state, <RequestCompleteAction>action);
|
||||
return completeRequest(state, action as RequestCompleteAction);
|
||||
}
|
||||
|
||||
default: {
|
||||
|
@@ -1,16 +1,20 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { RequestEntry, RequestState } from "./request.reducer";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { Request } from "./request.models";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
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";
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { request } from 'http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { RequestEntry, RequestState } from './request.reducer';
|
||||
import { Request } from './request.models';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
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 { SuccessResponse } from '../cache/response-cache.models';
|
||||
|
||||
@Injectable()
|
||||
export class RequestService {
|
||||
@@ -46,9 +50,9 @@ export class RequestService {
|
||||
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);
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceUUIDs)
|
||||
.map((resourceUUIDs: string[]) => resourceUUIDs.every((uuid) => this.objectCache.has(uuid)))
|
||||
.subscribe((c) => isCached = c);
|
||||
}
|
||||
|
||||
const isPending = this.isPending(request.href);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DSpaceRESTv2Serializer } from "./dspace-rest-v2.serializer";
|
||||
import { autoserialize, autoserializeAs } from "cerialize";
|
||||
import { autoserialize, autoserializeAs } from 'cerialize';
|
||||
|
||||
import { DSpaceRESTv2Serializer } from './dspace-rest-v2.serializer';
|
||||
|
||||
class TestModel {
|
||||
@autoserialize
|
||||
@@ -9,55 +10,54 @@ class TestModel {
|
||||
name: string;
|
||||
|
||||
@autoserializeAs(TestModel)
|
||||
parents?: Array<TestModel>;
|
||||
parents?: TestModel[];
|
||||
}
|
||||
|
||||
const testModels = [
|
||||
{
|
||||
"id": "d4466d54-d73b-4d8f-b73f-c702020baa14",
|
||||
"name": "Model 1",
|
||||
id: 'd4466d54-d73b-4d8f-b73f-c702020baa14',
|
||||
name: 'Model 1',
|
||||
},
|
||||
{
|
||||
"id": "752a1250-949a-46ad-9bea-fbc45f0b656d",
|
||||
"name": "Model 2",
|
||||
id: '752a1250-949a-46ad-9bea-fbc45f0b656d',
|
||||
name: 'Model 2',
|
||||
}
|
||||
];
|
||||
|
||||
const testResponses = [
|
||||
{
|
||||
"_links": {
|
||||
"self": "/testmodels/9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
"parents": [
|
||||
{ "href": "/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78" },
|
||||
{ "href": "/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5" }
|
||||
_links: {
|
||||
self: '/testmodels/9e32a2e2-6b91-4236-a361-995ccdc14c60',
|
||||
parents: [
|
||||
{ href: '/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78' },
|
||||
{ href: '/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5' }
|
||||
]
|
||||
},
|
||||
"id": "9e32a2e2-6b91-4236-a361-995ccdc14c60",
|
||||
"type": "testModels",
|
||||
"name": "A Test Model"
|
||||
id: '9e32a2e2-6b91-4236-a361-995ccdc14c60',
|
||||
type: 'testModels',
|
||||
name: 'A Test Model'
|
||||
},
|
||||
{
|
||||
"_links": {
|
||||
"self": "/testmodels/598ce822-c357-46f3-ab70-63724d02d6ad",
|
||||
"parents": [
|
||||
{ "href": "/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5" },
|
||||
{ "href": "/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78" }
|
||||
_links: {
|
||||
self: '/testmodels/598ce822-c357-46f3-ab70-63724d02d6ad',
|
||||
parents: [
|
||||
{ href: '/testmodels/be8325f7-243b-49f4-8a4b-df2b793ff3b5' },
|
||||
{ href: '/testmodels/21539b1d-9ef1-4eda-9c77-49565b5bfb78' }
|
||||
]
|
||||
},
|
||||
"id": "598ce822-c357-46f3-ab70-63724d02d6ad",
|
||||
"type": "testModels",
|
||||
"name": "Another Test Model"
|
||||
id: '598ce822-c357-46f3-ab70-63724d02d6ad',
|
||||
type: 'testModels',
|
||||
name: 'Another Test Model'
|
||||
}
|
||||
];
|
||||
|
||||
const parentHrefRegex = /^\/testmodels\/(.+)$/g;
|
||||
|
||||
describe('DSpaceRESTv2Serializer', () => {
|
||||
|
||||
describe("DSpaceRESTv2Serializer", () => {
|
||||
describe('serialize', () => {
|
||||
|
||||
describe("serialize", () => {
|
||||
|
||||
it("should turn a model in to a valid document", () => {
|
||||
it('should turn a model in to a valid document', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
const doc = serializer.serialize(testModels[0]);
|
||||
expect(testModels[0].id).toBe(doc.id);
|
||||
@@ -66,9 +66,9 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
|
||||
});
|
||||
|
||||
describe("serializeArray", () => {
|
||||
describe('serializeArray', () => {
|
||||
|
||||
it("should turn an array of models in to a valid document", () => {
|
||||
it('should turn an array of models in to a valid document', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
const doc = serializer.serializeArray(testModels);
|
||||
|
||||
@@ -80,9 +80,9 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
|
||||
});
|
||||
|
||||
describe("deserialize", () => {
|
||||
describe('deserialize', () => {
|
||||
|
||||
it("should turn a valid document describing a single entity in to a valid model", () => {
|
||||
it('should turn a valid document describing a single entity in to a valid model', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
const model = serializer.deserialize(testResponses[0]);
|
||||
|
||||
@@ -90,12 +90,12 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
expect(model.name).toBe(testResponses[0].name);
|
||||
});
|
||||
|
||||
//TODO cant implement/test this yet - depends on how relationships
|
||||
// TODO: cant implement/test this yet - depends on how relationships
|
||||
// will be handled in the rest api
|
||||
// it("should retain relationship information", () => {
|
||||
// it('should retain relationship information', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = {
|
||||
// "_embedded": testResponses[0],
|
||||
// '_embedded': testResponses[0],
|
||||
// };
|
||||
//
|
||||
// const model = serializer.deserialize(doc);
|
||||
@@ -112,7 +112,7 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// });
|
||||
|
||||
// TODO enable once validation is enabled in the serializer
|
||||
// it("should throw an error when dealing with an invalid document", () => {
|
||||
// it('should throw an error when dealing with an invalid document', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = testResponses[0];
|
||||
//
|
||||
@@ -121,7 +121,7 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// }).toThrow();
|
||||
// });
|
||||
|
||||
it("should throw an error when dealing with a document describing an array", () => {
|
||||
it('should throw an error when dealing with a document describing an array', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
expect(() => {
|
||||
serializer.deserialize(testResponses);
|
||||
@@ -130,13 +130,13 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
|
||||
});
|
||||
|
||||
describe("deserializeArray", () => {
|
||||
describe('deserializeArray', () => {
|
||||
|
||||
//TODO rewrite to incorporate normalisation.
|
||||
// it("should turn a valid document describing a collection of objects in to an array of valid models", () => {
|
||||
// TODO: rewrite to incorporate normalisation.
|
||||
// it('should turn a valid document describing a collection of objects in to an array of valid models', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = {
|
||||
// "_embedded": testResponses
|
||||
// '_embedded': testResponses
|
||||
// };
|
||||
//
|
||||
// const models = serializer.deserializeArray(doc);
|
||||
@@ -147,12 +147,12 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// expect(models[1].name).toBe(doc._embedded[1].name);
|
||||
// });
|
||||
|
||||
//TODO cant implement/test this yet - depends on how relationships
|
||||
// TODO: cant implement/test this yet - depends on how relationships
|
||||
// will be handled in the rest api
|
||||
// it("should retain relationship information", () => {
|
||||
// it('should retain relationship information', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = {
|
||||
// "_embedded": testResponses,
|
||||
// '_embedded': testResponses,
|
||||
// };
|
||||
//
|
||||
// const models = serializer.deserializeArray(doc);
|
||||
@@ -169,7 +169,7 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// });
|
||||
|
||||
// TODO enable once validation is enabled in the serializer
|
||||
// it("should throw an error when dealing with an invalid document", () => {
|
||||
// it('should throw an error when dealing with an invalid document', () => {
|
||||
// const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
// const doc = testResponses[0];
|
||||
//
|
||||
@@ -178,10 +178,10 @@ describe("DSpaceRESTv2Serializer", () => {
|
||||
// }).toThrow();
|
||||
// });
|
||||
|
||||
it("should throw an error when dealing with a document describing a single model", () => {
|
||||
it('should throw an error when dealing with a document describing a single model', () => {
|
||||
const serializer = new DSpaceRESTv2Serializer(TestModel);
|
||||
const doc = {
|
||||
"_embedded": testResponses[0]
|
||||
_embedded: testResponses[0]
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { Serialize, Deserialize } from "cerialize";
|
||||
import { Serializer } from "../serializer";
|
||||
import { DSpaceRESTV2Response } from "./dspace-rest-v2-response.model";
|
||||
import { DSpaceRESTv2Validator } from "./dspace-rest-v2.validator";
|
||||
import { GenericConstructor } from "../shared/generic-constructor";
|
||||
import { hasNoValue, hasValue } from "../../shared/empty.util";
|
||||
import { Serialize, Deserialize } from 'cerialize';
|
||||
|
||||
import { Serializer } from '../serializer';
|
||||
import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model';
|
||||
import { DSpaceRESTv2Validator } from './dspace-rest-v2.validator';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { hasNoValue, hasValue } from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This Serializer turns responses from v2 of DSpace's REST API
|
||||
@@ -36,7 +37,7 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
|
||||
* @param models The array of models to serialize
|
||||
* @returns An object to send to the backend
|
||||
*/
|
||||
serializeArray(models: Array<T>): any {
|
||||
serializeArray(models: T[]): any {
|
||||
return Serialize(models, this.modelType);
|
||||
}
|
||||
|
||||
@@ -52,8 +53,8 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
|
||||
if (Array.isArray(response)) {
|
||||
throw new Error('Expected a single model, use deserializeArray() instead');
|
||||
}
|
||||
let normalized = Object.assign({}, response, this.normalizeLinks(response._links));
|
||||
return <T>Deserialize(normalized, this.modelType);
|
||||
const normalized = Object.assign({}, response, this.normalizeLinks(response._links));
|
||||
return Deserialize(normalized, this.modelType) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,28 +63,27 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
|
||||
* @param response An object returned by the backend
|
||||
* @returns an array of models of type T
|
||||
*/
|
||||
deserializeArray(response: any): Array<T> {
|
||||
//TODO enable validation, once rest data stabilizes
|
||||
deserializeArray(response: any): T[] {
|
||||
// TODO: enable validation, once rest data stabilizes
|
||||
// new DSpaceRESTv2Validator(response).validate();
|
||||
if (!Array.isArray(response)) {
|
||||
throw new Error('Expected an Array, use deserialize() instead');
|
||||
}
|
||||
let normalized = response.map((resource) => {
|
||||
const normalized = response.map((resource) => {
|
||||
return Object.assign({}, resource, this.normalizeLinks(resource._links));
|
||||
});
|
||||
|
||||
return <Array<T>>Deserialize(normalized, this.modelType);
|
||||
return Deserialize(normalized, this.modelType) as T[];
|
||||
}
|
||||
|
||||
private normalizeLinks(links: any): any {
|
||||
let normalizedLinks = links;
|
||||
for (let link in normalizedLinks) {
|
||||
const normalizedLinks = links;
|
||||
for (const link in normalizedLinks) {
|
||||
if (Array.isArray(normalizedLinks[link])) {
|
||||
normalizedLinks[link] = normalizedLinks[link].map(linkedResource => {
|
||||
normalizedLinks[link] = normalizedLinks[link].map((linkedResource) => {
|
||||
return linkedResource.href;
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
normalizedLinks[link] = normalizedLinks[link].href;
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,18 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Http, RequestOptionsArgs } from '@angular/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { RESTURLCombiner } from "../url-combiner/rest-url-combiner";
|
||||
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model';
|
||||
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
import { DSpaceRESTV2Response } from "./dspace-rest-v2-response.model";
|
||||
|
||||
/**
|
||||
* Service to access DSpace's REST API
|
||||
*/
|
||||
@Injectable()
|
||||
export class DSpaceRESTv2Service {
|
||||
|
||||
constructor(private http: Http, @Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) {
|
||||
|
||||
}
|
||||
@@ -27,8 +29,8 @@ export class DSpaceRESTv2Service {
|
||||
*/
|
||||
get(absoluteURL: string, options?: RequestOptionsArgs): Observable<DSpaceRESTV2Response> {
|
||||
return this.http.get(absoluteURL, options)
|
||||
.map(res => ({ payload: res.json(), statusCode: res.statusText }))
|
||||
.catch(err => {
|
||||
.map((res) => ({ payload: res.json(), statusCode: res.statusText }))
|
||||
.catch((err) => {
|
||||
console.log('Error: ', err);
|
||||
return Observable.throw(err);
|
||||
});
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Validator } from 'jsonschema';
|
||||
|
||||
import schema from './dspace-rest-v2.schema.json'
|
||||
import { Validator } from "jsonschema";
|
||||
|
||||
/**
|
||||
* Verifies a document is a valid response from
|
||||
@@ -22,11 +23,10 @@ export class DSpaceRESTv2Validator {
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
const message = result.errors
|
||||
.map((error) => error.message)
|
||||
.join("\n");
|
||||
.join('\n');
|
||||
throw new Error(message);
|
||||
}
|
||||
else {
|
||||
throw new Error("JSON API validation failed for an unknown reason");
|
||||
} else {
|
||||
throw new Error('JSON API validation failed for an unknown reason');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,19 +5,23 @@ import {
|
||||
inject,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import {
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
DebugElement
|
||||
} from "@angular/core";
|
||||
} from '@angular/core';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateModule, TranslateLoader } from "@ngx-translate/core";
|
||||
import { Store, StoreModule } from "@ngrx/store";
|
||||
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
|
||||
// Load the implementations that should be tested
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MockTranslateLoader } from "../../shared/testing/mock-translate-loader";
|
||||
import { MockTranslateLoader } from '../../shared/testing/mock-translate-loader';
|
||||
|
||||
let comp: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
@@ -1,18 +1,12 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-footer',
|
||||
styleUrls: ['footer.component.scss'],
|
||||
templateUrl: 'footer.component.html'
|
||||
})
|
||||
export class FooterComponent implements OnInit {
|
||||
export class FooterComponent {
|
||||
|
||||
dateObj: number = Date.now();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../../shared/ngrx/type";
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
|
||||
/**
|
||||
* The list of HrefIndexAction type definitions
|
||||
@@ -9,6 +10,7 @@ export const HrefIndexActionTypes = {
|
||||
REMOVE_UUID: type('dspace/core/index/href/REMOVE_UUID')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
/**
|
||||
* An ngrx action to add an href to the index
|
||||
*/
|
||||
@@ -48,11 +50,11 @@ export class RemoveUUIDFromHrefIndexAction implements Action {
|
||||
constructor(uuid: string) {
|
||||
this.payload = uuid;
|
||||
}
|
||||
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A type to encompass all HrefIndexActions
|
||||
*/
|
||||
export type HrefIndexAction
|
||||
= AddToHrefIndexAction
|
||||
| RemoveUUIDFromHrefIndexAction;
|
||||
export type HrefIndexAction = AddToHrefIndexAction | RemoveUUIDFromHrefIndexAction;
|
||||
|
@@ -1,19 +1,16 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Effect, Actions } from "@ngrx/effects";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Effect, Actions } from '@ngrx/effects';
|
||||
|
||||
import {
|
||||
ObjectCacheActionTypes, AddToObjectCacheAction,
|
||||
RemoveFromObjectCacheAction
|
||||
} from "../cache/object-cache.actions";
|
||||
import { AddToHrefIndexAction, RemoveUUIDFromHrefIndexAction } from "./href-index.actions";
|
||||
import { hasValue } from "../../shared/empty.util";
|
||||
} from '../cache/object-cache.actions';
|
||||
import { AddToHrefIndexAction, RemoveUUIDFromHrefIndexAction } from './href-index.actions';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
|
||||
@Injectable()
|
||||
export class HrefIndexEffects {
|
||||
|
||||
constructor(
|
||||
private actions$: Actions
|
||||
) { }
|
||||
|
||||
@Effect() add$ = this.actions$
|
||||
.ofType(ObjectCacheActionTypes.ADD)
|
||||
.filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.self))
|
||||
@@ -29,4 +26,9 @@ export class HrefIndexEffects {
|
||||
.map((action: RemoveFromObjectCacheAction) => {
|
||||
return new RemoveUUIDFromHrefIndexAction(action.payload);
|
||||
});
|
||||
|
||||
constructor(private actions$: Actions) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import {
|
||||
HrefIndexAction, HrefIndexActionTypes, AddToHrefIndexAction,
|
||||
HrefIndexAction,
|
||||
HrefIndexActionTypes,
|
||||
AddToHrefIndexAction,
|
||||
RemoveUUIDFromHrefIndexAction
|
||||
} from "./href-index.actions";
|
||||
} from './href-index.actions';
|
||||
|
||||
export interface HrefIndexState {
|
||||
[href: string]: string
|
||||
}
|
||||
@@ -13,11 +16,11 @@ export const hrefIndexReducer = (state = initialState, action: HrefIndexAction):
|
||||
switch (action.type) {
|
||||
|
||||
case HrefIndexActionTypes.ADD: {
|
||||
return addToHrefIndex(state, <AddToHrefIndexAction>action);
|
||||
return addToHrefIndex(state, action as AddToHrefIndexAction);
|
||||
}
|
||||
|
||||
case HrefIndexActionTypes.REMOVE_UUID: {
|
||||
return removeUUIDFromHrefIndex(state, <RemoveUUIDFromHrefIndexAction>action)
|
||||
return removeUUIDFromHrefIndex(state, action as RemoveUUIDFromHrefIndexAction)
|
||||
}
|
||||
|
||||
default: {
|
||||
@@ -33,8 +36,8 @@ function addToHrefIndex(state: HrefIndexState, action: AddToHrefIndexAction): Hr
|
||||
}
|
||||
|
||||
function removeUUIDFromHrefIndex(state: HrefIndexState, action: RemoveUUIDFromHrefIndexAction): HrefIndexState {
|
||||
let newState = Object.create(null);
|
||||
for (let href in state) {
|
||||
const newState = Object.create(null);
|
||||
for (const href in state) {
|
||||
if (state[href] !== action.payload) {
|
||||
newState[href] = state[href];
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { combineReducers } from "@ngrx/store";
|
||||
import { HrefIndexState, hrefIndexReducer } from "./href-index.reducer";
|
||||
import { combineReducers } from '@ngrx/store';
|
||||
|
||||
import { HrefIndexState, hrefIndexReducer } from './href-index.reducer';
|
||||
|
||||
export interface IndexState {
|
||||
href: HrefIndexState
|
||||
|
@@ -18,7 +18,7 @@ export interface Serializer<T> {
|
||||
* @param models The array of models to serialize
|
||||
* @returns An object to send to the backend
|
||||
*/
|
||||
serializeArray(models: Array<T>): any;
|
||||
serializeArray(models: T[]): any;
|
||||
|
||||
/**
|
||||
* Convert a response from the backend in to a model.
|
||||
@@ -34,5 +34,5 @@ export interface Serializer<T> {
|
||||
* @param response An object returned by the backend
|
||||
* @returns an array of models of type T
|
||||
*/
|
||||
deserializeArray(response: any): Array<T>;
|
||||
deserializeArray(response: any): T[];
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { Item } from "./item.model";
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Item } from './item.model';
|
||||
|
||||
export class Bitstream extends DSpaceObject {
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { Item } from "./item.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
|
||||
export class Bundle extends DSpaceObject {
|
||||
/**
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { Item } from "./item.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
|
||||
export class Collection extends DSpaceObject {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description
|
||||
*/
|
||||
get introductoryText(): string {
|
||||
return this.findMetadata("dc.description");
|
||||
return this.findMetadata('dc.description');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description.abstract
|
||||
*/
|
||||
get shortDescription(): string {
|
||||
return this.findMetadata("dc.description.abstract");
|
||||
return this.findMetadata('dc.description.abstract');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.rights
|
||||
*/
|
||||
get copyrightText(): string {
|
||||
return this.findMetadata("dc.rights");
|
||||
return this.findMetadata('dc.rights');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.rights.license
|
||||
*/
|
||||
get license(): string {
|
||||
return this.findMetadata("dc.rights.license");
|
||||
return this.findMetadata('dc.rights.license');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,7 @@ export class Collection extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description.tableofcontents
|
||||
*/
|
||||
get sidebarText(): string {
|
||||
return this.findMetadata("dc.description.tableofcontents");
|
||||
return this.findMetadata('dc.description.tableofcontents');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { Collection } from "./collection.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Collection } from './collection.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
|
||||
export class Community extends DSpaceObject {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Community extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description
|
||||
*/
|
||||
get introductoryText(): string {
|
||||
return this.findMetadata("dc.description");
|
||||
return this.findMetadata('dc.description');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export class Community extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description.abstract
|
||||
*/
|
||||
get shortDescription(): string {
|
||||
return this.findMetadata("dc.description.abstract");
|
||||
return this.findMetadata('dc.description.abstract');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ export class Community extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.rights
|
||||
*/
|
||||
get copyrightText(): string {
|
||||
return this.findMetadata("dc.rights");
|
||||
return this.findMetadata('dc.rights');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ export class Community extends DSpaceObject {
|
||||
* Corresponds to the metadata field dc.description.tableofcontents
|
||||
*/
|
||||
get sidebarText(): string {
|
||||
return this.findMetadata("dc.description.tableofcontents");
|
||||
return this.findMetadata('dc.description.tableofcontents');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { Metadatum } from "./metadatum.model"
|
||||
import { isEmpty, isNotEmpty } from "../../shared/empty.util";
|
||||
import { CacheableObject } from "../cache/object-cache.reducer";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { ResourceType } from "./resource-type";
|
||||
import { Metadatum } from './metadatum.model'
|
||||
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { ResourceType } from './resource-type';
|
||||
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
@@ -34,7 +34,7 @@ export abstract class DSpaceObject implements CacheableObject {
|
||||
/**
|
||||
* An array containing all metadata of this DSpaceObject
|
||||
*/
|
||||
metadata: Array<Metadatum>;
|
||||
metadata: Metadatum[];
|
||||
|
||||
/**
|
||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||
@@ -58,15 +58,12 @@ export abstract class DSpaceObject implements CacheableObject {
|
||||
* @return string
|
||||
*/
|
||||
findMetadata(key: string, language?: string): string {
|
||||
const metadatum = this.metadata
|
||||
.find((metadatum: Metadatum) => {
|
||||
return metadatum.key === key &&
|
||||
(isEmpty(language) || metadatum.language === language)
|
||||
const metadatum = this.metadata.find((m: Metadatum) => {
|
||||
return m.key === key && (isEmpty(language) || m.language === language)
|
||||
});
|
||||
if (isNotEmpty(metadatum)) {
|
||||
return metadatum.value;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -81,10 +78,10 @@ export abstract class DSpaceObject implements CacheableObject {
|
||||
* @param key(s)
|
||||
* @return Array<Metadatum>
|
||||
*/
|
||||
filterMetadata(keys: string[]): Array<Metadatum> {
|
||||
return this.metadata
|
||||
.filter((metadatum: Metadatum) => {
|
||||
return keys.some(key => key === metadatum.key);
|
||||
filterMetadata(keys: string[]): Metadatum[] {
|
||||
return this.metadata.filter((metadatum: Metadatum) => {
|
||||
return keys.some((key) => key === metadatum.key);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,4 +3,6 @@
|
||||
* more details:
|
||||
* https://github.com/Microsoft/TypeScript/issues/204#issuecomment-257722306
|
||||
*/
|
||||
/* tslint:disable:interface-over-type-literal */
|
||||
export type GenericConstructor<T> = { new(...args: any[]): T };
|
||||
/* tslint:enable:interface-over-type-literal */
|
||||
|
@@ -1,22 +1,21 @@
|
||||
import { Item } from "./item.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { isEmpty } from "../../shared/empty.util";
|
||||
import { PageInfo } from "./page-info.model";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { isEmpty } from '../../shared/empty.util';
|
||||
import { PageInfo } from './page-info.model';
|
||||
|
||||
describe('Item', () => {
|
||||
|
||||
|
||||
let item: Item;
|
||||
const thumbnailBundleName = "THUMBNAIL";
|
||||
const originalBundleName = "ORIGINAL";
|
||||
const thumbnailPath = "thumbnail.jpg";
|
||||
const bitstream1Path = "document.pdf";
|
||||
const bitstream2Path = "otherfile.doc";
|
||||
const thumbnailBundleName = 'THUMBNAIL';
|
||||
const originalBundleName = 'ORIGINAL';
|
||||
const thumbnailPath = 'thumbnail.jpg';
|
||||
const bitstream1Path = 'document.pdf';
|
||||
const bitstream2Path = 'otherfile.doc';
|
||||
|
||||
const nonExistingBundleName = "c1e568f7-d14e-496b-bdd7-07026998cc00";
|
||||
const nonExistingBundleName = 'c1e568f7-d14e-496b-bdd7-07026998cc00';
|
||||
let bitstreams;
|
||||
let remoteDataThumbnail;
|
||||
let remoteDataFiles;
|
||||
@@ -37,7 +36,6 @@ describe('Item', () => {
|
||||
remoteDataFiles = createRemoteDataObject(bitstreams);
|
||||
remoteDataAll = createRemoteDataObject([...bitstreams, thumbnail]);
|
||||
|
||||
|
||||
// Create Bundles
|
||||
|
||||
const bundles =
|
||||
@@ -52,51 +50,47 @@ describe('Item', () => {
|
||||
bitstreams: remoteDataFiles
|
||||
}];
|
||||
|
||||
|
||||
item = Object.assign(new Item(), { bitstreams: remoteDataAll });
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should return the bitstreams related to this item with the specified bundle name', () => {
|
||||
const bitObs: Observable<Bitstream[]> = item.getBitstreamsByBundleName(thumbnailBundleName);
|
||||
bitObs.take(1).subscribe(bs =>
|
||||
expect(bs.every(b => b.name === thumbnailBundleName)).toBeTruthy());
|
||||
bitObs.take(1).subscribe((bs) =>
|
||||
expect(bs.every((b) => b.name === thumbnailBundleName)).toBeTruthy());
|
||||
});
|
||||
|
||||
it('should return an empty array when no bitstreams with this bundleName exist for this item', () => {
|
||||
const bitstreams: Observable<Bitstream[]> = item.getBitstreamsByBundleName(nonExistingBundleName);
|
||||
bitstreams.take(1).subscribe(bs => expect(isEmpty(bs)).toBeTruthy());
|
||||
const bs: Observable<Bitstream[]> = item.getBitstreamsByBundleName(nonExistingBundleName);
|
||||
bs.take(1).subscribe((b) => expect(isEmpty(b)).toBeTruthy());
|
||||
});
|
||||
|
||||
|
||||
describe("get thumbnail", () => {
|
||||
describe('get thumbnail', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(item, 'getBitstreamsByBundleName').and.returnValue(Observable.of([remoteDataThumbnail]));
|
||||
});
|
||||
|
||||
it('should return the thumbnail of this item', () => {
|
||||
let path: string = thumbnailPath;
|
||||
let bitstream: Observable<Bitstream> = item.getThumbnail();
|
||||
bitstream.map(b => expect(b.retrieve).toBe(path));
|
||||
const path: string = thumbnailPath;
|
||||
const bitstream: Observable<Bitstream> = item.getThumbnail();
|
||||
bitstream.map((b) => expect(b.retrieve).toBe(path));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("get files", () => {
|
||||
describe('get files', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(item, 'getBitstreamsByBundleName').and.returnValue(Observable.of(bitstreams));
|
||||
});
|
||||
|
||||
it('should return all bitstreams with "ORIGINAL" as bundleName', () => {
|
||||
let paths = [bitstream1Path, bitstream2Path];
|
||||
it("should return all bitstreams with 'ORIGINAL' as bundleName", () => {
|
||||
const paths = [bitstream1Path, bitstream2Path];
|
||||
|
||||
let files: Observable<Bitstream[]> = item.getFiles();
|
||||
const files: Observable<Bitstream[]> = item.getFiles();
|
||||
let index = 0;
|
||||
files.map(f => expect(f.length).toBe(2));
|
||||
files.map((f) => expect(f.length).toBe(2));
|
||||
files.subscribe(
|
||||
array => array.forEach(
|
||||
file => {
|
||||
(array) => array.forEach(
|
||||
(file) => {
|
||||
expect(file.retrieve).toBe(paths[index]);
|
||||
index++;
|
||||
}
|
||||
@@ -106,16 +100,15 @@ describe('Item', () => {
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
function createRemoteDataObject(object: Object) {
|
||||
const self = "";
|
||||
function createRemoteDataObject(object: any) {
|
||||
const self = '';
|
||||
const requestPending = Observable.of(false);
|
||||
const responsePending = Observable.of(false);
|
||||
const isSuccessful = Observable.of(true);
|
||||
const errorMessage = Observable.of(undefined);
|
||||
const statusCode = Observable.of("200");
|
||||
const statusCode = Observable.of('200');
|
||||
const pageInfo = Observable.of(new PageInfo());
|
||||
const payload = Observable.of(object);
|
||||
return new RemoteData(
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { DSpaceObject } from "./dspace-object.model";
|
||||
import { Collection } from "./collection.model";
|
||||
import { RemoteData } from "../data/remote-data";
|
||||
import { Bitstream } from "./bitstream.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { isNotEmpty } from "../../shared/empty.util";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Collection } from './collection.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
|
||||
export class Item extends DSpaceObject {
|
||||
|
||||
@@ -48,38 +49,36 @@ export class Item extends DSpaceObject {
|
||||
|
||||
bitstreams: RemoteData<Bitstream[]>;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the thumbnail of this item
|
||||
* @returns {Observable<Bitstream>} the primaryBitstream of the "THUMBNAIL" bundle
|
||||
* @returns {Observable<Bitstream>} the primaryBitstream of the 'THUMBNAIL' bundle
|
||||
*/
|
||||
getThumbnail(): Observable<Bitstream> {
|
||||
//TODO currently this just picks the first thumbnail
|
||||
// TODO: currently this just picks the first thumbnail
|
||||
// should be adjusted when we have a way to determine
|
||||
// the primary thumbnail from rest
|
||||
return this.getBitstreamsByBundleName("THUMBNAIL")
|
||||
.filter(thumbnails => isNotEmpty(thumbnails))
|
||||
.map(thumbnails => thumbnails[0])
|
||||
return this.getBitstreamsByBundleName('THUMBNAIL')
|
||||
.filter((thumbnails) => isNotEmpty(thumbnails))
|
||||
.map((thumbnails) => thumbnails[0])
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the thumbnail for the given original of this item
|
||||
* @returns {Observable<Bitstream>} the primaryBitstream of the "THUMBNAIL" bundle
|
||||
* @returns {Observable<Bitstream>} the primaryBitstream of the 'THUMBNAIL' bundle
|
||||
*/
|
||||
getThumbnailForOriginal(original: Bitstream): Observable<Bitstream> {
|
||||
return this.getBitstreamsByBundleName("THUMBNAIL").map(files => files
|
||||
.find(thumbnail => thumbnail
|
||||
.name.startsWith(original.name)
|
||||
)
|
||||
).startWith(undefined);
|
||||
return this.getBitstreamsByBundleName('THUMBNAIL')
|
||||
.map((files) => {
|
||||
return files.find((thumbnail) => thumbnail.name.startsWith(original.name))
|
||||
}).startWith(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all files that should be displayed on the item page of this item
|
||||
* @returns {Observable<Array<Observable<Bitstream>>>} an array of all Bitstreams in the "ORIGINAL" bundle
|
||||
* @returns {Observable<Array<Observable<Bitstream>>>} an array of all Bitstreams in the 'ORIGINAL' bundle
|
||||
*/
|
||||
getFiles(): Observable<Bitstream[]> {
|
||||
return this.getBitstreamsByBundleName("ORIGINAL");
|
||||
return this.getBitstreamsByBundleName('ORIGINAL');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,9 +88,9 @@ export class Item extends DSpaceObject {
|
||||
*/
|
||||
getBitstreamsByBundleName(bundleName: string): Observable<Bitstream[]> {
|
||||
return this.bitstreams.payload.startWith([])
|
||||
.map(bitstreams => bitstreams
|
||||
.filter(bitstream => bitstream.bundleName === bundleName)
|
||||
);
|
||||
.map((bitstreams) => {
|
||||
return bitstreams.filter((bitstream) => bitstream.bundleName === bundleName)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { autoserialize } from "cerialize";
|
||||
import { autoserialize } from 'cerialize';
|
||||
|
||||
export class Metadatum {
|
||||
|
||||
/**
|
||||
@@ -18,4 +19,5 @@ export class Metadatum {
|
||||
*/
|
||||
@autoserialize
|
||||
value: string;
|
||||
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { autoserialize, autoserializeAs } from "cerialize";
|
||||
import { autoserialize, autoserializeAs } from 'cerialize';
|
||||
|
||||
/**
|
||||
* Represents the state of a paginated response
|
||||
@@ -27,4 +27,5 @@ export class PageInfo {
|
||||
*/
|
||||
@autoserializeAs(Number, 'number')
|
||||
currentPage: number;
|
||||
|
||||
}
|
||||
|
@@ -3,10 +3,10 @@
|
||||
* https://github.com/Microsoft/TypeScript/pull/15486
|
||||
*/
|
||||
export enum ResourceType {
|
||||
Bundle = <any>"bundle",
|
||||
Bitstream = <any>"bitstream",
|
||||
BitstreamFormat = <any>"bitstreamformat",
|
||||
Item = <any>"item",
|
||||
Collection = <any>"collection",
|
||||
Community = <any>"community"
|
||||
Bundle = 'bundle' as any,
|
||||
Bitstream = 'bitstream' as any,
|
||||
BitstreamFormat = 'bitstreamformat' as any,
|
||||
Item = 'item' as any,
|
||||
Collection = 'collection' as any,
|
||||
Community = 'community' as any,
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { URLCombiner } from "./url-combiner";
|
||||
import { URLCombiner } from './url-combiner';
|
||||
|
||||
import { GlobalConfig } from '../../../config';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { GlobalConfig } from '../../../config';
|
||||
* TODO write tests once GlobalConfig becomes injectable
|
||||
*/
|
||||
export class RESTURLCombiner extends URLCombiner {
|
||||
constructor(EnvConfig: GlobalConfig, ...parts: Array<string>) {
|
||||
constructor(EnvConfig: GlobalConfig, ...parts: string[]) {
|
||||
super(EnvConfig.rest.baseUrl, EnvConfig.rest.nameSpace, ...parts);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { URLCombiner } from "./url-combiner";
|
||||
import { GlobalConfig } from "../../../config";
|
||||
import { URLCombiner } from './url-combiner';
|
||||
import { GlobalConfig } from '../../../config';
|
||||
|
||||
/**
|
||||
* Combines a variable number of strings representing parts
|
||||
@@ -8,7 +8,7 @@ import { GlobalConfig } from "../../../config";
|
||||
* TODO write tests once GlobalConfig becomes injectable
|
||||
*/
|
||||
export class UIURLCombiner extends URLCombiner {
|
||||
constructor(EnvConfig: GlobalConfig, ...parts: Array<string>) {
|
||||
constructor(EnvConfig: GlobalConfig, ...parts: string[]) {
|
||||
super(EnvConfig.ui.baseUrl, EnvConfig.ui.nameSpace, ...parts);
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +1,23 @@
|
||||
import { URLCombiner } from "./url-combiner";
|
||||
import { URLCombiner } from './url-combiner';
|
||||
|
||||
describe("URLCombiner", () => {
|
||||
describe('URLCombiner', () => {
|
||||
|
||||
it("should return a valid URL when created with a valid set of url parts", () => {
|
||||
it('should return a valid URL when created with a valid set of url parts', () => {
|
||||
const url = new URLCombiner('http://foo.com', 'bar', 'id', '5').toString();
|
||||
expect(url).toBe('http://foo.com/bar/id/5');
|
||||
});
|
||||
|
||||
it("should return a URL with the protocol followed by two slashes", () => {
|
||||
it('should return a URL with the protocol followed by two slashes', () => {
|
||||
const url = new URLCombiner('http:/foo.com').toString();
|
||||
expect(url).toBe('http://foo.com');
|
||||
});
|
||||
|
||||
it("should return a URL with a single slash between each part", () => {
|
||||
it('should return a URL with a single slash between each part', () => {
|
||||
const url = new URLCombiner('http://foo.com/', '/bar/', '//id', '///5').toString();
|
||||
expect(url).toBe('http://foo.com/bar/id/5');
|
||||
});
|
||||
|
||||
it("should return a URL without a trailing slash before its parameters", () => {
|
||||
it('should return a URL without a trailing slash before its parameters', () => {
|
||||
const url1 = new URLCombiner('http://foo.com/', '?bar=25').toString();
|
||||
const url2 = new URLCombiner('http://foo.com/', '#bar').toString();
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("URLCombiner", () => {
|
||||
expect(url2).toBe('http://foo.com#bar');
|
||||
});
|
||||
|
||||
it("should return an empty string when created without url parts", () => {
|
||||
it('should return an empty string when created without url parts', () => {
|
||||
const url = new URLCombiner().toString();
|
||||
expect(url).toBe('');
|
||||
});
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { isEmpty } from "../../shared/empty.util";
|
||||
import { isEmpty } from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* Combines a variable number of strings representing parts
|
||||
* of a URL in to a single, normalized URL
|
||||
*/
|
||||
export class URLCombiner {
|
||||
private parts: Array<string>;
|
||||
private parts: string[];
|
||||
|
||||
/**
|
||||
* Creates a new URLCombiner
|
||||
@@ -13,7 +13,7 @@ export class URLCombiner {
|
||||
* @param parts
|
||||
* a variable number of strings representing parts of a URL
|
||||
*/
|
||||
constructor(...parts: Array<string>) {
|
||||
constructor(...parts: string[]) {
|
||||
// can't do this in the constructor signature,
|
||||
// because of the spread operator
|
||||
this.parts = parts;
|
||||
@@ -32,8 +32,7 @@ export class URLCombiner {
|
||||
toString(): string {
|
||||
if (isEmpty(this.parts)) {
|
||||
return '';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let url = this.parts.join('/');
|
||||
|
||||
// make sure protocol is followed by two slashes
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Action } from "@ngrx/store";
|
||||
import { type } from "../shared/ngrx/type";
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../shared/ngrx/type';
|
||||
|
||||
/**
|
||||
* For each action type in an action group, make a simple
|
||||
@@ -15,23 +16,19 @@ export const HeaderActionTypes = {
|
||||
TOGGLE: type('dspace/header/TOGGLE')
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class HeaderCollapseAction implements Action {
|
||||
type = HeaderActionTypes.COLLAPSE;
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
|
||||
export class HeaderExpandAction implements Action {
|
||||
type = HeaderActionTypes.EXPAND;
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
|
||||
export class HeaderToggleAction implements Action {
|
||||
type = HeaderActionTypes.TOGGLE;
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* Export a type alias of all actions in this action group
|
||||
|
@@ -1,13 +1,16 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { HeaderComponent } from "./header.component";
|
||||
import { Store, StoreModule } from "@ngrx/store";
|
||||
import { HeaderState } from "./header.reducer";
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { HeaderComponent } from './header.component';
|
||||
import { HeaderState } from './header.reducer';
|
||||
import { HeaderToggleAction } from './header.actions';
|
||||
|
||||
import Spy = jasmine.Spy;
|
||||
import { HeaderToggleAction } from "./header.actions";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { NgbCollapseModule } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
let comp: HeaderComponent;
|
||||
let fixture: ComponentFixture<HeaderComponent>;
|
||||
@@ -30,7 +33,6 @@ describe('HeaderComponent', () => {
|
||||
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
|
||||
store = fixture.debugElement.injector.get(Store);
|
||||
spyOn(store, 'dispatch');
|
||||
});
|
||||
@@ -38,17 +40,17 @@ describe('HeaderComponent', () => {
|
||||
describe('when the toggle button is clicked', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
let navbarToggler = fixture.debugElement.query(By.css('.navbar-toggler'));
|
||||
const navbarToggler = fixture.debugElement.query(By.css('.navbar-toggler'));
|
||||
navbarToggler.triggerEventHandler('click', null);
|
||||
});
|
||||
|
||||
it("should dispatch a HeaderToggleAction", () => {
|
||||
it('should dispatch a HeaderToggleAction', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new HeaderToggleAction());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("when navCollapsed in the store is true", () => {
|
||||
describe('when navCollapsed in the store is true', () => {
|
||||
let menu: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -57,13 +59,13 @@ describe('HeaderComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should close the menu", () => {
|
||||
it('should close the menu', () => {
|
||||
expect(menu.classList).not.toContain('show');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("when navCollapsed in the store is false", () => {
|
||||
describe('when navCollapsed in the store is false', () => {
|
||||
let menu: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -72,7 +74,7 @@ describe('HeaderComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should open the menu", () => {
|
||||
it('should open the menu', () => {
|
||||
expect(menu.classList).toContain('show');
|
||||
});
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { HeaderState } from "./header.reducer";
|
||||
import { Observable } from "rxjs";
|
||||
import { HeaderToggleAction } from "./header.actions";
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { HeaderState } from './header.reducer';
|
||||
import { HeaderToggleAction } from './header.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-header',
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { TestBed, inject } from "@angular/core/testing";
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { EffectsTestingModule, EffectsRunner } from '@ngrx/effects/testing';
|
||||
import { HeaderEffects } from "./header.effects";
|
||||
import { HeaderCollapseAction } from "./header.actions";
|
||||
import { HostWindowResizeAction } from "../shared/host-window.actions";
|
||||
import { routerActions } from "@ngrx/router-store";
|
||||
import { routerActions } from '@ngrx/router-store';
|
||||
|
||||
import { HeaderEffects } from './header.effects';
|
||||
import { HeaderCollapseAction } from './header.actions';
|
||||
import { HostWindowResizeAction } from '../shared/host-window.actions';
|
||||
|
||||
describe('HeaderEffects', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
@@ -32,7 +33,7 @@ describe('HeaderEffects', () => {
|
||||
it('should return a COLLAPSE action in response to a RESIZE action', () => {
|
||||
runner.queue(new HostWindowResizeAction(800, 600));
|
||||
|
||||
headerEffects.resize$.subscribe(result => {
|
||||
headerEffects.resize$.subscribe((result) => {
|
||||
expect(result).toEqual(new HeaderCollapseAction());
|
||||
});
|
||||
});
|
||||
@@ -44,7 +45,7 @@ describe('HeaderEffects', () => {
|
||||
it('should return a COLLAPSE action in response to an UPDATE_LOCATION action', () => {
|
||||
runner.queue({ type: routerActions.UPDATE_LOCATION });
|
||||
|
||||
headerEffects.resize$.subscribe(result => {
|
||||
headerEffects.resize$.subscribe((result) => {
|
||||
expect(result).toEqual(new HeaderCollapseAction());
|
||||
});
|
||||
});
|
||||
|
@@ -1,16 +1,13 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Effect, Actions } from '@ngrx/effects'
|
||||
import { HostWindowActionTypes } from "../shared/host-window.actions";
|
||||
import { routerActions } from "@ngrx/router-store";
|
||||
import { HeaderCollapseAction } from "./header.actions";
|
||||
import { routerActions } from '@ngrx/router-store';
|
||||
|
||||
import { HostWindowActionTypes } from '../shared/host-window.actions';
|
||||
import { HeaderCollapseAction } from './header.actions';
|
||||
|
||||
@Injectable()
|
||||
export class HeaderEffects {
|
||||
|
||||
constructor(
|
||||
private actions$: Actions
|
||||
) { }
|
||||
|
||||
@Effect() resize$ = this.actions$
|
||||
.ofType(HostWindowActionTypes.RESIZE)
|
||||
.map(() => new HeaderCollapseAction());
|
||||
@@ -18,4 +15,9 @@ export class HeaderEffects {
|
||||
@Effect() routeChange$ = this.actions$
|
||||
.ofType(routerActions.UPDATE_LOCATION)
|
||||
.map(() => new HeaderCollapseAction());
|
||||
|
||||
constructor(private actions$: Actions) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import * as deepFreeze from "deep-freeze";
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
|
||||
import { headerReducer } from "./header.reducer";
|
||||
import { headerReducer } from './header.reducer';
|
||||
import {
|
||||
HeaderCollapseAction,
|
||||
HeaderExpandAction,
|
||||
HeaderToggleAction
|
||||
} from "./header.actions";
|
||||
} from './header.actions';
|
||||
|
||||
class NullAction extends HeaderCollapseAction {
|
||||
type = null;
|
||||
@@ -15,8 +15,8 @@ class NullAction extends HeaderCollapseAction {
|
||||
}
|
||||
}
|
||||
|
||||
describe("headerReducer", () => {
|
||||
it("should return the current state when no valid actions have been made", () => {
|
||||
describe('headerReducer', () => {
|
||||
it('should return the current state when no valid actions have been made', () => {
|
||||
const state = { navCollapsed: false };
|
||||
const action = new NullAction();
|
||||
const newState = headerReducer(state, action);
|
||||
@@ -24,7 +24,7 @@ describe("headerReducer", () => {
|
||||
expect(newState).toEqual(state);
|
||||
});
|
||||
|
||||
it("should start with navCollapsed = true", () => {
|
||||
it('should start with navCollapsed = true', () => {
|
||||
const action = new NullAction();
|
||||
const initialState = headerReducer(undefined, action);
|
||||
|
||||
@@ -32,7 +32,7 @@ describe("headerReducer", () => {
|
||||
expect(initialState.navCollapsed).toEqual(true);
|
||||
});
|
||||
|
||||
it("should set navCollapsed to true in response to the COLLAPSE action", () => {
|
||||
it('should set navCollapsed to true in response to the COLLAPSE action', () => {
|
||||
const state = { navCollapsed: false };
|
||||
const action = new HeaderCollapseAction();
|
||||
const newState = headerReducer(state, action);
|
||||
@@ -40,7 +40,7 @@ describe("headerReducer", () => {
|
||||
expect(newState.navCollapsed).toEqual(true);
|
||||
});
|
||||
|
||||
it("should perform the COLLAPSE action without affecting the previous state", () => {
|
||||
it('should perform the COLLAPSE action without affecting the previous state', () => {
|
||||
const state = { navCollapsed: false };
|
||||
deepFreeze(state);
|
||||
|
||||
@@ -51,7 +51,7 @@ describe("headerReducer", () => {
|
||||
// is mutated, and any uncaught exception will cause the test to fail
|
||||
});
|
||||
|
||||
it("should set navCollapsed to false in response to the EXPAND action", () => {
|
||||
it('should set navCollapsed to false in response to the EXPAND action', () => {
|
||||
const state = { navCollapsed: true };
|
||||
const action = new HeaderExpandAction();
|
||||
const newState = headerReducer(state, action);
|
||||
@@ -59,7 +59,7 @@ describe("headerReducer", () => {
|
||||
expect(newState.navCollapsed).toEqual(false);
|
||||
});
|
||||
|
||||
it("should perform the EXPAND action without affecting the previous state", () => {
|
||||
it('should perform the EXPAND action without affecting the previous state', () => {
|
||||
const state = { navCollapsed: true };
|
||||
deepFreeze(state);
|
||||
|
||||
@@ -67,7 +67,7 @@ describe("headerReducer", () => {
|
||||
headerReducer(state, action);
|
||||
});
|
||||
|
||||
it("should flip the value of navCollapsed in response to the TOGGLE action", () => {
|
||||
it('should flip the value of navCollapsed in response to the TOGGLE action', () => {
|
||||
const state1 = { navCollapsed: true };
|
||||
const action = new HeaderToggleAction();
|
||||
|
||||
@@ -78,7 +78,7 @@ describe("headerReducer", () => {
|
||||
expect(state3.navCollapsed).toEqual(true);
|
||||
});
|
||||
|
||||
it("should perform the TOGGLE action without affecting the previous state", () => {
|
||||
it('should perform the TOGGLE action without affecting the previous state', () => {
|
||||
const state = { navCollapsed: true };
|
||||
deepFreeze(state);
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HeaderAction, HeaderActionTypes } from "./header.actions";
|
||||
import { HeaderAction, HeaderActionTypes } from './header.actions';
|
||||
|
||||
export interface HeaderState {
|
||||
navCollapsed: boolean;
|
||||
|
@@ -1,19 +1,10 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-home-news',
|
||||
styleUrls: ['./home-news.component.scss'],
|
||||
templateUrl: './home-news.component.html'
|
||||
})
|
||||
export class HomeNewsComponent implements OnInit {
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
export class HomeNewsComponent {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
}
|
||||
|
@@ -1,19 +1,10 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-home',
|
||||
styleUrls: ['./home.component.scss'],
|
||||
templateUrl: './home.component.html'
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
export class HomeComponent {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
}
|
||||
|
@@ -2,12 +2,12 @@ import { NgModule } from '@angular/core';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
import { HomeRoutingModule } from './home-routing.module';
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { TopLevelCommunityListComponent } from "./top-level-community-list/top-level-community-list.component";
|
||||
import { HomeNewsComponent } from "./home-news/home-news.component";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component';
|
||||
import { HomeNewsComponent } from './home-news/home-news.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { Component, OnInit, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { RemoteData } from "../../core/data/remote-data";
|
||||
import { CommunityDataService } from "../../core/data/community-data.service";
|
||||
import { Community } from "../../core/shared/community.model";
|
||||
import { PaginationComponentOptions } from "../../shared/pagination/pagination-component-options.model";
|
||||
import { SortOptions, SortDirection } from "../../core/cache/models/sort-options.model";
|
||||
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortOptions, SortDirection } from '../../core/cache/models/sort-options.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-top-level-community-list',
|
||||
@@ -20,16 +21,12 @@ export class TopLevelCommunityListComponent implements OnInit {
|
||||
private cds: CommunityDataService,
|
||||
private ref: ChangeDetectorRef
|
||||
) {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.config = new PaginationComponentOptions();
|
||||
this.config.id = "top-level-pagination";
|
||||
this.config.id = 'top-level-pagination';
|
||||
this.config.pageSizeOptions = [4];
|
||||
this.config.pageSize = 4;
|
||||
this.sortConfig = new SortOptions();
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Collection } from "../../../core/shared/collection.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { Item } from "../../../core/shared/item.model";
|
||||
import { RemoteDataBuildService } from "../../../core/cache/builders/remote-data-build.service";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
||||
|
||||
/**
|
||||
* This component renders the parent collections section of the item
|
||||
@@ -17,30 +18,23 @@ export class CollectionsComponent implements OnInit {
|
||||
|
||||
@Input() item: Item;
|
||||
|
||||
label: string = "item.page.collections";
|
||||
label = 'item.page.collections';
|
||||
|
||||
separator: string = "<br/>";
|
||||
separator = '<br/>';
|
||||
|
||||
collections: Observable<Collection[]>;
|
||||
|
||||
constructor(
|
||||
private rdbs: RemoteDataBuildService
|
||||
) {
|
||||
this.universalInit();
|
||||
constructor(private rdbs: RemoteDataBuildService) {
|
||||
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// this.collections = this.item.parents.payload;
|
||||
//TODO this should use parents, but the collections
|
||||
|
||||
// TODO: this should use parents, but the collections
|
||||
// for an Item aren't returned by the REST API yet,
|
||||
// only the owning collection
|
||||
this.collections = this.item.owner.payload.map(c => [c]);
|
||||
this.collections = this.item.owner.payload.map((c) => [c]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import { Component, Input } from '@angular/core';
|
||||
* This component renders any content inside this wrapper.
|
||||
* The wrapper prints a label before the content (if available)
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-field-wrapper',
|
||||
styleUrls: ['./metadata-field-wrapper.component.scss'],
|
||||
@@ -14,13 +13,4 @@ export class MetadataFieldWrapperComponent {
|
||||
|
||||
@Input() label: string;
|
||||
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MetadataValuesComponent } from "../metadata-values/metadata-values.component";
|
||||
|
||||
import { MetadataValuesComponent } from '../metadata-values/metadata-values.component';
|
||||
|
||||
/**
|
||||
* This component renders the configured 'values' into the ds-metadata-field-wrapper component as a link.
|
||||
@@ -8,7 +9,6 @@ import { MetadataValuesComponent } from "../metadata-values/metadata-values.comp
|
||||
* using the 'linktext' as it's value (if it exists)
|
||||
* and using the values as the 'href' attribute (and as value of the tag when no 'linktext' is defined)
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-uri-values',
|
||||
styleUrls: ['./metadata-uri-values.component.scss'],
|
||||
|
@@ -4,7 +4,6 @@ import { Component, Input } from '@angular/core';
|
||||
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
|
||||
* It puts the given 'separator' between each two values.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-values',
|
||||
styleUrls: ['./metadata-values.component.scss'],
|
||||
@@ -18,13 +17,4 @@ export class MetadataValuesComponent {
|
||||
|
||||
@Input() label: string;
|
||||
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Bitstream } from "../../../../core/shared/bitstream.model";
|
||||
import { Item } from "../../../../core/shared/item.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { FileSectionComponent } from "../../../simple/field-components/file-section/file-section.component";
|
||||
import { hasValue } from "../../../../shared/empty.util";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This component renders the file section of the item
|
||||
@@ -23,25 +24,20 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
|
||||
|
||||
files: Observable<Bitstream[]>;
|
||||
|
||||
|
||||
thumbnails: Map<string, Observable<Bitstream>> = new Map();
|
||||
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
const originals = this.item.getFiles();
|
||||
const licenses = this.item.getBitstreamsByBundleName("LICENSE");
|
||||
this.files = Observable.combineLatest(originals, licenses, (originals, licenses) => [...originals, ...licenses]);
|
||||
const licenses = this.item.getBitstreamsByBundleName('LICENSE');
|
||||
this.files = Observable.combineLatest(originals, licenses, (o, l) => [...o, ...l]);
|
||||
this.files.subscribe(
|
||||
files =>
|
||||
(files) =>
|
||||
files.forEach(
|
||||
original => {
|
||||
(original) => {
|
||||
const thumbnail: Observable<Bitstream> = this.item.getThumbnailForOriginal(original);
|
||||
this.thumbnails.set(original.id, thumbnail);
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from "rxjs";
|
||||
import { ItemPageComponent } from "../simple/item-page.component";
|
||||
import { Metadatum } from "../../core/shared/metadatum.model";
|
||||
import { ItemDataService } from "../../core/data/item-data.service";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { RemoteData } from "../../core/data/remote-data";
|
||||
import { Item } from "../../core/shared/item.model";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ItemPageComponent } from '../simple/item-page.component';
|
||||
import { Metadatum } from '../../core/shared/metadatum.model';
|
||||
import { ItemDataService } from '../../core/data/item-data.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -22,16 +23,12 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
||||
|
||||
item: RemoteData<Item>;
|
||||
|
||||
metadata: Observable<Array<Metadatum>>;
|
||||
metadata: Observable<Metadatum[]>;
|
||||
|
||||
constructor(route: ActivatedRoute, items: ItemDataService) {
|
||||
super(route, items);
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
|
||||
}
|
||||
|
||||
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
@@ -39,7 +36,7 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
||||
|
||||
initialize(params) {
|
||||
super.initialize(params);
|
||||
this.metadata = this.item.payload.map(i => i.metadata);
|
||||
this.metadata = this.item.payload.map((i) => i.metadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ItemPageComponent } from './simple/item-page.component';
|
||||
import { ItemPageRoutingModule } from './item-page-routing.module';
|
||||
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
|
||||
@@ -12,10 +13,10 @@ import { ItemPageUriFieldComponent } from './simple/field-components/specific-fi
|
||||
import { ItemPageTitleFieldComponent } from './simple/field-components/specific-field/title/item-page-title-field.component';
|
||||
import { ItemPageSpecificFieldComponent } from './simple/field-components/specific-field/item-page-specific-field.component';
|
||||
import { SharedModule } from './../shared/shared.module';
|
||||
import { FileSectionComponent } from "./simple/field-components/file-section/file-section.component";
|
||||
import { CollectionsComponent } from "./field-components/collections/collections.component";
|
||||
import { FullItemPageComponent } from "./full/full-item-page.component";
|
||||
import { FullFileSectionComponent } from "./full/field-components/file-section/full-file-section.component";
|
||||
import { FileSectionComponent } from './simple/field-components/file-section/file-section.component';
|
||||
import { CollectionsComponent } from './field-components/collections/collections.component';
|
||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
||||
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Bitstream } from "../../../../core/shared/bitstream.model";
|
||||
import { Item } from "../../../../core/shared/item.model";
|
||||
import { Observable } from "rxjs";
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
|
||||
/**
|
||||
* This component renders the file section of the item
|
||||
* inside a 'ds-metadata-field-wrapper' component.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page-file-section',
|
||||
templateUrl: './file-section.component.html'
|
||||
@@ -16,19 +16,12 @@ export class FileSectionComponent implements OnInit {
|
||||
|
||||
@Input() item: Item;
|
||||
|
||||
label : string = "item.page.files";
|
||||
label = 'item.page.files';
|
||||
|
||||
separator: string = "<br/>";
|
||||
separator = '<br/>';
|
||||
|
||||
files: Observable<Bitstream[]>;
|
||||
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initialize();
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../../core/shared/item.model";
|
||||
import { ItemPageSpecificFieldComponent } from "../item-page-specific-field.component";
|
||||
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { ItemPageSpecificFieldComponent } from '../item-page-specific-field.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page-abstract-field',
|
||||
@@ -13,9 +14,9 @@ export class ItemPageAbstractFieldComponent extends ItemPageSpecificFieldCompone
|
||||
separator: string;
|
||||
|
||||
fields: string[] = [
|
||||
"dc.description.abstract"
|
||||
'dc.description.abstract'
|
||||
];
|
||||
|
||||
label : string = "item.page.abstract";
|
||||
label = 'item.page.abstract';
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../../core/shared/item.model";
|
||||
import { ItemPageSpecificFieldComponent } from "../item-page-specific-field.component";
|
||||
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { ItemPageSpecificFieldComponent } from '../item-page-specific-field.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page-author-field',
|
||||
@@ -13,11 +14,11 @@ export class ItemPageAuthorFieldComponent extends ItemPageSpecificFieldComponent
|
||||
separator: string;
|
||||
|
||||
fields: string[] = [
|
||||
"dc.contributor.author",
|
||||
"dc.creator",
|
||||
"dc.contributor"
|
||||
'dc.contributor.author',
|
||||
'dc.creator',
|
||||
'dc.contributor'
|
||||
];
|
||||
|
||||
label : string = "item.page.author";
|
||||
label = 'item.page.author';
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../../core/shared/item.model";
|
||||
import { ItemPageSpecificFieldComponent } from "../item-page-specific-field.component";
|
||||
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { ItemPageSpecificFieldComponent } from '../item-page-specific-field.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page-date-field',
|
||||
@@ -10,12 +11,12 @@ export class ItemPageDateFieldComponent extends ItemPageSpecificFieldComponent {
|
||||
|
||||
@Input() item: Item;
|
||||
|
||||
separator : string = ", ";
|
||||
separator = ', ';
|
||||
|
||||
fields: string[] = [
|
||||
"dc.date.issued"
|
||||
'dc.date.issued'
|
||||
];
|
||||
|
||||
label : string = "item.page.date";
|
||||
label = 'item.page.date';
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from "../../../../core/shared/item.model";
|
||||
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
|
||||
/**
|
||||
* This component can be used to represent metadata on a simple item page.
|
||||
@@ -28,13 +29,6 @@ export class ItemPageSpecificFieldComponent {
|
||||
* Separator string between multiple values of the metadata fields defined
|
||||
* @type {string}
|
||||
*/
|
||||
separator : string = "<br/>";
|
||||
|
||||
constructor() {
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
separator = '<br/>';
|
||||
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user