Skip to content

Commit 654ebdc

Browse files
Merged dspace-cris-2023_02_x into DSC-909
2 parents fc19eaf + 2d6a375 commit 654ebdc

15 files changed

Lines changed: 240 additions & 19 deletions

bitbucket-pipelines.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
options:
2-
runs-on: ubuntu-latest
2+
runs-on: self.hosted
33

44
definitions:
55
steps:
@@ -15,7 +15,7 @@ definitions:
1515
- yarn install --frozen-lockfile
1616
- yarn run lint --quiet
1717
- yarn run check-circ-deps
18-
- yarn run build:prod
18+
- yarn run build:prod:ci
1919
- yarn run test:headless
2020

2121
pipelines:

config/config.example.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,10 @@ item:
308308
# Rounded to the nearest size in the list of selectable sizes on the
309309
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
310310
pageSize: 5
311+
# The maximum number of metadata values to add to the metatag list of the item page
312+
metatagLimit: 20
313+
# The maximum number of values for repeatable metadata to show in the full item
314+
metadataLimit: 20
311315

312316
# When the search results are retrieved, for each item type the metadata with a valid authority value are inspected.
313317
# Referenced items will be fetched with a find all by id strategy to avoid individual rest requests

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
"build:stats": "ng build --stats-json",
1818
"build:ci": "ng config cli.cache.environment ci && yarn run build:ssr",
1919
"build:prod": "cross-env NODE_ENV=production yarn run build:ssr",
20+
"build:prod:ci": "cross-env NODE_ENV=production yarn run build:ssr:ci",
2021
"build:ssr": "npm run ng-high-memory -- build --configuration production && npm run ng-high-memory -- run dspace-angular:server:production",
22+
"build:ssr:ci": "npm run ng-mid-memory -- build --configuration production && npm run ng-mid-memory -- run dspace-angular:server:production",
2123
"ng-high-memory": "node --max-old-space-size=8192 node_modules/@angular/cli/bin/ng",
24+
"ng-mid-memory": "node --max-old-space-size=4096 node_modules/@angular/cli/bin/ng",
2225
"test": "npm run ng-high-memory -- test --source-map=true --watch=false --configuration test",
2326
"test:watch": "nodemon --exec \"npm run ng-high-memory -- test --source-map=true --watch=true --configuration test\"",
2427
"test:headless": "npm run ng-high-memory -- test --source-map=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage",

src/app/core/metadata/metadata.service.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import { TranslateService } from '@ngx-translate/core';
88
import {
99
BehaviorSubject,
1010
combineLatest,
11-
Observable,
12-
of as observableOf,
1311
concat as observableConcat,
14-
EMPTY
12+
EMPTY,
13+
Observable,
14+
of as observableOf
1515
} from 'rxjs';
16-
import { filter, map, switchMap, take, mergeMap } from 'rxjs/operators';
16+
import { filter, map, mergeMap, switchMap, take } from 'rxjs/operators';
1717

