Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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