Skip to content

Commit e0393ba

Browse files
authored
Retrieve configurable CC license fields from the back-end in DSpace 9.0 (DSpace#3165)
* Make the CC license field component configurable * Retrieve the configuration values from the backend... ... rather than from a frontend configuration * Remove unused imports * Restore configurations other than uri and license fields They were deleted by mistake * Add missing example config * Delete config properties that are no longer needed * Fix lint issues * Add missing injection token * Fex minor changes * Restore the Input properties that allow configuring the license URI and name fields from a template * Update test cases using a stub configuration data service * Fix lint issues * Use optional chaining, in case the property is not exposed * Reformat some code to ease code review * Declare static constant at the beginning of the class * Fix trailing spaces * Initialize property in declaration * Multiple changes * Move all properties dependent of the CC URI field and CC name field to asyncs (since this configuration must be retrieved also asynchronously from the backend) * Refactor the regex, and create a public static method (that may be used to externally check if a license is a CC license). * Update test cases * Move `ccLicense` inside the `item`configuration key * Change `variant` to `position` * Make variant property mandatory (since it should be set depending on where the field is placed) * Finish moving the ccLicense config key into the item key * Allow rendering only the icon * Fix lint errors * Fix lint errors * Fix test cases to reflect new disclaimer behavior * Fix missing comma * Remove look & feel configuration options * Remove unused import * Don't subscribe multiple type to same asyncs * Set type for non-initialiazed properties * Refactor CC license function to a dedicated utility file * Do not use manual subscribes * Reorder imports * Remove unused method * Bugfix: check the right property
1 parent ec6940a commit e0393ba

5 files changed

Lines changed: 141 additions & 45 deletions

File tree

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
1+
@let uri = (uri$ | async);
2+
@let name = (name$ | async);
13
@if (uri && name) {
24
<div class="item-page-field">
3-
<ds-metadata-field-wrapper [label]="'item.page.cc.license.title' | translate">
5+
<ds-metadata-field-wrapper [label]="'item.page.cc.license.title' | translate" [hideIfNoTextContent]="false">
46
<div [ngClass]="{'row': variant === 'full', 'col': variant === 'small'}">
57
<!-- 'img' tag is not rendered if any errors occurs when loading it -->
68
@if (showImage) {
79
<div [ngClass]="{'col-auto': variant === 'full', 'row': variant === 'small'}"
810
style="align-content: center;"
911
>
1012
<a [href]="uri" target="_blank" class="link-anchor dont-break-out ds-simple-metadata-link">
11-
<img (error)="showImage = false" [src]="imgSrc" [alt]="name" class="cc-image"
12-
[ngStyle]="{
13-
'width': 'var(--ds-thumbnail-max-width)',
14-
'margin-bottom': variant === 'small'? '1ch' : '0',
15-
}"
13+
<img (error)="showImage = false" [src]="imgSrc$ | async" [alt]="name" class="cc-image"
14+
[ngStyle]="{
15+
'width': 'var(--ds-thumbnail-max-width)',
16+
'margin-bottom': variant === 'small'? '1ch' : '0',
17+
}"
1618
/>
1719
</a>
1820
</div>
1921
}
2022
<!-- CC name is always displayed if the image fails to load -->
21-
<div [ngClass]="{ 'row': variant === 'small', 'col': variant === 'full' }">
22-
<span>
23-
{{ variant === 'full' && showDisclaimer ? ('item.page.cc.license.disclaimer' | translate) : '' }}
24-
@if (showName || !showImage) {
23+
@if (showName || !showImage) {
24+
<div [ngClass]="{ 'row': variant === 'small', 'col': variant === 'full' }">
25+
<span>
26+
{{ showDisclaimer ? ('item.page.cc.license.disclaimer' | translate) : '' }}
2527
<a [href]="uri" target="_blank" id="cc-name">{{ name }}</a>
26-
}
27-
</span>
28-
</div>
28+
</span>
29+
</div>
30+
}
2931
</div>
3032
</ds-metadata-field-wrapper>
3133
</div>
3234
}
35+

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

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import {
1212
TranslateLoader,
1313
TranslateModule,
1414
} from '@ngx-translate/core';
15+
import { ConfigurationDataService } from 'src/app/core/data/configuration-data.service';
16+
import { ConfigurationProperty } from 'src/app/core/shared/configuration-property.model';
1517
import { Item } from 'src/app/core/shared/item.model';
1618
import {
1719
MetadataMap,
1820
MetadataValue,
1921
} from 'src/app/core/shared/metadata.models';
2022
import { createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils';
23+
import { ConfigurationDataServiceStub } from 'src/app/shared/testing/configuration-data.service.stub';
2124
import { createPaginatedList } from 'src/app/shared/testing/utils.test';
2225

2326
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
@@ -57,6 +60,7 @@ const testCases: TestCase[] = [
5760
{
5861
testInstance: {
5962
metadata: { 'dc.rights.uri': undefined, 'dc.rights': undefined },
63+
componentInputs: { variant: 'small' },
6064
},
6165
expected: {
6266
render: false,
@@ -68,6 +72,7 @@ const testCases: TestCase[] = [
6872
{
6973
testInstance: {
7074
metadata: { 'dc.rights.uri': null, 'dc.rights': null },
75+
componentInputs: { variant: 'small' },
7176
},
7277
expected: {
7378
render: false,
@@ -79,6 +84,7 @@ const testCases: TestCase[] = [
7984
{
8085
testInstance: {
8186
metadata: { 'dc.rights.uri': 'https://creativecommons.org/licenses/by/4.0', 'dc.rights': null },
87+
componentInputs: { variant: 'small' },
8288
},
8389
expected: {
8490
render: false,
@@ -90,6 +96,7 @@ const testCases: TestCase[] = [
9096
{
9197
testInstance: {
9298
metadata: { 'dc.rights.uri': null, 'dc.rights': licenseNameMock },
99+
componentInputs: { variant: 'small' },
93100
},
94101
expected: {
95102
render: false,
@@ -101,23 +108,25 @@ const testCases: TestCase[] = [
101108
{
102109
testInstance: {
103110
metadata: { 'dc.rights.uri': 'https://creativecommons.org/licenses/by/4.0', 'dc.rights': licenseNameMock },
111+
componentInputs: { variant: 'small' },
104112
},
105113
expected: {
106114
render: true,
107115
showName: true,
108116
showImage: true,
109-
showDisclaimer: false,
117+
showDisclaimer: true,
110118
},
111119
},
112120
{
113121
testInstance: {
114122
metadata: { 'dc.rights.uri': 'https://creativecommons.org/', 'dc.rights': licenseNameMock },
123+
componentInputs: { variant: 'small' },
115124
},
116125
expected: {
117126
render: true,
118127
showName: true,
119128
showImage: false,
120-
showDisclaimer: false,
129+
showDisclaimer: true,
121130
},
122131
},
123132
{
@@ -135,19 +144,31 @@ const testCases: TestCase[] = [
135144
{
136145
testInstance: {
137146
metadata: { 'dc.rights.uri': 'https://creativecommons.org/', 'dc.rights': licenseNameMock },
138-
componentInputs: { showName: false },
147+
componentInputs: { variant: 'small', showName: false },
139148
},
140149
expected: {
141150
render: true,
142151
showName: true,
143152
showImage: false,
144-
showDisclaimer: false,
153+
showDisclaimer: true,
154+
},
155+
},
156+
{
157+
testInstance: {
158+
metadata: { 'dc.rights.uri': 'https://creativecommons.org/', 'dc.rights': licenseNameMock },
159+
componentInputs: { variant: 'full', showName: false },
160+
},
161+
expected: {
162+
render: true,
163+
showName: true,
164+
showImage: false,
165+
showDisclaimer: true,
145166
},
146167
},
147168
{
148169
testInstance: {
149170
metadata: { 'dc.rights.uri': 'https://creativecommons.org/licenses/by/4.0', 'dc.rights': licenseNameMock },
150-
componentInputs: { showName: false },
171+
componentInputs: { variant: 'small', showName: false },
151172
},
152173
expected: {
153174
render: true,
@@ -202,8 +223,22 @@ function configureFixture(
202223

203224
describe('ItemPageCcLicenseFieldComponent', () => {
204225
let fixture: ComponentFixture<ItemPageCcLicenseFieldComponent>;
226+
let configurationDataService = new ConfigurationDataServiceStub();
205227

206228
beforeEach(waitForAsync(() => {
229+
configurationDataService.findByPropertyName = jasmine.createSpy()
230+
.withArgs('cc.license.name').and.returnValue(createSuccessfulRemoteDataObject$({
231+
... new ConfigurationProperty(),
232+
name: 'cc.license.name',
233+
values: [ 'dc.rights' ],
234+
},
235+
))
236+
.withArgs('cc.license.uri').and.returnValue(createSuccessfulRemoteDataObject$({
237+
... new ConfigurationProperty(),
238+
name: 'cc.license.uri',
239+
values: [ 'dc.rights.uri' ],
240+
},
241+
));
207242
void TestBed.configureTestingModule({
208243
imports: [
209244
TranslateModule.forRoot({
@@ -214,7 +249,10 @@ describe('ItemPageCcLicenseFieldComponent', () => {
214249
}),
215250
ItemPageCcLicenseFieldComponent,
216251
],
217-
providers: [{ provide: APP_CONFIG, useValue: environment }],
252+
providers: [
253+
{ provide: APP_CONFIG, useValue: environment },
254+
{ provide: ConfigurationDataService, useValue: configurationDataService },
255+
],
218256
schemas: [NO_ERRORS_SCHEMA],
219257
})
220258
.overrideComponent(ItemPageCcLicenseFieldComponent, {
@@ -282,14 +320,13 @@ describe('ItemPageCcLicenseFieldComponent', () => {
282320
it('should show or not CC license disclaimer',
283321
() => {
284322
const disclaimerEl = fixture.debugElement.query(By.css('span'));
285-
if (testCase.expected.showDisclaimer) {
286-
expect(disclaimerEl).toBeTruthy();
287-
expect(disclaimerEl.nativeElement.innerHTML).toContain('item.page.cc.license.disclaimer');
288-
} else if (testCase.expected.render) {
289-
expect(disclaimerEl).toBeTruthy();
290-
expect(disclaimerEl.nativeElement.innerHTML).not.toContain('item.page.cc.license.disclaimer');
291-
} else {
292-
expect(disclaimerEl).toBeFalsy();
323+
expect(Boolean(disclaimerEl)).toBe(testCase.expected.showName);
324+
if (testCase.expected.showName) {
325+
if (testCase.expected.showDisclaimer) {
326+
expect(disclaimerEl.nativeElement.innerHTML).toContain('item.page.cc.license.disclaimer');
327+
} else {
328+
expect(disclaimerEl.nativeElement.innerHTML).not.toContain('item.page.cc.license.disclaimer');
329+
}
293330
}
294331
},
295332
);
Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
AsyncPipe,
23
NgClass,
34
NgStyle,
45
} from '@angular/common';
@@ -8,19 +9,34 @@ import {
89
OnInit,
910
} from '@angular/core';
1011
import { TranslateModule } from '@ngx-translate/core';
12+
import {
13+
map,
14+
Observable,
15+
of,
16+
} from 'rxjs';
17+
import { ConfigurationDataService } from 'src/app/core/data/configuration-data.service';
18+
import { ConfigurationProperty } from 'src/app/core/shared/configuration-property.model';
1119
import { Item } from 'src/app/core/shared/item.model';
20+
import {
21+
getFirstCompletedRemoteData,
22+
getRemoteDataPayload,
23+
} from 'src/app/core/shared/operators';
24+
import { hasValue } from 'src/app/shared/empty.util';
1225
import { MetadataFieldWrapperComponent } from 'src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component';
26+
import { parseCcCode } from 'src/app/shared/utils/license.utils';
27+
1328

1429
@Component({
1530
selector: 'ds-item-page-cc-license-field',
1631
templateUrl: './item-page-cc-license-field.component.html',
1732
standalone: true,
18-
imports: [NgClass, NgStyle, TranslateModule, MetadataFieldWrapperComponent],
33+
imports: [AsyncPipe, NgClass, NgStyle, TranslateModule, MetadataFieldWrapperComponent],
1934
})
2035
/**
2136
* Displays the item's Creative Commons license image in it's simple item page
2237
*/
2338
export class ItemPageCcLicenseFieldComponent implements OnInit {
39+
2440
/**
2541
* The item to display the CC license image for
2642
*/
@@ -33,16 +49,14 @@ export class ItemPageCcLicenseFieldComponent implements OnInit {
3349
@Input() variant?: 'small' | 'full' = 'small';
3450

3551
/**
36-
* Filed name containing the CC license URI, as configured in the back-end, in the 'dspace.cfg' file, property
37-
* 'cc.license.uri'
52+
* Field name containing the CC license URI
3853
*/
39-
@Input() ccLicenseUriField? = 'dc.rights.uri';
54+
@Input() ccLicenseUriField?: string;
4055

4156
/**
42-
* Filed name containing the CC license name, as configured in the back-end, in the 'dspace.cfg' file, property
43-
* 'cc.license.name'
57+
* Field name containing the CC license name
4458
*/
45-
@Input() ccLicenseNameField? = 'dc.rights';
59+
@Input() ccLicenseNameField?: string;
4660

4761
/**
4862
* Shows the CC license name with the image. Always show if image fails to load
@@ -54,19 +68,47 @@ export class ItemPageCcLicenseFieldComponent implements OnInit {
5468
*/
5569
@Input() showDisclaimer? = true;
5670

57-
uri: string;
58-
name: string;
71+
5972
showImage = true;
60-
imgSrc: string;
6173

62-
ngOnInit() {
63-
this.uri = this.item.firstMetadataValue(this.ccLicenseUriField);
64-
this.name = this.item.firstMetadataValue(this.ccLicenseNameField);
74+
name$: Observable<string>;
75+
uri$: Observable<string>;
76+
imgSrc$: Observable<string>;
77+
78+
79+
constructor(
80+
protected configService: ConfigurationDataService,
81+
) {
82+
}
83+
84+
ngOnInit(): void {
85+
if (hasValue(this.ccLicenseNameField)) {
86+
this.name$ = of(this.item.firstMetadataValue(this.ccLicenseNameField));
87+
} else {
88+
this.name$ = this.configService.findByPropertyName('cc.license.name').pipe(
89+
getFirstCompletedRemoteData(),
90+
getRemoteDataPayload(),
91+
map((configurationProperty: ConfigurationProperty) => configurationProperty?.values?.[0]),
92+
map((metadataField: string) => hasValue(metadataField) ? metadataField : 'dc.rights'),
93+
map((metadataField: string) => this.item.firstMetadataValue(metadataField)),
94+
);
95+
}
96+
97+
if (hasValue(this.ccLicenseUriField)) {
98+
this.uri$ = of(this.item.firstMetadataValue(this.ccLicenseUriField));
99+
} else {
100+
this.uri$ = this.configService.findByPropertyName('cc.license.uri').pipe(
101+
getFirstCompletedRemoteData(),
102+
getRemoteDataPayload(),
103+
map((configurationProperty: ConfigurationProperty) => configurationProperty?.values?.[0]),
104+
map((metadataField: string) => hasValue(metadataField) ? metadataField : 'dc.rights.uri'),
105+
map((metadataField: string) => this.item.firstMetadataValue(metadataField)),
106+
);
107+
}
65108

66-
// Extracts the CC license code from the URI
67-
const regex = /.*creativecommons.org\/(licenses|publicdomain)\/([^/]+)/gm;
68-
const matches = regex.exec(this.uri ?? '') ?? [];
69-
const ccCode = matches.length > 2 ? matches[2] : null;
70-
this.imgSrc = ccCode ? `assets/images/cc-licenses/${ccCode}.png` : null;
109+
this.imgSrc$ = this.uri$.pipe(
110+
map((uri: string) => parseCcCode(uri)),
111+
map((ccCode: string) => ccCode ? `assets/images/cc-licenses/${ccCode}.png` : null),
112+
);
71113
}
72114
}

src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
Observable,
2222
of,
2323
} from 'rxjs';
24+
import { ConfigurationDataService } from 'src/app/core/data/configuration-data.service';
25+
import { ConfigurationDataServiceStub } from 'src/app/shared/testing/configuration-data.service.stub';
2426

2527
import { APP_CONFIG } from '../../../../../config/app-config.interface';
2628
import { environment } from '../../../../../environments/environment.test';
@@ -88,6 +90,7 @@ function getItem(metadata: MetadataMap) {
8890
describe('UntypedItemComponent', () => {
8991
let comp: UntypedItemComponent;
9092
let fixture: ComponentFixture<UntypedItemComponent>;
93+
let configurationDataService = new ConfigurationDataServiceStub();
9194

9295
beforeEach(waitForAsync(() => {
9396
const mockBitstreamDataService = {
@@ -130,6 +133,7 @@ describe('UntypedItemComponent', () => {
130133
{ provide: ItemVersionsSharedService, useValue: {} },
131134
{ provide: RouteService, useValue: mockRouteService },
132135
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
136+
{ provide: ConfigurationDataService, useValue: configurationDataService },
133137
{ provide: APP_CONFIG, useValue: environment },
134138
],
135139
schemas: [NO_ERRORS_SCHEMA],
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Parse a URI an return its CC code. URIs pointing to non-CC licenses will return null.
3+
* @param uri
4+
* @returns the CC code or null if uri is not a valid CC URI
5+
*/
6+
export function parseCcCode(uri: string): string {
7+
const regex = /.*creativecommons.org\/(licenses|publicdomain)\/([^/]+)/gm;
8+
const matches = regex.exec(uri ?? '') ?? [];
9+
return matches.length > 2 ? matches[2] : null;
10+
}

0 commit comments

Comments
 (0)