Skip to content

Commit 327a5bd

Browse files
alisaismailatiatarix83
authored andcommitted
Merged in task/dspace-cris-2023_02_x/DSC-1988 (pull request DSpace#2458)
[DSC-1988] Limit the number of items requested by search manager Approved-by: Giuseppe Digilio
2 parents 29a111a + bf28fa3 commit 327a5bd

File tree

15 files changed

+178
-31
lines changed

15 files changed

+178
-31
lines changed

config/config.example.yml

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -327,15 +327,6 @@ item:
327327
# The maximum number of values for repeatable metadata to show in the full item
328328
metadataLimit: 20
329329

330-
# When the search results are retrieved, for each item type the metadata with a valid authority value are inspected.
331-
# Referenced items will be fetched with a find all by id strategy to avoid individual rest requests
332-
# to efficiently display the search results.
333-
followAuthorityMetadata:
334-
- type: Publication
335-
metadata: dc.contributor.author
336-
- type: Product
337-
metadata: dc.contributor.author
338-
339330
# Collection Page Config
340331
collection:
341332
edit:
@@ -517,3 +508,19 @@ addToAnyPlugin:
517508
title: DSpace CRIS 7 demo
518509
# The link to be shown in the shared post, if different from document.location.origin (optional)
519510
# link: https://dspacecris7.4science.cloud/
511+
512+
# When the search results are retrieved, for each item type the metadata with a valid authority value are inspected.
513+
# Referenced items will be fetched with a find all by id strategy to avoid individual rest requests
514+
# to efficiently display the search results.
515+
followAuthorityMetadata:
516+
- type: Publication
517+
metadata: dc.contributor.author
518+
- type: Product
519+
metadata: dc.contributor.author
520+
521+
# The maximum number of item to process when following authority metadata values.
522+
followAuthorityMaxItemLimit: 100
523+
524+
# The maximum number of metadata values to process for each metadata key
525+
# when following authority metadata values.
526+
followAuthorityMetadataValuesLimit: 5

src/app/core/browse/search-manager.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ export class SearchManager {
113113
})
114114
.filter((item) => hasValue(item));
115115

116-
const uuidList = this.extractUUID(items, environment.followAuthorityMetadata);
116+
const uuidList = this.extractUUID(items, environment.followAuthorityMetadata, environment.followAuthorityMaxItemLimit);
117+
117118
return uuidList.length > 0 ? this.itemService.findAllById(uuidList).pipe(
118119
getFirstCompletedRemoteData(),
119120
map(data => {
@@ -126,27 +127,31 @@ export class SearchManager {
126127
) : of(null);
127128
}
128129

129-
protected extractUUID(items: Item[], metadataToFollow: FollowAuthorityMetadata[]): string[] {
130+
protected extractUUID(items: Item[], metadataToFollow: FollowAuthorityMetadata[], numberOfElementsToReturn?: number): string[] {
130131
const uuidMap = {};
131132

132133
items.forEach((item) => {
133134
metadataToFollow.forEach((followMetadata: FollowAuthorityMetadata) => {
134135
if (item.entityType === followMetadata.type) {
135136
if (isArray(followMetadata.metadata)) {
136-
followMetadata.metadata.forEach((metadata) => {
137-
Metadata.all(item.metadata, metadata)
137+
followMetadata.metadata.forEach((metadata) => {
138+
Metadata.all(item.metadata, metadata, null, environment.followAuthorityMetadataValuesLimit)
138139
.filter((metadataValue: MetadataValue) => Metadata.hasValidItemAuthority(metadataValue.authority))
139140
.forEach((metadataValue: MetadataValue) => uuidMap[metadataValue.authority] = metadataValue);
140141
});
141142
} else {
142-
Metadata.all(item.metadata, followMetadata.metadata)
143+
Metadata.all(item.metadata, followMetadata.metadata, null, environment.followAuthorityMetadataValuesLimit)
143144
.filter((metadataValue: MetadataValue) => Metadata.hasValidItemAuthority(metadataValue.authority))
144145
.forEach((metadataValue: MetadataValue) => uuidMap[metadataValue.authority] = metadataValue);
145146
}
146147
}
147148
});
148149
});
149150

151+
if (hasValue(numberOfElementsToReturn) && numberOfElementsToReturn > 0) {
152+
return Object.keys(uuidMap).slice(0, numberOfElementsToReturn);
153+
}
154+
150155
return Object.keys(uuidMap);
151156
}
152157
}

src/app/core/browse/search.manager.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ describe('SearchManager', () => {
182182
const uuidList = (service as any).extractUUID([firstPublication, firstPublication], [{type: 'Publication', metadata: ['dc.contributor.author']}]);
183183
expect(uuidList).toEqual([validAuthority]);
184184
});
185+
186+
it('should limit the number of extracted uuids', () => {
187+
const uuidList = (service as any).extractUUID([firstPublication, secondPublication, invalidAuthorityPublication], [{type: 'Publication', metadata: ['dc.contributor.author']}], 2);
188+
expect(uuidList.length).toBe(2);
189+
});
185190
});
186191

187192
});

