1
0

followlinks

This commit is contained in:
Art Lowel
2020-01-17 17:50:24 +01:00
parent ad4e8eeb8c
commit 44facb8dcb
68 changed files with 636 additions and 283 deletions

View File

@@ -6,6 +6,7 @@ import { CollectionDataService } from '../core/data/collection-data.service';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { find } from 'rxjs/operators'; import { find } from 'rxjs/operators';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { followLink } from '../shared/utils/follow-link-config.model';
/** /**
* This class represents a resolver that requests a specific collection before the route is activated * This class represents a resolver that requests a specific collection before the route is activated
@@ -23,7 +24,7 @@ export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
* or an error if something went wrong * or an error if something went wrong
*/ */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
return this.collectionService.findById(route.params.id).pipe( return this.collectionService.findById(route.params.id, followLink('logo')).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded), find((RD) => hasValue(RD.error) || RD.hasSucceeded),
); );
} }

View File

@@ -6,6 +6,7 @@ import { Community } from '../core/shared/community.model';
import { CommunityDataService } from '../core/data/community-data.service'; import { CommunityDataService } from '../core/data/community-data.service';
import { find } from 'rxjs/operators'; import { find } from 'rxjs/operators';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { followLink } from '../shared/utils/follow-link-config.model';
/** /**
* This class represents a resolver that requests a specific community before the route is activated * This class represents a resolver that requests a specific community before the route is activated
@@ -23,7 +24,12 @@ export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
* or an error if something went wrong * or an error if something went wrong
*/ */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
return this.communityService.findById(route.params.id).pipe( return this.communityService.findById(
route.params.id,
followLink('logo'),
followLink('subcommunities'),
followLink('collections')
).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded) find((RD) => hasValue(RD.error) || RD.hasSucceeded)
); );
} }

View File

@@ -1,12 +1,12 @@
import { Component, Injector, Input, OnInit } from '@angular/core'; import { Component, Injector, Input, OnInit } from '@angular/core';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators'; import { map, startWith } from 'rxjs/operators';
import { getBitstreamBuilder } from '../../../../core/cache/builders/bitstream-builder';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Bitstream } from '../../../../core/shared/bitstream.model';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators'; import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators';
import { followLink } from '../../../../shared/utils/follow-link-config.model';
import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component'; import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component';
/** /**
@@ -28,8 +28,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
bitstreams$: Observable<Bitstream[]>; bitstreams$: Observable<Bitstream[]>;
constructor( constructor(
bitstreamDataService: BitstreamDataService, bitstreamDataService: BitstreamDataService
private parentInjector: Injector
) { ) {
super(bitstreamDataService); super(bitstreamDataService);
} }
@@ -40,11 +39,21 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
initialize(): void { initialize(): void {
// TODO pagination // TODO pagination
const originals$ = this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'ORIGINAL', { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe( const originals$ = this.bitstreamDataService.findAllByItemAndBundleName(
this.item,
'ORIGINAL',
{ elementsPerPage: Number.MAX_SAFE_INTEGER },
followLink( 'format')
).pipe(
getFirstSucceededRemoteListPayload(), getFirstSucceededRemoteListPayload(),
startWith([]) startWith([])
); );
const licenses$ = this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'LICENSE', { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe( const licenses$ = this.bitstreamDataService.findAllByItemAndBundleName(
this.item,
'LICENSE',
{ elementsPerPage: Number.MAX_SAFE_INTEGER },
followLink( 'format')
).pipe(
getFirstSucceededRemoteListPayload(), getFirstSucceededRemoteListPayload(),
startWith([]) startWith([])
); );
@@ -53,10 +62,8 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
map((files: Bitstream[]) => map((files: Bitstream[]) =>
files.map( files.map(
(original) => { (original) => {
return getBitstreamBuilder(this.parentInjector, original) original.thumbnail = this.bitstreamDataService.getMatchingThumbnail(this.item, original);
.loadThumbnail(this.item) return original;
.loadBitstreamFormat()
.build();
} }
) )
) )

View File

@@ -6,6 +6,7 @@ import { ItemDataService } from '../core/data/item-data.service';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { find } from 'rxjs/operators'; import { find } from 'rxjs/operators';
import { followLink } from '../shared/utils/follow-link-config.model';
/** /**
* This class represents a resolver that requests a specific item before the route is activated * This class represents a resolver that requests a specific item before the route is activated
@@ -23,9 +24,12 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
* or an error if something went wrong * or an error if something went wrong
*/ */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
return this.itemService.findById(route.params.id) return this.itemService.findById(route.params.id,
.pipe( followLink('owningCollection'),
find((RD) => hasValue(RD.error) || RD.hasSucceeded), followLink('bundles'),
); followLink('relationships')
).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
);
} }
} }

View File

