Merge pull request #1261 from atmire/w2p-80369_Use-thumbnail-links-for-Items-and-Bitstreams

Use thumbnail links for items and bitstreams
This commit is contained in:
Tim Donohue
2021-06-30 14:30:32 -05:00
committed by GitHub
49 changed files with 223 additions and 260 deletions

View File

@@ -35,7 +35,7 @@ export class BitstreamPageResolver implements Resolve<RemoteData<Bitstream>> {
*/
get followLinks(): FollowLinkConfig<Bitstream>[] {
return [
followLink('bundle', undefined, true, true, true, followLink('item')),
followLink('bundle', {}, followLink('item')),
followLink('format')
];
}

View File

@@ -14,7 +14,7 @@ import { ResolvedAction } from '../core/resolving/resolver.actions';
* Requesting them as embeds will limit the number of requests
*/
export const COLLECTION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Collection>[] = [
followLink('parentCommunity', undefined, true, true, true,
followLink('parentCommunity', {},
followLink('parentCommunity')
),
followLink('logo')

View File

@@ -4,10 +4,9 @@ import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
import { catchError, filter, first, map, mergeMap, take } from 'rxjs/operators';
import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
import {
getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteDataWithNotEmptyPayload
getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataWithNotEmptyPayload,
} from '../../../core/shared/operators';
import { Item } from '../../../core/shared/item.model';
import { followLink } from '../../../shared/utils/follow-link-config.model';
@@ -15,7 +14,6 @@ import { LinkService } from '../../../core/cache/builders/link.service';
import { Bundle } from '../../../core/shared/bundle.model';
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { Bitstream } from '../../../core/shared/bitstream.model';
import { FindListOptions } from '../../../core/data/request.models';
/**
* Interface for a bundle's bitstream map entry
@@ -79,7 +77,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
getFirstSucceededRemoteDataWithNotEmptyPayload(),
map((item: Item) => this.linkService.resolveLink(
item,
followLink('bundles', new FindListOptions(), true, true, true, followLink('bitstreams'))
followLink('bundles', {}, followLink('bitstreams'))
))
) as Observable<Item>;

View File

@@ -68,7 +68,8 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
{elementsPerPage: options.pageSize, currentPage: options.currentPage},
true,
true,
followLink('format')
followLink('format'),
followLink('thumbnail'),
)),
tap((rd: RemoteData<PaginatedList<Bitstream>>) => {
if (hasValue(rd.errorMessage)) {
@@ -85,7 +86,8 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
{elementsPerPage: options.pageSize, currentPage: options.currentPage},
true,
true,
followLink('format')
followLink('format'),
followLink('thumbnail'),
)),
tap((rd: RemoteData<PaginatedList<Bitstream>>) => {
if (hasValue(rd.errorMessage)) {

View File

@@ -5,7 +5,6 @@ import { RemoteData } from '../core/data/remote-data';
import { ItemDataService } from '../core/data/item-data.service';
import { Item } from '../core/shared/item.model';
import { followLink, FollowLinkConfig } from '../shared/utils/follow-link-config.model';
import { FindListOptions } from '../core/data/request.models';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { Store } from '@ngrx/store';
import { ResolvedAction } from '../core/resolving/resolver.actions';
@@ -15,13 +14,13 @@ import { ResolvedAction } from '../core/resolving/resolver.actions';
* Requesting them as embeds will limit the number of requests
*/
export const ITEM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Item>[] = [
followLink('owningCollection', undefined, true, true, true,
followLink('parentCommunity', undefined, true, true, true,
followLink('owningCollection', {},
followLink('parentCommunity', {},
followLink('parentCommunity'))
),
followLink('bundles', new FindListOptions(), true, true, true, followLink('bitstreams')),
followLink('relationships'),
followLink('version', undefined, true, true, true, followLink('versionhistory')),
followLink('version', {}, followLink('versionhistory')),
followLink('thumbnail')
];
/**

View File

@@ -10,7 +10,7 @@
<div class="col-xs-12 col-md-4">
<ng-container *ngIf="!mediaViewer.image">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
</ng-container>
<ng-container *ngIf="mediaViewer.image">

View File

@@ -1,12 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { environment } from '../../../../../environments/environment';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { Bitstream } from '../../../../core/shared/bitstream.model';
import { Item } from '../../../../core/shared/item.model';
import { takeUntilCompletedRemoteData } from '../../../../core/shared/operators';
import { getItemPageRoute } from '../../../item-page-routing-paths';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { RemoteData } from '../../../../core/data/remote-data';
@Component({
selector: 'ds-item',
@@ -18,28 +13,14 @@ import { RemoteData } from '../../../../core/data/remote-data';
export class ItemComponent implements OnInit {
@Input() object: Item;
/**
* The Item's thumbnail
*/
thumbnail$: BehaviorSubject<RemoteData<Bitstream>>;
/**
* Route to the item page
*/
itemPageRoute: string;
mediaViewer = environment.mediaViewer;
constructor(protected bitstreamDataService: BitstreamDataService) {
}
mediaViewer = environment.mediaViewer;
ngOnInit(): void {
this.itemPageRoute = getItemPageRoute(this.object);
this.thumbnail$ = new BehaviorSubject<RemoteData<Bitstream>>(undefined);
this.bitstreamDataService.getThumbnailFor(this.object).pipe(
takeUntilCompletedRemoteData(),
).subscribe((rd: RemoteData<Bitstream>) => {
this.thumbnail$.next(rd);
});
}
}

View File

@@ -10,7 +10,7 @@
<div class="col-xs-12 col-md-4">
<ng-container *ngIf="!mediaViewer.image">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
</ng-container>
<ng-container *ngIf="mediaViewer.image">

View File

@@ -16,9 +16,11 @@ import { SearchResult } from '../shared/search/search-result.model';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { SearchService } from '../core/shared/search/search.service';
import { currentPath } from '../shared/utils/route.utils';
import { Router} from '@angular/router';
import { Router } from '@angular/router';
import { Context } from '../core/shared/context.model';
import { SortOptions } from '../core/cache/models/sort-options.model';
import { followLink } from '../shared/utils/follow-link-config.model';
import { Item } from '../core/shared/item.model';
@Component({
selector: 'ds-search',
@@ -128,8 +130,11 @@ export class SearchComponent implements OnInit {
this.searchLink = this.getSearchLink();
this.searchOptions$ = this.getSearchOptions();
this.sub = this.searchOptions$.pipe(
switchMap((options) => this.service.search(options).pipe(getFirstSucceededRemoteData(), startWith(undefined))))
.subscribe((results) => {
switchMap((options) => this.service.search(
options, undefined, true, true, followLink<Item>('thumbnail', { isOptional: true })
).pipe(getFirstSucceededRemoteData(), startWith(undefined))
)
).subscribe((results) => {
this.resultsRD$.next(results);
});
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(

View File

@@ -174,8 +174,8 @@ export class CommunityListService {
direction: options.sort.direction
}
},
followLink('subcommunities', this.configOnePage, true, true),
followLink('collections', this.configOnePage, true, true))
followLink('subcommunities', { findListOptions: this.configOnePage }),
followLink('collections', { findListOptions: this.configOnePage }))
.pipe(
getFirstSucceededRemoteData(),
map((results) => results.payload),
@@ -242,8 +242,8 @@ export class CommunityListService {
elementsPerPage: MAX_COMCOLS_PER_PAGE,
currentPage: i
},
followLink('subcommunities', this.configOnePage, true, true),
followLink('collections', this.configOnePage, true, true))
followLink('subcommunities', { findListOptions: this.configOnePage }),
followLink('collections', { findListOptions: this.configOnePage }))
.pipe(
getFirstCompletedRemoteData(),
switchMap((rd: RemoteData<PaginatedList<Community>>) => {

View File

@@ -102,7 +102,7 @@ describe('LinkService', () => {
describe('resolveLink', () => {
describe(`when the linkdefinition concerns a single object`, () => {
beforeEach(() => {
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
service.resolveLink(testModel, followLink('predecessor', {}, followLink('successor')));
});
it('should call dataservice.findByHref with the correct href and nested links', () => {
expect(testDataService.findByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, true, true, followLink('successor'));
@@ -116,7 +116,7 @@ describe('LinkService', () => {
propertyName: 'predecessor',
isList: true
});
service.resolveLink(testModel, followLink('predecessor', { some: 'options ' } as any, true, true, true, followLink('successor')));
service.resolveLink(testModel, followLink('predecessor', { findListOptions: { some: 'options ' } as any }, followLink('successor')));
});
it('should call dataservice.findAllByHref with the correct href, findListOptions, and nested links', () => {
expect(testDataService.findAllByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, { some: 'options ' } as any, true, true, followLink('successor'));
@@ -124,7 +124,7 @@ describe('LinkService', () => {
});
describe('either way', () => {
beforeEach(() => {
result = service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
result = service.resolveLink(testModel, followLink('predecessor', {}, followLink('successor')));
});
it('should call getLinkDefinition with the correct model and link', () => {
@@ -149,7 +149,7 @@ describe('LinkService', () => {
});
it('should throw an error', () => {
expect(() => {
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
service.resolveLink(testModel, followLink('predecessor', {}, followLink('successor')));
}).toThrow();
});
});
@@ -160,7 +160,7 @@ describe('LinkService', () => {
});
it('should throw an error', () => {
expect(() => {
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
service.resolveLink(testModel, followLink('predecessor', {}, followLink('successor')));
}).toThrow();
});
});

View File

@@ -39,7 +39,7 @@ export class LinkService {
*/
public resolveLinks<T extends HALResource>(model: T, ...linksToFollow: FollowLinkConfig<T>[]): T {
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
this.resolveLink(model, linkToFollow);
this.resolveLink(model, linkToFollow);
});
return model;
}
@@ -55,9 +55,7 @@ export class LinkService {
public resolveLinkWithoutAttaching<T extends HALResource, U extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): Observable<RemoteData<U>> {
const matchingLinkDef = this.getLinkDefinition(model.constructor, linkToFollow.name);
if (hasNoValue(matchingLinkDef)) {
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 {
if (hasValue(matchingLinkDef)) {
const provider = this.getDataServiceFor(matchingLinkDef.resourceType);
if (hasNoValue(provider)) {
@@ -84,7 +82,10 @@ export class LinkService {
throw e;
}
}
} else if (!linkToFollow.isOptional) {
throw new Error(`followLink('${linkToFollow.name}') was used as a required link for a ${model.constructor.name}, but there is no property on ${model.constructor.name} models with an @link() for ${linkToFollow.name}`);
}
return EMPTY;
}

View File

@@ -523,7 +523,7 @@ describe('RemoteDataBuildService', () => {
let paginatedLinksToFollow;
beforeEach(() => {
paginatedLinksToFollow = [
followLink('page', undefined, true, true, true, ...linksToFollow),
followLink('page', {}, ...linksToFollow),
...linksToFollow
];
});

View File

@@ -271,7 +271,7 @@ export class RemoteDataBuildService {
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
buildList<T extends HALResource>(href$: string | Observable<string>, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
return this.buildFromHref<PaginatedList<T>>(href$, followLink('page', undefined, false, true, true, ...linksToFollow));
return this.buildFromHref<PaginatedList<T>>(href$, followLink('page', { shouldEmbed: false }, ...linksToFollow));
}
/**

View File

@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { map, switchMap, take } from 'rxjs/operators';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
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';
@@ -18,7 +18,7 @@ import { Item } from '../shared/item.model';
import { BundleDataService } from './bundle-data.service';
import { DataService } from './data.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { PaginatedList, buildPaginatedList } from './paginated-list.model';
import { buildPaginatedList, PaginatedList } from './paginated-list.model';
import { RemoteData } from './remote-data';
import { FindListOptions, PutRequest } from './request.models';
import { RequestService } from './request.service';
@@ -28,7 +28,6 @@ import { HttpOptions } from '../dspace-rest/dspace-rest.service';
import { sendRequest } from '../shared/operators';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { PageInfo } from '../shared/page-info.model';
import { RequestEntryState } from './request.reducer';
/**
* A service to retrieve {@link Bitstream}s from the REST API
@@ -75,92 +74,6 @@ export class BitstreamDataService extends DataService<Bitstream> {
return this.findAllByHref(bundle._links.bitstreams.href, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Retrieves the thumbnail for the given item
* @returns {Observable<RemoteData<{@link Bitstream}>>} the first bitstream in the THUMBNAIL bundle
*/
// TODO should be implemented rest side. {@link Item} should get a thumbnail link
public getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return this.bundleService.findByItemAndName(item, 'THUMBNAIL').pipe(
switchMap((bundleRD: RemoteData<Bundle>) => {
if (isNotEmpty(bundleRD.payload)) {
return this.findAllByBundle(bundleRD.payload, { elementsPerPage: 1 }).pipe(
map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => {
if (hasValue(bitstreamRD.payload) && hasValue(bitstreamRD.payload.page)) {
return new RemoteData(
bitstreamRD.timeCompleted,
bitstreamRD.msToLive,
bitstreamRD.lastUpdated,
bitstreamRD.state,
bitstreamRD.errorMessage,
bitstreamRD.payload.page[0],
bitstreamRD.statusCode
);
} else {
return bitstreamRD as any;
}
})
);
} else {
return [bundleRD as any];
}
})
);
}
/**
* Retrieve the matching thumbnail for a {@link Bitstream}.
*
* The {@link Item} is technically redundant, but is available
* in all current use cases, and having it simplifies this method
*
* @param item The {@link Item} the {@link Bitstream} and its thumbnail are a part of
* @param bitstreamInOriginal The original {@link Bitstream} to find the thumbnail for
*/
// TODO should be implemented rest side
public getMatchingThumbnail(item: Item, bitstreamInOriginal: Bitstream): Observable<RemoteData<Bitstream>> {
return this.bundleService.findByItemAndName(item, 'THUMBNAIL').pipe(
switchMap((bundleRD: RemoteData<Bundle>) => {
if (isNotEmpty(bundleRD.payload)) {
return this.findAllByBundle(bundleRD.payload, { elementsPerPage: 9999 }).pipe(
map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => {
if (hasValue(bitstreamRD.payload) && hasValue(bitstreamRD.payload.page)) {
const matchingThumbnail = bitstreamRD.payload.page.find((thumbnail: Bitstream) =>
thumbnail.name.startsWith(bitstreamInOriginal.name)
);
if (hasValue(matchingThumbnail)) {
return new RemoteData(
bitstreamRD.timeCompleted,
bitstreamRD.msToLive,
bitstreamRD.lastUpdated,
bitstreamRD.state,
bitstreamRD.errorMessage,
matchingThumbnail,
bitstreamRD.statusCode
);
} else {
return new RemoteData(
bitstreamRD.timeCompleted,
bitstreamRD.msToLive,
bitstreamRD.lastUpdated,
RequestEntryState.Error,
'No matching thumbnail found',
undefined,
404
);
}
} else {
return bitstreamRD as any;
}
})
);
} else {
return [bundleRD as any];
}
})
);
}
/**
* Retrieve all {@link Bitstream}s in a certain {@link Bundle}.
*

View File

@@ -233,7 +233,7 @@ describe('DataService', () => {
const config: FindListOptions = Object.assign(new FindListOptions(), {
elementsPerPage: 5
});
(service as any).getFindAllHref({}, null, followLink('bundles', config, true, true, true)).subscribe((value) => {
(service as any).getFindAllHref({}, null, followLink('bundles', { findListOptions: config })).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -253,7 +253,7 @@ describe('DataService', () => {
elementsPerPage: 2
});
(service as any).getFindAllHref({}, null, followLink('bundles'), followLink('owningCollection', config, true, true, true), followLink('templateItemOf')).subscribe((value) => {
(service as any).getFindAllHref({}, null, followLink('bundles'), followLink('owningCollection', { findListOptions: config }), followLink('templateItemOf')).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -261,7 +261,13 @@ describe('DataService', () => {
it('should not include linksToFollow with shouldEmbed = false', () => {
const expected = `${endpoint}?embed=templateItemOf`;
(service as any).getFindAllHref({}, null, followLink('bundles', undefined, false), followLink('owningCollection', undefined, false), followLink('templateItemOf')).subscribe((value) => {
(service as any).getFindAllHref(
{},
null,
followLink('bundles', { shouldEmbed: false }),
followLink('owningCollection', { shouldEmbed: false }),
followLink('templateItemOf')
).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -269,7 +275,7 @@ describe('DataService', () => {
it('should include nested linksToFollow 3lvl', () => {
const expected = `${endpoint}?embed=owningCollection/itemtemplate/relationships`;
(service as any).getFindAllHref({}, null, followLink('owningCollection', undefined, true, true, true, followLink('itemtemplate', undefined, true, true, true, followLink('relationships')))).subscribe((value) => {
(service as any).getFindAllHref({}, null, followLink('owningCollection', {}, followLink('itemtemplate', {}, followLink('relationships')))).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -279,7 +285,7 @@ describe('DataService', () => {
const config: FindListOptions = Object.assign(new FindListOptions(), {
elementsPerPage: 4
});
(service as any).getFindAllHref({}, null, followLink('owningCollection', undefined, true, true, true, followLink('itemtemplate', config, true, true, true))).subscribe((value) => {
(service as any).getFindAllHref({}, null, followLink('owningCollection', {}, followLink('itemtemplate', { findListOptions: config }))).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -308,13 +314,19 @@ describe('DataService', () => {
it('should not include linksToFollow with shouldEmbed = false', () => {
const expected = `${endpointMock}/${resourceIdMock}?embed=templateItemOf`;
const result = (service as any).getIDHref(endpointMock, resourceIdMock, followLink('bundles', undefined, false), followLink('owningCollection', undefined, false), followLink('templateItemOf'));
const result = (service as any).getIDHref(
endpointMock,
resourceIdMock,
followLink('bundles', { shouldEmbed: false }),
followLink('owningCollection', { shouldEmbed: false }),
followLink('templateItemOf')
);
expect(result).toEqual(expected);
});
it('should include nested linksToFollow 3lvl', () => {
const expected = `${endpointMock}/${resourceIdMock}?embed=owningCollection/itemtemplate/relationships`;
const result = (service as any).getIDHref(endpointMock, resourceIdMock, followLink('owningCollection', undefined, true, true, true,followLink('itemtemplate', undefined, true, true, true, followLink('relationships'))));
const result = (service as any).getIDHref(endpointMock, resourceIdMock, followLink('owningCollection', {}, followLink('itemtemplate', {}, followLink('relationships'))));
expect(result).toEqual(expected);
});
});

View File

@@ -119,7 +119,7 @@ describe('DsoRedirectDataService', () => {
});
it('should navigate to entities route with the corresponding entity type', () => {
remoteData.payload.type = 'item';
remoteData.payload.metadata = {
remoteData.payload.metadata = {
'dspace.entity.type': [
{
language: 'en_US',
@@ -174,13 +174,29 @@ describe('DsoRedirectDataService', () => {
it('should not include linksToFollow with shouldEmbed = false', () => {
const expected = `${requestUUIDURL}&embed=templateItemOf`;
const result = (service as any).getIDHref(pidLink, dsoUUID, followLink('bundles', undefined, false), followLink('owningCollection', undefined, false), followLink('templateItemOf'));
const result = (service as any).getIDHref(
pidLink,
dsoUUID,
followLink('bundles', { shouldEmbed: false }),
followLink('owningCollection', { shouldEmbed: false }),
followLink('templateItemOf')
);
expect(result).toEqual(expected);
});
it('should include nested linksToFollow 3lvl', () => {
const expected = `${requestUUIDURL}&embed=owningCollection/itemtemplate/relationships`;
const result = (service as any).getIDHref(pidLink, dsoUUID, followLink('owningCollection', undefined, true, true, true, followLink('itemtemplate', undefined, true, true, true, followLink('relationships'))));
const result = (service as any).getIDHref(
pidLink,
dsoUUID,
followLink('owningCollection',
{},
followLink('itemtemplate',
{},
followLink('relationships')
)
)
);
expect(result).toEqual(expected);
});
});

View File

@@ -43,13 +43,14 @@ export class Bitstream extends DSpaceObject implements HALResource {
bundle: HALLink;
format: HALLink;
content: HALLink;
thumbnail: HALLink;
};
/**
* The thumbnail for this Bitstream
* Needs to be resolved first, but isn't available as a {@link HALLink} yet
* Use BitstreamDataService.getThumbnailFor(…) for now.
* Will be undefined unless the thumbnail {@link HALLink} has been resolved.
*/
@link(BITSTREAM, false, 'thumbnail')
thumbnail?: Observable<RemoteData<Bitstream>>;
/**

View File

@@ -19,6 +19,8 @@ import { ITEM } from './item.resource-type';
import { ChildHALResource } from './child-hal-resource.model';
import { Version } from './version.model';
import { VERSION } from './version.resource-type';
import { BITSTREAM } from './bitstream.resource-type';
import { Bitstream } from './bitstream.model';
/**
* Class representing a DSpace Item
@@ -69,6 +71,7 @@ export class Item extends DSpaceObject implements ChildHALResource {
owningCollection: HALLink;
templateItemOf: HALLink;
version: HALLink;
thumbnail: HALLink;
self: HALLink;
};
@@ -100,6 +103,13 @@ export class Item extends DSpaceObject implements ChildHALResource {
@link(RELATIONSHIP, true)
relationships?: Observable<RemoteData<PaginatedList<Relationship>>>;
/**
* The thumbnail for this Item
* Will be undefined unless the thumbnail {@link HALLink} has been resolved.
*/
@link(BITSTREAM, false, 'thumbnail')
thumbnail?: Observable<RemoteData<Bitstream>>;
/**
* Method that returns as which type of object this object should be rendered
*/

View File

@@ -41,6 +41,43 @@ import { SearchConfig } from './search-filters/search-config.model';
import { PaginationService } from '../../pagination/pagination.service';
import { SearchConfigurationService } from './search-configuration.service';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { DataService } from '../../data/data.service';
import { Store } from '@ngrx/store';
import { CoreState } from '../../core.reducers';
import { ObjectCacheService } from '../../cache/object-cache.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { DSOChangeAnalyzer } from '../../data/dso-change-analyzer.service';
/* tslint:disable:max-classes-per-file */
/**
* A class that lets us delegate some methods to DataService
*/
class DataServiceImpl extends DataService<any> {
protected linkPath = 'discover';
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DSOChangeAnalyzer<any>) {
super();
}
/**
* Adds the embed options to the link for the request
* @param href The href the params are to be added to
* @param args params for the query string
* @param linksToFollow links we want to embed in query string if shouldEmbed is true
*/
public addEmbedParams(href: string, args: string[], ...linksToFollow: FollowLinkConfig<any>[]) {
return super.addEmbedParams(href, args, ...linksToFollow);
}
}
/**
* Service that performs all general actions that have to do with the search page
@@ -78,6 +115,11 @@ export class SearchService implements OnDestroy {
*/
private sub;
/**
* Instance of DataServiceImpl that lets us delegate some methods to DataService
*/
private searchDataService: DataServiceImpl;
constructor(private router: Router,
private routeService: RouteService,
protected requestService: RequestService,
@@ -89,6 +131,16 @@ export class SearchService implements OnDestroy {
private paginationService: PaginationService,
private searchConfigurationService: SearchConfigurationService
) {
this.searchDataService = new DataServiceImpl(
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined
);
}
/**
@@ -131,7 +183,17 @@ export class SearchService implements OnDestroy {
search<T extends DSpaceObject>(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<SearchObjects<T>>> {
const href$ = this.getEndpoint(searchOptions);
href$.pipe(take(1)).subscribe((url: string) => {
href$.pipe(
take(1),
map((href: string) => {
const args = this.searchDataService.addEmbedParams(href, [], ...linksToFollow);
if (isNotEmpty(args)) {
return new URLCombiner(href, `?${args.join('&')}`).toString();
} else {
return href;
}
})
).subscribe((url: string) => {
const request = new this.request(this.requestService.generateRequestId(), url);
const getResponseParserFn: () => GenericConstructor<ResponseParsingService> = () => {
@@ -152,7 +214,7 @@ export class SearchService implements OnDestroy {
);
return this.directlyAttachIndexableObjects(sqr$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
}
/**
* Method to retrieve request entries for search results from the server
@@ -399,9 +461,9 @@ export class SearchService implements OnDestroy {
let pageParams = { page: 1 };
const queryParams = { view: viewMode };
if (viewMode === ViewMode.DetailedListElement) {
pageParams = Object.assign(pageParams, {pageSize: 1});
pageParams = Object.assign(pageParams, { pageSize: 1 });
} else if (config.pageSize === 1) {
pageParams = Object.assign(pageParams, {pageSize: 10});
pageParams = Object.assign(pageParams, { pageSize: 10 });
}
this.paginationService.updateRouteWithUrl(this.searchConfigurationService.paginationID, hasValue(searchLinkParts) ? searchLinkParts : [this.getSearchLink()], pageParams, queryParams);
});
@@ -413,7 +475,7 @@ export class SearchService implements OnDestroy {
* @param {string} configurationName the name of the configuration
* @returns {Observable<RemoteData<SearchConfig[]>>} The found configuration
*/
getSearchConfigurationFor(scope?: string, configurationName?: string ): Observable<RemoteData<SearchConfig>> {
getSearchConfigurationFor(scope?: string, configurationName?: string): Observable<RemoteData<SearchConfig>> {
const href$ = this.halService.getEndpoint(this.configurationLinkPath).pipe(
map((url: string) => this.getConfigUrl(url, scope, configurationName)),
);

View File

@@ -173,7 +173,7 @@ export class VocabularyService {
);
// TODO remove false for the entries embed when https://github.com/DSpace/DSpace/issues/3096 is solved
return this.findVocabularyById(vocabularyOptions.name, true, true, followLink('entries', options, false)).pipe(
return this.findVocabularyById(vocabularyOptions.name, true, true, followLink('entries', { findListOptions: options, shouldEmbed: false })).pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((vocabulary: Vocabulary) => vocabulary.entries),
);
@@ -200,7 +200,7 @@ export class VocabularyService {
);
// TODO remove false for the entries embed when https://github.com/DSpace/DSpace/issues/3096 is solved
return this.findVocabularyById(vocabularyOptions.name, true, true, followLink('entries', options, false)).pipe(
return this.findVocabularyById(vocabularyOptions.name, true, true, followLink('entries', { findListOptions: options, shouldEmbed: false })).pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((vocabulary: Vocabulary) => vocabulary.entries),
);
@@ -249,7 +249,7 @@ export class VocabularyService {
);
// TODO remove false for the entries embed when https://github.com/DSpace/DSpace/issues/3096 is solved
return this.findVocabularyById(vocabularyOptions.name, true, true, followLink('entries', options, false)).pipe(
return this.findVocabularyById(vocabularyOptions.name, true, true, followLink('entries', { findListOptions: options, shouldEmbed: false })).pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((vocabulary: Vocabulary) => vocabulary.entries),
getFirstSucceededRemoteListPayload(),

View File

@@ -8,13 +8,13 @@
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</a>
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</span>

View File

@@ -8,13 +8,13 @@
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</a>
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</span>

View File

@@ -8,13 +8,13 @@
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</a>
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</span>

View File

@@ -9,7 +9,7 @@
<div class="row">
<div class="col-xs-12 col-md-4">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
<ds-generic-item-page-field [item]="object"
[fields]="['publicationvolume.volumeNumber']"

View File

@@ -9,7 +9,7 @@
<div class="row">
<div class="col-xs-12 col-md-4">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
<ds-generic-item-page-field [item]="object"
[fields]="['publicationvolume.volumeNumber']"

View File

@@ -9,7 +9,7 @@
<div class="row">
<div class="col-xs-12 col-md-4">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
<ds-generic-item-page-field class="item-page-fields" [item]="object"
[fields]="['creativeworkseries.issn']"

View File

@@ -8,13 +8,13 @@
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</a>
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</span>

View File

@@ -8,13 +8,13 @@
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</a>
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</span>

View File

@@ -8,13 +8,13 @@
rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</a>
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</span>

View File

@@ -9,7 +9,7 @@
<div class="row">
<div class="col-xs-12 col-md-4">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="thumbnail$ | async"
<ds-thumbnail [thumbnail]="object?.thumbnail | async"
[defaultImage]="'assets/images/orgunit-placeholder.svg'"
[alt]="'thumbnail.orgunit.alt'"
[placeholder]="'thumbnail.orgunit.placeholder'"

View File

@@ -9,7 +9,7 @@
<div class="row">
<div class="col-xs-12 col-md-4">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="thumbnail$ | async"
<ds-thumbnail [thumbnail]="object?.thumbnail | async"
[defaultImage]="'assets/images/person-placeholder.svg'"
[alt]="'thumbnail.person.alt'"
[placeholder]="'thumbnail.person.placeholder'">

View File

@@ -10,7 +10,7 @@
<div class="col-xs-12 col-md-4">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail
[thumbnail]="thumbnail$ | async"
[thumbnail]="object?.thumbnail | async"
[defaultImage]="'assets/images/project-placeholder.svg'"
[alt]="'thumbnail.project.alt'"
[placeholder]="'thumbnail.project.placeholder'">

View File

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

View File

@@ -1,8 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
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';
@@ -113,11 +110,4 @@ 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,8 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
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';
@@ -108,11 +105,4 @@ 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

@@ -49,8 +49,8 @@ export class ClaimedTaskSearchResultDetailElementComponent extends SearchResultD
*/
ngOnInit() {
super.ngOnInit();
this.linkService.resolveLinks(this.dso, followLink('workflowitem', null, true, true, true,
followLink('item', null, true, true, true, followLink('bundles')),
this.linkService.resolveLinks(this.dso, followLink('workflowitem', {},
followLink('item', {}, followLink('bundles')),
followLink('submitter')
), followLink('action'));
this.workflowitemRD$ = this.dso.workflowitem as Observable<RemoteData<WorkflowItem>>;

View File

@@ -10,7 +10,7 @@
<div class="row mb-1">
<div class="col-xs-12 col-md-4">
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
<ds-thumbnail [thumbnail]="item?.thumbnail | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
<ng-container *ngVar="(getFiles() | async) as bitstreams">
<ds-metadata-field-wrapper [label]="('item.page.files' | translate)">

View File

@@ -126,13 +126,6 @@ describe('ItemDetailPreviewComponent', () => {
}));
it('should get item thumbnail', (done) => {
component.getThumbnail().subscribe((thumbnail) => {
expect(thumbnail).toBeDefined();
done();
});
});
it('should get item bitstreams', (done) => {
component.getFiles().subscribe((bitstreams) => {
expect(bitstreams).toBeDefined();

View File

@@ -5,10 +5,7 @@ import { first } from 'rxjs/operators';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { Item } from '../../../../core/shared/item.model';
import {
getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteListPayload
} from '../../../../core/shared/operators';
import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators';
import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
import { fadeInOut } from '../../../animations/fade';
import { Bitstream } from '../../../../core/shared/bitstream.model';
@@ -57,11 +54,6 @@ export class ItemDetailPreviewComponent {
*/
public separator = ', ';
/**
* The item's thumbnail
*/
public thumbnail$: Observable<Bitstream>;
/**
* Initialize instance variables
*
@@ -86,13 +78,6 @@ export class ItemDetailPreviewComponent {
});
}
// TODO refactor this method to return RemoteData, and the template to deal with loading and errors
public getThumbnail(): Observable<Bitstream> {
return this.bitstreamDataService.getThumbnailFor(this.item).pipe(
getFirstSucceededRemoteDataPayload()
);
}
// TODO refactor this method to return RemoteData, and the template to deal with loading and errors
public getFiles(): Observable<Bitstream[]> {
return this.bitstreamDataService

View File

@@ -48,8 +48,8 @@ export class PoolSearchResultDetailElementComponent extends SearchResultDetailEl
*/
ngOnInit() {
super.ngOnInit();
this.linkService.resolveLinks(this.dso, followLink('workflowitem', null, true, true, true,
followLink('item', null, true, true, true, followLink('bundles')),
this.linkService.resolveLinks(this.dso, followLink('workflowitem', {},
followLink('item', {}, followLink('bundles')),
followLink('submitter')
), followLink('action'));
this.workflowitemRD$ = this.dso.workflowitem as Observable<RemoteData<WorkflowItem>>;

View File

@@ -6,13 +6,13 @@
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="[itemPageRoute]"
class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</a>
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
<div>
<ds-thumbnail [thumbnail]="getThumbnail() | async" [limitWidth]="false">
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
</ds-thumbnail>
</div>
</span>
@@ -43,4 +43,3 @@
</ds-truncatable>
<ng-content></ng-content>
</div>

View File

@@ -3,13 +3,11 @@ import { Observable } from 'rxjs';
import { SearchResult } from '../../search/search-result.model';
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
import { Bitstream } from '../../../core/shared/bitstream.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { Metadata } from '../../../core/shared/metadata.utils';
import { hasValue } from '../../empty.util';
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
import { TruncatableService } from '../../truncatable/truncatable.service';
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
@Component({
selector: 'ds-search-result-grid-element',
@@ -66,11 +64,4 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
private isCollapsed(): Observable<boolean> {
return this.truncatableService.isCollapsed(this.dso.id);
}
// TODO refactor to return RemoteData, and thumbnail template to deal with loading
getThumbnail(): Observable<Bitstream> {
return this.bitstreamDataService.getThumbnailFor(this.dso as any).pipe(
getFirstSucceededRemoteDataPayload()
);
}
}

View File

@@ -55,10 +55,7 @@ export class ClaimedApprovedSearchResultListElementComponent extends SearchResul
super.ngOnInit();
this.linkService.resolveLinks(this.dso,
followLink('workflowitem',
null,
true,
false,
true,
{ useCachedVersionIfAvailable: false },
followLink('item'),
followLink('submitter')
),

View File

@@ -56,10 +56,7 @@ export class ClaimedDeclinedSearchResultListElementComponent extends SearchResul
super.ngOnInit();
this.linkService.resolveLinks(this.dso,
followLink('workflowitem',
null,
true,
false,
true,
{ useCachedVersionIfAvailable: false },
followLink('item'),
followLink('submitter')
),

View File

@@ -50,7 +50,7 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle
*/
ngOnInit() {
super.ngOnInit();
this.linkService.resolveLinks(this.dso, followLink('workflowitem', null, true, true, true,
this.linkService.resolveLinks(this.dso, followLink('workflowitem', {},
followLink('item'), followLink('submitter')
), followLink('action'));
this.workflowitemRD$ = this.dso.workflowitem as Observable<RemoteData<WorkflowItem>>;

View File

@@ -60,7 +60,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen
*/
ngOnInit() {
super.ngOnInit();
this.linkService.resolveLinks(this.dso, followLink('workflowitem', null, true, true, true,
this.linkService.resolveLinks(this.dso, followLink('workflowitem', {},
followLink('item'), followLink('submitter')
), followLink('action'));
this.workflowitemRD$ = this.dso.workflowitem as Observable<RemoteData<WorkflowItem>>;

View File

@@ -1,5 +1,6 @@
import { FindListOptions } from '../../core/data/request.models';
import { HALResource } from '../../core/shared/hal-resource.model';
import { hasValue } from '../empty.util';
/**
* A class to send the retrieval of a {@link HALLink}
@@ -40,6 +41,12 @@ export class FollowLinkConfig<R extends HALResource> {
* Defaults to true
*/
reRequestOnStale? = true;
/**
* If this is false an error will be thrown if the link doesn't exist on the model it is used on
* Defaults to false
*/
isOptional? = false;
}
/**
@@ -57,23 +64,35 @@ export class FollowLinkConfig<R extends HALResource> {
* no valid cached version. Defaults
* @param reRequestOnStale: Whether or not the link should automatically be re-requested after the
* response becomes stale
* @param isOptional: Whether or not to fail if the link doesn't exist
* @param linksToFollow: a list of {@link FollowLinkConfig}s to
* use on the retrieved object.
*/
export const followLink = <R extends HALResource>(
linkName: keyof R['_links'],
findListOptions?: FindListOptions,
shouldEmbed = true,
useCachedVersionIfAvailable = true,
reRequestOnStale = true,
...linksToFollow: FollowLinkConfig<any>[]
): FollowLinkConfig<R> => {
return {
name: linkName,
{
findListOptions,
shouldEmbed,
useCachedVersionIfAvailable,
reRequestOnStale,
isOptional
}: {
findListOptions?: FindListOptions,
shouldEmbed?: boolean,
useCachedVersionIfAvailable?: boolean,
reRequestOnStale?: boolean,
isOptional?: boolean,
} = {},
...linksToFollow: FollowLinkConfig<any>[]
): FollowLinkConfig<R> => {
const followLinkConfig = {
name: linkName,
findListOptions: hasValue(findListOptions) ? findListOptions : new FindListOptions(),
shouldEmbed: hasValue(shouldEmbed) ? shouldEmbed : true,
useCachedVersionIfAvailable: hasValue(useCachedVersionIfAvailable) ? useCachedVersionIfAvailable : true,
reRequestOnStale: hasValue(reRequestOnStale) ? reRequestOnStale : true,
isOptional: hasValue(isOptional) ? isOptional : false,
linksToFollow
};
return followLinkConfig;
};

View File

@@ -78,6 +78,7 @@ describe('ThumbnailComponent', () => {
bundle: { href: 'bundle.url' },
format: { href: 'format.url' },
content: { href: 'content.url' },
thumbnail: undefined,
};
});
@@ -126,6 +127,7 @@ describe('ThumbnailComponent', () => {
bundle: { href: 'bundle.url' },
format: { href: 'format.url' },
content: { href: 'content.url' },
thumbnail: undefined,
};
thumbnail = createSuccessfulRemoteDataObject(bitstream);
});