src/app/core/shared/dspace-object.model.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
115115
return Metadata.all(this.metadata, keyOrKeys, valueFilter);
116116
}
117117

118+
/**
119+
* Gets all matching metadata in this DSpaceObject, up to a limit.
120+
*
121+
* @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported; see [[Metadata]].
122+
* @param {number} limit The maximum number of results to return.
123+
* @param {MetadataValueFilter} valueFilter The value filter to use. If unspecified, no filtering will be done.
124+
* @returns {MetadataValue[]} the matching values or an empty array.
125+
*/
126+
limitedMetadata(keyOrKeys: string | string[], limit: number, valueFilter?: MetadataValueFilter): MetadataValue[] {
127+
return Metadata.all(this.metadata, keyOrKeys, valueFilter, limit);
128+
}
129+
118130
/**
119131
* Like [[allMetadata]], but only returns string values.
120132
*

src/app/core/shared/metadata.utils.spec.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,20 @@ const multiViewModelList = [
4444
{ key: 'foo', ...bar, order: 0 }
4545
];
4646

47-
const testMethod = (fn, resultKind, mapOrMaps, keyOrKeys, expected, filter?) => {
47+
const testMethod = (fn, resultKind, mapOrMaps, keyOrKeys, expected, filter?, limit?: number) => {
4848
const keys = keyOrKeys instanceof Array ? keyOrKeys : [keyOrKeys];
4949
describe('and key' + (keys.length === 1 ? (' ' + keys[0]) : ('s ' + JSON.stringify(keys)))
5050
+ ' with ' + (isUndefined(filter) ? 'no filter' : 'filter ' + JSON.stringify(filter)), () => {
51-
const result = fn(mapOrMaps, keys, filter);
51+
const result = fn(mapOrMaps, keys, filter, limit);
5252
let shouldReturn;
5353
if (resultKind === 'boolean') {
5454
shouldReturn = expected;
5555
} else if (isUndefined(expected)) {
5656
shouldReturn = 'undefined';
5757
} else if (expected instanceof Array) {
5858
shouldReturn = 'an array with ' + expected.length + ' ' + (expected.length > 1 ? 'ordered ' : '')
59-
+ resultKind + (expected.length !== 1 ? 's' : '');
59+
+ resultKind + (expected.length !== 1 ? 's' : '')
60+
+ (isUndefined(limit) ? '' : ' (limited to ' + limit + ')');
6061
} else {
6162
shouldReturn = 'a ' + resultKind;
6263
}
@@ -297,4 +298,12 @@ describe('Metadata', () => {
297298

298299
});
299300

301+
describe('all method with limit', () => {
302+
const testAllWithLimit = (mapOrMaps, keyOrKeys, expected, limit) =>
303+
testMethod(Metadata.all, 'value', mapOrMaps, keyOrKeys, expected, undefined, limit);
304+
305+
describe('with multiMap and limit', () => {
306+
testAllWithLimit(multiMap, 'dc.title', [dcTitle1], 1);
307+
});
308+
});
300309
});

src/app/core/shared/metadata.utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ export class Metadata {
3737
* checked in order, and only values from the first with at least one match will be returned.
3838
* @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported; see above.
3939
* @param {MetadataValueFilter} filter The value filter to use. If unspecified, no filtering will be done.
40+
* @param {number} limit The maximum number of values to return. If unspecified, all matching values will be returned.
4041
* @returns {MetadataValue[]} the matching values or an empty array.
4142
*/
4243
public static all(mapOrMaps: MetadataMapInterface | MetadataMapInterface[], keyOrKeys: string | string[],
43-
filter?: MetadataValueFilter): MetadataValue[] {
44+
filter?: MetadataValueFilter, limit?: number): MetadataValue[] {
4445
const mdMaps: MetadataMapInterface[] = mapOrMaps instanceof Array ? mapOrMaps : [mapOrMaps];
4546
const matches: MetadataValue[] = [];
4647
for (const mdMap of mdMaps) {
@@ -50,6 +51,9 @@ export class Metadata {
5051
for (const candidate of candidates) {
5152
if (Metadata.valueMatches(candidate as MetadataValue, filter)) {
5253
matches.push(candidate as MetadataValue);
54+
if (hasValue(limit) && matches.length >= limit) {
55+
return matches;
56+
}
5357
}
5458
}
5559
}

src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.html

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,20 @@ <h3 [innerHTML]="dsoTitle" [ngClass]="{'lead': true,'text-muted': !item.firstMet
2222
<span class="item-list-date" [innerHTML]="item.firstMetadataValue('dc.date.issued') || ('mydspace.results.no-date' | translate)"></span>)
2323
<span *ngIf="item.hasMetadata(authorMetadata);" class="item-list-authors">
2424
<span *ngIf="item.allMetadataValues(authorMetadata).length === 0">{{'mydspace.results.no-authors' | translate}}</span>
25-
<span *ngFor="let author of item.allMetadata(authorMetadata); let i=index; let last=last;">
26-
<ds-metadata-link-view *ngIf="!!item && !!author" [item]="item" [metadataName]="authorMetadata"
27-
[metadata]="author"></ds-metadata-link-view><span
28-
*ngIf="!last">; </span>
29-
</span>
25+
<span *ngIf="!(isCollapsed$ | async)">
26+
<span *ngFor="let author of item.allMetadata(authorMetadata); let i=index; let last=last;">
27+
<ds-metadata-link-view *ngIf="!!item && !!author" [item]="item" [metadataName]="authorMetadata"
28+
[metadata]="author"></ds-metadata-link-view><span
29+
*ngIf="!last">; </span>
30+
</span>
31+
</span>
32+
<span *ngIf="isCollapsed$ | async">
33+
<span *ngFor="let author of item.limitedMetadata(authorMetadata, authorMetadataLimit); let i=index; let last=last;">
34+
<ds-metadata-link-view *ngIf="!!item && !!author" [item]="item" [metadataName]="authorMetadata"
35+
[metadata]="author"></ds-metadata-link-view><span
36+
*ngIf="!last">; </span>
37+
</span>
38+
</span>
3039
</span>
3140
</ds-truncatable-part>
3241
</span>

src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.spec.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock';
1212
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
1313
import { VarDirective } from '../../../utils/var.directive';
1414
import { APP_CONFIG } from '../../../../../config/app-config.interface';
15+
import { TruncatableService } from '../../../truncatable/truncatable.service';
1516

1617
let component: ItemListPreviewComponent;
1718
let fixture: ComponentFixture<ItemListPreviewComponent>;
@@ -80,6 +81,10 @@ const enviromentNoThumbs = {
8081
}
8182
};
8283

