mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 19:43:04 +00:00
fixed issues with related resources that have a single link for a hasMany relationship (e.g. item.bitstreams)
This commit is contained in:
@@ -16,7 +16,7 @@ export const getMapsTo = function(target: any) {
|
|||||||
return Reflect.getOwnMetadata(mapsToMetadataKey, target);
|
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) {
|
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||||
if (!target || !propertyKey) {
|
if (!target || !propertyKey) {
|
||||||
return;
|
return;
|
||||||
@@ -28,11 +28,11 @@ export const relationship = function(value: ResourceType): any {
|
|||||||
}
|
}
|
||||||
relationshipMap.set(target.constructor, metaDataList);
|
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);
|
return Reflect.getMetadata(relationshipKey, target, propertyKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ import { ErrorResponse, SuccessResponse } from "../response-cache.models";
|
|||||||
import { Observable } from "rxjs/Observable";
|
import { Observable } from "rxjs/Observable";
|
||||||
import { RemoteData } from "../../data/remote-data";
|
import { RemoteData } from "../../data/remote-data";
|
||||||
import { GenericConstructor } from "../../shared/generic-constructor";
|
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 { NormalizedObjectFactory } from "../models/normalized-object-factory";
|
||||||
import { Request } from "../../data/request.models";
|
import { Request } from "../../data/request.models";
|
||||||
|
|
||||||
@@ -64,11 +64,26 @@ export class RemoteDataBuildService {
|
|||||||
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).pageInfo)
|
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).pageInfo)
|
||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
|
|
||||||
const payload = this.objectCache.getBySelfLink<TNormalized>(href, normalizedType)
|
const payload =
|
||||||
.map((normalized: TNormalized) => {
|
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 this.build<TNormalized, TDomain>(normalized);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return new RemoteData(
|
return new RemoteData(
|
||||||
href,
|
href,
|
||||||
requestPending,
|
requestPending,
|
||||||
@@ -143,7 +158,7 @@ export class RemoteDataBuildService {
|
|||||||
|
|
||||||
relationships.forEach((relationship: string) => {
|
relationships.forEach((relationship: string) => {
|
||||||
if (hasValue(normalized[relationship])) {
|
if (hasValue(normalized[relationship])) {
|
||||||
const resourceType = getResourceType(normalized, relationship);
|
const { resourceType, isList } = getRelationMetadata(normalized, relationship);
|
||||||
const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType);
|
const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType);
|
||||||
if (Array.isArray(normalized[relationship])) {
|
if (Array.isArray(normalized[relationship])) {
|
||||||
// without the setTimeout, the actions inside requestService.configure
|
// without the setTimeout, the actions inside requestService.configure
|
||||||
@@ -168,7 +183,14 @@ export class RemoteDataBuildService {
|
|||||||
this.requestService.configure(new Request(normalized[relationship]));
|
this.requestService.configure(new Request(normalized[relationship]));
|
||||||
},0);
|
},0);
|
||||||
|
|
||||||
links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor);
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
11
src/app/core/cache/models/normalized-bitstream-format.model.ts
vendored
Normal file
11
src/app/core/cache/models/normalized-bitstream-format.model.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -9,10 +9,10 @@ import { ResourceType } from "../../shared/resource-type";
|
|||||||
export class NormalizedBitstream extends NormalizedDSpaceObject {
|
export class NormalizedBitstream extends NormalizedDSpaceObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The size of this bitstream in bytes(?)
|
* The size of this bitstream in bytes
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
size: number;
|
sizeBytes: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The relative path to this Bitstream's file
|
* 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
|
* An array of Bundles that are direct parents of this Bitstream
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Item)
|
@relationship(ResourceType.Item, true)
|
||||||
parents: Array<string>;
|
parents: Array<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Bundle that owns this Bitstream
|
* The Bundle that owns this Bitstream
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Item)
|
@relationship(ResourceType.Item, false)
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -11,7 +11,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
|
|||||||
* The primary bitstream of this Bundle
|
* The primary bitstream of this Bundle
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Bitstream)
|
@relationship(ResourceType.Bitstream, false)
|
||||||
primaryBitstream: string;
|
primaryBitstream: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +25,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
|
|||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Bitstream)
|
@relationship(ResourceType.Bitstream, true)
|
||||||
bitstreams: Array<string>;
|
bitstreams: Array<string>;
|
||||||
}
|
}
|
||||||
|
@@ -18,25 +18,25 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
|
|||||||
* The Bitstream that represents the logo of this Collection
|
* The Bitstream that represents the logo of this Collection
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Bitstream)
|
@relationship(ResourceType.Bitstream, false)
|
||||||
logo: string;
|
logo: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of Communities that are direct parents of this Collection
|
* An array of Communities that are direct parents of this Collection
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Community)
|
@relationship(ResourceType.Community, true)
|
||||||
parents: Array<string>;
|
parents: Array<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Community that owns this Collection
|
* The Community that owns this Collection
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Community)
|
@relationship(ResourceType.Community, false)
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Item)
|
@relationship(ResourceType.Item, true)
|
||||||
items: Array<string>;
|
items: Array<string>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -18,25 +18,25 @@ export class NormalizedCommunity extends NormalizedDSpaceObject {
|
|||||||
* The Bitstream that represents the logo of this Community
|
* The Bitstream that represents the logo of this Community
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Bitstream)
|
@relationship(ResourceType.Bitstream, false)
|
||||||
logo: string;
|
logo: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of Communities that are direct parents of this Community
|
* An array of Communities that are direct parents of this Community
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Community)
|
@relationship(ResourceType.Community, true)
|
||||||
parents: Array<string>;
|
parents: Array<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Community that owns this Community
|
* The Community that owns this Community
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Community)
|
@relationship(ResourceType.Community, false)
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Collection)
|
@relationship(ResourceType.Collection, true)
|
||||||
collections: Array<string>;
|
collections: Array<string>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,19 @@
|
|||||||
import { autoserialize, autoserializeAs } from "cerialize";
|
import { autoserialize, autoserializeAs, inheritSerialization } from "cerialize";
|
||||||
import { CacheableObject } from "../object-cache.reducer";
|
|
||||||
import { Metadatum } from "../../shared/metadatum.model";
|
import { Metadatum } from "../../shared/metadatum.model";
|
||||||
import { ResourceType } from "../../shared/resource-type";
|
import { ResourceType } from "../../shared/resource-type";
|
||||||
|
import { NormalizedObject } from "./normalized-object.model";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract model class for a DSpaceObject.
|
* 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
|
@autoserialize
|
||||||
self: string;
|
self: string;
|
||||||
|
|
||||||
@@ -22,6 +28,9 @@ export abstract class NormalizedDSpaceObject implements CacheableObject {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The universally unique identifier of this DSpaceObject
|
* 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
|
@autoserialize
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
@@ -42,17 +42,17 @@ export class NormalizedItem extends NormalizedDSpaceObject {
|
|||||||
* An array of Collections that are direct parents of this Item
|
* An array of Collections that are direct parents of this Item
|
||||||
*/
|
*/
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Collection)
|
@relationship(ResourceType.Collection, true)
|
||||||
parents: Array<string>;
|
parents: Array<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Collection that owns this Item
|
* The Collection that owns this Item
|
||||||
*/
|
*/
|
||||||
@autoserializeAs(String, 'owningCollection')
|
@autoserializeAs(String, 'owningCollection')
|
||||||
@relationship(ResourceType.Collection)
|
@relationship(ResourceType.Collection, false)
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
@autoserialize
|
@autoserialize
|
||||||
@relationship(ResourceType.Bitstream)
|
@relationship(ResourceType.Bitstream, true)
|
||||||
bitstreams: Array<string>;
|
bitstreams: Array<string>;
|
||||||
}
|
}
|
||||||
|
@@ -6,13 +6,20 @@ import { NormalizedCollection } from "./normalized-collection.model";
|
|||||||
import { GenericConstructor } from "../../shared/generic-constructor";
|
import { GenericConstructor } from "../../shared/generic-constructor";
|
||||||
import { NormalizedCommunity } from "./normalized-community.model";
|
import { NormalizedCommunity } from "./normalized-community.model";
|
||||||
import { ResourceType } from "../../shared/resource-type";
|
import { ResourceType } from "../../shared/resource-type";
|
||||||
|
import { NormalizedObject } from "./normalized-object.model";
|
||||||
|
import { NormalizedBitstreamFormat } from "./normalized-bitstream-format.model";
|
||||||
|
|
||||||
export class NormalizedObjectFactory {
|
export class NormalizedObjectFactory {
|
||||||
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedDSpaceObject> {
|
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ResourceType.Bitstream: {
|
case ResourceType.Bitstream: {
|
||||||
return NormalizedBitstream
|
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: {
|
case ResourceType.Bundle: {
|
||||||
return NormalizedBundle
|
return NormalizedBundle
|
||||||
}
|
}
|
||||||
|
20
src/app/core/cache/models/normalized-object.model.ts
vendored
Normal file
20
src/app/core/cache/models/normalized-object.model.ts
vendored
Normal 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;
|
||||||
|
|
||||||
|
}
|
@@ -72,7 +72,16 @@ export class RequestEffects {
|
|||||||
.filter(property => data.hasOwnProperty(property))
|
.filter(property => data.hasOwnProperty(property))
|
||||||
.filter(property => hasValue(data[property]))
|
.filter(property => hasValue(data[property]))
|
||||||
.forEach(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;
|
return uuids;
|
||||||
}
|
}
|
||||||
@@ -122,13 +131,15 @@ export class RequestEffects {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//TODO move check to Validator?
|
//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 {
|
else {
|
||||||
//TODO move check to Validator
|
//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 [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,9 +5,9 @@ import { Item } from "./item.model";
|
|||||||
export class Bitstream extends DSpaceObject {
|
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
|
* The mime type of this Bitstream
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
export enum ResourceType {
|
export enum ResourceType {
|
||||||
Bundle = <any> "bundle",
|
Bundle = <any> "bundle",
|
||||||
Bitstream = <any> "bitstream",
|
Bitstream = <any> "bitstream",
|
||||||
|
BitstreamFormat = <any> "bitstreamformat",
|
||||||
Item = <any> "item",
|
Item = <any> "item",
|
||||||
Collection = <any> "collection",
|
Collection = <any> "collection",
|
||||||
Community = <any> "community"
|
Community = <any> "community"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<ds-metadata-field-wrapper [label]="label | translate">
|
<ds-metadata-field-wrapper [label]="label | translate">
|
||||||
<div class="collections">
|
<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>
|
<span>{{collection?.name}}</span><span *ngIf="!last" [innerHTML]="separator"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -19,7 +19,7 @@ export class CollectionsComponent implements OnInit {
|
|||||||
|
|
||||||
label : string = "item.page.collections";
|
label : string = "item.page.collections";
|
||||||
|
|
||||||
separator: string = "<br/>"
|
separator: string = "<br/>";
|
||||||
|
|
||||||
collections: Observable<Collection[]>;
|
collections: Observable<Collection[]>;
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export class CollectionsComponent implements OnInit {
|
|||||||
//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,
|
// for an Item aren't returned by the REST API yet,
|
||||||
// only the owning collection
|
// only the owning collection
|
||||||
this.collections = this.rdbs.aggregate([this.item.owner]).payload
|
this.collections = this.item.owner.payload.map(c => [c]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="file-section">
|
<div class="file-section">
|
||||||
<a *ngFor="let file of (files | async); let last=last;" [href]="file?.url">
|
<a *ngFor="let file of (files | async); let last=last;" [href]="file?.url">
|
||||||
<span>{{file?.name}}</span>
|
<span>{{file?.name}}</span>
|
||||||
<span>({{(file?.size) | dsFileSize }})</span>
|
<span>({{(file?.sizeBytes) | dsFileSize }})</span>
|
||||||
<span *ngIf="!last" innerHTML="{{separator}}"></span>
|
<span *ngIf="!last" innerHTML="{{separator}}"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user