Skip to content

Commit

Permalink
Merge pull request DSpace#1049 from atmire/w2p-77178_Support-embed-si…
Browse files Browse the repository at this point in the history
…zes-and-use-in-ComCol-tree

Use of embed sizes in the ComCol tree
  • Loading branch information
tdonohue authored Mar 16, 2021
2 parents d1ab193 + e033cdf commit 1115c58
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 33 deletions.
42 changes: 26 additions & 16 deletions src/app/community-list-page/community-list-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { CommunityListState } from './community-list.reducer';
import { getCommunityPageRoute } from '../+community-page/community-page-routing-paths';
import { getCollectionPageRoute } from '../+collection-page/collection-page-routing-paths';
import { getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../core/shared/operators';
import { followLink } from '../shared/utils/follow-link-config.model';

/**
* Each node in the tree is represented by a flatNode which contains info about the node itself and its position and
Expand Down Expand Up @@ -101,7 +102,7 @@ const communityListStateSelector = (state: AppState) => state.communityList;
const expandedNodesSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.expandedNodes);
const loadingNodeSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.loadingNode);

export const MAX_COMCOLS_PER_PAGE = 50;
export const MAX_COMCOLS_PER_PAGE = 20;

/**
* Service class for the community list, responsible for the creating of the flat list used by communityList dataSource
Expand All @@ -115,6 +116,10 @@ export class CommunityListService {
private store: Store<any>) {
}

private configOnePage: FindListOptions = Object.assign(new FindListOptions(), {
elementsPerPage: 1
});

saveCommunityListStateToStore(expandedNodes: FlatNode[], loadingNode: FlatNode): void {
this.store.dispatch(new CommunityListSaveAction(expandedNodes, loadingNode));
}
Expand Down Expand Up @@ -162,16 +167,19 @@ export class CommunityListService {
*/
private getTopCommunities(options: FindListOptions): Observable<PaginatedList<Community>> {
return this.communityDataService.findTop({
currentPage: options.currentPage,
elementsPerPage: MAX_COMCOLS_PER_PAGE,
sort: {
field: options.sort.field,
direction: options.sort.direction
}
}).pipe(
getFirstSucceededRemoteData(),
map((results) => results.payload),
);
currentPage: options.currentPage,
elementsPerPage: MAX_COMCOLS_PER_PAGE,
sort: {
field: options.sort.field,
direction: options.sort.direction
}
},
followLink('subcommunities', this.configOnePage, true, true),
followLink('collections', this.configOnePage, true, true))
.pipe(
getFirstSucceededRemoteData(),
map((results) => results.payload),
);
}

