diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index 177d7b5683..fcb7929fad 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -177,14 +177,14 @@ describe('DataService', () => { }); }); - it('should not include linksToFollow with forwardToRest = false', () => { + it('should not include linksToFollow with shootEmbed = false', () => { const mockFollowLinkConfig: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { name: 'bundles' as any, - forwardToRest: false, + shootEmbed: false, }); const mockFollowLinkConfig2: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { name: 'owningCollection' as any, - forwardToRest: false, + shootEmbed: false, }); const mockFollowLinkConfig3: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { name: 'templateItemOf' as any, @@ -215,6 +215,75 @@ describe('DataService', () => { }); }); }); + + describe('getIDHref', () => { + const endpointMock = 'https://dspace7-internal.atmire.com/server/api/core/items'; + const resourceIdMock = '003c99b4-d4fe-44b0-a945-e12182a7ca89'; + + it('should return endpoint', () => { + const result = (service as any).getIDHref(endpointMock, resourceIdMock); + expect(result).toEqual(endpointMock + '/' + resourceIdMock); + }); + + it('should include single linksToFollow as embed', () => { + const mockFollowLinkConfig: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'bundles' as any, + }); + const expected = `${endpointMock}/${resourceIdMock}?embed=bundles`; + const result = (service as any).getIDHref(endpointMock, resourceIdMock, mockFollowLinkConfig); + expect(result).toEqual(expected); + }); + + it('should include multiple linksToFollow as embed', () => { + const mockFollowLinkConfig: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'bundles' as any, + }); + const mockFollowLinkConfig2: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'owningCollection' as any, + }); + const mockFollowLinkConfig3: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'templateItemOf' as any, + }); + const expected = `${endpointMock}/${resourceIdMock}?embed=bundles&embed=owningCollection&embed=templateItemOf`; + const result = (service as any).getIDHref(endpointMock, resourceIdMock, mockFollowLinkConfig, mockFollowLinkConfig2, mockFollowLinkConfig3); + expect(result).toEqual(expected); + }); + + it('should not include linksToFollow with shootEmbed = false', () => { + const mockFollowLinkConfig: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'bundles' as any, + shootEmbed: false, + }); + const mockFollowLinkConfig2: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'owningCollection' as any, + shootEmbed: false, + }); + const mockFollowLinkConfig3: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'templateItemOf' as any, + }); + const expected = `${endpointMock}/${resourceIdMock}?embed=templateItemOf`; + const result = (service as any).getIDHref(endpointMock, resourceIdMock, mockFollowLinkConfig, mockFollowLinkConfig2, mockFollowLinkConfig3); + expect(result).toEqual(expected); + }); + + it('should include nested linksToFollow 3lvl', () => { + const mockFollowLinkConfig3: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'relationships' as any, + }); + const mockFollowLinkConfig2: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'itemtemplate' as any, + linksToFollow: mockFollowLinkConfig3, + }); + const mockFollowLinkConfig: FollowLinkConfig = Object.assign(new FollowLinkConfig(), { + name: 'owningCollection' as any, + linksToFollow: mockFollowLinkConfig2, + }); + const expected = `${endpointMock}/${resourceIdMock}?embed=owningCollection/itemtemplate/relationships`; + const result = (service as any).getIDHref(endpointMock, resourceIdMock, mockFollowLinkConfig); + expect(result).toEqual(expected); + }); + }); + describe('patch', () => { let operations; let selfLink; diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 5bd5db0af7..ee48ca1a83 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -129,7 +129,6 @@ export abstract class DataService { * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved */ protected buildHrefFromFindOptions(href: string, options: FindListOptions, extraArgs: string[] = [], ...linksToFollow: Array>): string { - let args = [...extraArgs]; if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { @@ -145,7 +144,7 @@ export abstract class DataService { if (hasValue(options.startsWith)) { args = [...args, `startsWith=${options.startsWith}`]; } - args = this.addEmbedParams(args, linksToFollow); + args = this.addEmbedParams(args, ...linksToFollow); if (isNotEmpty(args)) { return new URLCombiner(href, `?${args.join('&')}`).toString(); } else { @@ -156,15 +155,15 @@ export abstract class DataService { /** * Adds the embed options to the link for the request * @param args params for the query string - * @param linksToFollow links we want to embed in query string if forwardToRest is true + * @param linksToFollow links we want to embed in query string if shootEmbed is true */ - protected addEmbedParams(args: any, linksToFollow: Array>) { + protected addEmbedParams(args: any, ...linksToFollow: Array>) { if (linksToFollow !== undefined) { - linksToFollow.forEach((linkToFollow: FollowLinkConfig) => { + [...linksToFollow].forEach((linkToFollow: FollowLinkConfig) => { console.log('linksToFollow', linksToFollow) - if (linkToFollow.forwardToRest) { + if (linkToFollow.shootEmbed) { const embedString = 'embed=' + String(linkToFollow.name); - const embedWithNestedString = this.addNestedEmbeds(embedString, linkToFollow.linksToFollow); + const embedWithNestedString = this.addNestedEmbeds(embedString, ...linkToFollow.linksToFollow); args = [...args, embedWithNestedString]; } }); @@ -174,17 +173,18 @@ export abstract class DataService { /** * Add the nested followLinks to the embed param, recursively, separated by a / - * @param embedString - * @param linksToFollow + * @param embedString embedString so far (recursive) + * @param linksToFollow links we want to embed in query string if shootEmbed is true */ - protected addNestedEmbeds(embedString: string, linksToFollow: Array>): string { + protected addNestedEmbeds(embedString: string, ...linksToFollow: Array>): string { let nestEmbed = embedString; if (linksToFollow !== undefined) { - linksToFollow.forEach((linkToFollow: FollowLinkConfig) => { - if (linkToFollow.forwardToRest) { + console.log('linksToFollow addNestedEmbed', linksToFollow); + [...linksToFollow].forEach((linkToFollow: FollowLinkConfig) => { + if (linkToFollow.shootEmbed) { nestEmbed = nestEmbed + '/' + String(linkToFollow.name); if (linkToFollow.linksToFollow !== undefined) { - nestEmbed = this.addNestedEmbeds(nestEmbed, linkToFollow.linksToFollow); + nestEmbed = this.addNestedEmbeds(nestEmbed, ...linkToFollow.linksToFollow); } } }) @@ -227,12 +227,13 @@ export abstract class DataService { } /** - * Create the HREF for a specific object based on its identifier + * Create the HREF for a specific object based on its identifier; with possible embed query params based on linksToFollow * @param endpoint The base endpoint for the type of object * @param resourceID The identifier for the object + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved */ - getIDHref(endpoint, resourceID): string { - return `${endpoint}/${resourceID}`; + getIDHref(endpoint, resourceID, ...linksToFollow: Array>): string { + return this.buildHrefFromFindOptions(endpoint + '/' + resourceID, {}, [], ...linksToFollow); } /** @@ -242,9 +243,9 @@ export abstract class DataService { * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved */ findById(id: string, ...linksToFollow: Array>): Observable> { - + console.log('findById'); const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id)))); + map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id), ...linksToFollow))); hrefObs.pipe( find((href: string) => hasValue(href))) diff --git a/src/app/core/data/dso-redirect-data.service.ts b/src/app/core/data/dso-redirect-data.service.ts index 232fde65d0..87259a4279 100644 --- a/src/app/core/data/dso-redirect-data.service.ts +++ b/src/app/core/data/dso-redirect-data.service.ts @@ -6,6 +6,7 @@ import { Observable } from 'rxjs'; import { take, tap } 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 { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; import { CoreState } from '../core.reducers'; @@ -45,10 +46,11 @@ export class DsoRedirectDataService extends DataService { } } - getIDHref(endpoint, resourceID): string { + getIDHref(endpoint, resourceID, ...linksToFollow: Array>): string { // Supporting both identifier (pid) and uuid (dso) endpoints - return endpoint.replace(/\{\?id\}/, `?id=${resourceID}`) - .replace(/\{\?uuid\}/, `?uuid=${resourceID}`); + return this.buildHrefFromFindOptions( endpoint.replace(/\{\?id\}/, `?id=${resourceID}`) + .replace(/\{\?uuid\}/, `?uuid=${resourceID}`), + {}, [], ...linksToFollow); } findByIdAndIDType(id: string, identifierType = IdentifierType.UUID): Observable> { diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 38e9f8d888..61cc98281e 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -3,6 +3,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { dataService } from '../cache/builders/build-decorators'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -31,8 +32,9 @@ class DataServiceImpl extends DataService { super(); } - getIDHref(endpoint, resourceID): string { - return endpoint.replace(/\{\?uuid\}/, `?uuid=${resourceID}`); + getIDHref(endpoint, resourceID, ...linksToFollow: Array>): string { + return this.buildHrefFromFindOptions( endpoint.replace(/\{\?uuid\}/, `?uuid=${resourceID}`), + {}, [], ...linksToFollow); } } diff --git a/src/app/shared/utils/follow-link-config.model.ts b/src/app/shared/utils/follow-link-config.model.ts index ea295bf78f..49866dc641 100644 --- a/src/app/shared/utils/follow-link-config.model.ts +++ b/src/app/shared/utils/follow-link-config.model.ts @@ -27,7 +27,7 @@ export class FollowLinkConfig { /** * Forward to rest which links we're following, so these can already be embedded */ - forwardToRest? = true; + shootEmbed? = true; } /** @@ -41,19 +41,19 @@ export class FollowLinkConfig { * in a certain way * @param linksToFollow: a list of {@link FollowLinkConfig}s to * use on the retrieved object. - * @param forwardToRest: boolean to check whether to forward info on followLinks to rest, + * @param shootEmbed: boolean to check whether to forward info on followLinks to rest, * so these can be embedded, default true */ export const followLink = ( linkName: keyof R['_links'], findListOptions?: FindListOptions, - forwardToRest = true, + shootEmbed = true, ...linksToFollow: Array> ): FollowLinkConfig => { return { name: linkName, findListOptions, - forwardToRest, + shootEmbed: shootEmbed, linksToFollow } };