1
0

refactor followlinks to use a single object for all params and add an isOptional param. Also add support for embedding links to search service

This commit is contained in:
Art Lowel
2021-06-28 12:28:59 +02:00
parent 7714ef47c8
commit 0d5fc8a1c0
22 changed files with 170 additions and 64 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

@@ -79,7 +79,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

@@ -323,7 +323,7 @@ export class EditRelationshipListComponent implements OnInit {
private getItemRelationships() {
this.linkService.resolveLink(this.item,
followLink('relationships', undefined, true, true, true,
followLink('relationships', {},
followLink('relationshipType'),
followLink('leftItem'),
followLink('rightItem'),

View File

@@ -15,13 +15,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('bundles', {}, followLink('bitstreams')),
followLink('relationships'),
followLink('version', undefined, true, true, true, followLink('versionhistory')),
followLink('version', {}, followLink('versionhistory')),
];
/**

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',

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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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;
};