fixed issues with related resources that have a single link for a hasMany relationship (e.g. item.bitstreams)

This commit is contained in:
Art Lowel
2017-06-20 15:04:42 +02:00
parent b69f2ff4cb
commit 21a6a80d13
17 changed files with 119 additions and 38 deletions

View File

@@ -16,7 +16,7 @@ export const getMapsTo = function(target: any) {
return Reflect.getOwnMetadata(mapsToMetadataKey, target);
};
export const relationship = function(value: ResourceType): any {
export const relationship = function(value: ResourceType, isList: boolean = false): any {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target || !propertyKey) {
return;
@@ -28,11 +28,11 @@ export const relationship = function(value: ResourceType): any {
}
relationshipMap.set(target.constructor, metaDataList);
return Reflect.metadata(relationshipKey, value).apply(this, arguments);
return Reflect.metadata(relationshipKey, { resourceType: value, isList }).apply(this, arguments);
};
};
export const getResourceType = function(target: any, propertyKey: string) {
export const getRelationMetadata = function(target: any, propertyKey: string) {
return Reflect.getMetadata(relationshipKey, target, propertyKey);
};

View File

@@ -12,7 +12,7 @@ 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, getResourceType, getRelationships } from "./build-decorators";
import { getMapsTo, getRelationMetadata, getRelationships } from "./build-decorators";
import { NormalizedObjectFactory } from "../models/normalized-object-factory";
import { Request } from "../../data/request.models";
@@ -64,11 +64,26 @@ export class RemoteDataBuildService {
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).pageInfo)
.distinctUntilChanged();
const payload = this.objectCache.getBySelfLink<TNormalized>(href, normalizedType)
.map((normalized: TNormalized) => {
const payload =
Observable.race(
this.objectCache.getBySelfLink<TNormalized>(href, normalizedType),
responseCacheObs
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.flatMap((resourceUUIDs: Array<string>) => {
if (isNotEmpty(resourceUUIDs)) {
return this.objectCache.get(resourceUUIDs[0], normalizedType);
}
else {
return Observable.of(undefined);
}
})
.distinctUntilChanged()
).map((normalized: TNormalized) => {
return this.build<TNormalized, TDomain>(normalized);
});
return new RemoteData(
href,
requestPending,
@@ -143,7 +158,7 @@ export class RemoteDataBuildService {
relationships.forEach((relationship: string) => {
if (hasValue(normalized[relationship])) {
const resourceType = getResourceType(normalized, relationship);
const { resourceType, isList } = getRelationMetadata(normalized, relationship);
const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType);
if (Array.isArray(normalized[relationship])) {
// without the setTimeout, the actions inside requestService.configure
@@ -168,9 +183,16 @@ export class RemoteDataBuildService {
this.requestService.configure(new Request(normalized[relationship]));
},0);
// The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
// in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
// but it should still be built as a list
if (isList) {
links[relationship] = this.buildList(normalized[relationship], resourceConstructor);
} else {
links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor);
}
}
}
});
const domainModel = getMapsTo(normalized.constructor);

View File

@@ -0,0 +1,11 @@
import { NormalizedObject } from "./normalized-object.model";
import { inheritSerialization } from "cerialize";
@inheritSerialization(NormalizedObject)
export class NormalizedBitstreamFormat extends NormalizedObject {
//TODO this class was created as a placeholder when we connected to the live rest api
get uuid(): string {
return this.self;
}
}

View File

@@ -9,10 +9,10 @@ import { ResourceType } from "../../shared/resource-type";
export class NormalizedBitstream extends NormalizedDSpaceObject {
/**
* The size of this bitstream in bytes(?)
* The size of this bitstream in bytes
*/
@autoserialize
size: number;
sizeBytes: number;
/**
* The relative path to this Bitstream's file
@@ -42,14 +42,14 @@ export class NormalizedBitstream extends NormalizedDSpaceObject {
* An array of Bundles that are direct parents of this Bitstream
*/
@autoserialize
@relationship(ResourceType.Item)
@relationship(ResourceType.Item, true)
parents: Array<string>;
/**
* The Bundle that owns this Bitstream
*/
@autoserialize
@relationship(ResourceType.Item)
@relationship(ResourceType.Item, false)
owner: string;
/**

View File

@@ -11,7 +11,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
* The primary bitstream of this Bundle
*/
@autoserialize
@relationship(ResourceType.Bitstream)
@relationship(ResourceType.Bitstream, false)
primaryBitstream: string;
/**
@@ -25,6 +25,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
owner: string;
@autoserialize
@relationship(ResourceType.Bitstream)
@relationship(ResourceType.Bitstream, true)
bitstreams: Array<string>;
}

View File

@@ -18,25 +18,25 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
* The Bitstream that represents the logo of this Collection
*/
@autoserialize
@relationship(ResourceType.Bitstream)
@relationship(ResourceType.Bitstream, false)
logo: string;
/**
* An array of Communities that are direct parents of this Collection
*/
@autoserialize
@relationship(ResourceType.Community)
@relationship(ResourceType.Community, true)
parents: Array<string>;
/**
* The Community that owns this Collection
*/
@autoserialize
@relationship(ResourceType.Community)
@relationship(ResourceType.Community, false)
owner: string;
@autoserialize
@relationship(ResourceType.Item)
@relationship(ResourceType.Item, true)
items: Array<string>;
}

View File

@@ -18,25 +18,25 @@ export class NormalizedCommunity extends NormalizedDSpaceObject {
* The Bitstream that represents the logo of this Community
*/
@autoserialize
@relationship(ResourceType.Bitstream)
@relationship(ResourceType.Bitstream, false)
logo: string;
/**
* An array of Communities that are direct parents of this Community
*/
@autoserialize
@relationship(ResourceType.Community)
@relationship(ResourceType.Community, true)
parents: Array<string>;
/**
* The Community that owns this Community
*/
@autoserialize
@relationship(ResourceType.Community)
@relationship(ResourceType.Community, false)
owner: string;
@autoserialize
@relationship(ResourceType.Collection)
@relationship(ResourceType.Collection, true)
collections: Array<string>;
}

View File

@@ -1,13 +1,19 @@
import { autoserialize, autoserializeAs } from "cerialize";
import { CacheableObject } from "../object-cache.reducer";
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.
*/
export abstract class NormalizedDSpaceObject implements CacheableObject {
export abstract class NormalizedDSpaceObject extends NormalizedObject{
/**
* The link to the rest endpoint where this object can be found
*
* Repeated here to make the serialization work,
* inheritSerialization doesn't seem to work for more than one level
*/
@autoserialize
self: string;
@@ -22,6 +28,9 @@ export abstract class NormalizedDSpaceObject implements CacheableObject {
/**
* The universally unique identifier of this DSpaceObject
*
* Repeated here to make the serialization work,
* inheritSerialization doesn't seem to work for more than one level
*/
@autoserialize
uuid: string;

View File

@@ -42,17 +42,17 @@ export class NormalizedItem extends NormalizedDSpaceObject {
* An array of Collections that are direct parents of this Item
*/
@autoserialize
@relationship(ResourceType.Collection)
@relationship(ResourceType.Collection, true)
parents: Array<string>;
/**
* The Collection that owns this Item
*/
@autoserializeAs(String, 'owningCollection')
@relationship(ResourceType.Collection)
@relationship(ResourceType.Collection, false)
owner: string;
@autoserialize
@relationship(ResourceType.Bitstream)
@relationship(ResourceType.Bitstream, true)
bitstreams: Array<string>;
}

View File

@@ -6,13 +6,20 @@ 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<NormalizedDSpaceObject> {
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
switch (type) {
case ResourceType.Bitstream: {
return NormalizedBitstream
}
// commented out for now, bitstreamformats aren't used in the UI yet
// and slow things down noticeably
// case ResourceType.BitstreamFormat: {
// return NormalizedBitstreamFormat
// }
case ResourceType.Bundle: {
return NormalizedBundle
}

View File

@@ -0,0 +1,20 @@
import { CacheableObject } from "../object-cache.reducer";
import { autoserialize } from "cerialize";
/**
* An abstract model class for a NormalizedObject.
*/
export abstract class NormalizedObject implements CacheableObject {
/**
* The link to the rest endpoint where this object can be found
*/
@autoserialize
self: string;
/**
* The universally unique identifier of this Object
*/
@autoserialize
uuid: string;
}

View File

@@ -72,7 +72,16 @@ export class RequestEffects {
.filter(property => data.hasOwnProperty(property))
.filter(property => hasValue(data[property]))
.forEach(property => {
uuids = [...uuids, ...this.deserializeAndCache(data[property], requestHref)];
let propertyUUIDs;
if (isPaginatedResponse(data[property])) {
propertyUUIDs = this.process(data[property], requestHref);
}
else {
propertyUUIDs = this.deserializeAndCache(data[property], requestHref);
}
uuids = [...uuids, ...propertyUUIDs];
});
return uuids;
}
@@ -122,13 +131,15 @@ export class RequestEffects {
}
else {
//TODO move check to Validator?
throw new Error(`The server returned an object with an unknown a known type: ${type}`);
// throw new Error(`The server returned an object with an unknown a known type: ${type}`);
return [];
}
}
else {
//TODO move check to Validator
throw new Error(`The server returned an object without a type: ${JSON.stringify(obj)}`);
// throw new Error(`The server returned an object without a type: ${JSON.stringify(obj)}`);
return [];
}
}

View File

@@ -5,9 +5,9 @@ import { Item } from "./item.model";
export class Bitstream extends DSpaceObject {
/**
* The size of this bitstream in bytes(?)
* The size of this bitstream in bytes
*/
size: number;
sizeBytes: number;
/**
* The mime type of this Bitstream

View File

@@ -5,6 +5,7 @@
export enum ResourceType {
Bundle = <any> "bundle",
Bitstream = <any> "bitstream",
BitstreamFormat = <any> "bitstreamformat",
Item = <any> "item",
Collection = <any> "collection",
Community = <any> "community"

View File

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

View File

@@ -19,7 +19,7 @@ export class CollectionsComponent implements OnInit {
label : string = "item.page.collections";
separator: string = "<br/>"
separator: string = "<br/>";
collections: Observable<Collection[]>;
@@ -38,7 +38,7 @@ export class CollectionsComponent implements OnInit {
//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.rdbs.aggregate([this.item.owner]).payload
this.collections = this.item.owner.payload.map(c => [c]);
}

View File

@@ -2,7 +2,7 @@
<div class="file-section">
<a *ngFor="let file of (files | async); let last=last;" [href]="file?.url">
<span>{{file?.name}}</span>
<span>({{(file?.size) | dsFileSize }})</span>
<span>({{(file?.sizeBytes) | dsFileSize }})</span>
<span *ngIf="!last" innerHTML="{{separator}}"></span>
</a>
</div>