/**
Expand Down Expand Up @@ -231,9 +239,11 @@ export class CommunityListService {
let subcoms = [];
for (let i = 1; i <= currentCommunityPage; i++) {
const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, {
elementsPerPage: MAX_COMCOLS_PER_PAGE,
currentPage: i
})
elementsPerPage: MAX_COMCOLS_PER_PAGE,
currentPage: i
},
followLink('subcommunities', this.configOnePage, true, true),
followLink('collections', this.configOnePage, true, true))
.pipe(
getFirstCompletedRemoteData(),
switchMap((rd: RemoteData<PaginatedList<Community>>) => {
Expand Down Expand Up @@ -289,7 +299,7 @@ export class CommunityListService {
public getIsExpandable(community: Community): Observable<boolean> {
let hasSubcoms$: Observable<boolean>;
let hasColls$: Observable<boolean>;
hasSubcoms$ = this.communityDataService.findByParent(community.uuid, { elementsPerPage: 1 })
hasSubcoms$ = this.communityDataService.findByParent(community.uuid, this.configOnePage)
.pipe(
map((rd: RemoteData<PaginatedList<Community>>) => {
if (hasValue(rd) && hasValue(rd.payload)) {
Expand All @@ -300,7 +310,7 @@ export class CommunityListService {
}),
);

hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
hasColls$ = this.collectionDataService.findByParent(community.uuid, this.configOnePage)
.pipe(
map((rd: RemoteData<PaginatedList<Collection>>) => {
if (hasValue(rd) && hasValue(rd.payload)) {
Expand Down
5 changes: 3 additions & 2 deletions src/app/core/data/comcol-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { BitstreamDataService } from './bitstream-data.service';
import { NoContent } from '../shared/NoContent.model';
import { createFailedRemoteDataObject$ } from '../../shared/remote-data.utils';
import { URLCombiner } from '../url-combiner/url-combiner';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';

export abstract class ComColDataService<T extends Community | Collection> extends DataService<T> {
protected abstract cds: CommunityDataService;
Expand Down Expand Up @@ -66,11 +67,11 @@ export abstract class ComColDataService<T extends Community | Collection> extend

protected abstract getFindByParentHref(parentUUID: string): Observable<string>;

public findByParent(parentUUID: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
public findByParent(parentUUID: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
const href$ = this.getFindByParentHref(parentUUID).pipe(
map((href: string) => this.buildHrefFromFindOptions(href, options))
);
return this.findAllByHref(href$);
return this.findAllByHref(href$, options, true, true, ...linksToFollow);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/app/core/data/community-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { RemoteData } from './remote-data';
import { FindListOptions } from './request.models';
import { RequestService } from './request.service';
import { BitstreamDataService } from './bitstream-data.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';

@Injectable()
@dataService(COMMUNITY)
Expand All @@ -45,9 +46,9 @@ export class CommunityDataService extends ComColDataService<Community> {
return this.halService.getEndpoint(this.linkPath);
}

findTop(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
findTop(options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<Community>[]): Observable<RemoteData<PaginatedList<Community>>> {
const hrefObs = this.getFindAllHref(options, this.topLinkPath);
return this.findAllByHref(hrefObs, undefined);
return this.findAllByHref(hrefObs, undefined, true, true, ...linksToFollow);
}

protected getFindByParentHref(parentUUID: string): Observable<string> {
Expand Down
32 changes: 32 additions & 0 deletions src/app/core/data/data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ describe('DataService', () => {
});
});

it('should include single linksToFollow as embed and its size', () => {
const expected = `${endpoint}?embed.size=bundles=5&embed=bundles`;
const config: FindListOptions = Object.assign(new FindListOptions(), {
elementsPerPage: 5
});
(service as any).getFindAllHref({}, null, followLink('bundles', config, true, true, true)).subscribe((value) => {
expect(value).toBe(expected);
});
});

it('should include multiple linksToFollow as embed', () => {
const expected = `${endpoint}?embed=bundles&embed=owningCollection&embed=templateItemOf`;

Expand All @@ -201,6 +211,18 @@ describe('DataService', () => {
});
});

it('should include multiple linksToFollow as embed and its sizes if given', () => {
const expected = `${endpoint}?embed=bundles&embed.size=owningCollection=2&embed=owningCollection&embed=templateItemOf`;

const config: FindListOptions = Object.assign(new FindListOptions(), {
elementsPerPage: 2
});

(service as any).getFindAllHref({}, null, followLink('bundles'), followLink('owningCollection', config, true, true, true), followLink('templateItemOf')).subscribe((value) => {
expect(value).toBe(expected);
});
});

it('should not include linksToFollow with shouldEmbed = false', () => {
const expected = `${endpoint}?embed=templateItemOf`;

Expand All @@ -216,6 +238,16 @@ describe('DataService', () => {
expect(value).toBe(expected);
});
});

it('should include nested linksToFollow 2lvl and nested embed\'s size', () => {
const expected = `${endpoint}?embed.size=owningCollection/itemtemplate=4&embed=owningCollection/itemtemplate`;
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) => {
expect(value).toBe(expected);
});
});
});

describe('getIDHref', () => {
Expand Down
36 changes: 27 additions & 9 deletions src/app/core/data/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,19 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
*/
protected addEmbedParams(href: string, args: string[], ...linksToFollow: FollowLinkConfig<T>[]) {
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
if (linkToFollow !== undefined && linkToFollow.shouldEmbed) {
if (hasValue(linkToFollow) && linkToFollow.shouldEmbed) {
const embedString = 'embed=' + String(linkToFollow.name);
const embedWithNestedString = this.addNestedEmbeds(embedString, ...linkToFollow.linksToFollow);
args = this.addHrefArg(href, args, embedWithNestedString);
// Add the embeds size if given in the FollowLinkConfig.FindListOptions
if (hasValue(linkToFollow.findListOptions) && hasValue(linkToFollow.findListOptions.elementsPerPage)) {
args = this.addHrefArg(href, args,
'embed.size=' + String(linkToFollow.name) + '=' + linkToFollow.findListOptions.elementsPerPage);
}
// Adds the nested embeds and their size if given
if (isNotEmpty(linkToFollow.linksToFollow)) {
args = this.addNestedEmbeds(embedString, href, args, ...linkToFollow.linksToFollow);
} else {
args = this.addHrefArg(href, args, embedString);
}
}
});
return args;
Expand All @@ -243,21 +252,30 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
}

