intermediate commit

This commit is contained in:
Lotte Hofstede
2018-05-17 17:10:38 +02:00
parent f21b3e390e
commit ca8c1830aa
21 changed files with 293 additions and 158 deletions

View File

@@ -2,7 +2,7 @@
<div *ngIf="subCollectionsRD?.hasSucceeded" @fadeIn>
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
<ul>
<li *ngFor="let collection of subCollectionsRD?.payload">
<li *ngFor="let collection of subCollectionsRD?.payload.page">
<p>
<span class="lead"><a [routerLink]="['/collections', collection.id]">{{collection.name}}</a></span><br>
<span class="text-muted">{{collection.shortDescription}}</span>

View File

@@ -6,6 +6,7 @@ import { Collection } from '../../core/shared/collection.model';
import { Community } from '../../core/shared/community.model';
import { fadeIn } from '../../shared/animations/fade';
import { PaginatedList } from '../../core/data/paginated-list';
@Component({
selector: 'ds-community-page-sub-collection-list',
@@ -15,7 +16,7 @@ import { fadeIn } from '../../shared/animations/fade';
})
export class CommunityPageSubCollectionListComponent implements OnInit {
@Input() community: Community;
subCollectionsRDObs: Observable<RemoteData<Collection[]>>;
subCollectionsRDObs: Observable<RemoteData<PaginatedList<Collection>>>;
ngOnInit(): void {
this.subCollectionsRDObs = this.community.collections;

View File

@@ -1,4 +1,4 @@
<ds-metadata-field-wrapper [label]="label | translate">
<ds-metadata-field-wrapper *ngIf="hasSucceeded() | async" [label]="label | translate">
<div class="collections">
<a *ngFor="let collection of (collections | async); let last=last;" [routerLink]="['/collections', collection.id]">
<span>{{collection?.name}}</span><span *ngIf="!last" [innerHTML]="separator"></span>

View File

@@ -38,4 +38,8 @@ export class CollectionsComponent implements OnInit {
this.collections = this.item.owner.map((rd: RemoteData<Collection>) => [rd.payload]);
}
hasSucceeded() {
this.item.owner.map((rd: RemoteData<Collection>) => rd.hasSucceeded)
}
}

View File

@@ -85,6 +85,7 @@ export class RemoteDataBuildService {
}
toRemoteDataObservable<T>(requestEntryObs: Observable<RequestEntry>, responseCacheObs: Observable<ResponseCacheEntry>, payloadObs: Observable<T>) {
return Observable.combineLatest(requestEntryObs, responseCacheObs.startWith(undefined), payloadObs,
(reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => {
const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
@@ -98,7 +99,6 @@ export class RemoteDataBuildService {
error = new RemoteDataError(resEntry.response.statusCode, errorMessage);
}
}
return new RemoteData(
requestPending,
responsePending,
@@ -109,7 +109,7 @@ export class RemoteDataBuildService {
});
}
buildList<TNormalized extends NormalizedObject, TDomain>(hrefObs: string | Observable<string>): Observable<RemoteData<TDomain[] | PaginatedList<TDomain>>> {
buildList<TNormalized extends NormalizedObject, TDomain>(hrefObs: string | Observable<string>): Observable<RemoteData<PaginatedList<TDomain>>> {
if (typeof hrefObs === 'string') {
hrefObs = Observable.of(hrefObs);
}
@@ -147,11 +147,7 @@ export class RemoteDataBuildService {
});
const payloadObs = Observable.combineLatest(tDomainListObs, pageInfoObs, (tDomainList, pageInfo) => {
if (hasValue(pageInfo)) {
return new PaginatedList(pageInfo, tDomainList);
} else {
return tDomainList;
}
});
return this.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);

View File

@@ -0,0 +1,46 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { mapsTo } from '../builders/build-decorators';
import { BitstreamFormat } from '../../shared/bitstream-format.model';
import { NormalizedObject } from './normalized-object.model';
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
// While BitstreamFormats don't have a UUID, but need to be cachable: use this serializer. Remove it when the REST API returns them with UUID
const BitstreamFormatUUIDSerializer = {
Serialize(json: any): any {
// No need to serialize again, de ID is already serialized by the id field itself
return {};
},
Deserialize(json: any): any {
return 'bitstream-format-' + json.id;
}
};
@mapsTo(BitstreamFormat)
@inheritSerialization(NormalizedObject)
export class NormalizedBitstreamFormat extends NormalizedObject {
@autoserialize
shortDescription: string;
@autoserialize
description: string;
@autoserialize
mimetype: string;
@autoserialize
supportLevel: number;
@autoserialize
internal: boolean;
@autoserialize
extensions: string;
@autoserialize
id: string;
@autoserializeAs(BitstreamFormatUUIDSerializer)
uuid: string;
}

View File

@@ -1,4 +1,4 @@
import { autoserialize, inheritSerialization, autoserializeAs } from 'cerialize';
import { autoserialize, inheritSerialization } from 'cerialize';
import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
import { Collection } from '../../shared/collection.model';

View File

@@ -49,6 +49,7 @@ export class NormalizedItem extends NormalizedDSpaceObject {
/**
* The Collection that owns this Item
*/
@autoserialize
@relationship(ResourceType.Collection, false)
owningCollection: string;

View File

@@ -6,6 +6,8 @@ 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 { NormalizedResourcePolicy } from './normalized-resource-policy.model';
export class NormalizedObjectFactory {
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
@@ -25,6 +27,12 @@ export class NormalizedObjectFactory {
case ResourceType.Community: {
return NormalizedCommunity
}
case ResourceType.BitstreamFormat: {
return NormalizedBitstreamFormat
}
case ResourceType.ResourcePolicy: {
return NormalizedResourcePolicy
}
default: {
return undefined;
}

View File

@@ -0,0 +1,34 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { mapsTo } from '../builders/build-decorators';
import { BitstreamFormat } from '../../shared/bitstream-format.model';
import { NormalizedObject } from './normalized-object.model';
import { ResourcePolicy } from '../../shared/resource-policy.model';
// While ResourcePolicyUUIDSerializer don't have a UUID, but need to be cachable: use this serializer. Remove it when the REST API returns them with UUID
const ResourcePolicyUUIDSerializer = {
Serialize(json: any): any {
// No need to serialize again, de ID is already serialized by the id field itself
return {};
},
Deserialize(json: any): any {
return 'resource-policy-' + json.id;
}
};
@mapsTo(ResourcePolicy)
@inheritSerialization(NormalizedObject)
export class NormalizedResourcePolicy extends NormalizedObject {
@autoserialize
name: string;
@autoserializeAs(String, 'groupUUID')
group: string;
@autoserialize
id: string;
@autoserializeAs(ResourcePolicyUUIDSerializer)
uuid: string;
}

View File

@@ -4,8 +4,9 @@ import { CacheableObject } from '../cache/object-cache.reducer';
import { PageInfo } from '../shared/page-info.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { GlobalConfig } from '../../../config/global-config.interface';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { GenericConstructor } from '../shared/generic-constructor';
import { PaginatedList } from './paginated-list';
import { NormalizedObject } from '../cache/models/normalized-object.model';
function isObjectLevel(halObj: any) {
return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
@@ -17,96 +18,93 @@ function isPaginatedResponse(halObj: any) {
/* tslint:disable:max-classes-per-file */
class ProcessRequestDTO<ObjectDomain> {
[key: string]: ObjectDomain[]
}
export abstract class BaseResponseParsingService {
protected abstract EnvConfig: GlobalConfig;
protected abstract objectCache: ObjectCacheService;
protected abstract objectFactory: any;
protected abstract toCache: boolean;
protected process<ObjectDomain,ObjectType>(data: any, requestHref: string): ProcessRequestDTO<ObjectDomain> {
protected process<ObjectDomain, ObjectType>(data: any, requestHref: string): any {
if (isNotEmpty(data)) {
if (isPaginatedResponse(data)) {
return this.process(data._embedded, requestHref);
if (hasNoValue(data) || (typeof data !== 'object')) {
return data;
} else if (isPaginatedResponse(data)) {
return this.processPaginatedList(data, requestHref);
} else if (Array.isArray(data)) {
return this.processArray(data, requestHref);
} else if (isObjectLevel(data)) {
return { topLevel: this.deserializeAndCache(data, requestHref) };
} else {
const result = new ProcessRequestDTO<ObjectDomain>();
if (Array.isArray(data)) {
result.topLevel = [];
data.forEach((datum) => {
if (isPaginatedResponse(datum)) {
const obj = this.process(datum, requestHref);
result.topLevel = [...result.topLevel, ...this.flattenSingleKeyObject(obj)];
} else {
result.topLevel = [...result.topLevel, ...this.deserializeAndCache<ObjectDomain,ObjectType>(datum, requestHref)];
}
const object = this.deserialize(data, requestHref);
this.cache(object, requestHref);
if (isNotEmpty(data._embedded)) {
const list = {};
Object
.keys(data._embedded)
.filter((property) => data._embedded.hasOwnProperty(property))
.forEach((property) => {
console.log(data._embedded[property]);
const parsedObj = this.process<ObjectDomain, ObjectType>(data._embedded[property], requestHref);
list[property] = parsedObj;
});
} else {
console.log(list);
Object.assign(object, list);
}
return object;
}
const result = {};
Object.keys(data)
.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] = this.flattenSingleKeyObject(obj);
} else {
result[property] = this.deserializeAndCache(data[property], requestHref);
}
result[property] = obj;
});
}
return result;
}
}
}
protected deserializeAndCache<ObjectDomain,ObjectType>(obj, requestHref: string): ObjectDomain[] {
if (Array.isArray(obj)) {
let result = [];
obj.forEach((o) => result = [...result, ...this.deserializeAndCache<ObjectDomain,ObjectType>(o, requestHref)]);
return result;
protected processPaginatedList<ObjectDomain, ObjectType>(data: any, requestHref: string): PaginatedList<ObjectDomain> {
const pageInfo: PageInfo = this.processPageInfo(data);
const list = this.flattenSingleKeyObject(data._embedded);
const page: ObjectDomain[] = this.processArray(list, requestHref);
return new PaginatedList<ObjectDomain>(pageInfo, page);
}
protected processArray<ObjectDomain, ObjectType>(data: any, requestHref: string): ObjectDomain[] {
let array: ObjectDomain[] = [];
data.forEach((datum) => {
array = [...array, this.process(datum, requestHref)];
}
);
return array;
}
protected deserialize<ObjectDomain, ObjectType>(obj, requestHref) {
const type: ObjectType = obj.type;
if (hasValue(type)) {
const normObjConstructor = this.objectFactory.getConstructor(type) as GenericConstructor<ObjectDomain>;
if (hasValue(normObjConstructor)) {
const serializer = new DSpaceRESTv2Serializer(normObjConstructor);
let processed;
if (isNotEmpty(obj._embedded)) {
processed = this.process<ObjectDomain,ObjectType>(obj._embedded, requestHref);
}
const normalizedObj: any = serializer.deserialize(obj);
if (isNotEmpty(processed)) {
const processedList = {};
Object.keys(processed).forEach((key) => {
processedList[key] = processed[key].map((no: NormalizedObject) => (this.toCache) ? no.self : no);
});
Object.assign(normalizedObj, processedList);
}
if (this.toCache) {
this.addToObjectCache(normalizedObj, requestHref);
}
return [normalizedObj] as any;
const res = serializer.deserialize(obj);
return res;
} else {
// TODO: move check to Validator?
// throw new Error(`The server returned an object with an unknown a known type: ${type}`);
return [];
return null;
}
} else {
// TODO: move check to Validator
// throw new Error(`The server returned an object without a type: ${JSON.stringify(obj)}`);
return [];
return null;
}
}
protected cache<ObjectDomain, ObjectType>(obj, requestHref) {
if (this.toCache) {
this.addToObjectCache(obj, requestHref);
}
}

View File

@@ -232,8 +232,11 @@ describe('ConfigResponseParsingService', () => {
expect(response.constructor).toBe(ErrorResponse);
});
it('should return a ConfigSuccessResponse with the ConfigDefinitions in data', () => {
fit('should return a ConfigSuccessResponse with the ConfigDefinitions in data', () => {
const response = service.parse(validRequest, validResponse);
debugger;
console.log(definitions);
console.log((response as any).configDefinition);
expect((response as any).configDefinition).toEqual(definitions);
});

View File

@@ -27,9 +27,10 @@ export class ConfigResponseParsingService extends BaseResponseParsingService imp
}
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
console.log(data);
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && data.statusCode === '200') {
const configDefinition = this.process<ConfigObject,ConfigType>(data.payload, request.href);
return new ConfigSuccessResponse(configDefinition[Object.keys(configDefinition)[0]], data.statusCode, this.processPageInfo(data.payload));
return new ConfigSuccessResponse(configDefinition, data.statusCode, this.processPageInfo(data.payload));
} else {
return new ErrorResponse(
Object.assign(

View File

@@ -12,6 +12,7 @@ import { RestRequest } from './request.models';
import { ResponseParsingService } from './parsing.service';
import { BaseResponseParsingService } from './base-response-parsing.service';
import { hasNoValue, hasValue } from '../../shared/empty.util';
@Injectable()
export class DSOResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
@@ -27,7 +28,16 @@ export class DSOResponseParsingService extends BaseResponseParsingService implem
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const processRequestDTO = this.process<NormalizedObject,ResourceType>(data.payload, request.href);
const selfLinks = this.flattenSingleKeyObject(processRequestDTO).map((no) => no.self);
let objectList = processRequestDTO;
if (hasNoValue(processRequestDTO)) {
return new DSOSuccessResponse(undefined, data.statusCode, undefined)
}
if (hasValue(processRequestDTO.page)) {
objectList = processRequestDTO.page;
} else if (!Array.isArray(processRequestDTO)) {
objectList = [processRequestDTO];
}
const selfLinks = objectList.map((no) => no.self);
return new DSOSuccessResponse(selfLinks, data.statusCode, this.processPageInfo(data.payload))
}

View File

@@ -1,24 +1,25 @@
import { autoserialize } from 'cerialize';
import { CacheableObject } from '../cache/object-cache.reducer';
import { ResourceType } from './resource-type';
export class BitstreamFormat {
export class BitstreamFormat implements CacheableObject {
@autoserialize
shortDescription: string;
@autoserialize
description: string;
@autoserialize
mimetype: string;
@autoserialize
supportLevel: number;
@autoserialize
internal: boolean;
@autoserialize
extensions: string;
self: string;
type: ResourceType;
uuid: string;
}

View File

@@ -3,6 +3,7 @@ import { Bitstream } from './bitstream.model';
import { Collection } from './collection.model';
import { RemoteData } from '../data/remote-data';
import { Observable } from 'rxjs/Observable';
import { PaginatedList } from '../data/paginated-list';
export class Community extends DSpaceObject {
@@ -58,6 +59,6 @@ export class Community extends DSpaceObject {
*/
owner: Observable<RemoteData<Community>>;
collections: Observable<RemoteData<Collection[]>>;
collections: Observable<RemoteData<PaginatedList<Collection>>>;
}

View File

@@ -1,6 +1,7 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { ConfigObject } from './config.model';
import { SubmissionSectionModel } from './config-submission-section.model';
import { PaginatedList } from '../../data/paginated-list';
@inheritSerialization(ConfigObject)
export class SubmissionDefinitionsModel extends ConfigObject {
@@ -9,6 +10,6 @@ export class SubmissionDefinitionsModel extends ConfigObject {
isDefault: boolean;
@autoserializeAs(SubmissionSectionModel)
sections: SubmissionSectionModel[];
sections: PaginatedList<SubmissionSectionModel>;
}

View File

@@ -5,6 +5,7 @@ import { Collection } from './collection.model';
import { RemoteData } from '../data/remote-data';
import { Bitstream } from './bitstream.model';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { PaginatedList } from '../data/paginated-list';
export class Item extends DSpaceObject {
@@ -47,7 +48,7 @@ export class Item extends DSpaceObject {
return this.owningCollection;
}
bitstreams: Observable<RemoteData<Bitstream[]>>;
bitstreams: Observable<RemoteData<PaginatedList<Bitstream>>>;
/**
* Retrieves the thumbnail of this item
@@ -88,7 +89,7 @@ export class Item extends DSpaceObject {
*/
getBitstreamsByBundleName(bundleName: string): Observable<Bitstream[]> {
return this.bitstreams
.map((rd: RemoteData<Bitstream[]>) => rd.payload)
.map((rd: RemoteData<PaginatedList<Bitstream>>) => rd.payload.page)
.filter((bitstreams: Bitstream[]) => hasValue(bitstreams))
.startWith([])
.map((bitstreams) => {

View File

@@ -0,0 +1,20 @@
import { CacheableObject } from '../cache/object-cache.reducer';
import { ResourceType } from './resource-type';
export class ResourcePolicy implements CacheableObject {
action: string;
name: string;
// TODO group should ofcourse become a group object
group: string;
self: string;
type: ResourceType;
uuid: string;
}

View File

@@ -6,4 +6,5 @@ export enum ResourceType {
Item = 'item',
Collection = 'collection',
Community = 'community',
ResourcePolicy = 'resourcePolicy'
}

View File

@@ -17,7 +17,14 @@ export const MockItem: Item = Object.assign(new Item(), {
errorMessage: '',
statusCode: '202',
pageInfo: {},
payload: [
payload: {
pageInfo: {
elementsPerPage: 20,
totalElements: 3,
totalPages: 1,
currentPage: 2
},
page: [
{
sizeBytes: 10201,
content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
@@ -89,6 +96,7 @@ export const MockItem: Item = Object.assign(new Item(), {
]
}
]
}
}),
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357',
id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357',