Skip to content

Commit b4d589d

Browse files
committed
Merge remote-tracking branch 'bitbucket4science/dspace-cris-2024_02_x' into dspace-cris-2024_02_x
2 parents fc071bf + d1c900a commit b4d589d

9 files changed

Lines changed: 159 additions & 44 deletions

File tree

bitbucket-pipelines.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ options:
22
runs-on: self.hosted
33

44
definitions:
5+
caches:
6+
node-cris8: ./node_modules
7+
58
steps:
69
- step: &unittest-code-checks
710
name: test-code-checks
@@ -10,7 +13,7 @@ definitions:
1013
run-as-user: 1000
1114
size: 4x
1215
caches:
13-
- node
16+
- node-cris8
1417
script:
1518
- yarn install --frozen-lockfile
1619
- yarn run lint --quiet

src/app/core/submission/vocabularies/models/vocabulary.model.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ export class Vocabulary implements CacheableObject {
7474
@autoserialize
7575
externalSource: VocabularyExternalSourceMap;
7676

77+
78+
/**
79+
* A boolean variable that indicates whether the functionality of
80+
* multiple value generation is enabled within a generator context.
81+
*/
82+
@autoserialize
83+
multiValueOnGenerator: boolean;
84+
7785
/**
7886
* A string representing the kind of Vocabulary model
7987
*/
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<div>
2-
<div class="modal-header">{{ headerLabel | translate:{ dsoName: dsoNameService.getName(dso) } }}
2+
<div class="modal-header">{{ headerLabel | translate:{ dsoName: name } }}
33
<button type="button" class="close" (click)="close()" aria-label="Close">
44
<span aria-hidden="true">×</span>
55
</button>
66
</div>
77
<div class="modal-body">
8-
<p>{{ infoLabel | translate:{ dsoName: dsoNameService.getName(dso) } }}</p>
8+
<p>{{ infoLabel | translate:{ dsoName: name } }}</p>
99
</div>
1010
<div class="modal-footer">
1111
<button type="button" class="cancel btn btn-outline-secondary" (click)="cancelPressed()" aria-label="Cancel">
12-
<i class="fas fa-times"></i> {{ cancelLabel | translate:{ dsoName: dsoNameService.getName(dso) } }}
12+
<i class="fas fa-times"></i> {{ cancelLabel | translate:{ dsoName: name } }}
1313
</button>
1414
<button type="button" class="confirm btn btn-{{brandColor}}" (click)="confirmPressed()" aria-label="Confirm" ngbAutofocus>
15-
<i *ngIf="confirmIcon" class="{{confirmIcon}}"></i> {{ confirmLabel | translate:{ dsoName: dsoNameService.getName(dso) } }}
15+
<i *ngIf="confirmIcon" class="{{confirmIcon}}"></i> {{ confirmLabel | translate:{ dsoName: name } }}
1616
</button>
1717
</div>
1818
</div>

src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,32 @@ describe('ConfirmationModalComponent', () => {
132132
});
133133
});
134134

135+
describe('displaying the name in the modal', () => {
136+
const testName = 'Test Name';
137+
beforeEach(() => {
138+
component.name = testName;
139+
component.headerLabel = `Header: ${component.name}`;
140+
component.infoLabel = `Info: ${component.name}`;
141+
component.cancelLabel = `Cancel: ${component.name}`;
142+
component.confirmLabel = `Confirm: ${component.name}`;
143+
fixture.detectChanges();
144+
});
145+
it('should display the name in the header', () => {
146+
const header = debugElement.query(By.css('.modal-header')).nativeElement.textContent;
147+
expect(header).toContain(testName);
148+
});
149+
it('should display the name in the body', () => {
150+
const body = debugElement.query(By.css('.modal-body')).nativeElement.textContent;
151+
expect(body).toContain(testName);
152+
});
153+
it('should display the name in the cancel button', () => {
154+
const cancelBtn = debugElement.query(By.css('button.cancel')).nativeElement.textContent;
155+
expect(cancelBtn).toContain(testName);
156+
});
157+
it('should display the name in the confirm button', () => {
158+
const confirmBtn = debugElement.query(By.css('button.confirm')).nativeElement.textContent;
159+
expect(confirmBtn).toContain(testName);
160+
});
161+
});
162+
135163
});