/**
* Add the nested followLinks to the embed param, recursively, separated by a /
* Add the nested followLinks to the embed param, separated by a /, and their sizes, recursively
* @param embedString embedString so far (recursive)
* @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
*/
protected addNestedEmbeds(embedString: string, ...linksToFollow: FollowLinkConfig<T>[]): string {
protected addNestedEmbeds(embedString: string, href: string, args: string[], ...linksToFollow: FollowLinkConfig<T>[]): string[] {
let nestEmbed = embedString;
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
if (linkToFollow !== undefined && linkToFollow.shouldEmbed) {
if (hasValue(linkToFollow) && linkToFollow.shouldEmbed) {
nestEmbed = nestEmbed + '/' + String(linkToFollow.name);
if (linkToFollow.linksToFollow !== undefined) {
nestEmbed = this.addNestedEmbeds(nestEmbed, ...linkToFollow.linksToFollow);
// Add the nested embeds size if given in the FollowLinkConfig.FindListOptions
if (hasValue(linkToFollow.findListOptions) && hasValue(linkToFollow.findListOptions.elementsPerPage)) {
const nestedEmbedSize = 'embed.size=' + nestEmbed.split('=')[1] + '=' + linkToFollow.findListOptions.elementsPerPage;
args = this.addHrefArg(href, args, nestedEmbedSize);
}
if (hasValue(linkToFollow.linksToFollow) && isNotEmpty(linkToFollow.linksToFollow)) {
args = this.addNestedEmbeds(nestEmbed, href, args, ...linkToFollow.linksToFollow);
} else {
args = this.addHrefArg(href, args, nestEmbed);
}
}
});
return nestEmbed;
return args;
}

/**
Expand Down
13 changes: 11 additions & 2 deletions src/app/core/data/dspace-rest-response-parsing.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { Injectable } from '@angular/core';
import { ResponseParsingService } from './parsing.service';
import { ParsedResponse } from '../cache/response.models';
import { RestRequestMethod } from './rest-request-method';
import { getUrlWithoutEmbedParams } from '../index/index.selectors';
import { getUrlWithoutEmbedParams, getEmbedSizeParams } from '../index/index.selectors';
import { URLCombiner } from '../url-combiner/url-combiner';

/* tslint:disable:max-classes-per-file */

Expand Down Expand Up @@ -86,6 +87,8 @@ export class DspaceRestResponseParsingService implements ResponseParsingService
}