@@ -14,7 +14,7 @@ describe('LookupGuard', () => {
guard = new LookupGuard(dsoService); guard = new LookupGuard(dsoService);
}); });
it('should call findById with handle params', () => { it('should call findByIdAndIDType with handle params', () => {
const scopedRoute = { const scopedRoute = {
params: { params: {
id: '1234', id: '1234',
@@ -25,7 +25,7 @@ describe('LookupGuard', () => {
expect(dsoService.findById).toHaveBeenCalledWith('123456789/1234', IdentifierType.HANDLE) expect(dsoService.findById).toHaveBeenCalledWith('123456789/1234', IdentifierType.HANDLE)
}); });
it('should call findById with handle params', () => { it('should call findByIdAndIDType with handle params', () => {
const scopedRoute = { const scopedRoute = {
params: { params: {
id: '123456789%2F1234', id: '123456789%2F1234',
@@ -36,7 +36,7 @@ describe('LookupGuard', () => {
expect(dsoService.findById).toHaveBeenCalledWith('123456789%2F1234', IdentifierType.HANDLE) expect(dsoService.findById).toHaveBeenCalledWith('123456789%2F1234', IdentifierType.HANDLE)
}); });
it('should call findById with UUID params', () => { it('should call findByIdAndIDType with UUID params', () => {
const scopedRoute = { const scopedRoute = {
params: { params: {
id: '34cfed7c-f597-49ef-9cbe-ea351f0023c2', id: '34cfed7c-f597-49ef-9cbe-ea351f0023c2',

View File

@@ -20,7 +20,7 @@ export class LookupGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const params = this.getLookupParams(route); const params = this.getLookupParams(route);
return this.dsoService.findById(params.id, params.type).pipe( return this.dsoService.findByIdAndIDType(params.id, params.type).pipe(
map((response: RemoteData<FindByIDRequest>) => response.hasFailed) map((response: RemoteData<FindByIDRequest>) => response.hasFailed)
); );
} }

View File

@@ -1,3 +1,4 @@
import { HALLink } from '../../shared/hal-link.model';
import { AuthError } from './auth-error.model'; import { AuthError } from './auth-error.model';
import { AuthTokenInfo } from './auth-token-info.model'; import { AuthTokenInfo } from './auth-token-info.model';
import { EPerson } from '../../eperson/models/eperson.model'; import { EPerson } from '../../eperson/models/eperson.model';
@@ -51,4 +52,8 @@ export class AuthStatus implements CacheableObject {
* The self link of this auth status' REST object * The self link of this auth status' REST object
*/ */
self: string; self: string;
_links: {
self: HALLink;
}
} }

View File

@@ -1,60 +0,0 @@
import { Injector } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { BitstreamDataService } from '../../data/bitstream-data.service';
import { BitstreamFormatDataService } from '../../data/bitstream-format-data.service';
import { RemoteData } from '../../data/remote-data';
import { BitstreamFormat } from '../../shared/bitstream-format.model';
import { Bitstream } from '../../shared/bitstream.model';
import { Item } from '../../shared/item.model';
export const getBitstreamBuilder = (parentInjector: Injector, bitstream: Bitstream) => {
const injector = Injector.create({
providers:[
{
provide: BitstreamBuilder,
useClass: BitstreamBuilder,
deps:[
BitstreamDataService,
BitstreamFormatDataService,
]
}
],
parent: parentInjector
});
return injector.get(BitstreamBuilder).initWithBitstream(bitstream);
};
export class BitstreamBuilder {
private bitstream: Bitstream;
private thumbnail: Observable<RemoteData<Bitstream>>;
private format: Observable<RemoteData<BitstreamFormat>>;
constructor(
protected bitstreamDataService: BitstreamDataService,
protected bitstreamFormatDataService: BitstreamFormatDataService
) {
}
initWithBitstream(bitstream: Bitstream): BitstreamBuilder {
this.bitstream = bitstream;
return this;
}
loadThumbnail(item: Item): BitstreamBuilder {
this.thumbnail = this.bitstreamDataService.getMatchingThumbnail(item, this.bitstream);
return this;
}
loadBitstreamFormat(): BitstreamBuilder {
this.format = this.bitstreamFormatDataService.findByBitstream(this.bitstream);
return this;
}
build(): Bitstream {
const bitstream = this.bitstream;
bitstream.thumbnail = this.thumbnail;
bitstream.format = this.format;
return bitstream;
}
}

View File

@@ -1,14 +1,22 @@
import 'reflect-metadata'; import 'reflect-metadata';
import { hasNoValue, hasValue } from '../../../shared/empty.util';
import { DataService } from '../../data/data.service';
import { PaginatedList } from '../../data/paginated-list';
import { GenericConstructor } from '../../shared/generic-constructor'; import { GenericConstructor } from '../../shared/generic-constructor';
import { HALResource } from '../../shared/hal-resource.model';
import { CacheableObject, TypedObject } from '../object-cache.reducer'; import { CacheableObject, TypedObject } from '../object-cache.reducer';
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
const mapsToMetadataKey = Symbol('mapsTo'); const mapsToMetadataKey = Symbol('mapsTo');
const relationshipKey = Symbol('relationship'); const relationshipKey = Symbol('relationship');
const resolvedLinkKey = Symbol('resolvedLink');
const relationshipMap = new Map(); const relationshipMap = new Map();
const resolvedLinkMap = new Map();
const typeMap = new Map(); const typeMap = new Map();
const dataServiceMap = new Map();
const linkMap = new Map();
/** /**
* Decorator function to map a normalized class to it's not-normalized counter part class * Decorator function to map a normalized class to it's not-normalized counter part class
@@ -79,3 +87,94 @@ export function getRelationMetadata(target: any, propertyKey: string) {
export function getRelationships(target: any) { export function getRelationships(target: any) {
return relationshipMap.get(target); return relationshipMap.get(target);
} }
export function dataService<T extends CacheableObject>(domainModelConstructor: GenericConstructor<T>): any {
return (target: any) => {
if (hasNoValue(domainModelConstructor)) {
throw new Error(`Invalid @dataService annotation on ${target}, domainModelConstructor needs to be defined`);
}
const existingDataservice = dataServiceMap.get(domainModelConstructor);
if (hasValue(existingDataservice)) {
throw new Error(`Multiple dataservices for ${domainModelConstructor}: ${existingDataservice} and ${target}`);
}
dataServiceMap.set(domainModelConstructor, target);
};
}
export function getDataServiceFor<T extends CacheableObject>(domainModelConstructor: GenericConstructor<T>) {
return dataServiceMap.get(domainModelConstructor);
}
export function resolvedLink<T extends DataService<any>, K extends keyof T>(provider: GenericConstructor<T>, methodName?: K, ...params: any[]): any {
return function r(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target || !propertyKey) {
return;
}
const metaDataList: string[] = resolvedLinkMap.get(target.constructor) || [];
if (metaDataList.indexOf(propertyKey) === -1) {
metaDataList.push(propertyKey);
}
resolvedLinkMap.set(target.constructor, metaDataList);
return Reflect.metadata(resolvedLinkKey, {
provider,
methodName,
params
}).apply(this, arguments);
};
}
export function getResolvedLinkMetadata(target: any, propertyKey: string) {
return Reflect.getMetadata(resolvedLinkKey, target, propertyKey);
}
export function getResolvedLinks(target: any) {
return resolvedLinkMap.get(target);
}
export class LinkDefinition<T extends HALResource> {
targetConstructor: GenericConstructor<CacheableObject>;
isList = false;
linkName?: keyof T['_links'];
}
export const link = <T extends HALResource>(
targetConstructor: GenericConstructor<HALResource>,
isList = false,
linkName?: keyof T['_links'],
) => {
return (target: T, key: string) => {
let targetMap = linkMap.get(target.constructor);
if (hasNoValue(targetMap)) {
targetMap = new Map<keyof T['_links'],LinkDefinition<T>>();
}
if (hasNoValue(linkName)) {
linkName = key;
}
targetMap.set(key, {
targetConstructor,
isList,
linkName
});
linkMap.set(target.constructor, targetMap);
}
};
export const getLinks = <T extends HALResource>(source: GenericConstructor<T>): Map<keyof T['_links'], LinkDefinition<T>> => {
return linkMap.get(source);
};
export const getLink = <T extends HALResource>(source: GenericConstructor<T>, linkName: keyof T['_links']): LinkDefinition<T> => {
const sourceMap = linkMap.get(source);
if (hasValue(sourceMap)) {
return sourceMap.get(linkName);
} else {
return undefined;
}
};

View File

@@ -0,0 +1,74 @@
import { Injector } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { BitstreamDataService } from '../../data/bitstream-data.service';
import { CollectionDataService } from '../../data/collection-data.service';
import { PaginatedList } from '../../data/paginated-list';
import { RemoteData } from '../../data/remote-data';
import { ResourcePolicyService } from '../../data/resource-policy.service';
import { Bitstream } from '../../shared/bitstream.model';
import { Collection } from '../../shared/collection.model';
import { Item } from '../../shared/item.model';
import { ResourcePolicy } from '../../shared/resource-policy.model';
export const getCollectionBuilder = (parentInjector: Injector, collection: Collection) => {
const injector = Injector.create({
providers:[
{
provide: CollectionBuilder,
useClass: CollectionBuilder,
deps:[
CollectionDataService
]
}
],
parent: parentInjector
});
return injector.get(CollectionBuilder).initWithCollection(collection);
};
export class CollectionBuilder {
private collection: Collection;
private logo: Observable<RemoteData<Bitstream>>;
// private license: Observable<RemoteData<License>>;
private defaultAccessConditions: Observable<RemoteData<PaginatedList<ResourcePolicy>>>;
constructor(
protected collectionDataService: CollectionDataService,
protected bitstreamDataService: BitstreamDataService,
protected resourcePolicyService: ResourcePolicyService,
) {
}
initWithCollection(collection: Collection): CollectionBuilder {
this.collection = collection;
return this;
}
loadLogo(item: Item): CollectionBuilder {
// this.logo = this.bitstreamDataService.getLogoFor(this.collection);
return this;
}
loadDefaultAccessConditions(item: Item): CollectionBuilder {
this.defaultAccessConditions = this.resourcePolicyService.getDefaultAccessConditionsFor(this.collection);
return this;
}
/**
* As far as I can tell, the rest api doesn't support licenses yet.
* So I'm keeping this commented out
*/
// loadLicense(): CollectionBuilder {
// this.license = this.bitstreamDataService.getLicenseFor(this.collection);
// return this;
// }
build(): Collection {
const collection = this.collection;
collection.logo = this.logo;
// collection.license = this.license;
collection.defaultAccessConditions = this.defaultAccessConditions;
return collection;
}
}

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import { import {
combineLatest as observableCombineLatest, combineLatest as observableCombineLatest,
@@ -16,16 +16,24 @@ import {
isNotEmpty, isNotEmpty,
isNotUndefined isNotUndefined
} from '../../../shared/empty.util'; } from '../../../shared/empty.util';
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
import { PaginatedList } from '../../data/paginated-list'; import { PaginatedList } from '../../data/paginated-list';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { RemoteDataError } from '../../data/remote-data-error'; import { RemoteDataError } from '../../data/remote-data-error';
import { GetRequest } from '../../data/request.models'; import { GetRequest } from '../../data/request.models';
import { RequestEntry } from '../../data/request.reducer'; import { RequestEntry } from '../../data/request.reducer';
import { RequestService } from '../../data/request.service'; import { RequestService } from '../../data/request.service';
import { HALResource } from '../../shared/hal-resource.model';
import { NormalizedObject } from '../models/normalized-object.model'; import { NormalizedObject } from '../models/normalized-object.model';
import { ObjectCacheService } from '../object-cache.service'; import { ObjectCacheService } from '../object-cache.service';
import { DSOSuccessResponse, ErrorResponse } from '../response.models'; import { DSOSuccessResponse, ErrorResponse } from '../response.models';
import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators'; import {
getDataServiceFor, getLink,
getLinks,
getMapsTo,
getRelationMetadata,
getRelationships
} from './build-decorators';
import { PageInfo } from '../../shared/page-info.model'; import { PageInfo } from '../../shared/page-info.model';
import { import {
filterSuccessfulResponses, filterSuccessfulResponses,
@@ -39,10 +47,11 @@ import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils
@Injectable() @Injectable()
export class RemoteDataBuildService { export class RemoteDataBuildService {
constructor(protected objectCache: ObjectCacheService, constructor(protected objectCache: ObjectCacheService,
private parentInjector: Injector,
protected requestService: RequestService) { protected requestService: RequestService) {
} }
buildSingle<T extends CacheableObject>(href$: string | Observable<string>): Observable<RemoteData<T>> { buildSingle<T extends CacheableObject>(href$: string | Observable<string>, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<T>> {
if (typeof href$ === 'string') { if (typeof href$ === 'string') {
href$ = observableOf(href$); href$ = observableOf(href$);
} }
@@ -83,7 +92,7 @@ export class RemoteDataBuildService {
}), }),
hasValueOperator(), hasValueOperator(),
map((normalized: NormalizedObject<T>) => { map((normalized: NormalizedObject<T>) => {
return this.build<T>(normalized); return this.build<T>(normalized, ...linksToFollow);
}), }),
startWith(undefined), startWith(undefined),
distinctUntilChanged() distinctUntilChanged()
@@ -120,7 +129,7 @@ export class RemoteDataBuildService {
); );
} }
buildList<T extends CacheableObject>(href$: string | Observable<string>): Observable<RemoteData<PaginatedList<T>>> { buildList<T extends CacheableObject>(href$: string | Observable<string>, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<PaginatedList<T>>> {
if (typeof href$ === 'string') { if (typeof href$ === 'string') {
href$ = observableOf(href$); href$ = observableOf(href$);
} }
@@ -132,7 +141,7 @@ export class RemoteDataBuildService {
return this.objectCache.getList(resourceUUIDs).pipe( return this.objectCache.getList(resourceUUIDs).pipe(
map((normList: Array<NormalizedObject<T>>) => { map((normList: Array<NormalizedObject<T>>) => {
return normList.map((normalized: NormalizedObject<T>) => { return normList.map((normalized: NormalizedObject<T>) => {
return this.build<T>(normalized); return this.build<T>(normalized, ...linksToFollow);
}); });
})); }));
}), }),
@@ -162,8 +171,8 @@ export class RemoteDataBuildService {
return this.toRemoteDataObservable(requestEntry$, payload$); return this.toRemoteDataObservable(requestEntry$, payload$);
} }
build<T extends CacheableObject>(normalized: NormalizedObject<T>): T { build<T extends CacheableObject>(normalized: NormalizedObject<T>, ...linksToFollow: Array<FollowLinkConfig<T>>): T {
const links: any = {}; const halLinks: any = {};
const relationships = getRelationships(normalized.constructor) || []; const relationships = getRelationships(normalized.constructor) || [];
relationships.forEach((relationship: string) => { relationships.forEach((relationship: string) => {
@@ -207,22 +216,61 @@ export class RemoteDataBuildService {
} }
if (hasValue(normalized[relationship].page)) { if (hasValue(normalized[relationship].page)) {
links[relationship] = this.toPaginatedList(result, normalized[relationship].pageInfo); halLinks[relationship] = this.toPaginatedList(result, normalized[relationship].pageInfo);
} else { } else {
links[relationship] = result; halLinks[relationship] = result;
} }
} else { } else {
if (hasNoValue(links._links)) { if (hasNoValue(halLinks._links)) {
links._links = {}; halLinks._links = {};
} }
links._links[relationship] = { halLinks._links[relationship] = {
href: objectList href: objectList
}; };
} }
} }
}); });
const domainModel = getMapsTo(normalized.constructor); const domainModelConstructor = getMapsTo(normalized.constructor);
return Object.assign(new domainModel(), normalized, links); const domainModel = Object.assign(new domainModelConstructor(), normalized, halLinks);
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
this.resolveLink(domainModel, linkToFollow);
});
console.log('domainModel._links', domainModel._links);
return domainModel;
}
public resolveLink<T extends HALResource>(model, linkToFollow: FollowLinkConfig<T>) {
console.log('resolveLink', model, linkToFollow);
const matchingLink = getLink(model.constructor, linkToFollow.name);
if (hasNoValue(matchingLink)) {
throw new Error(`followLink('${linkToFollow.name}') was used for a ${model.constructor.name}, but there is no property on ${model.constructor.name} models with an @link() for ${linkToFollow.name}`);
} else {
const provider = getDataServiceFor(matchingLink.targetConstructor);
if (hasNoValue(provider)) {
throw new Error(`The @link() for ${linkToFollow.name} on ${model.constructor.name} models refers to a ${matchingLink.targetConstructor.name}, but there is no service with an @dataService(${matchingLink.targetConstructor.name}) annotation in order to retrieve it`);
}
const service = Injector.create({
providers: [],
parent: this.parentInjector
}).get(provider);
const href = model._links[matchingLink.linkName].href;
if (matchingLink.isList) {
model[linkToFollow.name] = service.findAllByHref(href, linkToFollow.findListOptions, ...linkToFollow.linksToFollow);
} else {
model[linkToFollow.name] = service.findByHref(href, ...linkToFollow.linksToFollow);
}
console.log(`model['${linkToFollow.name}']`, model[linkToFollow.name]);
}
} }
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> { aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {

View File

@@ -25,32 +25,32 @@ export class NormalizedCommunity extends NormalizedDSpaceObject<Community> {
* The Bitstream that represents the logo of this Community * The Bitstream that represents the logo of this Community
*/ */
@deserialize @deserialize
@relationship(Bitstream, false) @relationship(Bitstream, false, 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
*/ */
@deserialize @deserialize
@relationship(Community, true) @relationship(Community, true, false)
parents: string[]; parents: string[];
/** /**
* The Community that owns this Community * The Community that owns this Community
*/ */
@deserialize @deserialize
@relationship(Community, false) @relationship(Community, false, false)
owner: string; owner: string;
/** /**
* List of Collections that are owned by this Community * List of Collections that are owned by this Community
*/ */
@deserialize @deserialize
@relationship(Collection, true) @relationship(Collection, true, false)
collections: string[]; collections: string[];
@deserialize @deserialize
@relationship(Community, true) @relationship(Community, true, false)
subcommunities: string[]; subcommunities: string[];
} }

View File

@@ -1,5 +1,6 @@
import { autoserializeAs, deserializeAs, autoserialize } from 'cerialize'; import { autoserializeAs, deserializeAs, autoserialize } from 'cerialize';
import { DSpaceObject } from '../../shared/dspace-object.model'; import { DSpaceObject } from '../../shared/dspace-object.model';
import { HALLink } from '../../shared/hal-link.model';
import { MetadataMap, MetadataMapSerializer } from '../../shared/metadata.models'; import { MetadataMap, MetadataMapSerializer } from '../../shared/metadata.models';
import { mapsTo } from '../builders/build-decorators'; import { mapsTo } from '../builders/build-decorators';
import { NormalizedObject } from './normalized-object.model'; import { NormalizedObject } from './normalized-object.model';
@@ -67,6 +68,7 @@ export class NormalizedDSpaceObject<T extends DSpaceObject> extends NormalizedOb
*/ */
@deserializeAs(Object) @deserializeAs(Object)
_links: { _links: {
[name: string]: string self: HALLink,
[name: string]: HALLink
} }
} }

View File

@@ -44,13 +44,6 @@ export class NormalizedItem extends NormalizedDSpaceObject<Item> {
@autoserializeAs(Boolean, 'withdrawn') @autoserializeAs(Boolean, 'withdrawn')
isWithdrawn: boolean; isWithdrawn: boolean;
/**
* An array of Collections that are direct parents of this Item
*/
@deserialize
@relationship(Collection, true, false)
parents: string[];
/** /**
* The Collection that owns this Item * The Collection that owns this Item
*/ */

View File

@@ -1,3 +1,4 @@
import { HALLink } from '../../shared/hal-link.model';
import { CacheableObject, TypedObject } from '../object-cache.reducer'; import { CacheableObject, TypedObject } from '../object-cache.reducer';
import { autoserialize, deserialize } from 'cerialize'; import { autoserialize, deserialize } from 'cerialize';
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
@@ -13,7 +14,8 @@ export abstract class NormalizedObject<T extends TypedObject> implements Cacheab
@deserialize @deserialize
_links: { _links: {
[name: string]: string self: HALLink,
[name: string]: HALLink
}; };
/** /**

View File

@@ -1,3 +1,5 @@
import { HALLink } from '../shared/hal-link.model';
import { HALResource } from '../shared/hal-resource.model';
import { import {
ObjectCacheAction, ObjectCacheAction,
ObjectCacheActionTypes, ObjectCacheActionTypes,
@@ -42,10 +44,14 @@ export abstract class TypedObject {
* *
* A cacheable object should have a self link * A cacheable object should have a self link
*/ */
export class CacheableObject extends TypedObject { export class CacheableObject extends TypedObject implements HALResource {
uuid?: string; uuid?: string;
handle?: string; handle?: string;
self: string; self: string;
_links: {
self: HALLink;
}
// isNew: boolean; // isNew: boolean;
// dirtyType: DirtyType; // dirtyType: DirtyType;
// hasDirtyAttributes: boolean; // hasDirtyAttributes: boolean;

View File

@@ -1,4 +1,5 @@
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../../shared/hal-link.model';
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
export abstract class ConfigObject implements CacheableObject { export abstract class ConfigObject implements CacheableObject {
@@ -11,8 +12,9 @@ export abstract class ConfigObject implements CacheableObject {
/** /**
* The links to all related resources returned by the rest api. * The links to all related resources returned by the rest api.
*/ */
public _links: { _links: {
[name: string]: string self: HALLink,
[name: string]: HALLink
}; };
/** /**

View File

@@ -1,6 +1,7 @@
import { autoserialize, inheritSerialization } from 'cerialize'; import { autoserialize, inheritSerialization } from 'cerialize';
import { NormalizedObject } from '../../cache/models/normalized-object.model'; import { NormalizedObject } from '../../cache/models/normalized-object.model';
import { CacheableObject, TypedObject } from '../../cache/object-cache.reducer'; import { CacheableObject, TypedObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../../shared/hal-link.model';
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
/** /**
@@ -20,7 +21,8 @@ export abstract class NormalizedConfigObject<T extends CacheableObject> implemen
*/ */
@autoserialize @autoserialize
public _links: { public _links: {
[name: string]: string self: HALLink,
[name: string]: HALLink
}; };
/** /**

View File

@@ -5,6 +5,8 @@ import { Observable } from 'rxjs/internal/Observable';
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { hasNoValue, hasValue } from '../../shared/empty.util'; import { hasNoValue, hasValue } from '../../shared/empty.util';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
@@ -26,6 +28,7 @@ import { RequestService } from './request.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@dataService(Bitstream)
export class BitstreamDataService extends DataService<Bitstream> { export class BitstreamDataService extends DataService<Bitstream> {
protected linkPath = 'bitstreams'; protected linkPath = 'bitstreams';
@@ -57,8 +60,8 @@ export class BitstreamDataService extends DataService<Bitstream> {
* @param bundle the bundle to retrieve bitstreams from * @param bundle the bundle to retrieve bitstreams from
* @param options options for the find all request * @param options options for the find all request
*/ */
findAllByBundle(bundle: Bundle, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bitstream>>> { findAllByBundle(bundle: Bundle, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Bitstream>>): Observable<RemoteData<PaginatedList<Bitstream>>> {
return this.findAllByHref(bundle._links.bitstreams.href, options); return this.findAllByHref(bundle._links.bitstreams.href, options, ...linksToFollow);
} }
/** /**
@@ -132,11 +135,11 @@ export class BitstreamDataService extends DataService<Bitstream> {
); );
} }
public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bitstream>>> { public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Bitstream>>): Observable<RemoteData<PaginatedList<Bitstream>>> {
return this.bundleService.findByItemAndName(item, bundleName).pipe( return this.bundleService.findByItemAndName(item, bundleName).pipe(
switchMap((bundleRD: RemoteData<Bundle>) => { switchMap((bundleRD: RemoteData<Bundle>) => {
if (hasValue(bundleRD.payload)) { if (hasValue(bundleRD.payload)) {
return this.findAllByBundle(bundleRD.payload, options); return this.findAllByBundle(bundleRD.payload, options, ...linksToFollow);
} else { } else {
return [bundleRD as any]; return [bundleRD as any];
} }

View File

@@ -1,4 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { dataService } from '../cache/builders/build-decorators';
import { Bitstream } from '../shared/bitstream.model'; import { Bitstream } from '../shared/bitstream.model';
import { DataService } from './data.service'; import { DataService } from './data.service';
import { BitstreamFormat } from '../shared/bitstream-format.model'; import { BitstreamFormat } from '../shared/bitstream-format.model';
@@ -40,6 +41,7 @@ const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSele
* A service responsible for fetching/sending data from/to the REST API on the bitstreamformats endpoint * A service responsible for fetching/sending data from/to the REST API on the bitstreamformats endpoint
*/ */
@Injectable() @Injectable()
@dataService(BitstreamFormat)
export class BitstreamFormatDataService extends DataService<BitstreamFormat> { export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
protected linkPath = 'bitstreamformats'; protected linkPath = 'bitstreamformats';

View File

@@ -5,6 +5,8 @@ import { Observable } from 'rxjs/internal/Observable';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
@@ -26,6 +28,7 @@ import { RequestService } from './request.service';
@Injectable( @Injectable(
{providedIn: 'root'} {providedIn: 'root'}
) )
@dataService(Bundle)
export class BundleDataService extends DataService<Bundle> { export class BundleDataService extends DataService<Bundle> {
protected linkPath = 'bundles'; protected linkPath = 'bundles';
protected forceBypassCache = false; protected forceBypassCache = false;
@@ -52,13 +55,13 @@ export class BundleDataService extends DataService<Bundle> {
return this.halService.getEndpoint(this.linkPath); return this.halService.getEndpoint(this.linkPath);
} }
findAllByItem(item: Item, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bundle>>> { findAllByItem(item: Item, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Bundle>>): Observable<RemoteData<PaginatedList<Bundle>>> {
return this.findAllByHref(item._links.bundles.href, options); return this.findAllByHref(item._links.bundles.href, options, ...linksToFollow);
} }
// TODO should be implemented rest side // TODO should be implemented rest side
findByItemAndName(item: Item, bundleName: string): Observable<RemoteData<Bundle>> { findByItemAndName(item: Item, bundleName: string, ...linksToFollow: Array<FollowLinkConfig<Bundle>>): Observable<RemoteData<Bundle>> {
return this.findAllByItem(item, { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe( return this.findAllByItem(item, { elementsPerPage: Number.MAX_SAFE_INTEGER }, ...linksToFollow).pipe(
map((rd: RemoteData<PaginatedList<Bundle>>) => { map((rd: RemoteData<PaginatedList<Bundle>>) => {
if (hasValue(rd.payload) && hasValue(rd.payload.page)) { if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
const matchingBundle = rd.payload.page.find((bundle: Bundle) => const matchingBundle = rd.payload.page.find((bundle: Bundle) =>

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators'; import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
@@ -48,6 +49,7 @@ import { INotification } from '../../shared/notifications/models/notification.mo
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
@Injectable() @Injectable()
@dataService(Collection)
export class CollectionDataService extends ComColDataService<Collection> { export class CollectionDataService extends ComColDataService<Collection> {
protected linkPath = 'collections'; protected linkPath = 'collections';
protected errorTitle = 'collection.source.update.notifications.error.title'; protected errorTitle = 'collection.source.update.notifications.error.title';

View File

@@ -8,6 +8,7 @@ import { merge as observableMerge, Observable, throwError as observableThrowErro
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { NormalizedCommunity } from '../cache/models/normalized-community.model';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { HALLink } from '../shared/hal-link.model';
import { CommunityDataService } from './community-data.service'; import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service'; import { DataService } from './data.service';
@@ -71,7 +72,8 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
filter((response) => response.isSuccessful), filter((response) => response.isSuccessful),
mergeMap(() => this.objectCache.getObjectByUUID(options.scopeID)), mergeMap(() => this.objectCache.getObjectByUUID(options.scopeID)),
map((nc: NormalizedCommunity) => nc._links[linkPath]), map((nc: NormalizedCommunity) => nc._links[linkPath]),
filter((href) => isNotEmpty(href)) filter((halLink: HALLink) => isNotEmpty(halLink)),
map((halLink: HALLink) => halLink.href)
); );
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share()); return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share());

View File

@@ -2,6 +2,7 @@ import { filter, switchMap, take } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
@@ -20,6 +21,7 @@ import { NormalizedObjectBuildService } from '../cache/builders/normalized-objec
import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
@Injectable() @Injectable()
@dataService(Community)
export class CommunityDataService extends ComColDataService<Community> { export class CommunityDataService extends ComColDataService<Community> {
protected linkPath = 'communities'; protected linkPath = 'communities';
protected topLinkPath = 'communities/search/top'; protected topLinkPath = 'communities/search/top';

View File

@@ -16,6 +16,7 @@ import {
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { URLCombiner } from '../url-combiner/url-combiner'; import { URLCombiner } from '../url-combiner/url-combiner';
@@ -30,7 +31,6 @@ import {
GetRequest GetRequest
} from './request.models'; } from './request.models';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { NormalizedObject } from '../cache/models/normalized-object.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { SearchParam } from '../cache/models/search-param.model'; import { SearchParam } from '../cache/models/search-param.model';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
@@ -140,11 +140,11 @@ export abstract class DataService<T extends CacheableObject> {
} }
} }
findAll(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> { findAll(options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<PaginatedList<T>>> {
return this.findList(this.getFindAllHref(options), options); return this.findList(this.getFindAllHref(options), options, ...linksToFollow);
} }
protected findList(href$, options: FindListOptions) { protected findList(href$, options: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<T>>) {
href$.pipe( href$.pipe(
first((href: string) => hasValue(href))) first((href: string) => hasValue(href)))
.subscribe((href: string) => { .subscribe((href: string) => {
@@ -155,7 +155,7 @@ export abstract class DataService<T extends CacheableObject> {
this.requestService.configure(request); this.requestService.configure(request);
}); });
return this.rdbService.buildList<T>(href$) as Observable<RemoteData<PaginatedList<T>>>; return this.rdbService.buildList<T>(href$, ...linksToFollow) as Observable<RemoteData<PaginatedList<T>>>;
} }
/** /**
@@ -167,7 +167,7 @@ export abstract class DataService<T extends CacheableObject> {
return `${endpoint}/${resourceID}`; return `${endpoint}/${resourceID}`;
} }
findById(id: string): Observable<RemoteData<T>> { findById(id: string, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<T>> {
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id)))); map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id))));
@@ -182,27 +182,27 @@ export abstract class DataService<T extends CacheableObject> {
this.requestService.configure(request); this.requestService.configure(request);
}); });
return this.rdbService.buildSingle<T>(hrefObs); return this.rdbService.buildSingle<T>(hrefObs, ...linksToFollow);
} }
findByHref(href: string, findListOptions: FindListOptions = {}, httpOptions?: HttpOptions): Observable<RemoteData<T>> { findByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<T>> {
const requestHref = this.buildHrefFromFindOptions(href, findListOptions, []); const requestHref = this.buildHrefFromFindOptions(href, {}, []);
const request = new GetRequest(this.requestService.generateRequestId(), requestHref, null, httpOptions); const request = new GetRequest(this.requestService.generateRequestId(), requestHref);
if (hasValue(this.responseMsToLive)) { if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive; request.responseMsToLive = this.responseMsToLive;
} }
this.requestService.configure(request); this.requestService.configure(request);
return this.rdbService.buildSingle<T>(href); return this.rdbService.buildSingle<T>(href, ...linksToFollow);
} }
findAllByHref(href: string, findListOptions: FindListOptions = {}, httpOptions?: HttpOptions): Observable<RemoteData<PaginatedList<T>>> { findAllByHref(href: string, findListOptions: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<PaginatedList<T>>> {
const requestHref = this.buildHrefFromFindOptions(href, findListOptions, []); const requestHref = this.buildHrefFromFindOptions(href, findListOptions, []);
const request = new GetRequest(this.requestService.generateRequestId(), requestHref, null, httpOptions); const request = new GetRequest(this.requestService.generateRequestId(), requestHref);
if (hasValue(this.responseMsToLive)) { if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive; request.responseMsToLive = this.responseMsToLive;
} }
this.requestService.configure(request); this.requestService.configure(request);
return this.rdbService.buildList<T>(requestHref); return this.rdbService.buildList<T>(requestHref, ...linksToFollow);
} }
/** /**
@@ -224,7 +224,7 @@ export abstract class DataService<T extends CacheableObject> {
* @return {Observable<RemoteData<PaginatedList<T>>} * @return {Observable<RemoteData<PaginatedList<T>>}
* Return an observable that emits response from the server * Return an observable that emits response from the server
*/ */
protected searchBy(searchMethod: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> { protected searchBy(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<PaginatedList<T>>> {
const hrefObs = this.getSearchByHref(searchMethod, options); const hrefObs = this.getSearchByHref(searchMethod, options);
@@ -241,7 +241,7 @@ export abstract class DataService<T extends CacheableObject> {
switchMap((href) => this.requestService.getByHref(href)), switchMap((href) => this.requestService.getByHref(href)),
skipWhile((requestEntry) => hasValue(requestEntry) && requestEntry.completed), skipWhile((requestEntry) => hasValue(requestEntry) && requestEntry.completed),
switchMap((href) => switchMap((href) =>
this.rdbService.buildList<T>(hrefObs) as Observable<RemoteData<PaginatedList<T>>> this.rdbService.buildList<T>(hrefObs, ...linksToFollow) as Observable<RemoteData<PaginatedList<T>>>
) )
); );
} }

View File

@@ -83,7 +83,7 @@ describe('DsoRedirectDataService', () => {
describe('findById', () => { describe('findById', () => {
it('should call HALEndpointService with the path to the pid endpoint', () => { it('should call HALEndpointService with the path to the pid endpoint', () => {
setup(); setup();
scheduler.schedule(() => service.findById(dsoHandle, IdentifierType.HANDLE)); scheduler.schedule(() => service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE));
scheduler.flush(); scheduler.flush();
expect(halService.getEndpoint).toHaveBeenCalledWith('pid'); expect(halService.getEndpoint).toHaveBeenCalledWith('pid');
@@ -91,7 +91,7 @@ describe('DsoRedirectDataService', () => {
it('should call HALEndpointService with the path to the dso endpoint', () => { it('should call HALEndpointService with the path to the dso endpoint', () => {
setup(); setup();
scheduler.schedule(() => service.findById(dsoUUID, IdentifierType.UUID)); scheduler.schedule(() => service.findByIdAndIDType(dsoUUID, IdentifierType.UUID));
scheduler.flush(); scheduler.flush();
expect(halService.getEndpoint).toHaveBeenCalledWith('dso'); expect(halService.getEndpoint).toHaveBeenCalledWith('dso');
@@ -99,7 +99,7 @@ describe('DsoRedirectDataService', () => {
it('should call HALEndpointService with the path to the dso endpoint when identifier type not specified', () => { it('should call HALEndpointService with the path to the dso endpoint when identifier type not specified', () => {
setup(); setup();
scheduler.schedule(() => service.findById(dsoUUID)); scheduler.schedule(() => service.findByIdAndIDType(dsoUUID));
scheduler.flush(); scheduler.flush();
expect(halService.getEndpoint).toHaveBeenCalledWith('dso'); expect(halService.getEndpoint).toHaveBeenCalledWith('dso');
@@ -107,7 +107,7 @@ describe('DsoRedirectDataService', () => {
it('should configure the proper FindByIDRequest for uuid', () => { it('should configure the proper FindByIDRequest for uuid', () => {
setup(); setup();
scheduler.schedule(() => service.findById(dsoUUID, IdentifierType.UUID)); scheduler.schedule(() => service.findByIdAndIDType(dsoUUID, IdentifierType.UUID));
scheduler.flush(); scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestUUIDURL, dsoUUID)); expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestUUIDURL, dsoUUID));
@@ -115,7 +115,7 @@ describe('DsoRedirectDataService', () => {
it('should configure the proper FindByIDRequest for handle', () => { it('should configure the proper FindByIDRequest for handle', () => {
setup(); setup();
scheduler.schedule(() => service.findById(dsoHandle, IdentifierType.HANDLE)); scheduler.schedule(() => service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE));
scheduler.flush(); scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestHandleURL, dsoHandle)); expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestHandleURL, dsoHandle));
@@ -124,7 +124,7 @@ describe('DsoRedirectDataService', () => {
it('should navigate to item route', () => { it('should navigate to item route', () => {
remoteData.payload.type = 'item'; remoteData.payload.type = 'item';
setup(); setup();
const redir = service.findById(dsoHandle, IdentifierType.HANDLE); const redir = service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE);
// The framework would normally subscribe but do it here so we can test navigation. // The framework would normally subscribe but do it here so we can test navigation.
redir.subscribe(); redir.subscribe();
scheduler.schedule(() => redir); scheduler.schedule(() => redir);
@@ -135,7 +135,7 @@ describe('DsoRedirectDataService', () => {
it('should navigate to collections route', () => { it('should navigate to collections route', () => {
remoteData.payload.type = 'collection'; remoteData.payload.type = 'collection';
setup(); setup();
const redir = service.findById(dsoHandle, IdentifierType.HANDLE); const redir = service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE);
redir.subscribe(); redir.subscribe();
scheduler.schedule(() => redir); scheduler.schedule(() => redir);
scheduler.flush(); scheduler.flush();
@@ -145,7 +145,7 @@ describe('DsoRedirectDataService', () => {
it('should navigate to communities route', () => { it('should navigate to communities route', () => {
remoteData.payload.type = 'community'; remoteData.payload.type = 'community';
setup(); setup();
const redir = service.findById(dsoHandle, IdentifierType.HANDLE); const redir = service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE);
redir.subscribe(); redir.subscribe();
scheduler.schedule(() => redir); scheduler.schedule(() => redir);
scheduler.flush(); scheduler.flush();

View File

@@ -58,9 +58,9 @@ export class DsoRedirectDataService extends DataService<any> {
.replace(/\{\?uuid\}/, `?uuid=${resourceID}`); .replace(/\{\?uuid\}/, `?uuid=${resourceID}`);
} }
findById(id: string, identifierType = IdentifierType.UUID): Observable<RemoteData<FindByIDRequest>> { findByIdAndIDType(id: string, identifierType = IdentifierType.UUID): Observable<RemoteData<FindByIDRequest>> {
this.setLinkPath(identifierType); this.setLinkPath(identifierType);
return super.findById(id).pipe( return this.findById(id).pipe(
getFinishedRemoteData(), getFinishedRemoteData(),
take(1), take(1),
tap((response) => { tap((response) => {

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
import { DSpaceObject } from '../shared/dspace-object.model'; import { DSpaceObject } from '../shared/dspace-object.model';
@@ -42,6 +43,7 @@ class DataServiceImpl extends DataService<DSpaceObject> {
} }
@Injectable() @Injectable()
@dataService(DSpaceObject)
export class DSpaceObjectDataService { export class DSpaceObjectDataService {
protected linkPath = 'dso'; protected linkPath = 'dso';
private dataService: DataServiceImpl; private dataService: DataServiceImpl;

View File

@@ -4,6 +4,7 @@ import { Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service'; import { BrowseService } from '../browse/browse.service';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
import { Item } from '../shared/item.model'; import { Item } from '../shared/item.model';
@@ -40,6 +41,7 @@ import { PaginatedList } from './paginated-list';
import { ExternalSourceEntry } from '../shared/external-source-entry.model'; import { ExternalSourceEntry } from '../shared/external-source-entry.model';
@Injectable() @Injectable()
@dataService(Item)
export class ItemDataService extends DataService<Item> { export class ItemDataService extends DataService<Item> {
protected linkPath = 'items'; protected linkPath = 'items';

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
@@ -42,6 +43,7 @@ class DataServiceImpl extends DataService<MetadataSchema> {
* A service responsible for fetching/sending data from/to the REST API on the metadataschemas endpoint * A service responsible for fetching/sending data from/to the REST API on the metadataschemas endpoint
*/ */
@Injectable() @Injectable()
@dataService(MetadataSchema)
export class MetadataSchemaDataService { export class MetadataSchemaDataService {
private dataService: DataServiceImpl; private dataService: DataServiceImpl;

View File

@@ -1,47 +1,36 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ReorderableRelationship } from '../../shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component'; import { dataService } from '../cache/builders/build-decorators';
import { RequestService } from './request.service'; import { MemoizedSelector, select, Store } from '@ngrx/store';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
import { import { compareArraysUsingIds, paginatedRelationsToItems, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
configureRequest, import { AppState, keySelector } from '../../app.reducer';
getRemoteDataPayload, import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
getResponseFromEntry, import { ReorderableRelationship } from '../../shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
getSucceededRemoteData import { RemoveNameVariantAction, SetNameVariantAction } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
} from '../shared/operators'; import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { configureRequest, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
import { SearchParam } from '../cache/models/search-param.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models'; import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
import { Observable } from 'rxjs/internal/Observable';
import { RestResponse } from '../cache/response.models'; import { RestResponse } from '../cache/response.models';
import { Item } from '../shared/item.model'; import { CoreState } from '../core.reducers';
import { Relationship } from '../shared/item-relationships/relationship.model'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RelationshipType } from '../shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
import { RemoteData, RemoteDataState } from './remote-data'; import { RemoteData, RemoteDataState } from './remote-data';
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
import { PaginatedList } from './paginated-list'; import { PaginatedList } from './paginated-list';
import { ItemDataService } from './item-data.service'; import { ItemDataService } from './item-data.service';
import { import { Relationship } from '../shared/item-relationships/relationship.model';
compareArraysUsingIds, import { Item } from '../shared/item.model';
paginatedRelationsToItems,
relationsToItems
} from '../../+item-page/simple/item-types/shared/item-relationships-utils';
import { ObjectCacheService } from '../cache/object-cache.service';
import { DataService } from './data.service'; import { DataService } from './data.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { MemoizedSelector, select, Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { SearchParam } from '../cache/models/search-param.model'; import { RequestService } from './request.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { Observable } from 'rxjs/internal/Observable';
import { AppState, keySelector } from '../../app.reducer';
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
import {
RemoveNameVariantAction,
SetNameVariantAction
} from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists; const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
@@ -57,9 +46,9 @@ const relationshipStateSelector = (listID: string, itemID: string): MemoizedSele
* The service handling all relationship requests * The service handling all relationship requests
*/ */
@Injectable() @Injectable()
@dataService(Relationship)
export class RelationshipService extends DataService<Relationship> { export class RelationshipService extends DataService<Relationship> {
protected linkPath = 'relationships'; protected linkPath = 'relationships';
protected forceBypassCache = false;
constructor(protected itemService: ItemDataService, constructor(protected itemService: ItemDataService,
protected requestService: RequestService, protected requestService: RequestService,

View File

@@ -7,6 +7,7 @@ import { Observable } from 'rxjs';
import { DataService } from '../data/data.service'; import { DataService } from '../data/data.service';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { FindListOptions } from '../data/request.models'; import { FindListOptions } from '../data/request.models';
import { Collection } from '../shared/collection.model';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { ResourcePolicy } from '../shared/resource-policy.model'; import { ResourcePolicy } from '../shared/resource-policy.model';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
@@ -17,7 +18,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { ChangeAnalyzer } from './change-analyzer'; import { ChangeAnalyzer } from './change-analyzer';
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { PaginatedList } from './paginated-list';
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
class DataServiceImpl extends DataService<ResourcePolicy> { class DataServiceImpl extends DataService<ResourcePolicy> {
@@ -60,7 +61,11 @@ export class ResourcePolicyService {
this.dataService = new DataServiceImpl(requestService, rdbService, dataBuildService, null, objectCache, halService, notificationsService, http, comparator); this.dataService = new DataServiceImpl(requestService, rdbService, dataBuildService, null, objectCache, halService, notificationsService, http, comparator);
} }
findByHref(href: string, options?: HttpOptions): Observable<RemoteData<ResourcePolicy>> { findByHref(href: string): Observable<RemoteData<ResourcePolicy>> {
return this.dataService.findByHref(href, {}, options); return this.dataService.findByHref(href);
}
getDefaultAccessConditionsFor(collection: Collection, findListOptions?: FindListOptions): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
return this.dataService.findAllByHref(collection._links.defaultAccessConditions.href, findListOptions);
} }
} }

View File

@@ -1,3 +1,4 @@
import { dataService } from '../cache/builders/build-decorators';
import { DataService } from './data.service'; import { DataService } from './data.service';
import { Site } from '../shared/site.model'; import { Site } from '../shared/site.model';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
@@ -22,6 +23,7 @@ import { getSucceededRemoteData } from '../shared/operators';
* Service responsible for handling requests related to the Site object * Service responsible for handling requests related to the Site object
*/ */
@Injectable() @Injectable()
@dataService(Site)
export class SiteDataService extends DataService<Site> { export class SiteDataService extends DataService<Site> {
protected linkPath = 'sites'; protected linkPath = 'sites';
protected forceBypassCache = false; protected forceBypassCache = false;

View File

@@ -77,14 +77,16 @@ export class DSpaceRESTv2Serializer<T> implements Serializer<T> {
} }
private normalizeLinks(links: any): any { private normalizeLinks(links: any): any {
const normalizedLinks = links; const normalizedLinks = {};
for (const link in normalizedLinks) { for (const link in links) {
if (Array.isArray(normalizedLinks[link])) { if (links.hasOwnProperty(link)) {
normalizedLinks[link] = normalizedLinks[link].map((linkedResource) => { if (Array.isArray(links[link])) {
return linkedResource.href; normalizedLinks[link] = links[link].map((linkedResource) => {
}); return linkedResource.href;
} else { });
normalizedLinks[link] = normalizedLinks[link].href; } else {
normalizedLinks[link] = links[link].href;
}
} }
} }
return normalizedLinks; return normalizedLinks;

View File

@@ -1,5 +1,6 @@
import { autoserialize } from 'cerialize'; import { autoserialize } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../../shared/hal-link.model';
export abstract class IntegrationModel implements CacheableObject { export abstract class IntegrationModel implements CacheableObject {
@@ -14,7 +15,8 @@ export abstract class IntegrationModel implements CacheableObject {
@autoserialize @autoserialize
public _links: { public _links: {
[name: string]: string self: HALLink,
[name: string]: HALLink
} }
} }

View File

@@ -1,5 +1,8 @@
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
import { link } from '../cache/builders/build-decorators';
import { HALLink } from '../shared/hal-link.model';
import { HALResource } from '../shared/hal-resource.model';
import { MetadataSchema } from './metadata-schema.model'; import { MetadataSchema } from './metadata-schema.model';
import { ResourceType } from '../shared/resource-type'; import { ResourceType } from '../shared/resource-type';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
@@ -7,7 +10,7 @@ import { GenericConstructor } from '../shared/generic-constructor';
/** /**
* Class the represents a metadata field * Class the represents a metadata field
*/ */
export class MetadataField extends ListableObject { export class MetadataField extends ListableObject implements HALResource {
static type = new ResourceType('metadatafield'); static type = new ResourceType('metadatafield');
/** /**
@@ -38,7 +41,14 @@ export class MetadataField extends ListableObject {
/** /**
* The metadata schema object of this metadata field * The metadata schema object of this metadata field
*/ */
schema: MetadataSchema; @link(MetadataSchema)
// TODO the responseparsingservice assumes schemas are always embedded. This should be remotedata instead.
schema?: MetadataSchema;
_links: {
self: HALLink,
schema: HALLink
};
/** /**
* Method to print this metadata field as a string * Method to print this metadata field as a string

View File

@@ -1,11 +1,13 @@
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { HALLink } from '../shared/hal-link.model';
import { HALResource } from '../shared/hal-resource.model';
import { ResourceType } from '../shared/resource-type'; import { ResourceType } from '../shared/resource-type';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
/** /**
* Class that represents a metadata schema * Class that represents a metadata schema
*/ */
export class MetadataSchema extends ListableObject { export class MetadataSchema extends ListableObject implements HALResource {
static type = new ResourceType('metadataschema'); static type = new ResourceType('metadataschema');
/** /**
@@ -28,6 +30,10 @@ export class MetadataSchema extends ListableObject {
*/ */
namespace: string; namespace: string;
_links: {
self: HALLink,
};
/** /**
* Method that returns as which type of object this object should be rendered * Method that returns as which type of object this object should be rendered
*/ */

View File

@@ -1,4 +1,5 @@
import { CacheableObject, TypedObject } from '../cache/object-cache.reducer'; import { CacheableObject, TypedObject } from '../cache/object-cache.reducer';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
import { BitstreamFormatSupportLevel } from './bitstream-format-support-level'; import { BitstreamFormatSupportLevel } from './bitstream-format-support-level';
@@ -56,4 +57,7 @@ export class BitstreamFormat implements CacheableObject {
*/ */
id: string; id: string;
_links: {
self: HALLink;
}
} }

View File

@@ -1,11 +1,14 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { link } from '../cache/builders/build-decorators';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { BitstreamFormat } from './bitstream-format.model'; import { BitstreamFormat } from './bitstream-format.model';
import { Bundle } from './bundle.model';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { HALLink } from './HALLink.model'; import { HALResource } from './hal-resource.model';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
export class Bitstream extends DSpaceObject { export class Bitstream extends DSpaceObject implements HALResource {
static type = new ResourceType('bitstream'); static type = new ResourceType('bitstream');
/** /**
@@ -31,17 +34,19 @@ export class Bitstream extends DSpaceObject {
/** /**
* The Bitstream Format for this Bitstream * The Bitstream Format for this Bitstream
*/ */
@link(BitstreamFormat)
format?: Observable<RemoteData<BitstreamFormat>>; format?: Observable<RemoteData<BitstreamFormat>>;
/**
* The URL to retrieve this Bitstream's file
*/
content: string;
_links: { _links: {
// @link(Bitstream)
self: HALLink; self: HALLink;
// @link(Bundle)
bundle: HALLink; bundle: HALLink;
content: HALLink;
// @link(BitstreamFormat)
format: HALLink; format: HALLink;
content: HALLink;
} }
} }

View File

@@ -1,5 +1,5 @@
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { HALLink } from './HALLink.model'; import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
export class Bundle extends DSpaceObject { export class Bundle extends DSpaceObject {

View File

@@ -1,6 +1,7 @@
import { link } from '../cache/builders/build-decorators';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { Bitstream } from './bitstream.model'; import { Bitstream } from './bitstream.model';
import { HALLink } from './HALLink.model'; import { HALLink } from './hal-link.model';
import { Item } from './item.model'; import { Item } from './item.model';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -60,29 +61,19 @@ export class Collection extends DSpaceObject {
/** /**
* The deposit license of this Collection * The deposit license of this Collection
*/ */
license: Observable<RemoteData<License>>; // license?: Observable<RemoteData<License>>;
/** /**
* The Bitstream that represents the logo of this Collection * The Bitstream that represents the logo of this Collection
*/ */
logo: Observable<RemoteData<Bitstream>>; @link(Bitstream)
logo?: Observable<RemoteData<Bitstream>>;
/** /**
* The default access conditions of this Collection * The default access conditions of this Collection
*/ */
defaultAccessConditions: Observable<RemoteData<PaginatedList<ResourcePolicy>>>; @link(ResourcePolicy, true)
defaultAccessConditions?: Observable<RemoteData<PaginatedList<ResourcePolicy>>>;
/**
* An array of Collections that are direct parents of this Collection
*/
parents: Observable<RemoteData<Collection[]>>;
/**
* The Collection that owns this Collection
*/
owner: Observable<RemoteData<Collection>>;
items: Observable<RemoteData<Item[]>>;
_links: { _links: {
license: HALLink; license: HALLink;

View File

@@ -1,9 +1,11 @@
import { link } from '../cache/builders/build-decorators';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { Bitstream } from './bitstream.model'; import { Bitstream } from './bitstream.model';
import { Collection } from './collection.model'; import { Collection } from './collection.model';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { PaginatedList } from '../data/paginated-list'; import { PaginatedList } from '../data/paginated-list';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
export class Community extends DSpaceObject { export class Community extends DSpaceObject {
@@ -49,20 +51,19 @@ export class Community extends DSpaceObject {
/** /**
* The Bitstream that represents the logo of this Community * The Bitstream that represents the logo of this Community
*/ */
logo: Observable<RemoteData<Bitstream>>; @link(Bitstream)
logo?: Observable<RemoteData<Bitstream>>;
/** @link(Collection, true)
* An array of Communities that are direct parents of this Community collections?: Observable<RemoteData<PaginatedList<Collection>>>;
*/
parents: Observable<RemoteData<DSpaceObject[]>>;
/** @link(Community, true)
* The Community that owns this Community subcommunities?: Observable<RemoteData<PaginatedList<Community>>>;
*/
owner: Observable<RemoteData<Community>>;
collections: Observable<RemoteData<PaginatedList<Collection>>>;
subcommunities: Observable<RemoteData<PaginatedList<Community>>>;
_links: {
collections: HALLink;
logo: HALLink;
subcommunities: HALLink;
self: HALLink;
}
} }

View File

@@ -1,5 +1,5 @@
import { Observable } from 'rxjs'; import { GenericConstructor } from './generic-constructor';
import { HALLink } from './hal-link.model';
import { import {
MetadataMap, MetadataMap,
MetadataValue, MetadataValue,
@@ -9,11 +9,9 @@ import {
import { Metadata } from './metadata.utils'; import { Metadata } from './metadata.utils';
import { hasNoValue, isUndefined } from '../../shared/empty.util'; import { hasNoValue, isUndefined } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { RemoteData } from '../data/remote-data';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { excludeFromEquals } from '../utilities/equals.decorators'; import { excludeFromEquals } from '../utilities/equals.decorators';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
import { GenericConstructor } from './generic-constructor';
/** /**
* An abstract model class for a DSpaceObject. * An abstract model class for a DSpaceObject.
@@ -67,6 +65,10 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
@excludeFromEquals @excludeFromEquals
metadata: MetadataMap; metadata: MetadataMap;
_links: {
self: HALLink,
};
/** /**
* Retrieve the current metadata as a list of MetadatumViewModels * Retrieve the current metadata as a list of MetadatumViewModels
*/ */
@@ -74,18 +76,6 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
return Metadata.toViewModelList(this.metadata); return Metadata.toViewModelList(this.metadata);
} }
/**
* An array of DSpaceObjects that are direct parents of this DSpaceObject
*/
@excludeFromEquals
parents: Observable<RemoteData<DSpaceObject[]>>;
/**
* The DSpaceObject that owns this DSpaceObject
*/
@excludeFromEquals
owner: Observable<RemoteData<DSpaceObject>>;
/** /**
* Gets all matching metadata in this DSpaceObject. * Gets all matching metadata in this DSpaceObject.
* *

View File

@@ -0,0 +1,8 @@
import { HALLink } from './hal-link.model';
export class HALResource {
_links: {
self: HALLink
[k: string]: HALLink;
};
}

View File

@@ -1,4 +1,5 @@
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../hal-link.model';
import { ResourceType } from '../resource-type'; import { ResourceType } from '../resource-type';
/** /**
@@ -23,4 +24,8 @@ export class ItemType implements CacheableObject {
* The universally unique identifier of this ItemType * The universally unique identifier of this ItemType
*/ */
uuid: string; uuid: string;
_links: {
self: HALLink,
};
} }

View File

@@ -1,6 +1,8 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { link } from '../../cache/builders/build-decorators';
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { HALLink } from '../hal-link.model';
import { ResourceType } from '../resource-type'; import { ResourceType } from '../resource-type';
import { ItemType } from './item-type.model'; import { ItemType } from './item-type.model';
@@ -63,10 +65,18 @@ export class RelationshipType implements CacheableObject {
/** /**
* The type of Item found to the left of this RelationshipType * The type of Item found to the left of this RelationshipType
*/ */
leftType: Observable<RemoteData<ItemType>>; @link(ItemType)
leftType?: Observable<RemoteData<ItemType>>;
/** /**
* The type of Item found to the right of this RelationshipType * The type of Item found to the right of this RelationshipType
*/ */
rightType: Observable<RemoteData<ItemType>>; @link(ItemType)
rightType?: Observable<RemoteData<ItemType>>;
_links: {
self: HALLink,
leftType: HALLink,
rightType: HALLink,
}
} }

View File

@@ -1,6 +1,8 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { link } from '../../cache/builders/build-decorators';
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { HALLink } from '../hal-link.model';
import { ResourceType } from '../resource-type'; import { ResourceType } from '../resource-type';
import { RelationshipType } from './relationship-type.model'; import { RelationshipType } from './relationship-type.model';
import { Item } from '../item.model'; import { Item } from '../item.model';
@@ -29,12 +31,14 @@ export class Relationship implements CacheableObject {
/** /**
* The item to the left of this relationship * The item to the left of this relationship
*/ */
leftItem: Observable<RemoteData<Item>>; @link(Item)
leftItem?: Observable<RemoteData<Item>>;
/** /**
* The item to the right of this relationship * The item to the right of this relationship
*/ */
rightItem: Observable<RemoteData<Item>>; @link(Item)
rightItem?: Observable<RemoteData<Item>>;
/** /**
* The place of the Item to the left side of this Relationship * The place of the Item to the left side of this Relationship
@@ -59,5 +63,14 @@ export class Relationship implements CacheableObject {
/** /**
* The type of Relationship * The type of Relationship
*/ */
relationshipType: Observable<RemoteData<RelationshipType>>; @link(RelationshipType)
relationshipType?: Observable<RemoteData<RelationshipType>>;
_links: {
self: HALLink,
leftItem: HALLink,
rightItem: HALLink,
relationshipType: HALLink,
}
} }

View File

@@ -1,10 +1,16 @@
import { Observable } from 'rxjs/internal/Observable';
import { isEmpty } from '../../shared/empty.util'; import { isEmpty } from '../../shared/empty.util';
import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator'; import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { link } from '../cache/builders/build-decorators';
import { PaginatedList } from '../data/paginated-list';
import { RemoteData } from '../data/remote-data';
import { Bundle } from './bundle.model';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { GenericConstructor } from './generic-constructor'; import { GenericConstructor } from './generic-constructor';
import { HALLink } from './HALLink.model'; import { HALLink } from './hal-link.model';
import { Relationship } from './item-relationships/relationship.model';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
/** /**
@@ -38,12 +44,19 @@ export class Item extends DSpaceObject {
*/ */
isWithdrawn: boolean; isWithdrawn: boolean;
@link(Bundle, true)
bundles: Observable<RemoteData<PaginatedList<Bundle>>>;
@link(Relationship, true)
relationships: Observable<RemoteData<PaginatedList<Relationship>>>;
_links: { _links: {
self: HALLink; mappedCollections: HALLink;
parents: HALLink;
owningCollection: HALLink;
bundles: HALLink;
relationships: HALLink; relationships: HALLink;
bundles: HALLink;
owningCollection: HALLink;
templateItemOf: HALLink;
self: HALLink;
}; };
/** /**

View File

@@ -1,4 +1,5 @@
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
import { ActionType } from '../cache/models/action-type.model'; import { ActionType } from '../cache/models/action-type.model';
@@ -33,4 +34,7 @@ export class ResourcePolicy implements CacheableObject {
*/ */
uuid: string; uuid: string;
_links: {
self: HALLink,
}
} }

View File

@@ -2,6 +2,7 @@ import { combineLatest as observableCombineLatest, Observable, of as observableO
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
import { NavigationExtras, Router } from '@angular/router'; import { NavigationExtras, Router } from '@angular/router';
import { first, map, switchMap, tap } from 'rxjs/operators'; import { first, map, switchMap, tap } from 'rxjs/operators';
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { FacetConfigSuccessResponse, FacetValueSuccessResponse, SearchSuccessResponse } from '../../cache/response.models'; import { FacetConfigSuccessResponse, FacetValueSuccessResponse, SearchSuccessResponse } from '../../cache/response.models';
import { PaginatedList } from '../../data/paginated-list'; import { PaginatedList } from '../../data/paginated-list';
import { ResponseParsingService } from '../../data/parsing.service'; import { ResponseParsingService } from '../../data/parsing.service';
@@ -340,6 +341,8 @@ export class SearchService implements OnDestroy {
switchMap((dsoRD: RemoteData<DSpaceObject>) => { switchMap((dsoRD: RemoteData<DSpaceObject>) => {
if ((dsoRD.payload as any).type === Community.type.value) { if ((dsoRD.payload as any).type === Community.type.value) {
const community: Community = dsoRD.payload as Community; const community: Community = dsoRD.payload as Community;
this.rdb.resolveLink(community, followLink('subcommunities'));
this.rdb.resolveLink(community, followLink('collections'));
return observableCombineLatest(community.subcommunities, community.collections).pipe( return observableCombineLatest(community.subcommunities, community.collections).pipe(
map(([subCommunities, collections]) => { map(([subCommunities, collections]) => {
/*if this is a community, we also need to show the direct children*/ /*if this is a community, we also need to show the direct children*/

View File

@@ -1,10 +1,12 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { link } from '../../cache/builders/build-decorators';
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { DSpaceObject } from '../../shared/dspace-object.model'; import { DSpaceObject } from '../../shared/dspace-object.model';
import { EPerson } from '../../eperson/models/eperson.model'; import { EPerson } from '../../eperson/models/eperson.model';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { Collection } from '../../shared/collection.model'; import { Collection } from '../../shared/collection.model';
import { HALLink } from '../../shared/hal-link.model';
import { Item } from '../../shared/item.model'; import { Item } from '../../shared/item.model';
import { SubmissionDefinitionsModel } from '../../config/models/config-submission-definitions.model'; import { SubmissionDefinitionsModel } from '../../config/models/config-submission-definitions.model';
import { WorkspaceitemSectionsObject } from './workspaceitem-sections.model'; import { WorkspaceitemSectionsObject } from './workspaceitem-sections.model';
@@ -37,12 +39,14 @@ export abstract class SubmissionObject extends DSpaceObject implements Cacheable
/** /**
* The collection this submission applies to * The collection this submission applies to
*/ */
collection: Observable<RemoteData<Collection>> | Collection; @link(Collection)
collection?: Observable<RemoteData<Collection>> | Collection;
/** /**
* The submission item * The submission item
*/ */
item: Observable<RemoteData<Item>> | Item; @link(Item)
item?: Observable<RemoteData<Item>> | Item;
/** /**
* The workspaceitem/workflowitem last sections data * The workspaceitem/workflowitem last sections data
@@ -52,15 +56,26 @@ export abstract class SubmissionObject extends DSpaceObject implements Cacheable
/** /**
* The configuration object that define this submission * The configuration object that define this submission
*/ */
submissionDefinition: Observable<RemoteData<SubmissionDefinitionsModel>> | SubmissionDefinitionsModel; @link(SubmissionDefinitionsModel)
submissionDefinition?: Observable<RemoteData<SubmissionDefinitionsModel>> | SubmissionDefinitionsModel;
/** /**
* The workspaceitem submitter * The workspaceitem submitter
*/ */
submitter: Observable<RemoteData<EPerson>> | EPerson; @link(EPerson)
submitter?: Observable<RemoteData<EPerson>> | EPerson;
/** /**
* The workspaceitem/workflowitem last sections errors * The workspaceitem/workflowitem last sections errors
*/ */
errors: SubmissionObjectError[]; errors: SubmissionObjectError[];
_links: {
self: HALLink,
collection: HALLink,
item: HALLink,
submissionDefinition: HALLink,
submitter: HALLink,
}
} }

View File

@@ -45,7 +45,7 @@ describe('SubmissionObjectDataService', () => {
service = new SubmissionObjectDataService(workspaceitemDataService, workflowItemDataService, submissionService); service = new SubmissionObjectDataService(workspaceitemDataService, workflowItemDataService, submissionService);
}); });
it('should forward the result of WorkspaceitemDataService.findById()', () => { it('should forward the result of WorkspaceitemDataService.findByIdAndIDType()', () => {
const result = service.findById(submissionId); const result = service.findById(submissionId);
expect(workspaceitemDataService.findById).toHaveBeenCalledWith(submissionId); expect(workspaceitemDataService.findById).toHaveBeenCalledWith(submissionId);
expect(result).toBe(wsiResult); expect(result).toBe(wsiResult);
@@ -60,7 +60,7 @@ describe('SubmissionObjectDataService', () => {
service = new SubmissionObjectDataService(workspaceitemDataService, workflowItemDataService, submissionService); service = new SubmissionObjectDataService(workspaceitemDataService, workflowItemDataService, submissionService);
}); });
it('should forward the result of WorkflowItemDataService.findById()', () => { it('should forward the result of WorkflowItemDataService.findByIdAndIDType()', () => {
const result = service.findById(submissionId); const result = service.findById(submissionId);
expect(workflowItemDataService.findById).toHaveBeenCalledWith(submissionId); expect(workflowItemDataService.findById).toHaveBeenCalledWith(submissionId);
expect(result).toBe(wfiResult); expect(result).toBe(wfiResult);

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
import { DataService } from '../data/data.service'; import { DataService } from '../data/data.service';
@@ -18,6 +19,7 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
* A service that provides methods to make REST requests with workflowitems endpoint. * A service that provides methods to make REST requests with workflowitems endpoint.
*/ */
@Injectable() @Injectable()
@dataService(WorkflowItem)
export class WorkflowItemDataService extends DataService<WorkflowItem> { export class WorkflowItemDataService extends DataService<WorkflowItem> {
protected linkPath = 'workflowitems'; protected linkPath = 'workflowitems';
protected responseMsToLive = 10 * 1000; protected responseMsToLive = 10 * 1000;

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
import { DataService } from '../data/data.service'; import { DataService } from '../data/data.service';
@@ -18,6 +19,7 @@ import { WorkspaceItem } from './models/workspaceitem.model';
* A service that provides methods to make REST requests with workspaceitems endpoint. * A service that provides methods to make REST requests with workspaceitems endpoint.
*/ */
@Injectable() @Injectable()
@dataService(WorkspaceItem)
export class WorkspaceitemDataService extends DataService<WorkspaceItem> { export class WorkspaceitemDataService extends DataService<WorkspaceItem> {
protected linkPath = 'workspaceitems'; protected linkPath = 'workspaceitems';
protected responseMsToLive = 10 * 1000; protected responseMsToLive = 10 * 1000;

View File

@@ -3,6 +3,7 @@ import { HttpClient } from '@angular/common/http';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
@@ -20,6 +21,7 @@ import { ProcessTaskResponse } from './models/process-task-response';
* The service handling all REST requests for ClaimedTask * The service handling all REST requests for ClaimedTask
*/ */
@Injectable() @Injectable()
@dataService(ClaimedTask)
export class ClaimedTaskDataService extends TasksService<ClaimedTask> { export class ClaimedTaskDataService extends TasksService<ClaimedTask> {
protected responseMsToLive = 10 * 1000; protected responseMsToLive = 10 * 1000;

View File

@@ -1,8 +1,10 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { link } from '../../cache/builders/build-decorators';
import { CacheableObject } from '../../cache/object-cache.reducer'; import { CacheableObject } from '../../cache/object-cache.reducer';
import { DSpaceObject } from '../../shared/dspace-object.model'; import { DSpaceObject } from '../../shared/dspace-object.model';
import { RemoteData } from '../../data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { HALLink } from '../../shared/hal-link.model';
import { WorkflowItem } from '../../submission/models/workflowitem.model'; import { WorkflowItem } from '../../submission/models/workflowitem.model';
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
import { EPerson } from '../../eperson/models/eperson.model'; import { EPerson } from '../../eperson/models/eperson.model';
@@ -32,15 +34,26 @@ export class TaskObject extends DSpaceObject implements CacheableObject {
/** /**
* The group of this task * The group of this task
*/ */
eperson: Observable<RemoteData<EPerson>>; @link(EPerson)
eperson?: Observable<RemoteData<EPerson>>;
/** /**
* The group of this task * The group of this task
*/ */
group: Observable<RemoteData<Group>>; @link(Group)
group?: Observable<RemoteData<Group>>;
/** /**
* The workflowitem object whom this task is related * The workflowitem object whom this task is related
*/ */
workflowitem: Observable<RemoteData<WorkflowItem>> | WorkflowItem; @link(WorkflowItem)
workflowitem?: Observable<RemoteData<WorkflowItem>> | WorkflowItem;
_links: {
self: HALLink,
eperson: HALLink,
group: HALLink,
workflowitem: HALLink,
}
} }

View File

@@ -3,6 +3,7 @@ import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
@@ -20,6 +21,7 @@ import { ProcessTaskResponse } from './models/process-task-response';
* The service handling all REST requests for PoolTask * The service handling all REST requests for PoolTask
*/ */
@Injectable() @Injectable()
@dataService(PoolTask)
export class PoolTaskDataService extends TasksService<PoolTask> { export class PoolTaskDataService extends TasksService<PoolTask> {
/** /**

View File

@@ -1,6 +1,6 @@
<div class="d-flex"> <div class="d-flex">
<div class="person-thumbnail pr-2"> <div class="person-thumbnail pr-2">
<ds-thumbnail [thumbnail]="dso.getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail> <ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
</div> </div>
<div class="flex-grow-1"> <div class="flex-grow-1">
<ds-org-unit-input-suggestions [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)" <ds-org-unit-input-suggestions [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)"

View File

@@ -1,4 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { Bitstream } from '../../../../../core/shared/bitstream.model';
import { getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
@@ -37,6 +41,7 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
private translateService: TranslateService, private translateService: TranslateService,
private modalService: NgbModal, private modalService: NgbModal,
private itemDataService: ItemDataService, private itemDataService: ItemDataService,
private bitstreamDataService: BitstreamDataService,
private selectableListService: SelectableListService) { private selectableListService: SelectableListService) {
super(truncatableService); super(truncatableService);
} }
@@ -95,4 +100,11 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
modalComp.value = value; modalComp.value = value;
return modalRef.result; return modalRef.result;
} }
// TODO refactor to return RemoteData, and thumbnail template to deal with loading
getThumbnail(): Observable<Bitstream> {
return this.bitstreamDataService.getThumbnailFor(this.dso).pipe(
getFirstSucceededRemoteDataPayload()
);
}
} }

View File

@@ -1,6 +1,6 @@
<div class="d-flex"> <div class="d-flex">
<div class="person-thumbnail pr-2"> <div class="person-thumbnail pr-2">
<ds-thumbnail [thumbnail]="dso.getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail> <ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
</div> </div>
<div class="flex-grow-1"> <div class="flex-grow-1">
<ds-person-input-suggestions [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)" (submitSuggestion)="selectCustom($event)"></ds-person-input-suggestions> <ds-person-input-suggestions [suggestions]="allSuggestions" [(ngModel)]="selectedName" (clickSuggestion)="select($event)" (submitSuggestion)="selectCustom($event)"></ds-person-input-suggestions>

View File

@@ -1,4 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { Bitstream } from '../../../../../core/shared/bitstream.model';
import { getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
@@ -37,6 +41,7 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
private translateService: TranslateService, private translateService: TranslateService,
private modalService: NgbModal, private modalService: NgbModal,
private itemDataService: ItemDataService, private itemDataService: ItemDataService,
private bitstreamDataService: BitstreamDataService,
private selectableListService: SelectableListService) { private selectableListService: SelectableListService) {
super(truncatableService); super(truncatableService);
} }
@@ -95,4 +100,11 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
modalComp.value = value; modalComp.value = value;
return modalRef.result; return modalRef.result;
} }
// TODO refactor to return RemoteData, and thumbnail template to deal with loading
getThumbnail(): Observable<Bitstream> {
return this.bitstreamDataService.getThumbnailFor(this.dso).pipe(
getFirstSucceededRemoteDataPayload()
);
}
} }

View File

@@ -1,6 +1,6 @@
import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core'; import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
import { QueryParamsDirectiveStub } from './query-params-directive-stub'; import { QueryParamsDirectiveStub } from './query-params-directive-stub';
import { MySimpleItemActionComponent } from '../../+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.spec'; // import { MySimpleItemActionComponent } from '../../+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.spec';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {SharedModule} from '../shared.module'; import {SharedModule} from '../shared.module';
import { RouterLinkDirectiveStub } from './router-link-directive-stub'; import { RouterLinkDirectiveStub } from './router-link-directive-stub';
@@ -19,7 +19,7 @@ import { NgComponentOutletDirectiveStub } from './ng-component-outlet-directive-
], ],
declarations: [ declarations: [
QueryParamsDirectiveStub, QueryParamsDirectiveStub,
MySimpleItemActionComponent, // MySimpleItemActionComponent,
RouterLinkDirectiveStub, RouterLinkDirectiveStub,
NgComponentOutletDirectiveStub NgComponentOutletDirectiveStub
], schemas: [ ], schemas: [

View File

@@ -0,0 +1,20 @@
import { FindListOptions } from '../../core/data/request.models';
import { HALResource } from '../../core/shared/hal-resource.model';
export class FollowLinkConfig<R extends HALResource> {
name: keyof R['_links'];
findListOptions?: FindListOptions;
linksToFollow?: Array<FollowLinkConfig<any>>;
}
export const followLink = <R extends HALResource>(
linkName: keyof R['_links'],
findListOptions?: FindListOptions,
...linksToFollow: Array<FollowLinkConfig<any>>
): FollowLinkConfig<R> => {
return {
name: linkName,
findListOptions,
linksToFollow
}
};

View File

@@ -109,8 +109,8 @@ describe('SubmissionObjectEffects test suite', () => {
const mappedActions = []; const mappedActions = [];
(submissionDefinitionResponse.sections as SubmissionSectionModel[]) (submissionDefinitionResponse.sections as SubmissionSectionModel[])
.forEach((sectionDefinition: SubmissionSectionModel) => { .forEach((sectionDefinition: SubmissionSectionModel) => {
const sectionId = sectionDefinition._links.self.substr(sectionDefinition._links.self.lastIndexOf('/') + 1); const sectionId = sectionDefinition._links.self.href.substr(sectionDefinition._links.self.href.lastIndexOf('/') + 1);
const config = sectionDefinition._links.config || ''; const config = sectionDefinition._links.config.href || '';
const enabled = (sectionDefinition.mandatory); const enabled = (sectionDefinition.mandatory);
const sectionData = {}; const sectionData = {};
const sectionErrors = null; const sectionErrors = null;

View File

@@ -57,8 +57,8 @@ export class SubmissionObjectEffects {
const definition = action.payload.submissionDefinition; const definition = action.payload.submissionDefinition;
const mappedActions = []; const mappedActions = [];
definition.sections.page.forEach((sectionDefinition: SubmissionSectionModel) => { definition.sections.page.forEach((sectionDefinition: SubmissionSectionModel) => {
const sectionId = sectionDefinition._links.self.substr(sectionDefinition._links.self.lastIndexOf('/') + 1); const sectionId = sectionDefinition._links.self.href.substr(sectionDefinition._links.self.href.lastIndexOf('/') + 1);
const config = sectionDefinition._links.config || ''; const config = sectionDefinition._links.config.href || '';
const enabled = (sectionDefinition.mandatory) || (isNotEmpty(action.payload.sections) && action.payload.sections.hasOwnProperty(sectionId)); const enabled = (sectionDefinition.mandatory) || (isNotEmpty(action.payload.sections) && action.payload.sections.hasOwnProperty(sectionId));
const sectionData = (isNotUndefined(action.payload.sections) && isNotUndefined(action.payload.sections[sectionId])) ? action.payload.sections[sectionId] : Object.create(null); const sectionData = (isNotUndefined(action.payload.sections) && isNotUndefined(action.payload.sections[sectionId])) ? action.payload.sections[sectionId] : Object.create(null);
const sectionErrors = null; const sectionErrors = null;

View File

@@ -134,7 +134,7 @@ export class SubmissionSectionLicenseComponent extends SectionModelComponent {
this.licenseText$ = this.collectionDataService.findById(this.collectionId).pipe( this.licenseText$ = this.collectionDataService.findById(this.collectionId).pipe(
filter((collectionData: RemoteData<Collection>) => isNotUndefined((collectionData.payload))), filter((collectionData: RemoteData<Collection>) => isNotUndefined((collectionData.payload))),
flatMap((collectionData: RemoteData<Collection>) => collectionData.payload.license), flatMap((collectionData: RemoteData<Collection>) => (collectionData.payload as any).license),
find((licenseData: RemoteData<License>) => isNotUndefined((licenseData.payload))), find((licenseData: RemoteData<License>) => isNotUndefined((licenseData.payload))),
map((licenseData: RemoteData<License>) => licenseData.payload.text), map((licenseData: RemoteData<License>) => licenseData.payload.text),
startWith('')); startWith(''));

View File

@@ -2,6 +2,7 @@ import { ChangeDetectorRef, Component, Inject } from '@angular/core';
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription} from 'rxjs'; import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription} from 'rxjs';
import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators';
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { SectionModelComponent } from '../models/section.model'; import { SectionModelComponent } from '../models/section.model';
import { hasValue, isNotEmpty, isNotUndefined, isUndefined } from '../../../shared/empty.util'; import { hasValue, isNotEmpty, isNotUndefined, isUndefined } from '../../../shared/empty.util';
@@ -160,13 +161,11 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent {
filter((submissionObject: SubmissionObjectEntry) => isNotUndefined(submissionObject) && !submissionObject.isLoading), filter((submissionObject: SubmissionObjectEntry) => isNotUndefined(submissionObject) && !submissionObject.isLoading),
filter((submissionObject: SubmissionObjectEntry) => isUndefined(this.collectionId) || this.collectionId !== submissionObject.collection), filter((submissionObject: SubmissionObjectEntry) => isUndefined(this.collectionId) || this.collectionId !== submissionObject.collection),
tap((submissionObject: SubmissionObjectEntry) => this.collectionId = submissionObject.collection), tap((submissionObject: SubmissionObjectEntry) => this.collectionId = submissionObject.collection),
flatMap((submissionObject: SubmissionObjectEntry) => this.collectionDataService.findById(submissionObject.collection)), flatMap((submissionObject: SubmissionObjectEntry) => this.collectionDataService.findById(submissionObject.collection, followLink('defaultAccessConditions'))),
filter((rd: RemoteData<Collection>) => isNotUndefined((rd.payload))), filter((rd: RemoteData<Collection>) => isNotUndefined((rd.payload))),
tap((collectionRemoteData: RemoteData<Collection>) => this.collectionName = collectionRemoteData.payload.name), tap((collectionRemoteData: RemoteData<Collection>) => this.collectionName = collectionRemoteData.payload.name),
flatMap((collectionRemoteData: RemoteData<Collection>) => { flatMap((collectionRemoteData: RemoteData<Collection>) => {
return this.resourcePolicyService.findByHref( return this.resourcePolicyService.findByHref((collectionRemoteData.payload as any).defaultAccessConditions);
(collectionRemoteData.payload as any)._links.defaultAccessConditions
);
}), }),
filter((defaultAccessConditionsRemoteData: RemoteData<ResourcePolicy>) => filter((defaultAccessConditionsRemoteData: RemoteData<ResourcePolicy>) =>
defaultAccessConditionsRemoteData.hasSucceeded), defaultAccessConditionsRemoteData.hasSucceeded),