src/app/shared/confirmation-modal/confirmation-modal.component.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ import {
88
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
99
import { TranslateModule } from '@ngx-translate/core';
1010

11-
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
12-
import { DSpaceObject } from '../../core/shared/dspace-object.model';
13-
1411
@Component({
1512
selector: 'ds-confirmation-modal',
1613
templateUrl: 'confirmation-modal.component.html',
@@ -28,7 +25,7 @@ export class ConfirmationModalComponent {
2825
*/
2926
@Input() brandColor = 'primary';
3027

31-
@Input() dso: DSpaceObject;
28+
@Input() name: string;
3229

3330
/**
3431
* An event fired when the cancel or confirm button is clicked, with respectively false or true
@@ -38,7 +35,6 @@ export class ConfirmationModalComponent {
3835

3936
constructor(
4037
protected activeModal: NgbActiveModal,
41-
public dsoNameService: DSONameService,
4238
) {
4339
}
4440

src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
switchMap,
2525
} from 'rxjs/operators';
2626

27+
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
2728
import {
2829
COLLECTION_EXPORT_SCRIPT_NAME,
2930
ScriptDataService,
@@ -65,7 +66,8 @@ export class ExportMetadataXlsSelectorComponent extends DSOSelectorModalWrapperC
6566
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router,
6667
protected notificationsService: NotificationsService, protected translationService: TranslateService,
6768
protected scriptDataService: ScriptDataService,
68-
private modalService: NgbModal) {
69+
private modalService: NgbModal,
70+
private dsoNameService: DSONameService) {
6971
super(activeModal, route);
7072
}
7173

@@ -76,7 +78,7 @@ export class ExportMetadataXlsSelectorComponent extends DSOSelectorModalWrapperC
7678
navigate(dso: DSpaceObject): Observable<boolean> {
7779
if (dso instanceof Collection) {
7880
const modalRef = this.modalService.open(ConfirmationModalComponent);
79-
modalRef.componentInstance.dso = dso;
81+
modalRef.componentInstance.name = this.dsoNameService.getName(dso);
8082
modalRef.componentInstance.headerLabel = 'confirmation-modal.export-metadata-xls.header';
8183
modalRef.componentInstance.infoLabel = 'confirmation-modal.export-metadata-xls.info';
8284
modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-metadata-xls.cancel';

src/app/shared/form/builder/ds-dynamic-form-ui/models/dynamic-vocabulary.component.ts

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
filter,
2323
map,
2424
take,
25+
tap,
2526
} from 'rxjs/operators';
2627

2728
import { Metadata } from '../../../../../core/shared/metadata.utils';
@@ -76,6 +77,8 @@ export abstract class DsDynamicVocabularyComponent extends DynamicFormControlCom
7677
public otherInfoValues: string[] = [];
7778
public otherInfoValuesUnformatted: string[] = [];
7879

80+
multiValueOnGenerator: boolean;
81+
7982
protected constructor(protected vocabularyService: VocabularyService,
8083
protected layoutService: DynamicFormLayoutService,
8184
protected validationService: DynamicFormValidationService,
@@ -195,6 +198,7 @@ export abstract class DsDynamicVocabularyComponent extends DynamicFormControlCom
195198
this.vocabulary$ = this.vocabularyService.findVocabularyById(this.model.vocabularyOptions.name).pipe(
196199
getFirstSucceededRemoteDataPayload(),
197200
distinctUntilChanged(),
201+
tap((vocabulary: Vocabulary) => this.multiValueOnGenerator = vocabulary.multiValueOnGenerator),
198202
);
199203
}
200204

@@ -277,12 +281,14 @@ export abstract class DsDynamicVocabularyComponent extends DynamicFormControlCom
277281
for (const key in otherInformation) {
278282
if (otherInformation.hasOwnProperty(key) && key.startsWith('data-')) {
279283
const fieldId = key.replace('data-', '');
280-
const newValue: FormFieldMetadataValueObject = this.getOtherInformationValue(otherInformation[key], key);
281-
if (isNotEmpty(newValue)) {
282-
const updatedModel = this.formBuilderService.updateModelValue(fieldId, newValue);
283-
if (isNotEmpty(updatedModel)) {
284-
updatedModels.push(updatedModel);
285-
}
284+
const newValues: FormFieldMetadataValueObject[] = this.getOtherInformationValue(otherInformation[key], key);
285+
if (isNotEmpty(newValues)) {
286+
newValues.forEach((newValue) => {
287+
const updatedModel = this.formBuilderService.updateModelValue(fieldId, newValue);
288+
if (isNotEmpty(updatedModel)) {
289+
updatedModels.push(updatedModel);
290+
}
291+
});
286292
}
287293
}
288294
}
@@ -297,43 +303,53 @@ export abstract class DsDynamicVocabularyComponent extends DynamicFormControlCom
297303
}
298304
}
299305

300-
getOtherInformationValue(value: string, key: string): FormFieldMetadataValueObject {
306+
getOtherInformationValue(value: string, key: string): FormFieldMetadataValueObject[] {
301307
if (isEmpty(value) || key === 'alternative-names' ) {
302308
return null;
303309
}
304310

305-
let returnValue;
311+
const returnValue = [];
312+
if (value.indexOf('|||') === -1) {
313+
returnValue.push(this.generateFormField(value));
314+
} else if (value.indexOf('|||') !== -1 && this.otherInfoValue) {
315+
const otherValues: string[] = value.split('|||');
316+
if (this.multiValueOnGenerator) {
317+
otherValues.forEach((tmpValue) => returnValue.push(this.generateFormField(tmpValue)));
318+
} else {
319+
const unformattedValue = this.otherInfoValuesUnformatted.find(otherInfoValue => otherInfoValue.includes(this.otherInfoValue || this.otherName));
320+
const authorityValue = hasValue(unformattedValue) ? unformattedValue.substring(unformattedValue.lastIndexOf('::') + 2) : null;
321+
const otherInfo = {};
322+
let alternativeValue: string;
323+
otherInfo[key] = value;
324+
if (hasValue(this.otherName)) {
325+
alternativeValue = otherValues[0].substring(0, otherValues[0].lastIndexOf('::'));
326+
}
327+
returnValue.push(new FormFieldMetadataValueObject(
328+
hasValue(alternativeValue) ? alternativeValue : this.otherInfoValue,
329+
null,
330+
null,
331+
authorityValue,
332+
null,
333+
null,
334+
null,
335+
otherInfo,
336+
));
337+
}
338+
}
339+
return returnValue;
340+
}
341+
342+
private generateFormField(value: string): FormFieldMetadataValueObject {
306343
if (value.indexOf('::') === -1) {
307-
returnValue = new FormFieldMetadataValueObject(value);
308-
} else if (value.indexOf('|||') === -1) {
309-
returnValue = new FormFieldMetadataValueObject(
344+
return new FormFieldMetadataValueObject(value);
345+
} else {
346+
return new FormFieldMetadataValueObject(
310347
value.substring(0, value.lastIndexOf('::')),
311348
null,
312349
null,
313350
value.substring(value.lastIndexOf('::') + 2),
314351
);
315-
} else if (value.indexOf('|||') !== -1 && this.otherInfoValue) {
316-
const unformattedValue = this.otherInfoValuesUnformatted.find(otherInfoValue => otherInfoValue.includes(this.otherInfoValue || this.otherName));
317-
const authorityValue = hasValue(unformattedValue) ? unformattedValue.substring(unformattedValue.lastIndexOf('::') + 2) : null;
318-
const otherInfo = {};
319-
let alternativeValue;
320-
otherInfo[key] = value;
321-
if (hasValue(this.otherName)) {
322-
const otherValues = value.split('|||');
323-
alternativeValue = otherValues[0].substring(0, otherValues[0].lastIndexOf('::'));
324-
}
325-
returnValue = new FormFieldMetadataValueObject(
326-
hasValue(alternativeValue) ? alternativeValue : this.otherInfoValue,
327-
null,
328-
null,
329-
authorityValue,
330-
null,
331-
null,
332-
null,
333-
otherInfo,
334-
);
335352
}
336-
return returnValue;
337353
}
338354

339355
private hasValidAuthority(formMetadataValue: FormFieldMetadataValueObject) {

src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,66 @@ describe('DsDynamicOneboxComponent test suite', () => {
570570
expect(oneboxComponent.currentValue.authority).toBeUndefined();
571571
});
572572
});
573+
574+
describe('test metadata enrichment', () => {
575+
beforeEach(() => {
576+
oneboxCompFixture = TestBed.createComponent(DsDynamicOneboxComponent);
577+
debugElement = oneboxCompFixture.debugElement;
578+
oneboxComponent = oneboxCompFixture.componentInstance;
579+
oneboxComponent.currentValue = new FormFieldMetadataValueObject('test', null, null, null, 'testDisplay');
580+
oneboxComponent.model = new DynamicOneboxModel(ONEBOX_TEST_MODEL_CONFIG);
581+
582+
spyOn(oneboxComponent, 'onSelectItem').and.returnValue(undefined);
583+
spyOn(oneboxComponent, 'toggleOtherInfoSelection').and.returnValue(undefined);
584+
});
585+
586+
587+
it('should return null if value is empty', () => {
588+
expect(oneboxComponent.getOtherInformationValue('', 'some-key')).toBeNull();
589+
});
590+
591+
it('should return null if key is "alternative-names"', () => {
592+
expect(oneboxComponent.getOtherInformationValue('some-value', 'alternative-names')).toBeNull();
593+
});
594+
595+
it('should return single FormFieldMetadataValueObject if no "|||" in value', () => {
596+
const result = oneboxComponent.getOtherInformationValue('value::authority', 'some-key');
597+
expect(result.length).toBe(1);
598+
expect(result[0]).toEqual(jasmine.any(FormFieldMetadataValueObject));
599+
expect(result[0].value).toBe('value');
600+
expect(result[0].authority).toBe('authority');
601+
});
602+
603+
it('should handle multiple values with multiValueOnGenerator true', () => {
604+
oneboxComponent.multiValueOnGenerator = true;
605+
(oneboxComponent as any).otherInfoValue = 'someValue';
606+
const result = oneboxComponent.getOtherInformationValue('val1::auth1|||val2::auth2', 'some-key');
607+
expect(result.length).toBe(2);
608+
expect(result[0].value).toBe('val1');
609+
expect(result[0].authority).toBe('auth1');
610+
expect(result[1].value).toBe('val2');
611+
expect(result[1].authority).toBe('auth2');
612+
});
613+
614+
it('should handle multiple values with multiValueOnGenerator false', () => {
615+
oneboxComponent.multiValueOnGenerator = false;
616+
(oneboxComponent as any).otherInfoValue = 'val1';
617+
oneboxComponent.otherInfoValuesUnformatted = ['val1::auth1'];
618+
const result = oneboxComponent.getOtherInformationValue('val1::auth1|||val2::auth2', 'data-key');
619+
expect(result.length).toBe(1);
620+
expect(result[0].value).toBe('val1');
621+
expect(result[0].authority).toBe('auth1');
622+
expect(result[0].otherInformation['data-key']).toBe('val1::auth1|||val2::auth2');
623+
});
624+
625+
it('should handle value without "::"', () => {
626+
const result = oneboxComponent.getOtherInformationValue('simpleValue', 'some-key');
627+
expect(result.length).toBe(1);
628+
expect(result[0].value).toBe('simpleValue');
629+
expect(result[0].authority).toBeNull();
630+
});
631+
632+
});
573633
});
574634
});
575635

src/assets/i18n/en.json5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4050,6 +4050,8 @@
40504050

40514051
"itemtemplate.edit.metadata.headers.value": "Value",
40524052

4053+
"itemtemplate.edit.metadata.headers.authority": "Authority",
4054+
40534055
"itemtemplate.edit.metadata.metadatafield": "Edit field",
40544056

40554057
"itemtemplate.edit.metadata.metadatafield.error": "An error occurred validating the metadata field",

0 commit comments

Comments
 (0)