1818
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
1919
import { DSONameService } from '../breadcrumbs/dso-name.service';
@@ -25,10 +25,7 @@ import { BitstreamFormat } from '../shared/bitstream-format.model';
2525
import { Bitstream } from '../shared/bitstream.model';
2626
import { DSpaceObject } from '../shared/dspace-object.model';
2727
import { Item } from '../shared/item.model';
28-
import {
29-
getFirstCompletedRemoteData,
30-
getFirstSucceededRemoteDataPayload
31-
} from '../shared/operators';
28+
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../shared/operators';
3229
import { RootDataService } from '../data/root-data.service';
3330
import { getBitstreamDownloadRoute } from '../../app-routing-paths';
3431
import { BundleDataService } from '../data/bundle-data.service';
@@ -245,7 +242,9 @@ export class MetadataService {
245242
* Add <meta name="citation_author" ... > to the <head>
246243
*/
247244
private setCitationAuthorTags(): void {
248-
const values: string[] = this.getMetaTagValues(['dc.author', 'dc.contributor.author', 'dc.creator']);
245+
// limit author to first 20 entries to avoid issue with item page rendering
246+
const values: string[] = this.getMetaTagValues(['dc.author', 'dc.contributor.author', 'dc.creator'])
247+
.slice(0, this.appConfig.item.metatagLimit);
249248
this.addMetaTags('citation_author', values);
250249
}
251250

src/app/item-page/full/full-item-page.component.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,18 @@
2020
<table class="table table-striped">
2121
<tbody>
2222
<ng-container *ngFor="let mdEntry of (metadata$ | async) | keyvalue">
23-
<tr *ngFor="let mdValue of mdEntry.value">
23+
<tr *ngFor="let mdValue of mdEntry.value | slice:0:(metadataMapLimit$ | async).get(mdEntry.key)">
2424
<td>{{mdEntry.key}}</td>
2525
<td>{{mdValue.value}}</td>
2626
<td>{{mdValue.language}}</td>
2727
</tr>
28+
<tr *ngIf="mdEntry.value.length > (metadataMapLimit$ | async).get(mdEntry.key)">
29+
<td class="text-center" colspan="3">
30+
<button class="btn btn-link" (click)="increaseLimit(mdEntry.key)" data-test="btn-more">
31+
{{'item.truncatable-part.show-more' | translate}}
32+
</button>
33+
</td>
34+
</tr>
2835
</ng-container>
2936
</tbody>
3037
</table>

src/app/item-page/full/full-item-page.component.spec.ts

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { RemoteData } from '../../core/data/remote-data';
2323
import { ServerResponseService } from '../../core/services/server-response.service';
2424
import { SignpostingDataService } from '../../core/data/signposting-data.service';
2525
import { LinkHeadService } from '../../core/services/link-head.service';
26+
import { APP_CONFIG } from '../../../config/app-config.interface';
2627

2728
const mockItem: Item = Object.assign(new Item(), {
2829
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -32,6 +33,77 @@ const mockItem: Item = Object.assign(new Item(), {
3233
language: 'en_US',
3334
value: 'test item'
3435
}
36+
],
37+
'dc.contributor.author': [
38+
{
39+
value: 'author1'
40+
},
41+
{
42+
value: 'author2'
43+
},
44+
{
45+
value: 'author3'
46+
},
47+
{
48+
value: 'author4'
49+
},
50+
{
51+
value: 'author5'
52+
},
53+
{
54+
value: 'author6'
55+
},
56+
{
57+
value: 'author7'
58+
},
59+
{
60+
value: 'author8'
61+
},
62+
{
63+
value: 'author9'
64+
},
65+
{
66+
value: 'author10'
67+
},
68+
{
69+
value: 'author11'
70+
},
71+
{
72+
value: 'author12'
73+
},
74+
{
75+
value: 'author13'
76+
},
77+
{
78+
value: 'author14'
79+
},
80+
{
81+
value: 'author15'
82+
},
83+
{
84+
value: 'author16'
85+
},
86+
{
87+
value: 'author17'
88+
},
89+
{
90+
value: 'author18'
91+
},
92+
{
93+
value: 'author19'
94+
},
95+
{
96+
value: 'author20'
97+
},
98+
{
99+
value: 'author21'
100+
},
101+
{
102+
value: 'author22'
103+
},
104+
{
105+
value: 'author23'
106+
}
35107
]
36108
}
37109
});
@@ -74,6 +146,12 @@ describe('FullItemPageComponent', () => {
74146
type: 'test'
75147
};
76148

149+
const appConfig = {
150+
item: {
151+
metadataLimit: 20
152+
}
153+
};
154+
77155
beforeEach(waitForAsync(() => {
78156
authService = jasmine.createSpyObj('authService', {
79157
isAuthenticated: observableOf(true),
@@ -122,7 +200,8 @@ describe('FullItemPageComponent', () => {
122200
{ provide: ServerResponseService, useValue: serverResponseService },
123201
{ provide: SignpostingDataService, useValue: signpostingDataService },
124202
{ provide: LinkHeadService, useValue: linkHeadService },
125-
{ provide: PLATFORM_ID, useValue: 'server' }
203+
{ provide: PLATFORM_ID, useValue: 'server' },
204+
{ provide: APP_CONFIG, useValue: appConfig },
126205
],
127206
schemas: [NO_ERRORS_SCHEMA]
128207
}).overrideComponent(FullItemPageComponent, {
@@ -142,7 +221,7 @@ describe('FullItemPageComponent', () => {
142221

143222
it('should display the item\'s metadata', () => {
144223
const table = fixture.debugElement.query(By.css('table'));
145-
for (const metadatum of mockItem.allMetadata(Object.keys(mockItem.metadata))) {
224+
for (const metadatum of mockItem.allMetadata('dc.title')) {
146225
expect(table.nativeElement.innerHTML).toContain(metadatum.value);
147226
}
148227
});
@@ -227,4 +306,28 @@ describe('FullItemPageComponent', () => {
227306
expect(linkHeadService.addTag).toHaveBeenCalledTimes(2);
228307
});
229308
});
309+
310+
describe('when the item has many metadata values', () => {
311+
beforeEach(() => {
312+
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockItem));
313+
fixture.detectChanges();
314+
});
315+
316+
it('should not display all the item\'s metadata', () => {
317+
const table = fixture.debugElement.query(By.css('table'));
318+
const visibleValues = mockItem.allMetadata('dc.contributor.author').slice(0, 20);
319+
const hiddenValues = mockItem.allMetadata('dc.contributor.author').slice(20, 40);
320+
for (const metadatum of visibleValues) {
321+
expect(table.nativeElement.innerHTML).toContain(metadatum.value);
322+
}
323+
for (const metadatum of hiddenValues) {
324+
expect(table.nativeElement.innerHTML).not.toContain(metadatum.value);
325+
}
326+
});
327+
328+
it('should display show more button', () => {
329+
const btn = fixture.debugElement.query(By.css('button[data-test="btn-more"]'));
330+
expect(btn).not.toBeNull();
331+
});
332+
});
230333
});

src/app/item-page/full/full-item-page.component.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { filter, map } from 'rxjs/operators';
1+
import { filter, map, tap } from 'rxjs/operators';
22
import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
33
import { ActivatedRoute, Data, Router } from '@angular/router';
44

@@ -19,6 +19,7 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/
1919
import { ServerResponseService } from '../../core/services/server-response.service';
2020
import { SignpostingDataService } from '../../core/data/signposting-data.service';
2121
import { LinkHeadService } from '../../core/services/link-head.service';
22+
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
2223

2324
/**
2425
* This component renders a full item page.
@@ -38,6 +39,10 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
3839

3940
metadata$: Observable<MetadataMap>;
4041

42+
metadataMapLimit$: BehaviorSubject<Map<string, number>> = new BehaviorSubject<Map<string, number>>(new Map<string, number>());
43+
44+
limitSize = this.appConfig.item.metadataLimit;
45+
4146
/**
4247
* True when the itemRD has been originated from its workspaceite/workflowitem, false otherwise.
4348
*/
@@ -56,6 +61,7 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
5661
protected signpostingDataService: SignpostingDataService,
5762
protected linkHeadService: LinkHeadService,
5863
@Inject(PLATFORM_ID) protected platformId: string,
64+
@Inject(APP_CONFIG) private appConfig: AppConfig,
5965
) {
6066
super(route, router, items, authService, authorizationService, responseService, signpostingDataService, linkHeadService, platformId);
6167
}
@@ -66,7 +72,9 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
6672
this.metadata$ = this.itemRD$.pipe(
6773
map((rd: RemoteData<Item>) => rd.payload),
6874
filter((item: Item) => hasValue(item)),
69-
map((item: Item) => item.metadata),);
75+
map((item: Item) => item.metadata),
76+
tap((metadataMap: MetadataMap) => this.nextMetadataMapLimit(metadataMap))
77+
);
7078

7179
this.subs.push(this.route.data.subscribe((data: Data) => {
7280
this.fromSubmissionObject = hasValue(data.wfi) || hasValue(data.wsi);
@@ -84,4 +92,18 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
8492
ngOnDestroy() {
8593
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
8694
}
95+
96+
protected increaseLimit(metadataKey: string) {
97+
const newMetadataMap: Map<string, number> = new Map(this.metadataMapLimit$.value);
98+
const newMetadataSize = newMetadataMap.get(metadataKey) + this.limitSize;
99+
newMetadataMap.set(metadataKey, newMetadataSize);
100+
this.metadataMapLimit$.next(newMetadataMap);
101+
}
102+
103+
protected nextMetadataMapLimit(metadataMap: MetadataMap) {
104+
const metadataMapLimit: Map<string, number> = new Map(this.metadataMapLimit$.value);
105+
Object.keys(metadataMap).forEach((key: string) => metadataMapLimit.set(key, this.limitSize));
106+
this.metadataMapLimit$.next(metadataMapLimit);
107+
}
108+
87109
}

src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ export class OrcidQueueComponent implements OnInit, OnDestroy {
120120
switch (orcidQueue.recordType.toLowerCase()) {
121121
case 'publication':
122122
return 'fas fa-book';
123+
case 'product':
124+
return 'fas fa-database';
123125
case 'funding':
124126
return 'fa fa-wallet';
125127
case 'project':

src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,29 @@ <h2>{{'person.orcid.sync.setting' | translate}}</h2>
4949
</div>
5050
</div>
5151
</div>
52+
<div class="col-md mb-3">
53+
<div class="card h-100" data-test="sync-mode-product">
54+
<div class="card-header">{{ 'person.page.orcid.products-preferences'| translate }}</div>
55+
<div class="card-body">
56+
<div class="container">
57+
<div class="row">
58+
<ds-alert [type]="'alert-info'">
59+
{{ 'person.page.orcid.synchronization-mode-product-message' | translate}}
60+
</ds-alert>
61+
</div>
62+
<div class="form-group">
63+
<div *ngFor="let option of syncProductOptions" class="row form-check">
64+
<input type="radio" [(ngModel)]="currentSyncProduct"
65+
name="syncProducts" id="productOption_{{option.value}}" [value]="option.value"
66+
required>
67+
<label for="productOption_{{option.value}}"
68+
class="ml-2">{{option.label | translate}}</label>
69+
</div>
70+
</div>
71+
</div>
72+
</div>
73+
</div>
74+
</div>
5275
<div class="col-md mb-3">
5376
<div class="card h-100" data-test="sync-mode-funding">
5477
<div class="card-header">{{ 'person.page.orcid.funding-preferences'| translate }}</div>

src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,20 @@ describe('OrcidSyncSettingsComponent test suite', () => {
168168
it('should create cards properly', () => {
169169
const modes = fixture.debugElement.query(By.css('[data-test="sync-mode"]'));
170170
const publication = fixture.debugElement.query(By.css('[data-test="sync-mode-publication"]'));
171+
const product = fixture.debugElement.query(By.css('[data-test="sync-mode-product"]'));
171172
const funding = fixture.debugElement.query(By.css('[data-test="sync-mode-funding"]'));
172173
const preferences = fixture.debugElement.query(By.css('[data-test="profile-preferences"]'));
173174
expect(modes).toBeTruthy();
174175
expect(publication).toBeTruthy();
176+
expect(product).toBeTruthy();
175177
expect(funding).toBeTruthy();
176178
expect(preferences).toBeTruthy();
177179
});
178180

179181
it('should init sync modes properly', () => {
180182
expect(comp.currentSyncMode).toBe('MANUAL');
181183
expect(comp.currentSyncPublications).toBe('ALL');
184+
expect(comp.currentSyncProduct).toBe('DISABLED');
182185
expect(comp.currentSyncFunding).toBe('DISABLED');
183186
});
184187

@@ -189,6 +192,7 @@ describe('OrcidSyncSettingsComponent test suite', () => {
189192
formGroup = new UntypedFormGroup({
190193
syncMode: new UntypedFormControl('MANUAL'),
191194
syncFundings: new UntypedFormControl('ALL'),
195+
syncProducts: new UntypedFormControl('ALL'),
192196
syncPublications: new UntypedFormControl('ALL'),
193197
syncProfile_BIOGRAPHICAL: new UntypedFormControl(true),
194198
syncProfile_IDENTIFIERS: new UntypedFormControl(true),
@@ -208,6 +212,10 @@ describe('OrcidSyncSettingsComponent test suite', () => {
208212
path: '/orcid/publications',
209213
op: 'replace',
210214
value: 'ALL'
215+
}, {
216+
path: '/orcid/products',
217+
op: 'replace',
218+
value: 'ALL'
211219
}, {
212220
path: '/orcid/fundings',
213221
op: 'replace',

0 commit comments

Comments
 (0)