Skip to content

Commit e316b11

Browse files
[DURACOM-426] add vocabulary template in item field, adapt tests
1 parent 252c249 commit e316b11

11 files changed

Lines changed: 162 additions & 4 deletions

src/app/item-page/field-components/metadata-uri-values/metadata-uri-values.component.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { By } from '@angular/platform-browser';
1212
import { APP_CONFIG } from '@dspace/config/app-config.interface';
1313
import { MetadataValue } from '@dspace/core/shared/metadata.models';
14+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
1415
import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock';
1516
import { isNotEmpty } from '@dspace/shared/utils/empty.util';
1617
import {
@@ -23,6 +24,10 @@ import { MetadataUriValuesComponent } from './metadata-uri-values.component';
2324

2425
let comp: MetadataUriValuesComponent;
2526
let fixture: ComponentFixture<MetadataUriValuesComponent>;
27+
const vocabularyServiceMock = {
28+
getPublicVocabularyEntryByID: jasmine.createSpy('getPublicVocabularyEntryByID'),
29+
};
30+
2631

2732
const mockMetadata = [
2833
{
@@ -38,6 +43,7 @@ const mockSeperator = '<br/>';
3843
const mockLabel = 'fake.message';
3944
const mockLinkText = 'fake link text';
4045

46+
4147
describe('MetadataUriValuesComponent', () => {
4248
beforeEach(waitForAsync(() => {
4349
TestBed.configureTestingModule({
@@ -49,6 +55,7 @@ describe('MetadataUriValuesComponent', () => {
4955
}), MetadataUriValuesComponent],
5056
providers: [
5157
{ provide: APP_CONFIG, useValue: environment },
58+
{ provide: VocabularyService, useValue: vocabularyServiceMock },
5259
],
5360
schemas: [NO_ERRORS_SCHEMA],
5461
}).overrideComponent(MetadataUriValuesComponent, {

src/app/item-page/field-components/metadata-values/metadata-values.component.html

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<ds-metadata-field-wrapper [label]="label | translate">
22
@for (mdValue of mdValues; track mdValue; let last = $last) {
3+
@let isVocabulary = isControlledVocabulary(mdValue);
34
<!--
4-
Choose a template. Priority: markdown, link, browse link.
5+
Choose a template. Priority: vocabulary, markdown, link, browse link.
56
-->
6-
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : (hasLink(mdValue) ? (hasValue(img) ? linkImg : link) : (hasBrowseDefinition() ? browselink : simple)));
7-
context: {value: mdValue.value, img}">
7+
<ng-container *ngTemplateOutlet="(isVocabulary ? controlledVocabulary : (renderMarkdown ? markdown : (hasLink(mdValue) ? (hasValue(img) ? linkImg : link) : (hasBrowseDefinition() ? browselink : simple))));
8+
context: {value: (isVocabulary ? mdValue : mdValue.value), img}">
89
</ng-container>
910
@if (!last) {
1011
<span class="separator" [innerHTML]="separator"></span>
@@ -50,3 +51,14 @@
5051
[routerLink]="['/browse', browseDefinition.id]"
5152
[queryParams]="getQueryParams(value)" role="link" tabindex="0">{{value}}</a>
5253
</ng-template>
54+
55+
56+
<!-- Render translated valueof vocabulary entry -->
57+
<ng-template #controlledVocabulary let-value="value">
58+
@let label = getVocabularyValue(value) | async;
59+
@if (hasBrowseDefinition()) {
60+
<ng-container *ngTemplateOutlet="browselink; context: {value: label}"></ng-container>
61+
} @else {
62+
<span class="dont-break-out preserve-line-breaks">{{label}}</span>
63+
}
64+
</ng-template>

src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ import {
99
} from '@angular/core/testing';
1010
import { By } from '@angular/platform-browser';
1111
import { APP_CONFIG } from '@dspace/config/app-config.interface';
12+
import { buildPaginatedList } from '@dspace/core/data/paginated-list.model';
1213
import { MetadataValue } from '@dspace/core/shared/metadata.models';
14+
import { PageInfo } from '@dspace/core/shared/page-info.model';
15+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
1316
import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock';
17+
import { createSuccessfulRemoteDataObject } from '@dspace/core/utilities/remote-data.utils';
1418
import {
1519
TranslateLoader,
1620
TranslateModule,
1721
} from '@ngx-translate/core';
22+
import { of } from 'rxjs';
1823

1924
import { environment } from '../../../../environments/environment';
2025
import { MetadataValuesComponent } from './metadata-values.component';
@@ -37,6 +42,18 @@ const mockMetadata = [
3742
}] as MetadataValue[];
3843
const mockSeperator = '<br/>';
3944
const mockLabel = 'fake.message';
45+
const vocabularyServiceMock = {
46+
getPublicVocabularyEntryByID: jasmine.createSpy('getPublicVocabularyEntryByID'),
47+
};
48+
49+
const controlledMetadata = {
50+
value: 'Original Value',
51+
authority: 'srsc:1234',
52+
uuid: 'metadata-uuid-1',
53+
language: 'en_US',
54+
place: null,
55+
confidence: 600,
56+
} as MetadataValue;
4057

4158
describe('MetadataValuesComponent', () => {
4259
beforeEach(waitForAsync(() => {
@@ -49,6 +66,7 @@ describe('MetadataValuesComponent', () => {
4966
}), MetadataValuesComponent],
5067
providers: [
5168
{ provide: APP_CONFIG, useValue: environment },
69+
{ provide: VocabularyService, useValue: vocabularyServiceMock },
5270
],
5371
schemas: [NO_ERRORS_SCHEMA],
5472
}).overrideComponent(MetadataValuesComponent, {
@@ -99,4 +117,29 @@ describe('MetadataValuesComponent', () => {
99117
expect(result.rel).toBe('noopener noreferrer');
100118
});
101119

120+
it('should detect controlled vocabulary metadata', () => {
121+
const result = comp.isControlledVocabulary(controlledMetadata);
122+
expect(result).toBeTrue();
123+
});
124+
125+
it('should return translated vocabulary value when available', (done) => {
126+
const vocabEntry = {
127+
display: 'Translated Value',
128+
};
129+
130+
vocabularyServiceMock.getPublicVocabularyEntryByID.and.returnValue(
131+
of(
132+
createSuccessfulRemoteDataObject(
133+
buildPaginatedList(new PageInfo(), [vocabEntry]),
134+
),
135+
),
136+
);
137+
138+
comp.getVocabularyValue(controlledMetadata).subscribe((value) => {
139+
expect(value).toBe('Translated Value');
140+
done();
141+
});
142+
});
143+
144+
102145
});

src/app/item-page/field-components/metadata-values/metadata-values.component.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { NgTemplateOutlet } from '@angular/common';
1+
import {
2+
AsyncPipe,
3+
NgTemplateOutlet,
4+
} from '@angular/common';
25
import {
36
Component,
47
Inject,
@@ -13,9 +16,20 @@ import {
1316
} from '@dspace/config/app-config.interface';
1417
import { BrowseDefinition } from '@dspace/core/shared/browse-definition.model';
1518
import { MetadataValue } from '@dspace/core/shared/metadata.models';
19+
import {
20+
getFirstCompletedRemoteData,
21+
getPaginatedListPayload,
22+
getRemoteDataPayload,
23+
} from '@dspace/core/shared/operators';
1624
import { VALUE_LIST_BROWSE_DEFINITION } from '@dspace/core/shared/value-list-browse-definition.resource-type';
25+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
1726
import { hasValue } from '@dspace/shared/utils/empty.util';
1827
import { TranslateModule } from '@ngx-translate/core';
28+
import { Observable } from 'rxjs';
29+
import {
30+
map,
31+
take,
32+
} from 'rxjs/operators';
1933

2034
import { environment } from '../../../../environments/environment';
2135
import { MetadataFieldWrapperComponent } from '../../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
@@ -31,6 +45,7 @@ import { ImageField } from '../../simple/field-components/specific-field/image-f
3145
styleUrls: ['./metadata-values.component.scss'],
3246
templateUrl: './metadata-values.component.html',
3347
imports: [
48+
AsyncPipe,
3449
MarkdownDirective,
3550
MetadataFieldWrapperComponent,
3651
NgTemplateOutlet,
@@ -41,6 +56,7 @@ import { ImageField } from '../../simple/field-components/specific-field/image-f
4156
export class MetadataValuesComponent implements OnChanges {
4257

4358
constructor(
59+
protected vocabularyService: VocabularyService,
4460
@Inject(APP_CONFIG) private appConfig: AppConfig,
4561
) {
4662
}
@@ -110,6 +126,30 @@ export class MetadataValuesComponent implements OnChanges {
110126
return false;
111127
}
112128

129+
/**
130+
* Whether the metadata is a controlled vocabulary
131+
* @param value A MetadataValue being displayed
132+
*/
133+
isControlledVocabulary(metadataValue: MetadataValue): boolean {
134+
const vocabularyId = this.getVocabularyIdFromAuthorityValue(metadataValue);
135+
return hasValue(this.getVocabularyName(vocabularyId));
136+
}
137+
138+
/**
139+
* Return configured vocabulary name for this metadata value
140+
*/
141+
getVocabularyName(vocabularyId: string): string | null {
142+
return this.appConfig.vocabularies.find(vocabulary => vocabulary.vocabulary === vocabularyId)?.vocabulary;
143+
}
144+
145+
/**
146+
* Get value from authority for vocabulary lookup
147+
*/
148+
getVocabularyIdFromAuthorityValue(metadataValue: MetadataValue): string {
149+
const authority = metadataValue.authority ? metadataValue.authority.split(':') : undefined;
150+
return authority?.length > 1 ? authority[0] : null;
151+
}
152+
113153
/**
114154
* Return a queryparams object for use in a link, with the key dependent on whether this browse
115155
* definition is metadata browse, or item browse
@@ -146,4 +186,21 @@ export class MetadataValuesComponent implements OnChanges {
146186
return { target: '_blank', rel: 'noopener noreferrer' };
147187
}
148188
}
189+
190+
/**
191+
* Get vocabulary translated value from metadata value
192+
*/
193+
getVocabularyValue(metadataValue: MetadataValue): Observable<string> {
194+
const vocabularyId = this.getVocabularyIdFromAuthorityValue(metadataValue);
195+
const vocabularyName = this.getVocabularyName(vocabularyId);
196+
197+
return this.vocabularyService.getPublicVocabularyEntryByID(vocabularyName, metadataValue.authority.split(':')[1]).pipe(
198+
getFirstCompletedRemoteData(),
199+
getRemoteDataPayload(),
200+
getPaginatedListPayload(),
201+
map((res) => res?.length > 0 ? res[0] : null),
202+
map((res) => res?.display ?? metadataValue.value),
203+
take(1),
204+
);
205+
}
149206
}

src/app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { By } from '@angular/platform-browser';
1111
import { APP_CONFIG } from '@dspace/config/app-config.interface';
1212
import { BrowseService } from '@dspace/core/browse/browse.service';
1313
import { BrowseDefinitionDataService } from '@dspace/core/browse/browse-definition-data.service';
14+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
1415
import { BrowseDefinitionDataServiceStub } from '@dspace/core/testing/browse-definition-data-service.stub';
1516
import { BrowseServiceStub } from '@dspace/core/testing/browse-service.stub';
1617
import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock';
@@ -24,6 +25,9 @@ import { ItemPageAbstractFieldComponent } from './item-page-abstract-field.compo
2425

2526
let comp: ItemPageAbstractFieldComponent;
2627
let fixture: ComponentFixture<ItemPageAbstractFieldComponent>;
28+
const vocabularyServiceMock = {
29+
getPublicVocabularyEntryByID: jasmine.createSpy('getPublicVocabularyEntryByID'),
30+
};
2731

2832
describe('ItemPageAbstractFieldComponent', () => {
2933
beforeEach(waitForAsync(() => {
@@ -41,6 +45,7 @@ describe('ItemPageAbstractFieldComponent', () => {
4145
{ provide: APP_CONFIG, useValue: environment },
4246
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
4347
{ provide: BrowseService, useValue: BrowseServiceStub },
48+
{ provide: VocabularyService, useValue: vocabularyServiceMock },
4449
],
4550
schemas: [NO_ERRORS_SCHEMA],
4651
}).overrideComponent(ItemPageAbstractFieldComponent, {

src/app/item-page/simple/field-components/specific-field/author/item-page-author-field.component.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ActivatedRoute } from '@angular/router';
1111
import { APP_CONFIG } from '@dspace/config/app-config.interface';
1212
import { BrowseService } from '@dspace/core/browse/browse.service';
1313
import { BrowseDefinitionDataService } from '@dspace/core/browse/browse-definition-data.service';
14+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
1415
import { ActivatedRouteStub } from '@dspace/core/testing/active-router.stub';
1516
import { BrowseDefinitionDataServiceStub } from '@dspace/core/testing/browse-definition-data-service.stub';
1617
import { BrowseServiceStub } from '@dspace/core/testing/browse-service.stub';
@@ -30,6 +31,9 @@ let fixture: ComponentFixture<ItemPageAuthorFieldComponent>;
3031

3132
const mockFields = ['dc.contributor.author', 'dc.creator', 'dc.contributor'];
3233
const mockValue = 'test value';
34+
const vocabularyServiceMock = {
35+
getPublicVocabularyEntryByID: jasmine.createSpy('getPublicVocabularyEntryByID'),
36+
};
3337

3438
describe('ItemPageAuthorFieldComponent', () => {
3539
beforeEach(waitForAsync(() => {
@@ -45,6 +49,7 @@ describe('ItemPageAuthorFieldComponent', () => {
4549
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
4650
{ provide: BrowseService, useValue: BrowseServiceStub },
4751
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub() },
52+
{ provide: VocabularyService, useValue: vocabularyServiceMock },
4853
],
4954
schemas: [NO_ERRORS_SCHEMA],
5055
}).overrideComponent(ItemPageAuthorFieldComponent, {

src/app/item-page/simple/field-components/specific-field/date/item-page-date-field.component.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ActivatedRoute } from '@angular/router';
1111
import { APP_CONFIG } from '@dspace/config/app-config.interface';
1212
import { BrowseService } from '@dspace/core/browse/browse.service';
1313
import { BrowseDefinitionDataService } from '@dspace/core/browse/browse-definition-data.service';
14+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
1415
import { ActivatedRouteStub } from '@dspace/core/testing/active-router.stub';
1516
import { BrowseDefinitionDataServiceStub } from '@dspace/core/testing/browse-definition-data-service.stub';
1617
import { BrowseServiceStub } from '@dspace/core/testing/browse-service.stub';
@@ -28,8 +29,12 @@ import { ItemPageDateFieldComponent } from './item-page-date-field.component';
2829
let comp: ItemPageDateFieldComponent;
2930
let fixture: ComponentFixture<ItemPageDateFieldComponent>;
3031

32+
3133
const mockField = 'dc.date.issued';
3234
const mockValue = 'test value';
35+
const vocabularyServiceMock = {
36+
getPublicVocabularyEntryByID: jasmine.createSpy('getPublicVocabularyEntryByID'),
37+
};
3338

3439
describe('ItemPageDateFieldComponent', () => {
3540
beforeEach(waitForAsync(() => {
@@ -45,6 +50,7 @@ describe('ItemPageDateFieldComponent', () => {
4550
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
4651
{ provide: BrowseService, useValue: BrowseServiceStub },
4752
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub() },
53+
{ provide: VocabularyService, useValue: vocabularyServiceMock },
4854
],
4955
schemas: [NO_ERRORS_SCHEMA],
5056
}).overrideComponent(ItemPageDateFieldComponent, {

src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ActivatedRoute } from '@angular/router';
1111
import { APP_CONFIG } from '@dspace/config/app-config.interface';
1212
import { BrowseService } from '@dspace/core/browse/browse.service';
1313
import { BrowseDefinitionDataService } from '@dspace/core/browse/browse-definition-data.service';
14+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
1415
import { ActivatedRouteStub } from '@dspace/core/testing/active-router.stub';
1516
import { BrowseDefinitionDataServiceStub } from '@dspace/core/testing/browse-definition-data-service.stub';
1617
import { BrowseServiceStub } from '@dspace/core/testing/browse-service.stub';
@@ -32,6 +33,10 @@ const mockValue = 'test value';
3233
const mockField = 'dc.test';
3334
const mockLabel = 'test label';
3435
const mockFields = [mockField];
36+
const vocabularyServiceMock = {
37+
getPublicVocabularyEntryByID: jasmine.createSpy('getPublicVocabularyEntryByID'),
38+
};
39+
3540

3641
describe('GenericItemPageFieldComponent', () => {
3742
beforeEach(waitForAsync(() => {
@@ -47,6 +52,7 @@ describe('GenericItemPageFieldComponent', () => {
4752
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
4853
{ provide: BrowseService, useValue: BrowseServiceStub },
4954
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub() },
55+
{ provide: VocabularyService, useValue: vocabularyServiceMock },
5056
],
5157
schemas: [NO_ERRORS_SCHEMA],
5258
}).overrideComponent(GenericItemPageFieldComponent, {

src/app/item-page/simple/field-components/specific-field/img/item-page-img-field.component.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { By } from '@angular/platform-browser';
1010
import { APP_CONFIG } from '@dspace/config/app-config.interface';
1111
import { BrowseService } from '@dspace/core/browse/browse.service';
1212
import { BrowseDefinitionDataService } from '@dspace/core/browse/browse-definition-data.service';
13+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
1314
import { BrowseDefinitionDataServiceStub } from '@dspace/core/testing/browse-definition-data-service.stub';
1415
import { BrowseServiceStub } from '@dspace/core/testing/browse-service.stub';
1516
import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock';
@@ -37,6 +38,10 @@ const mockImg = {
3738
alt: 'item.page.image.alt.ROR',
3839
heightVar: '--ds-item-page-img-field-ror-inline-height',
3940
} as ImageField;
41+
const vocabularyServiceMock = {
42+
getPublicVocabularyEntryByID: jasmine.createSpy('getPublicVocabularyEntryByID'),
43+
};
44+
4045

4146
describe('ItemPageImgFieldComponent', () => {
4247

@@ -52,6 +57,7 @@ describe('ItemPageImgFieldComponent', () => {
5257
{ provide: APP_CONFIG, useValue: environment },
5358
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
5459
{ provide: BrowseService, useValue: BrowseServiceStub },
60+
{ provide: VocabularyService, useValue: vocabularyServiceMock },
5561
],
5662
schemas: [NO_ERRORS_SCHEMA],
5763
})

src/app/item-page/simple/field-components/specific-field/item-page-field.component.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
MetadataMap,
1919
MetadataValue,
2020
} from '@dspace/core/shared/metadata.models';
21+
import { VocabularyService } from '@dspace/core/submission/vocabularies/vocabulary.service';
2122
import { BrowseDefinitionDataServiceStub } from '@dspace/core/testing/browse-definition-data-service.stub';
2223
import { BrowseServiceStub } from '@dspace/core/testing/browse-service.stub';
2324
import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock';
@@ -43,6 +44,9 @@ const mockLabel = 'test label';
4344
const mockAuthorField = 'dc.contributor.author';
4445
const mockDateIssuedField = 'dc.date.issued';
4546
const mockFields = [mockField, mockAuthorField, mockDateIssuedField];
47+
const vocabularyServiceMock = {
48+
getPublicVocabularyEntryByID: jasmine.createSpy('getPublicVocabularyEntryByID'),
49+
};
4650

4751
describe('ItemPageFieldComponent', () => {
4852

@@ -70,6 +74,7 @@ describe('ItemPageFieldComponent', () => {
7074
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
7175
{ provide: BrowseService, useValue: BrowseServiceStub },
7276
{ provide: MathService, useValue: {} },
77+
{ provide: VocabularyService, useValue: vocabularyServiceMock },
7378
],
7479
schemas: [NO_ERRORS_SCHEMA],
7580
}).overrideComponent(ItemPageFieldComponent, {

0 commit comments

Comments
 (0)