84+
const truncatableServiceStub: any = {
85+
isCollapsed: (id: number) => observableOf(true),
86+
};
87+
8388
describe('ItemListPreviewComponent', () => {
8489
beforeEach(waitForAsync(() => {
8590
TestBed.configureTestingModule({
@@ -95,7 +100,8 @@ describe('ItemListPreviewComponent', () => {
95100
declarations: [ItemListPreviewComponent, TruncatePipe, VarDirective],
96101
providers: [
97102
{ provide: 'objectElementProvider', useValue: { mockItemWithAuthorAndDate }},
98-
{ provide: APP_CONFIG, useValue: environmentUseThumbs }
103+
{ provide: APP_CONFIG, useValue: environmentUseThumbs },
104+
{ provide: TruncatableService, useValue: truncatableServiceStub },
99105
],
100106

101107
schemas: [NO_ERRORS_SCHEMA]
@@ -198,6 +204,33 @@ describe('ItemListPreviewComponent', () => {
198204
expect(entityField).toBeNull();
199205
});
200206
});
207+
208+
209+
describe('When truncatable section is collapsed', () => {
210+
beforeEach(() => {
211+
component.isCollapsed$ = observableOf(true);
212+
component.item = mockItemWithAuthorAndDate;
213+
fixture.detectChanges();
214+
});
215+
216+
it('should show limitedMetadata', () => {
217+
const authorElements = fixture.debugElement.queryAll(By.css('span.item-list-authors ds-metadata-link-view'));
218+
expect(authorElements.length).toBe(mockItemWithAuthorAndDate.limitedMetadata(component.authorMetadata, component.authorMetadataLimit).length);
219+
});
220+
});
221+
222+
describe('When truncatable section is expanded', () => {
223+
beforeEach(() => {
224+
component.isCollapsed$ = observableOf(false);
225+
component.item = mockItemWithAuthorAndDate;
226+
fixture.detectChanges();
227+
});
228+
229+
it('should show allMetadata', () => {
230+
const authorElements = fixture.debugElement.queryAll(By.css('span.item-list-authors ds-metadata-link-view'));
231+
expect(authorElements.length).toBe(mockItemWithAuthorAndDate.allMetadata(component.authorMetadata).length);
232+
});
233+
});
201234
});
202235

203236
describe('ItemListPreviewComponent', () => {
@@ -215,7 +248,8 @@ describe('ItemListPreviewComponent', () => {
215248
declarations: [ItemListPreviewComponent, TruncatePipe],
216249
providers: [
217250
{provide: 'objectElementProvider', useValue: {mockItemWithAuthorAndDate}},
218-
{provide: APP_CONFIG, useValue: enviromentNoThumbs}
251+
{provide: APP_CONFIG, useValue: enviromentNoThumbs},
252+
{ provide: TruncatableService, useValue: truncatableServiceStub },
219253
],
220254

221255
schemas: [NO_ERRORS_SCHEMA]

src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
} from '../../../../submission/sections/detect-duplicate/models/duplicate-detail-metadata.model';
1313
import { parseISO, differenceInDays, differenceInMilliseconds } from 'date-fns';
1414
import { environment } from '../../../../../environments/environment';
15+
import { TruncatableService } from '../../../truncatable/truncatable.service';
16+
import { Observable } from 'rxjs';
1517

1618
/**
1719
* This component show metadata for the given item object in the list view.
@@ -78,9 +80,14 @@ export class ItemListPreviewComponent implements OnInit {
7880

7981
authorMetadata = environment.searchResult.authorMetadata;
8082

83+
authorMetadataLimit = environment.followAuthorityMetadataValuesLimit;
84+
85+
isCollapsed$: Observable<boolean>;
86+
8187
constructor(
8288
@Inject(APP_CONFIG) protected appConfig: AppConfig,
8389
public dsoNameService: DSONameService,
90+
public truncateService: TruncatableService
8491
) {
8592
}
8693

@@ -96,6 +103,6 @@ export class ItemListPreviewComponent implements OnInit {
96103
ngOnInit(): void {
97104
this.showThumbnails = this.showThumbnails ?? this.appConfig.browseBy.showThumbnails;
98105
this.dsoTitle = this.dsoNameService.getHitHighlights(this.object, this.item);
106+
this.isCollapsed$ = this.truncateService.isCollapsed(this.item.uuid);
99107
}
100-
101108
}

src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,18 @@
3939
[innerHTML]="firstMetadataValue('dc.date.issued')"></span>)
4040
</ng-container>
4141
<span *ngIf="dso.allMetadataValues(authorMetadata).length > 0" class="item-list-authors">
42-
<span *ngFor="let author of dso.allMetadata(authorMetadata); let i=index; let last=last;">
43-
<ds-metadata-link-view [item]="dso" [metadata]="author" [metadataName]="authorMetadata"></ds-metadata-link-view><span *ngIf="!last">; </span>
44-
</span>
42+
<ng-container *ngVar="(isCollapsed() | async) as isCollapsed">
43+
<ng-container *ngIf="isCollapsed">
44+
<span *ngFor="let author of dso.limitedMetadata(authorMetadata, additionalMetadataLimit); let i=index; let last=last;">
45+
<ds-metadata-link-view [item]="dso" [metadata]="author" [metadataName]="authorMetadata"></ds-metadata-link-view><span *ngIf="!last">; </span>
46+
</span>
47+
</ng-container>
48+
<ng-container *ngIf="!isCollapsed">
49+
<span *ngFor="let author of dso.allMetadata(authorMetadata); let i=index; let last=last;">
50+
<ds-metadata-link-view [item]="dso" [metadata]="author" [metadataName]="authorMetadata"></ds-metadata-link-view><span *ngIf="!last">; </span>
51+
</span>
52+
</ng-container>
53+
</ng-container>
4554
</span>
4655
</ds-truncatable-part>
4756
</span>

0 commit comments

Comments
 (0)