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 { find } from 'rxjs/operators';
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
@@ -23,7 +24,7 @@ export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
* or an error if something went wrong
*/
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),
);
}

View File

@@ -6,6 +6,7 @@ import { Community } from '../core/shared/community.model';
import { CommunityDataService } from '../core/data/community-data.service';
import { find } from 'rxjs/operators';
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
@@ -23,7 +24,12 @@ export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
* or an error if something went wrong
*/
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)
);
}

View File

@@ -1,12 +1,12 @@
import { Component, Injector, Input, OnInit } from '@angular/core';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { getBitstreamBuilder } from '../../../../core/cache/builders/bitstream-builder';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { Bitstream } from '../../../../core/shared/bitstream.model';
import { Item } from '../../../../core/shared/item.model';
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';
/**
@@ -28,8 +28,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
bitstreams$: Observable<Bitstream[]>;
constructor(
bitstreamDataService: BitstreamDataService,
private parentInjector: Injector
bitstreamDataService: BitstreamDataService
) {
super(bitstreamDataService);
}
@@ -40,11 +39,21 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
initialize(): void {
// 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(),
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(),
startWith([])
);
@@ -53,10 +62,8 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
map((files: Bitstream[]) =>
files.map(
(original) => {
return getBitstreamBuilder(this.parentInjector, original)
.loadThumbnail(this.item)
.loadBitstreamFormat()
.build();
original.thumbnail = this.bitstreamDataService.getMatchingThumbnail(this.item, original);
return original;
}
)
)

View File

@@ -6,6 +6,7 @@ import { ItemDataService } from '../core/data/item-data.service';
import { Item } from '../core/shared/item.model';
import { hasValue } from '../shared/empty.util';
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
@@ -23,9 +24,12 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
return this.itemService.findById(route.params.id)
.pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
);
return this.itemService.findById(route.params.id,
followLink('owningCollection'),
followLink('bundles'),
followLink('relationships')
).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
);
}
}

View File

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

View File

@@ -20,7 +20,7 @@ export class LookupGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
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)
);
}

View File

@@ -1,3 +1,4 @@
import { HALLink } from '../../shared/hal-link.model';
import { AuthError } from './auth-error.model';
import { AuthTokenInfo } from './auth-token-info.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
*/
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 { 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 { HALResource } from '../../shared/hal-resource.model';
import { CacheableObject, TypedObject } from '../object-cache.reducer';
import { ResourceType } from '../../shared/resource-type';
const mapsToMetadataKey = Symbol('mapsTo');
const relationshipKey = Symbol('relationship');
const resolvedLinkKey = Symbol('resolvedLink');
const relationshipMap = new Map();
const resolvedLinkMap = 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
@@ -79,3 +87,94 @@ export function getRelationMetadata(target: any, propertyKey: string) {
export function getRelationships(target: any) {
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 {
combineLatest as observableCombineLatest,
@@ -16,16 +16,24 @@ import {
isNotEmpty,
isNotUndefined
} from '../../../shared/empty.util';
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
import { PaginatedList } from '../../data/paginated-list';
import { RemoteData } from '../../data/remote-data';
import { RemoteDataError } from '../../data/remote-data-error';
import { GetRequest } from '../../data/request.models';
import { RequestEntry } from '../../data/request.reducer';
import { RequestService } from '../../data/request.service';
import { HALResource } from '../../shared/hal-resource.model';
import { NormalizedObject } from '../models/normalized-object.model';
import { ObjectCacheService } from '../object-cache.service';
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 {
filterSuccessfulResponses,
@@ -39,10 +47,11 @@ import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils
@Injectable()
export class RemoteDataBuildService {
constructor(protected objectCache: ObjectCacheService,
private parentInjector: Injector,
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') {
href$ = observableOf(href$);
}
@@ -83,7 +92,7 @@ export class RemoteDataBuildService {
}),
hasValueOperator(),
map((normalized: NormalizedObject<T>) => {
return this.build<T>(normalized);
return this.build<T>(normalized, ...linksToFollow);
}),
startWith(undefined),
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') {
href$ = observableOf(href$);
}
@@ -132,7 +141,7 @@ export class RemoteDataBuildService {
return this.objectCache.getList(resourceUUIDs).pipe(
map((normList: Array<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$);
}
build<T extends CacheableObject>(normalized: NormalizedObject<T>): T {
const links: any = {};
build<T extends CacheableObject>(normalized: NormalizedObject<T>, ...linksToFollow: Array<FollowLinkConfig<T>>): T {
const halLinks: any = {};
const relationships = getRelationships(normalized.constructor) || [];
relationships.forEach((relationship: string) => {
@@ -207,22 +216,61 @@ export class RemoteDataBuildService {
}
if (hasValue(normalized[relationship].page)) {
links[relationship] = this.toPaginatedList(result, normalized[relationship].pageInfo);
halLinks[relationship] = this.toPaginatedList(result, normalized[relationship].pageInfo);
} else {
links[relationship] = result;
halLinks[relationship] = result;
}
} else {
if (hasNoValue(links._links)) {
links._links = {};
if (hasNoValue(halLinks._links)) {
halLinks._links = {};
}
links._links[relationship] = {
halLinks._links[relationship] = {
href: objectList
};
}
}
});
const domainModel = getMapsTo(normalized.constructor);
return Object.assign(new domainModel(), normalized, links);
const domainModelConstructor = getMapsTo(normalized.constructor);
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[]>> {

View File

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

View File

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

View File

@@ -44,13 +44,6 @@ export class NormalizedItem extends NormalizedDSpaceObject<Item> {
@autoserializeAs(Boolean, 'withdrawn')
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
*/

View File

@@ -1,3 +1,4 @@
import { HALLink } from '../../shared/hal-link.model';
import { CacheableObject, TypedObject } from '../object-cache.reducer';
import { autoserialize, deserialize } from 'cerialize';
import { ResourceType } from '../../shared/resource-type';
@@ -13,7 +14,8 @@ export abstract class NormalizedObject<T extends TypedObject> implements Cacheab
@deserialize
_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 {
ObjectCacheAction,
ObjectCacheActionTypes,
@@ -42,10 +44,14 @@ export abstract class TypedObject {
*
* A cacheable object should have a self link
*/
export class CacheableObject extends TypedObject {
export class CacheableObject extends TypedObject implements HALResource {
uuid?: string;
handle?: string;
self: string;
_links: {
self: HALLink;
}
// isNew: boolean;
// dirtyType: DirtyType;
// hasDirtyAttributes: boolean;

View File

@@ -1,4 +1,5 @@
import { CacheableObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../../shared/hal-link.model';
import { ResourceType } from '../../shared/resource-type';
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.
*/
public _links: {
[name: string]: string
_links: {
self: HALLink,
[name: string]: HALLink
};
/**

View File

@@ -1,6 +1,7 @@
import { autoserialize, inheritSerialization } from 'cerialize';
import { NormalizedObject } from '../../cache/models/normalized-object.model';
import { CacheableObject, TypedObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../../shared/hal-link.model';
import { ResourceType } from '../../shared/resource-type';
/**
@@ -20,7 +21,8 @@ export abstract class NormalizedConfigObject<T extends CacheableObject> implemen
*/
@autoserialize
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 { hasNoValue, hasValue } from '../../shared/empty.util';
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 { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
@@ -26,6 +28,7 @@ import { RequestService } from './request.service';
@Injectable({
providedIn: 'root'
})
@dataService(Bitstream)
export class BitstreamDataService extends DataService<Bitstream> {
protected linkPath = 'bitstreams';
@@ -57,8 +60,8 @@ export class BitstreamDataService extends DataService<Bitstream> {
* @param bundle the bundle to retrieve bitstreams from
* @param options options for the find all request
*/
findAllByBundle(bundle: Bundle, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bitstream>>> {
return this.findAllByHref(bundle._links.bitstreams.href, options);
findAllByBundle(bundle: Bundle, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Bitstream>>): Observable<RemoteData<PaginatedList<Bitstream>>> {
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(
switchMap((bundleRD: RemoteData<Bundle>) => {
if (hasValue(bundleRD.payload)) {
return this.findAllByBundle(bundleRD.payload, options);
return this.findAllByBundle(bundleRD.payload, options, ...linksToFollow);
} else {
return [bundleRD as any];
}

View File

@@ -1,4 +1,5 @@
import { Injectable } from '@angular/core';
import { dataService } from '../cache/builders/build-decorators';
import { Bitstream } from '../shared/bitstream.model';
import { DataService } from './data.service';
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
*/
@Injectable()
@dataService(BitstreamFormat)
export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
protected linkPath = 'bitstreamformats';

View File

@@ -5,6 +5,8 @@ import { Observable } from 'rxjs/internal/Observable';
import { map } from 'rxjs/operators';
import { hasValue } from '../../shared/empty.util';
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 { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
@@ -26,6 +28,7 @@ import { RequestService } from './request.service';
@Injectable(
{providedIn: 'root'}
)
@dataService(Bundle)
export class BundleDataService extends DataService<Bundle> {
protected linkPath = 'bundles';
protected forceBypassCache = false;
@@ -52,13 +55,13 @@ export class BundleDataService extends DataService<Bundle> {
return this.halService.getEndpoint(this.linkPath);
}
findAllByItem(item: Item, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bundle>>> {
return this.findAllByHref(item._links.bundles.href, options);
findAllByItem(item: Item, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Bundle>>): Observable<RemoteData<PaginatedList<Bundle>>> {
return this.findAllByHref(item._links.bundles.href, options, ...linksToFollow);
}
// TODO should be implemented rest side
findByItemAndName(item: Item, bundleName: string): Observable<RemoteData<Bundle>> {
return this.findAllByItem(item, { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
findByItemAndName(item: Item, bundleName: string, ...linksToFollow: Array<FollowLinkConfig<Bundle>>): Observable<RemoteData<Bundle>> {
return this.findAllByItem(item, { elementsPerPage: Number.MAX_SAFE_INTEGER }, ...linksToFollow).pipe(
map((rd: RemoteData<PaginatedList<Bundle>>) => {
if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
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 { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.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';
@Injectable()
@dataService(Collection)
export class CollectionDataService extends ComColDataService<Collection> {
protected linkPath = 'collections';
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 { NormalizedCommunity } from '../cache/models/normalized-community.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { HALLink } from '../shared/hal-link.model';
import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service';
@@ -71,7 +72,8 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
filter((response) => response.isSuccessful),
mergeMap(() => this.objectCache.getObjectByUUID(options.scopeID)),
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());

View File

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

View File

@@ -16,6 +16,7 @@ import {
import { Store } from '@ngrx/store';
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 { HALEndpointService } from '../shared/hal-endpoint.service';
import { URLCombiner } from '../url-combiner/url-combiner';
@@ -30,7 +31,6 @@ import {
GetRequest
} from './request.models';
import { RequestService } from './request.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { SearchParam } from '../cache/models/search-param.model';
import { Operation } from 'fast-json-patch';
@@ -140,11 +140,11 @@ export abstract class DataService<T extends CacheableObject> {
}
}
findAll(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
return this.findList(this.getFindAllHref(options), options);
findAll(options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<PaginatedList<T>>> {
return this.findList(this.getFindAllHref(options), options, ...linksToFollow);
}
protected findList(href$, options: FindListOptions) {
protected findList(href$, options: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<T>>) {
href$.pipe(
first((href: string) => hasValue(href)))
.subscribe((href: string) => {
@@ -155,7 +155,7 @@ export abstract class DataService<T extends CacheableObject> {
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}`;
}
findById(id: string): Observable<RemoteData<T>> {
findById(id: string, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<T>> {
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id))));
@@ -182,27 +182,27 @@ export abstract class DataService<T extends CacheableObject> {
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>> {
const requestHref = this.buildHrefFromFindOptions(href, findListOptions, []);
const request = new GetRequest(this.requestService.generateRequestId(), requestHref, null, httpOptions);
findByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<T>> {
const requestHref = this.buildHrefFromFindOptions(href, {}, []);
const request = new GetRequest(this.requestService.generateRequestId(), requestHref);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
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 request = new GetRequest(this.requestService.generateRequestId(), requestHref, null, httpOptions);
const request = new GetRequest(this.requestService.generateRequestId(), requestHref);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
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 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);
@@ -241,7 +241,7 @@ export abstract class DataService<T extends CacheableObject> {
switchMap((href) => this.requestService.getByHref(href)),
skipWhile((requestEntry) => hasValue(requestEntry) && requestEntry.completed),
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', () => {
it('should call HALEndpointService with the path to the pid endpoint', () => {
setup();
scheduler.schedule(() => service.findById(dsoHandle, IdentifierType.HANDLE));
scheduler.schedule(() => service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE));
scheduler.flush();
expect(halService.getEndpoint).toHaveBeenCalledWith('pid');
@@ -91,7 +91,7 @@ describe('DsoRedirectDataService', () => {
it('should call HALEndpointService with the path to the dso endpoint', () => {
setup();
scheduler.schedule(() => service.findById(dsoUUID, IdentifierType.UUID));
scheduler.schedule(() => service.findByIdAndIDType(dsoUUID, IdentifierType.UUID));
scheduler.flush();
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', () => {
setup();
scheduler.schedule(() => service.findById(dsoUUID));
scheduler.schedule(() => service.findByIdAndIDType(dsoUUID));
scheduler.flush();
expect(halService.getEndpoint).toHaveBeenCalledWith('dso');
@@ -107,7 +107,7 @@ describe('DsoRedirectDataService', () => {
it('should configure the proper FindByIDRequest for uuid', () => {
setup();
scheduler.schedule(() => service.findById(dsoUUID, IdentifierType.UUID));
scheduler.schedule(() => service.findByIdAndIDType(dsoUUID, IdentifierType.UUID));
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestUUIDURL, dsoUUID));
@@ -115,7 +115,7 @@ describe('DsoRedirectDataService', () => {
it('should configure the proper FindByIDRequest for handle', () => {
setup();
scheduler.schedule(() => service.findById(dsoHandle, IdentifierType.HANDLE));
scheduler.schedule(() => service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE));
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestHandleURL, dsoHandle));
@@ -124,7 +124,7 @@ describe('DsoRedirectDataService', () => {
it('should navigate to item route', () => {
remoteData.payload.type = 'item';
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.
redir.subscribe();
scheduler.schedule(() => redir);
@@ -135,7 +135,7 @@ describe('DsoRedirectDataService', () => {
it('should navigate to collections route', () => {
remoteData.payload.type = 'collection';
setup();
const redir = service.findById(dsoHandle, IdentifierType.HANDLE);
const redir = service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE);
redir.subscribe();
scheduler.schedule(() => redir);
scheduler.flush();
@@ -145,7 +145,7 @@ describe('DsoRedirectDataService', () => {
it('should navigate to communities route', () => {
remoteData.payload.type = 'community';
setup();
const redir = service.findById(dsoHandle, IdentifierType.HANDLE);
const redir = service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE);
redir.subscribe();
scheduler.schedule(() => redir);
scheduler.flush();

View File

@@ -58,9 +58,9 @@ export class DsoRedirectDataService extends DataService<any> {
.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);
return super.findById(id).pipe(
return this.findById(id).pipe(
getFinishedRemoteData(),
take(1),
tap((response) => {

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
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
*/
@Injectable()
@dataService(MetadataSchema)
export class MetadataSchemaDataService {
private dataService: DataServiceImpl;

View File

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

View File

@@ -7,6 +7,7 @@ import { Observable } from 'rxjs';
import { DataService } from '../data/data.service';
import { RequestService } from '../data/request.service';
import { FindListOptions } from '../data/request.models';
import { Collection } from '../shared/collection.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { ResourcePolicy } from '../shared/resource-policy.model';
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 { ChangeAnalyzer } from './change-analyzer';
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 */
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);
}
findByHref(href: string, options?: HttpOptions): Observable<RemoteData<ResourcePolicy>> {
return this.dataService.findByHref(href, {}, options);
findByHref(href: string): Observable<RemoteData<ResourcePolicy>> {
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 { Site } from '../shared/site.model';
import { RequestService } from './request.service';
@@ -22,6 +23,7 @@ import { getSucceededRemoteData } from '../shared/operators';
* Service responsible for handling requests related to the Site object
*/
@Injectable()
@dataService(Site)
export class SiteDataService extends DataService<Site> {
protected linkPath = 'sites';
protected forceBypassCache = false;

View File

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

View File

@@ -1,5 +1,6 @@
import { autoserialize } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { HALLink } from '../../shared/hal-link.model';
export abstract class IntegrationModel implements CacheableObject {
@@ -14,7 +15,8 @@ export abstract class IntegrationModel implements CacheableObject {
@autoserialize
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 { 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 { ResourceType } from '../shared/resource-type';
import { GenericConstructor } from '../shared/generic-constructor';
@@ -7,7 +10,7 @@ import { GenericConstructor } from '../shared/generic-constructor';
/**
* Class the represents a metadata field
*/
export class MetadataField extends ListableObject {
export class MetadataField extends ListableObject implements HALResource {
static type = new ResourceType('metadatafield');
/**
@@ -38,7 +41,14 @@ export class MetadataField extends ListableObject {
/**
* 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

View File

@@ -1,11 +1,13 @@
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 { GenericConstructor } from '../shared/generic-constructor';
/**
* Class that represents a metadata schema
*/
export class MetadataSchema extends ListableObject {
export class MetadataSchema extends ListableObject implements HALResource {
static type = new ResourceType('metadataschema');
/**
@@ -28,6 +30,10 @@ export class MetadataSchema extends ListableObject {
*/
namespace: string;
_links: {
self: HALLink,
};
/**
* 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 { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { BitstreamFormatSupportLevel } from './bitstream-format-support-level';
@@ -56,4 +57,7 @@ export class BitstreamFormat implements CacheableObject {
*/
id: string;
_links: {
self: HALLink;
}
}

View File

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

View File

@@ -1,5 +1,5 @@
import { DSpaceObject } from './dspace-object.model';
import { HALLink } from './HALLink.model';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
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 { Bitstream } from './bitstream.model';
import { HALLink } from './HALLink.model';
import { HALLink } from './hal-link.model';
import { Item } from './item.model';
import { RemoteData } from '../data/remote-data';
import { Observable } from 'rxjs';
@@ -60,29 +61,19 @@ export class Collection extends DSpaceObject {
/**
* The deposit license of this Collection
*/
license: Observable<RemoteData<License>>;
// license?: Observable<RemoteData<License>>;
/**
* 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
*/
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[]>>;
@link(ResourcePolicy, true)
defaultAccessConditions?: Observable<RemoteData<PaginatedList<ResourcePolicy>>>;
_links: {
license: HALLink;

View File

@@ -1,9 +1,11 @@
import { link } from '../cache/builders/build-decorators';
import { DSpaceObject } from './dspace-object.model';
import { Bitstream } from './bitstream.model';
import { Collection } from './collection.model';
import { RemoteData } from '../data/remote-data';
import { Observable } from 'rxjs';
import { PaginatedList } from '../data/paginated-list';
import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
export class Community extends DSpaceObject {
@@ -49,20 +51,19 @@ export class Community extends DSpaceObject {
/**
* The Bitstream that represents the logo of this Community
*/
logo: Observable<RemoteData<Bitstream>>;
@link(Bitstream)
logo?: Observable<RemoteData<Bitstream>>;
/**
* An array of Communities that are direct parents of this Community
*/
parents: Observable<RemoteData<DSpaceObject[]>>;
@link(Collection, true)
collections?: Observable<RemoteData<PaginatedList<Collection>>>;
/**
* The Community that owns this Community
*/
owner: Observable<RemoteData<Community>>;
collections: Observable<RemoteData<PaginatedList<Collection>>>;
subcommunities: Observable<RemoteData<PaginatedList<Community>>>;
@link(Community, true)
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 {
MetadataMap,
MetadataValue,
@@ -9,11 +9,9 @@ import {
import { Metadata } from './metadata.utils';
import { hasNoValue, isUndefined } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer';
import { RemoteData } from '../data/remote-data';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { ResourceType } from './resource-type';
import { GenericConstructor } from './generic-constructor';
/**
* An abstract model class for a DSpaceObject.
@@ -67,6 +65,10 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
@excludeFromEquals
metadata: MetadataMap;
_links: {
self: HALLink,
};
/**
* 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);
}
/**
* 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.
*

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 { HALLink } from '../hal-link.model';
import { ResourceType } from '../resource-type';
/**
@@ -23,4 +24,8 @@ export class ItemType implements CacheableObject {
* The universally unique identifier of this ItemType
*/
uuid: string;
_links: {
self: HALLink,
};
}

View File

@@ -1,6 +1,8 @@
import { Observable } from 'rxjs';
import { link } from '../../cache/builders/build-decorators';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { RemoteData } from '../../data/remote-data';
import { HALLink } from '../hal-link.model';
import { ResourceType } from '../resource-type';
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
*/
leftType: Observable<RemoteData<ItemType>>;
@link(ItemType)
leftType?: Observable<RemoteData<ItemType>>;
/**
* 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 { link } from '../../cache/builders/build-decorators';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { RemoteData } from '../../data/remote-data';
import { HALLink } from '../hal-link.model';
import { ResourceType } from '../resource-type';
import { RelationshipType } from './relationship-type.model';
import { Item } from '../item.model';
@@ -29,12 +31,14 @@ export class Relationship implements CacheableObject {
/**
* 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
*/
rightItem: Observable<RemoteData<Item>>;
@link(Item)
rightItem?: Observable<RemoteData<Item>>;
/**
* 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
*/
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 { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
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 { 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';
/**
@@ -38,12 +44,19 @@ export class Item extends DSpaceObject {
*/
isWithdrawn: boolean;
@link(Bundle, true)
bundles: Observable<RemoteData<PaginatedList<Bundle>>>;
@link(Relationship, true)
relationships: Observable<RemoteData<PaginatedList<Relationship>>>;
_links: {
self: HALLink;
parents: HALLink;
owningCollection: HALLink;
bundles: HALLink;
mappedCollections: 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 { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { ActionType } from '../cache/models/action-type.model';
@@ -33,4 +34,7 @@ export class ResourcePolicy implements CacheableObject {
*/
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 { NavigationExtras, Router } from '@angular/router';
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 { PaginatedList } from '../../data/paginated-list';
import { ResponseParsingService } from '../../data/parsing.service';
@@ -340,6 +341,8 @@ export class SearchService implements OnDestroy {
switchMap((dsoRD: RemoteData<DSpaceObject>) => {
if ((dsoRD.payload as any).type === Community.type.value) {
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(
map(([subCommunities, collections]) => {
/*if this is a community, we also need to show the direct children*/

View File

@@ -1,10 +1,12 @@
import { Observable } from 'rxjs';
import { link } from '../../cache/builders/build-decorators';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { DSpaceObject } from '../../shared/dspace-object.model';
import { EPerson } from '../../eperson/models/eperson.model';
import { RemoteData } from '../../data/remote-data';
import { Collection } from '../../shared/collection.model';
import { HALLink } from '../../shared/hal-link.model';
import { Item } from '../../shared/item.model';
import { SubmissionDefinitionsModel } from '../../config/models/config-submission-definitions.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
*/
collection: Observable<RemoteData<Collection>> | Collection;
@link(Collection)
collection?: Observable<RemoteData<Collection>> | Collection;
/**
* The submission item
*/
item: Observable<RemoteData<Item>> | Item;
@link(Item)
item?: Observable<RemoteData<Item>> | Item;
/**
* 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
*/
submissionDefinition: Observable<RemoteData<SubmissionDefinitionsModel>> | SubmissionDefinitionsModel;
@link(SubmissionDefinitionsModel)
submissionDefinition?: Observable<RemoteData<SubmissionDefinitionsModel>> | SubmissionDefinitionsModel;
/**
* The workspaceitem submitter
*/
submitter: Observable<RemoteData<EPerson>> | EPerson;
@link(EPerson)
submitter?: Observable<RemoteData<EPerson>> | EPerson;
/**
* The workspaceitem/workflowitem last sections errors
*/
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);
});
it('should forward the result of WorkspaceitemDataService.findById()', () => {
it('should forward the result of WorkspaceitemDataService.findByIdAndIDType()', () => {
const result = service.findById(submissionId);
expect(workspaceitemDataService.findById).toHaveBeenCalledWith(submissionId);
expect(result).toBe(wsiResult);
@@ -60,7 +60,7 @@ describe('SubmissionObjectDataService', () => {
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);
expect(workflowItemDataService.findById).toHaveBeenCalledWith(submissionId);
expect(result).toBe(wfiResult);

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers';
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.
*/
@Injectable()
@dataService(WorkflowItem)
export class WorkflowItemDataService extends DataService<WorkflowItem> {
protected linkPath = 'workflowitems';
protected responseMsToLive = 10 * 1000;

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers';
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.
*/
@Injectable()
@dataService(WorkspaceItem)
export class WorkspaceitemDataService extends DataService<WorkspaceItem> {
protected linkPath = 'workspaceitems';
protected responseMsToLive = 10 * 1000;

View File

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

View File

@@ -1,8 +1,10 @@
import { Observable } from 'rxjs';
import { link } from '../../cache/builders/build-decorators';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { DSpaceObject } from '../../shared/dspace-object.model';
import { RemoteData } from '../../data/remote-data';
import { HALLink } from '../../shared/hal-link.model';
import { WorkflowItem } from '../../submission/models/workflowitem.model';
import { ResourceType } from '../../shared/resource-type';
import { EPerson } from '../../eperson/models/eperson.model';
@@ -32,15 +34,26 @@ export class TaskObject extends DSpaceObject implements CacheableObject {
/**
* The group of this task
*/
eperson: Observable<RemoteData<EPerson>>;
@link(EPerson)
eperson?: Observable<RemoteData<EPerson>>;
/**
* The group of this task
*/
group: Observable<RemoteData<Group>>;
@link(Group)
group?: Observable<RemoteData<Group>>;
/**
* 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 { Store } from '@ngrx/store';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers';
@@ -20,6 +21,7 @@ import { ProcessTaskResponse } from './models/process-task-response';
* The service handling all REST requests for PoolTask
*/
@Injectable()
@dataService(PoolTask)
export class PoolTaskDataService extends TasksService<PoolTask> {
/**

View File

@@ -1,6 +1,6 @@
<div class="d-flex">
<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 class="flex-grow-1">
<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 { 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 { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
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 modalService: NgbModal,
private itemDataService: ItemDataService,
private bitstreamDataService: BitstreamDataService,
private selectableListService: SelectableListService) {
super(truncatableService);
}
@@ -95,4 +100,11 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
modalComp.value = value;
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="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 class="flex-grow-1">
<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 { 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 { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
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 modalService: NgbModal,
private itemDataService: ItemDataService,
private bitstreamDataService: BitstreamDataService,
private selectableListService: SelectableListService) {
super(truncatableService);
}
@@ -95,4 +100,11 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
modalComp.value = value;
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 { 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 {SharedModule} from '../shared.module';
import { RouterLinkDirectiveStub } from './router-link-directive-stub';
@@ -19,7 +19,7 @@ import { NgComponentOutletDirectiveStub } from './ng-component-outlet-directive-
],
declarations: [
QueryParamsDirectiveStub,
MySimpleItemActionComponent,
// MySimpleItemActionComponent,
RouterLinkDirectiveStub,
NgComponentOutletDirectiveStub
], 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 = [];
(submissionDefinitionResponse.sections as SubmissionSectionModel[])
.forEach((sectionDefinition: SubmissionSectionModel) => {
const sectionId = sectionDefinition._links.self.substr(sectionDefinition._links.self.lastIndexOf('/') + 1);
const config = sectionDefinition._links.config || '';
const sectionId = sectionDefinition._links.self.href.substr(sectionDefinition._links.self.href.lastIndexOf('/') + 1);
const config = sectionDefinition._links.config.href || '';
const enabled = (sectionDefinition.mandatory);
const sectionData = {};
const sectionErrors = null;

View File

@@ -57,8 +57,8 @@ export class SubmissionObjectEffects {
const definition = action.payload.submissionDefinition;
const mappedActions = [];
definition.sections.page.forEach((sectionDefinition: SubmissionSectionModel) => {
const sectionId = sectionDefinition._links.self.substr(sectionDefinition._links.self.lastIndexOf('/') + 1);
const config = sectionDefinition._links.config || '';
const sectionId = sectionDefinition._links.self.href.substr(sectionDefinition._links.self.href.lastIndexOf('/') + 1);
const config = sectionDefinition._links.config.href || '';
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 sectionErrors = null;

View File

@@ -134,7 +134,7 @@ export class SubmissionSectionLicenseComponent extends SectionModelComponent {
this.licenseText$ = this.collectionDataService.findById(this.collectionId).pipe(
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))),
map((licenseData: RemoteData<License>) => licenseData.payload.text),
startWith(''));

View File

@@ -2,6 +2,7 @@ import { ChangeDetectorRef, Component, Inject } from '@angular/core';
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription} from 'rxjs';
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 { 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) => isUndefined(this.collectionId) || 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))),
tap((collectionRemoteData: RemoteData<Collection>) => this.collectionName = collectionRemoteData.payload.name),
flatMap((collectionRemoteData: RemoteData<Collection>) => {
return this.resourcePolicyService.findByHref(
(collectionRemoteData.payload as any)._links.defaultAccessConditions
);
return this.resourcePolicyService.findByHref((collectionRemoteData.payload as any).defaultAccessConditions);
}),
filter((defaultAccessConditionsRemoteData: RemoteData<ResourcePolicy>) =>
defaultAccessConditionsRemoteData.hasSucceeded),