public process<ObjectDomain>(data: any, request: RestRequest, alternativeURL?: string): any {
const embedSizeParams = getEmbedSizeParams(request.href);

if (isNotEmpty(data)) {
if (hasNoValue(data) || (typeof data !== 'object')) {
return data;
Expand All @@ -100,7 +103,13 @@ export class DspaceRestResponseParsingService implements ResponseParsingService
.keys(data._embedded)
.filter((property) => data._embedded.hasOwnProperty(property))
.forEach((property) => {
this.process<ObjectDomain>(data._embedded[property], request, data._links[property].href);
let embedAltUrl = data._links[property].href;
const match = embedSizeParams
.find((param: { name: string, size: number }) => param.name === property);
if (hasValue(match)) {
embedAltUrl = new URLCombiner(embedAltUrl, `?size=${match.size}`).toString();
}
this.process<ObjectDomain>(data._embedded[property], request, embedAltUrl);
});
}

Expand Down
37 changes: 36 additions & 1 deletion src/app/core/index/index.selectors.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getUrlWithoutEmbedParams } from './index.selectors';
import { getEmbedSizeParams, getUrlWithoutEmbedParams } from './index.selectors';


describe(`index selectors`, () => {

Expand Down Expand Up @@ -29,4 +30,38 @@ describe(`index selectors`, () => {

});

describe(`getEmbedSizeParams`, () => {

it(`url with single embed size param => should return list with ['subcommunities' - size]`, () => {
const source = 'https://rest.api/core/communities/search/top?page=0&size=50&sort=dc.title,ASC&embed.size=subcommunities=5&embed=subcommunities';
const result = getEmbedSizeParams(source);
expect(result).toHaveSize(1);
expect(result[0]).toEqual({name: 'subcommunities', size: 5});
});

it(`url with multiple embed size param => should return list with {name, size}`, () => {
const source = 'https://rest.api/core/communities/search/top?page=0&size=50&sort=dc.title,ASC&embed.size=subcommunities=5&embed=subcommunities&embed.size=collections=1&embed=collections';
const result = getEmbedSizeParams(source);
expect(result).toHaveSize(2);
expect(result[0]).toEqual({name: 'subcommunities', size: 5});
expect(result[1]).toEqual({name: 'collections', size: 1});
});

it(`url without params => should return empty list`, () => {
const source = 'https://rest.api/core/collections/uuid';
expect(getEmbedSizeParams(source)).toHaveSize(0);
});

it(`url without embed size params => should return empty list`, () => {
const source = 'https://rest.api/core/collections/uuid?page=0&size=50';
expect(getEmbedSizeParams(source)).toHaveSize(0);
});

it(`undefined or null url => should return empty list`, () => {
expect(getEmbedSizeParams(undefined)).toHaveSize(0);
expect(getEmbedSizeParams(null)).toHaveSize(0);
});

});

});
23 changes: 22 additions & 1 deletion src/app/core/index/index.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const getUrlWithoutEmbedParams = (url: string): string => {
if (isNotEmpty(parsed.query)) {
const parts = parsed.query.split(/[?|&]/)
.filter((part: string) => isNotEmpty(part))
.filter((part: string) => !part.startsWith('embed='));
.filter((part: string) => !(part.startsWith('embed=') || part.startsWith('embed.size=')));
let args = '';
if (isNotEmpty(parts)) {
args = `?${parts.join('&')}`;
Expand All @@ -37,6 +37,27 @@ export const getUrlWithoutEmbedParams = (url: string): string => {
return url;
};

/**
* Parse the embed size params from a url
* @param url The url to parse
*/
export const getEmbedSizeParams = (url: string): { name: string, size: number }[] => {
if (isNotEmpty(url)) {
const parsed = parse(url);
if (isNotEmpty(parsed.query)) {
return parsed.query.split(/[?|&]/)
.filter((part: string) => isNotEmpty(part))
.map((part: string) => part.match(/^embed.size=([^=]+)=(\d+)$/))
.filter((matches: RegExpMatchArray) => hasValue(matches) && hasValue(matches[1]) && hasValue(matches[2]))
.map((matches: RegExpMatchArray) => {
return { name: matches[1], size: Number(matches[2]) };
});
}
}

return [];
};

/**
* Return the MetaIndexState based on the CoreSate
*
Expand Down

0 comments on commit 1115c58

Please sign in to comment.