From fffc43f443ca3bd72ee00cca1aeeb96887235b9d Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 7 Dec 2021 10:14:22 +0100 Subject: [PATCH 001/157] 85451: Add a button to export search results as CSV --- .../search-export-csv.component.html | 7 + .../search-export-csv.component.scss | 4 + .../search-export-csv.component.spec.ts | 179 ++++++++++++++++++ .../search-export-csv.component.ts | 102 ++++++++++ .../search-results.component.html | 3 + src/app/shared/shared.module.ts | 2 + src/assets/i18n/en.json5 | 5 + 7 files changed, 302 insertions(+) create mode 100644 src/app/shared/search/search-export-csv/search-export-csv.component.html create mode 100644 src/app/shared/search/search-export-csv/search-export-csv.component.scss create mode 100644 src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts create mode 100644 src/app/shared/search/search-export-csv/search-export-csv.component.ts diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.html b/src/app/shared/search/search-export-csv/search-export-csv.component.html new file mode 100644 index 0000000000..7bf8704300 --- /dev/null +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.scss b/src/app/shared/search/search-export-csv/search-export-csv.component.scss new file mode 100644 index 0000000000..4b0ab3c44a --- /dev/null +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.scss @@ -0,0 +1,4 @@ +.export-button { + background: var(--ds-admin-sidebar-bg); + border-color: var(--ds-admin-sidebar-bg); +} \ No newline at end of file diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts new file mode 100644 index 0000000000..f8dc089c6a --- /dev/null +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts @@ -0,0 +1,179 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { of as observableOf } from 'rxjs'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { SearchExportCsvComponent } from './search-export-csv.component'; +import { ScriptDataService } from '../../../core/data/processes/script-data.service'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { Script } from '../../../process-page/scripts/script.model'; +import { Process } from '../../../process-page/processes/process.model'; +import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { Router } from '@angular/router'; +import { By } from '@angular/platform-browser'; +import { PaginatedSearchOptions } from '../paginated-search-options.model'; +import { SearchFilter } from '../search-filter.model'; +import { getProcessDetailRoute } from '../../../process-page/process-page-routing.paths'; + +describe('SearchExportCsvComponent', () => { + let component: SearchExportCsvComponent; + let fixture: ComponentFixture; + + let scriptDataService: ScriptDataService; + let authorizationDataService: AuthorizationDataService; + let notificationsService; + let router; + + const script = Object.assign(new Script(), {id: 'metadata-export-search', name: 'metadata-export-search'}); + const process = Object.assign(new Process(), {processId: 5, scriptName: 'metadata-export-search'}); + + const searchConfig = new PaginatedSearchOptions({ + configuration: 'test-configuration', + scope: 'test-scope', + query: 'test-query', + filters: [ + new SearchFilter('f.filter1', ['filter1value1,equals', 'filter1value2,equals']), + new SearchFilter('f.filter2', ['filter2value1,contains']) + ] + }); + + function initBeforeEachAsync() { + scriptDataService = jasmine.createSpyObj('scriptDataService', { + findById: createSuccessfulRemoteDataObject$(script), + invoke: createSuccessfulRemoteDataObject$(process) + }); + authorizationDataService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true) + }); + + notificationsService = new NotificationsServiceStub(); + + router = jasmine.createSpyObj('authorizationService', ['navigateByUrl']); + TestBed.configureTestingModule({ + declarations: [SearchExportCsvComponent], + imports: [TranslateModule.forRoot(), NgbModule], + providers: [ + {provide: ScriptDataService, useValue: scriptDataService}, + {provide: AuthorizationDataService, useValue: authorizationDataService}, + {provide: NotificationsService, useValue: notificationsService}, + {provide: Router, useValue: router}, + ] + }).compileComponents(); + } + + function initBeforeEach() { + fixture = TestBed.createComponent(SearchExportCsvComponent); + component = fixture.componentInstance; + component.searchConfig = searchConfig; + fixture.detectChanges(); + } + + describe('init', () => { + describe('comp', () => { + beforeEach(waitForAsync(() => { + initBeforeEachAsync(); + })); + beforeEach(() => { + initBeforeEach(); + }); + it('should init the comp', () => { + expect(component).toBeTruthy(); + }); + }); + describe('when the user is an admin and the metadata-export-search script is present ', () => { + beforeEach(waitForAsync(() => { + initBeforeEachAsync(); + })); + beforeEach(() => { + initBeforeEach(); + }); + it('should add the button', () => { + const debugElement = fixture.debugElement.query(By.css('button.export-button')); + expect(debugElement).toBeDefined(); + }); + }); + describe('when the user is not an admin', () => { + beforeEach(waitForAsync(() => { + initBeforeEachAsync(); + (authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + })); + beforeEach(() => { + initBeforeEach(); + }); + it('should not add the button', () => { + const debugElement = fixture.debugElement.query(By.css('button.export-button')); + expect(debugElement).toBeNull(); + }); + }); + describe('when the metadata-export-search script is not present', () => { + beforeEach(waitForAsync(() => { + initBeforeEachAsync(); + (scriptDataService.findById as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Not found', 404)); + })); + beforeEach(() => { + initBeforeEach(); + }); + it('should should not add the button', () => { + const debugElement = fixture.debugElement.query(By.css('button.export-button')); + expect(debugElement).toBeNull(); + }); + }); + }); + describe('export', () => { + beforeEach(waitForAsync(() => { + initBeforeEachAsync(); + })); + beforeEach(() => { + initBeforeEach(); + }); + it('should call the invoke script method with the correct parameters', () => { + component.export(); + expect(scriptDataService.invoke).toHaveBeenCalledWith('metadata-export-search', + [ + {name: '-q', value: searchConfig.query}, + {name: '-s', value: searchConfig.scope}, + {name: '-c', value: searchConfig.configuration}, + {name: '-f', value: 'filter1,equals=filter1value1,filter1value2'}, + {name: '-f', value: 'filter2,contains=filter2value1'}, + ], []); + + component.searchConfig = null; + fixture.detectChanges(); + + component.export(); + expect(scriptDataService.invoke).toHaveBeenCalledWith('metadata-export-search', [], []); + + }); + it('should show a success message when the script was invoked successfully and redirect to the corresponding process page', () => { + component.export(); + + expect(notificationsService.success).toHaveBeenCalled(); + expect(router.navigateByUrl).toHaveBeenCalledWith(getProcessDetailRoute(process.processId)); + }); + it('should show an error message when the script was not invoked successfully and stay on the current page', () => { + (scriptDataService.invoke as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Error', 500)); + + component.export(); + + expect(notificationsService.error).toHaveBeenCalled(); + expect(router.navigateByUrl).not.toHaveBeenCalled(); + }); + }); + describe('clicking the button', () => { + beforeEach(waitForAsync(() => { + initBeforeEachAsync(); + })); + beforeEach(() => { + initBeforeEach(); + }); + it('should trigger the export function', () => { + spyOn(component, 'export'); + + const debugElement = fixture.debugElement.query(By.css('button.export-button')); + debugElement.triggerEventHandler('click', null); + + expect(component.export).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.ts new file mode 100644 index 0000000000..a1bdf1c7e1 --- /dev/null +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.ts @@ -0,0 +1,102 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { PaginatedSearchOptions } from '../paginated-search-options.model'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { ScriptDataService } from '../../../core/data/processes/script-data.service'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { map, tap } from 'rxjs/operators'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { hasValue, isNotEmpty } from '../../empty.util'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Process } from '../../../process-page/processes/process.model'; +import { getProcessDetailRoute } from '../../../process-page/process-page-routing.paths'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'ds-search-export-csv', + styleUrls: ['./search-export-csv.component.scss'], + templateUrl: './search-export-csv.component.html', +}) +/** + * Display a button to export the current search results as csv + */ +export class SearchExportCsvComponent implements OnInit { + + /** + * The current configuration of the search + */ + @Input() searchConfig: PaginatedSearchOptions; + + /** + * Observable used to determine whether the button should be shown + */ + shouldShowButton$: Observable; + + /** + * The message key used for the tooltip of the button + */ + tooltipMsg = 'metadata-export-search.tooltip'; + + constructor(private scriptDataService: ScriptDataService, + private authorizationDataService: AuthorizationDataService, + private notificationsService: NotificationsService, + private translateService: TranslateService, + private router: Router + ) { + } + + ngOnInit(): void { + const scriptExists$ = this.scriptDataService.findById('metadata-export-search').pipe( + getFirstCompletedRemoteData(), + map((rd) => rd.isSuccess && hasValue(rd.payload)) + ); + + const isAuthorized$ = this.authorizationDataService.isAuthorized(FeatureID.AdministratorOf); + + this.shouldShowButton$ = observableCombineLatest([scriptExists$, isAuthorized$]).pipe( + tap((v) => console.log('showbutton', v)), + map(([scriptExists, isAuthorized]: [boolean, boolean]) => scriptExists && isAuthorized) + ); + } + + /** + * Start the export of the items based on the current search configuration + */ + export() { + const parameters = []; + if (hasValue(this.searchConfig)) { + if (isNotEmpty(this.searchConfig.query)) { + parameters.push({name: '-q', value: this.searchConfig.query}); + } + if (isNotEmpty(this.searchConfig.scope)) { + parameters.push({name: '-s', value: this.searchConfig.scope}); + } + if (isNotEmpty(this.searchConfig.configuration)) { + parameters.push({name: '-c', value: this.searchConfig.configuration}); + } + if (isNotEmpty(this.searchConfig.filters)) { + this.searchConfig.filters.forEach((filter) => { + let operator = 'equals'; + if (hasValue(filter.values)) { + operator = filter.values[0].substring(filter.values[0].indexOf(',') + 1); + } + const filterValue = `${filter.key.substring(2)},${operator}=${filter.values.map((v) => v.substring(0, v.indexOf(','))).join()}`; + parameters.push({name: '-f', value: filterValue}); + }); + } + } + + this.scriptDataService.invoke('metadata-export-search', parameters, []).pipe( + getFirstCompletedRemoteData() + ).subscribe((rd: RemoteData) => { + if (rd.hasSucceeded) { + this.notificationsService.success(this.translateService.get('metadata-export-search.submit.success')); + this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId)); + } else { + this.notificationsService.error(this.translateService.get('metadata-export-search.submit.error')); + } + }); + } +} diff --git a/src/app/shared/search/search-results/search-results.component.html b/src/app/shared/search/search-results/search-results.component.html index 4e6bca094e..01c63e9c9e 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,4 +1,7 @@ +

{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}

+ +
Date: Thu, 9 Dec 2021 15:09:46 +0100 Subject: [PATCH 002/157] 85451: Fixed filter syntax --- .../search-export-csv.component.spec.ts | 7 +++++-- .../search-export-csv.component.ts | 20 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts index f8dc089c6a..cb0d7e6266 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts @@ -34,7 +34,8 @@ describe('SearchExportCsvComponent', () => { query: 'test-query', filters: [ new SearchFilter('f.filter1', ['filter1value1,equals', 'filter1value2,equals']), - new SearchFilter('f.filter2', ['filter2value1,contains']) + new SearchFilter('f.filter2', ['filter2value1,contains']), + new SearchFilter('f.filter3', ['[2000 TO 2001]'], 'equals') ] }); @@ -134,8 +135,10 @@ describe('SearchExportCsvComponent', () => { {name: '-q', value: searchConfig.query}, {name: '-s', value: searchConfig.scope}, {name: '-c', value: searchConfig.configuration}, - {name: '-f', value: 'filter1,equals=filter1value1,filter1value2'}, + {name: '-f', value: 'filter1,equals=filter1value1'}, + {name: '-f', value: 'filter1,equals=filter1value2'}, {name: '-f', value: 'filter2,contains=filter2value1'}, + {name: '-f', value: 'filter3,equals=[2000 TO 2001]'}, ], []); component.searchConfig = null; diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.ts index a1bdf1c7e1..d499097bd3 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.ts +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.ts @@ -3,7 +3,7 @@ import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { ScriptDataService } from '../../../core/data/processes/script-data.service'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; -import { map, tap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { hasValue, isNotEmpty } from '../../empty.util'; @@ -56,7 +56,6 @@ export class SearchExportCsvComponent implements OnInit { const isAuthorized$ = this.authorizationDataService.isAuthorized(FeatureID.AdministratorOf); this.shouldShowButton$ = observableCombineLatest([scriptExists$, isAuthorized$]).pipe( - tap((v) => console.log('showbutton', v)), map(([scriptExists, isAuthorized]: [boolean, boolean]) => scriptExists && isAuthorized) ); } @@ -78,12 +77,21 @@ export class SearchExportCsvComponent implements OnInit { } if (isNotEmpty(this.searchConfig.filters)) { this.searchConfig.filters.forEach((filter) => { - let operator = 'equals'; if (hasValue(filter.values)) { - operator = filter.values[0].substring(filter.values[0].indexOf(',') + 1); + filter.values.forEach((value) => { + let operator; + let filterValue; + if (hasValue(filter.operator)) { + operator = filter.operator; + filterValue = value; + } else { + operator = value.substring(value.indexOf(',') + 1); + filterValue = value.substring(0, value.indexOf(',')); + } + const valueToAdd = `${filter.key.substring(2)},${operator}=${filterValue}`; + parameters.push({name: '-f', value: valueToAdd}); + }); } - const filterValue = `${filter.key.substring(2)},${operator}=${filter.values.map((v) => v.substring(0, v.indexOf(','))).join()}`; - parameters.push({name: '-f', value: filterValue}); }); } } From 55431f1e06fb325e477bf98ae226461d5393d9b0 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 18 Mar 2022 12:04:48 +0100 Subject: [PATCH 003/157] 85451: Fix config example issue and author filters --- config/config.example.yml | 2 +- .../search/search-export-csv/search-export-csv.component.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index a066838ee5..ecb2a3cfb9 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -16,7 +16,7 @@ ui: # The REST API server settings # NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. - +rest: ssl: true host: api7.dspace.org port: 443 diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.ts index 78e352e556..6ad105342f 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.ts +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.ts @@ -85,8 +85,8 @@ export class SearchExportCsvComponent implements OnInit { operator = filter.operator; filterValue = value; } else { - operator = value.substring(value.indexOf(',') + 1); - filterValue = value.substring(0, value.indexOf(',')); + operator = value.substring(value.lastIndexOf(',') + 1); + filterValue = value.substring(0, value.lastIndexOf(',')); } const valueToAdd = `${filter.key.substring(2)},${operator}=${filterValue}`; parameters.push({name: '-f', value: valueToAdd}); From 1697d1396e2643a201edfd7a0470393e02a0c156 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Mon, 21 Mar 2022 10:23:44 +0100 Subject: [PATCH 004/157] 88599: Support versioning of entities --- .../item-pages/journal-issue/journal-issue.component.html | 3 +++ .../item-pages/journal-issue/journal-issue.component.ts | 4 ++-- .../item-pages/journal-volume/journal-volume.component.html | 3 +++ .../item-pages/journal-volume/journal-volume.component.ts | 4 ++-- .../item-pages/journal/journal.component.html | 3 +++ .../journal-entities/item-pages/journal/journal.component.ts | 4 ++-- .../item-pages/org-unit/org-unit.component.html | 3 +++ .../item-pages/org-unit/org-unit.component.ts | 4 ++-- .../research-entities/item-pages/person/person.component.html | 3 +++ .../research-entities/item-pages/person/person.component.ts | 4 ++-- .../item-pages/project/project.component.html | 3 +++ .../research-entities/item-pages/project/project.component.ts | 4 ++-- .../simple/item-types/publication/publication.component.html | 3 +++ .../simple/item-types/publication/publication.component.ts | 4 ++-- 14 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html index 5847be7dd2..9b96d2a1b2 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html @@ -3,6 +3,9 @@ {{'journalissue.page.titleprefix' | translate}}
+
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts index f96379dafd..f5e9dc9b2b 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts +++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; -import { ItemComponent } from '../../../../item-page/simple/item-types/shared/item.component'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { VersionedItemComponent } from '../../../../item-page/simple/item-types/versioned-item/versioned-item.component'; @listableObjectComponent('JournalIssue', ViewMode.StandalonePage) @Component({ @@ -12,5 +12,5 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh /** * The component for displaying metadata and relations of an item of the type Journal Issue */ -export class JournalIssueComponent extends ItemComponent { +export class JournalIssueComponent extends VersionedItemComponent { } diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html index a0a25766ef..07ecb33ced 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html @@ -3,6 +3,9 @@ {{'journalvolume.page.titleprefix' | translate}}
+
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts index eeb93e7070..cc09be7959 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts +++ b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; -import { ItemComponent } from '../../../../item-page/simple/item-types/shared/item.component'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { VersionedItemComponent } from '../../../../item-page/simple/item-types/versioned-item/versioned-item.component'; @listableObjectComponent('JournalVolume', ViewMode.StandalonePage) @Component({ @@ -12,5 +12,5 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh /** * The component for displaying metadata and relations of an item of the type Journal Volume */ -export class JournalVolumeComponent extends ItemComponent { +export class JournalVolumeComponent extends VersionedItemComponent { } diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index af3ac85959..b1b35db724 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -3,6 +3,9 @@ {{'journal.page.titleprefix' | translate}}
+
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts index 3fe0903145..acfd31d8f6 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; -import { ItemComponent } from '../../../../item-page/simple/item-types/shared/item.component'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { VersionedItemComponent } from '../../../../item-page/simple/item-types/versioned-item/versioned-item.component'; @listableObjectComponent('Journal', ViewMode.StandalonePage) @Component({ @@ -12,5 +12,5 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh /** * The component for displaying metadata and relations of an item of the type Journal */ -export class JournalComponent extends ItemComponent { +export class JournalComponent extends VersionedItemComponent { } diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index c9ea8fb549..7c8ce67b88 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -3,6 +3,9 @@ {{'orgunit.page.titleprefix' | translate}}
+
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.ts b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.ts index ab756db562..cbf8497f35 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.ts +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; -import { ItemComponent } from '../../../../item-page/simple/item-types/shared/item.component'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { VersionedItemComponent } from '../../../../item-page/simple/item-types/versioned-item/versioned-item.component'; @listableObjectComponent('OrgUnit', ViewMode.StandalonePage) @Component({ @@ -12,5 +12,5 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh /** * The component for displaying metadata and relations of an item of the type Organisation Unit */ -export class OrgUnitComponent extends ItemComponent { +export class OrgUnitComponent extends VersionedItemComponent { } diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index 5c2fd227fd..6e71f775d6 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -3,6 +3,9 @@ {{'person.page.titleprefix' | translate}}
+
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.ts b/src/app/entity-groups/research-entities/item-pages/person/person.component.ts index 8b104cc9b1..ad2863034a 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.ts +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; -import { ItemComponent } from '../../../../item-page/simple/item-types/shared/item.component'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { VersionedItemComponent } from '../../../../item-page/simple/item-types/versioned-item/versioned-item.component'; @listableObjectComponent('Person', ViewMode.StandalonePage) @Component({ @@ -12,5 +12,5 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh /** * The component for displaying metadata and relations of an item of the type Person */ -export class PersonComponent extends ItemComponent { +export class PersonComponent extends VersionedItemComponent { } diff --git a/src/app/entity-groups/research-entities/item-pages/project/project.component.html b/src/app/entity-groups/research-entities/item-pages/project/project.component.html index 8f2ff6adcd..243dae8b43 100644 --- a/src/app/entity-groups/research-entities/item-pages/project/project.component.html +++ b/src/app/entity-groups/research-entities/item-pages/project/project.component.html @@ -3,6 +3,9 @@ {{'project.page.titleprefix' | translate}}
+
diff --git a/src/app/entity-groups/research-entities/item-pages/project/project.component.ts b/src/app/entity-groups/research-entities/item-pages/project/project.component.ts index e53d8afd69..066427fc0d 100644 --- a/src/app/entity-groups/research-entities/item-pages/project/project.component.ts +++ b/src/app/entity-groups/research-entities/item-pages/project/project.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; -import { ItemComponent } from '../../../../item-page/simple/item-types/shared/item.component'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { VersionedItemComponent } from '../../../../item-page/simple/item-types/versioned-item/versioned-item.component'; @listableObjectComponent('Project', ViewMode.StandalonePage) @Component({ @@ -12,5 +12,5 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh /** * The component for displaying metadata and relations of an item of the type Project */ -export class ProjectComponent extends ItemComponent { +export class ProjectComponent extends VersionedItemComponent { } diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index bace9fcd0a..667dee96f5 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -12,6 +12,9 @@ {{'publication.page.titleprefix' | translate}}
+
diff --git a/src/app/item-page/simple/item-types/publication/publication.component.ts b/src/app/item-page/simple/item-types/publication/publication.component.ts index 5ace8d0473..ba5037a104 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.ts +++ b/src/app/item-page/simple/item-types/publication/publication.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { ItemComponent } from '../shared/item.component'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { VersionedItemComponent } from '../versioned-item/versioned-item.component'; /** * Component that represents a publication Item page @@ -14,6 +14,6 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh templateUrl: './publication.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class PublicationComponent extends ItemComponent { +export class PublicationComponent extends VersionedItemComponent { } From 2b17bb8f1e5ebdb1c1bcb49beffcab7ff265133f Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 7 Apr 2022 11:29:22 +0200 Subject: [PATCH 005/157] 89676: Themeable browse-by-page decorator & search-results component --- .../browse-by-switcher/browse-by-decorator.ts | 27 ++++++-- .../browse-by-switcher.component.ts | 6 +- .../themed-search-results.component.ts | 64 +++++++++++++++++++ src/app/shared/search/search.component.html | 4 +- src/app/shared/search/search.module.ts | 4 +- 5 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 src/app/shared/search/search-results/themed-search-results.component.ts diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts index 1ebaa7face..ceb4c6a6c6 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts @@ -1,6 +1,10 @@ import { hasNoValue } from '../../shared/empty.util'; import { InjectionToken } from '@angular/core'; import { GenericConstructor } from '../../core/shared/generic-constructor'; +import { + DEFAULT_THEME, + resolveTheme +} from '../../shared/object-collection/shared/listable-object/listable-object.decorator'; export enum BrowseByDataType { Title = 'title', @@ -10,7 +14,7 @@ export enum BrowseByDataType { export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; -export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType) => GenericConstructor>('getComponentByBrowseByType', { +export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType, theme) => GenericConstructor>('getComponentByBrowseByType', { providedIn: 'root', factory: () => getComponentByBrowseByType }); @@ -20,13 +24,17 @@ const map = new Map(); /** * Decorator used for rendering Browse-By pages by type * @param browseByType The type of page + * @param theme The optional theme for the component */ -export function rendersBrowseBy(browseByType: BrowseByDataType) { +export function rendersBrowseBy(browseByType: BrowseByDataType, theme = DEFAULT_THEME) { return function decorator(component: any) { if (hasNoValue(map.get(browseByType))) { - map.set(browseByType, component); + map.set(browseByType, new Map()); + } + if (hasNoValue(map.get(browseByType).get(theme))) { + map.get(browseByType).set(theme, component); } else { - throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}"`); + throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}" and theme "${theme}"`); } }; } @@ -34,11 +42,16 @@ export function rendersBrowseBy(browseByType: BrowseByDataType) { /** * Get the component used for rendering a Browse-By page by type * @param browseByType The type of page + * @param theme the theme to match */ -export function getComponentByBrowseByType(browseByType) { - const comp = map.get(browseByType); +export function getComponentByBrowseByType(browseByType, theme) { + let themeMap = map.get(browseByType); + if (hasNoValue(themeMap)) { + themeMap = map.get(DEFAULT_BROWSE_BY_TYPE); + } + const comp = resolveTheme(themeMap, theme); if (hasNoValue(comp)) { - map.get(DEFAULT_BROWSE_BY_TYPE); + return themeMap.get(DEFAULT_THEME); } return comp; } diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts index cf4c1d9856..0d3a35bebf 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts @@ -5,6 +5,7 @@ import { map } from 'rxjs/operators'; import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; +import { ThemeService } from '../../shared/theme-support/theme.service'; @Component({ selector: 'ds-browse-by-switcher', @@ -21,7 +22,8 @@ export class BrowseBySwitcherComponent implements OnInit { browseByComponent: Observable; public constructor(protected route: ActivatedRoute, - @Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType) => GenericConstructor) { + protected themeService: ThemeService, + @Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor) { } /** @@ -29,7 +31,7 @@ export class BrowseBySwitcherComponent implements OnInit { */ ngOnInit(): void { this.browseByComponent = this.route.data.pipe( - map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.dataType)) + map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.dataType, this.themeService.getThemeName())) ); } diff --git a/src/app/shared/search/search-results/themed-search-results.component.ts b/src/app/shared/search/search-results/themed-search-results.component.ts new file mode 100644 index 0000000000..19a8fc55e8 --- /dev/null +++ b/src/app/shared/search/search-results/themed-search-results.component.ts @@ -0,0 +1,64 @@ +import { ThemedComponent } from '../../theme-support/themed.component'; +import { SearchResultsComponent, SelectionConfig } from './search-results.component'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { CollectionElementLinkType } from '../../object-collection/collection-element-link.type'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { SearchResult } from '../models/search-result.model'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { PaginatedSearchOptions } from '../models/paginated-search-options.model'; +import { SortOptions } from '../../../core/cache/models/sort-options.model'; +import { ViewMode } from '../../../core/shared/view-mode.model'; +import { Context } from '../../../core/shared/context.model'; +import { ListableObject } from '../../object-collection/shared/listable-object.model'; + +/** + * Themed wrapper for SearchResultsComponent + */ +@Component({ + selector: 'ds-themed-search-results', + styleUrls: [], + templateUrl: '../../theme-support/themed.component.html', +}) +export class ThemedSearchResultsComponent extends ThemedComponent { + protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'deselectObject', 'selectObject']; + + @Input() linkType: CollectionElementLinkType; + + @Input() searchResults: RemoteData>>; + + @Input() searchConfig: PaginatedSearchOptions; + + @Input() sortConfig: SortOptions; + + @Input() viewMode: ViewMode; + + @Input() configuration: string; + + @Input() disableHeader = false; + + @Input() selectable = false; + + @Input() context: Context; + + @Input() hidePaginationDetail = false; + + @Input() selectionConfig: SelectionConfig = null; + + @Output() deselectObject: EventEmitter = new EventEmitter(); + + @Output() selectObject: EventEmitter = new EventEmitter(); + + protected getComponentName(): string { + return 'SearchResultsComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../../themes/${themeName}/app/shared/search/search-results/search-results.component`); + } + + protected importUnthemedComponent(): Promise { + return import('./search-results.component'); + } + +} diff --git a/src/app/shared/search/search.component.html b/src/app/shared/search/search.component.html index 5ca3e925a5..aac77b3570 100644 --- a/src/app/shared/search/search.component.html +++ b/src/app/shared/search/search.component.html @@ -29,7 +29,7 @@ | translate}} - + (selectObject)="selectObject.emit($event)"> diff --git a/src/app/shared/search/search.module.ts b/src/app/shared/search/search.module.ts index 668d260c23..991fad409b 100644 --- a/src/app/shared/search/search.module.ts +++ b/src/app/shared/search/search.module.ts @@ -28,6 +28,7 @@ import { MissingTranslationHelper } from '../translate/missing-translation.helpe import { SharedModule } from '../shared.module'; import { SearchResultsComponent } from './search-results/search-results.component'; import { SearchComponent } from './search.component'; +import { ThemedSearchResultsComponent } from './search-results/themed-search-results.component'; const COMPONENTS = [ SearchComponent, @@ -50,7 +51,8 @@ const COMPONENTS = [ SearchAuthorityFilterComponent, SearchSwitchConfigurationComponent, ConfigurationSearchPageComponent, - ThemedConfigurationSearchPageComponent + ThemedConfigurationSearchPageComponent, + ThemedSearchResultsComponent, ]; const ENTRY_COMPONENTS = [ From 0ed7f2e0aa0d1b43f9ed82f5cadf8cc74c5b7cb6 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 8 Apr 2022 11:12:26 +0200 Subject: [PATCH 006/157] 89676: Test fixes --- .../browse-by-switcher.component.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts index cb82ddb7c4..c2e1c9cb68 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts @@ -4,7 +4,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; -import { BehaviorSubject, of as observableOf } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; +import { ThemeService } from '../../shared/theme-support/theme.service'; describe('BrowseBySwitcherComponent', () => { let comp: BrowseBySwitcherComponent; @@ -44,11 +45,20 @@ describe('BrowseBySwitcherComponent', () => { data }; + let themeService: ThemeService; + let themeName: string; + beforeEach(waitForAsync(() => { + themeName = 'dspace'; + themeService = jasmine.createSpyObj('themeService', { + getThemeName: themeName, + }); + TestBed.configureTestingModule({ declarations: [BrowseBySwitcherComponent], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, + { provide: ThemeService, useValue: themeService }, { provide: BROWSE_BY_COMPONENT_FACTORY, useValue: jasmine.createSpy('getComponentByBrowseByType').and.returnValue(null) } ], schemas: [NO_ERRORS_SCHEMA] @@ -68,7 +78,7 @@ describe('BrowseBySwitcherComponent', () => { }); it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => { - expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType); + expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType, themeName); }); }); }); From e68e605211fa01678eaa4ce1f019ebba5d1777c1 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 14 Apr 2021 15:17:18 +0200 Subject: [PATCH 007/157] [CST-5309] Added LoginOrcid component --- src/app/core/auth/models/auth.method-type.ts | 3 +- src/app/core/auth/models/auth.method.ts | 5 + .../methods/orcid/log-in-orcid.component.html | 3 + .../orcid/log-in-orcid.component.spec.ts | 155 ++++++++++++++++++ .../methods/orcid/log-in-orcid.component.ts | 110 +++++++++++++ src/app/shared/shared.module.ts | 3 + src/assets/i18n/en.json5 | 2 + 7 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 src/app/shared/log-in/methods/orcid/log-in-orcid.component.html create mode 100644 src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts create mode 100644 src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts diff --git a/src/app/core/auth/models/auth.method-type.ts b/src/app/core/auth/models/auth.method-type.ts index 9d999c4c3f..594d6d8b39 100644 --- a/src/app/core/auth/models/auth.method-type.ts +++ b/src/app/core/auth/models/auth.method-type.ts @@ -4,5 +4,6 @@ export enum AuthMethodType { Ldap = 'ldap', Ip = 'ip', X509 = 'x509', - Oidc = 'oidc' + Oidc = 'oidc', + Orcid = 'orcid' } diff --git a/src/app/core/auth/models/auth.method.ts b/src/app/core/auth/models/auth.method.ts index 5a362e8606..0579ae0cd1 100644 --- a/src/app/core/auth/models/auth.method.ts +++ b/src/app/core/auth/models/auth.method.ts @@ -34,6 +34,11 @@ export class AuthMethod { this.location = location; break; } + case 'orcid': { + this.authMethodType = AuthMethodType.Orcid; + this.location = location; + break; + } default: { break; diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html new file mode 100644 index 0000000000..6f5453fd60 --- /dev/null +++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts new file mode 100644 index 0000000000..001f0a4959 --- /dev/null +++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts @@ -0,0 +1,155 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { provideMockStore } from '@ngrx/store/testing'; +import { Store, StoreModule } from '@ngrx/store'; +import { TranslateModule } from '@ngx-translate/core'; + +import { EPerson } from '../../../../core/eperson/models/eperson.model'; +import { EPersonMock } from '../../../testing/eperson.mock'; +import { authReducer } from '../../../../core/auth/auth.reducer'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { AuthServiceStub } from '../../../testing/auth-service.stub'; +import { storeModuleConfig } from '../../../../app.reducer'; +import { AuthMethod } from '../../../../core/auth/models/auth.method'; +import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; +import { LogInOrcidComponent } from './log-in-orcid.component'; +import { NativeWindowService } from '../../../../core/services/window.service'; +import { RouterStub } from '../../../testing/router.stub'; +import { ActivatedRouteStub } from '../../../testing/active-router.stub'; +import { NativeWindowMockFactory } from '../../../mocks/mock-native-window-ref'; +import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; + + +describe('LogInOrcidComponent', () => { + + let component: LogInOrcidComponent; + let fixture: ComponentFixture; + let page: Page; + let user: EPerson; + let componentAsAny: any; + let setHrefSpy; + let orcidBaseUrl; + let location; + let initialState: any; + let hardRedirectService: HardRedirectService; + + beforeEach(() => { + user = EPersonMock; + orcidBaseUrl = 'dspace-rest.test/orcid?redirectUrl='; + location = orcidBaseUrl + 'http://dspace-angular.test/home'; + + hardRedirectService = jasmine.createSpyObj('hardRedirectService', { + getCurrentRoute: {}, + redirect: {} + }); + + initialState = { + core: { + auth: { + authenticated: false, + loaded: false, + blocking: false, + loading: false, + authMethods: [] + } + } + }; + }); + + beforeEach(waitForAsync(() => { + // refine the test module by declaring the test component + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({ auth: authReducer }, storeModuleConfig), + TranslateModule.forRoot() + ], + declarations: [ + LogInOrcidComponent + ], + providers: [ + { provide: AuthService, useClass: AuthServiceStub }, + { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Orcid, location) }, + { provide: 'isStandalonePage', useValue: true }, + { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, + { provide: Router, useValue: new RouterStub() }, + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, + { provide: HardRedirectService, useValue: hardRedirectService }, + provideMockStore({ initialState }), + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA + ] + }) + .compileComponents(); + + })); + + beforeEach(() => { + // create component and test fixture + fixture = TestBed.createComponent(LogInOrcidComponent); + + // get test component from the fixture + component = fixture.componentInstance; + componentAsAny = component; + + // create page + page = new Page(component, fixture); + setHrefSpy = spyOnProperty(componentAsAny._window.nativeWindow.location, 'href', 'set').and.callThrough(); + + }); + + it('should set the properly a new redirectUrl', () => { + const currentUrl = 'http://dspace-angular.test/collections/12345'; + componentAsAny._window.nativeWindow.location.href = currentUrl; + + fixture.detectChanges(); + + expect(componentAsAny.injectedAuthMethodModel.location).toBe(location); + expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl); + + component.redirectToOrcid(); + + expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); + + }); + + it('should not set a new redirectUrl', () => { + const currentUrl = 'http://dspace-angular.test/home'; + componentAsAny._window.nativeWindow.location.href = currentUrl; + + fixture.detectChanges(); + + expect(componentAsAny.injectedAuthMethodModel.location).toBe(location); + expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl); + + component.redirectToOrcid(); + + expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); + + }); + +}); + +/** + * I represent the DOM elements and attach spies. + * + * @class Page + */ +class Page { + + public emailInput: HTMLInputElement; + public navigateSpy: jasmine.Spy; + public passwordInput: HTMLInputElement; + + constructor(private component: LogInOrcidComponent, private fixture: ComponentFixture) { + // use injector to get services + const injector = fixture.debugElement.injector; + const store = injector.get(Store); + + // add spies + this.navigateSpy = spyOn(store, 'dispatch'); + } + +} diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts new file mode 100644 index 0000000000..df234bcbb4 --- /dev/null +++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts @@ -0,0 +1,110 @@ +import { Component, Inject, OnInit, } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { select, Store } from '@ngrx/store'; + +import { renderAuthMethodFor } from '../log-in.methods-decorator'; +import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; +import { AuthMethod } from '../../../../core/auth/models/auth.method'; + +import { CoreState } from '../../../../core/core.reducers'; +import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors'; +import { NativeWindowRef, NativeWindowService } from '../../../../core/services/window.service'; +import { isNotNull, isEmpty } from '../../../empty.util'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; +import { take } from 'rxjs/operators'; +import { URLCombiner } from '../../../../core/url-combiner/url-combiner'; + +@Component({ + selector: 'ds-log-in-orcid', + templateUrl: './log-in-orcid.component.html', +}) +@renderAuthMethodFor(AuthMethodType.Orcid) +export class LogInOrcidComponent implements OnInit { + + /** + * The authentication method data. + * @type {AuthMethod} + */ + public authMethod: AuthMethod; + + /** + * True if the authentication is loading. + * @type {boolean} + */ + public loading: Observable; + + /** + * The orcid authentication location url. + * @type {string} + */ + public location: string; + + /** + * Whether user is authenticated. + * @type {Observable} + */ + public isAuthenticated: Observable; + + /** + * @constructor + * @param {AuthMethod} injectedAuthMethodModel + * @param {boolean} isStandalonePage + * @param {NativeWindowRef} _window + * @param {AuthService} authService + * @param {HardRedirectService} hardRedirectService + * @param {Store} store + */ + constructor( + @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod, + @Inject('isStandalonePage') public isStandalonePage: boolean, + @Inject(NativeWindowService) protected _window: NativeWindowRef, + private authService: AuthService, + private hardRedirectService: HardRedirectService, + private store: Store + ) { + this.authMethod = injectedAuthMethodModel; + } + + ngOnInit(): void { + // set isAuthenticated + this.isAuthenticated = this.store.pipe(select(isAuthenticated)); + + // set loading + this.loading = this.store.pipe(select(isAuthenticationLoading)); + + // set location + this.location = decodeURIComponent(this.injectedAuthMethodModel.location); + + } + + redirectToOrcid() { + + this.authService.getRedirectUrl().pipe(take(1)).subscribe((redirectRoute) => { + if (!this.isStandalonePage) { + redirectRoute = this.hardRedirectService.getCurrentRoute(); + } else if (isEmpty(redirectRoute)) { + redirectRoute = '/'; + } + const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString(); + + let orcidServerUrl = this.location; + const myRegexp = /\?redirectUrl=(.*)/g; + const match = myRegexp.exec(this.location); + const redirectUrlFromServer = (match && match[1]) ? match[1] : null; + + // Check whether the current page is different from the redirect url received from rest + if (isNotNull(redirectUrlFromServer) && redirectUrlFromServer !== correctRedirectUrl) { + // change the redirect url with the current page url + const newRedirectUrl = `?redirectUrl=${correctRedirectUrl}`; + orcidServerUrl = this.location.replace(/\?redirectUrl=(.*)/g, newRedirectUrl); + } + + // redirect to orcid authentication url + this.hardRedirectService.redirect(orcidServerUrl); + }); + + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 7b799bfaea..574d890ede 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -173,6 +173,7 @@ import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-p import { DsSelectComponent } from './ds-select/ds-select.component'; import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component'; import { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; +import { LogInOrcidComponent } from './log-in/methods/orcid/log-in-orcid.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -306,6 +307,7 @@ const COMPONENTS = [ LogInShibbolethComponent, LogInOidcComponent, + LogInOrcidComponent, LogInPasswordComponent, LogInContainerComponent, ItemVersionsComponent, @@ -378,6 +380,7 @@ const ENTRY_COMPONENTS = [ LogInPasswordComponent, LogInShibbolethComponent, LogInOidcComponent, + LogInOrcidComponent, BundleListElementComponent, ClaimedTaskActionsApproveComponent, ClaimedTaskActionsRejectComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c3c68a6882..fe034ac34a 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2389,6 +2389,8 @@ "login.form.oidc": "Log in with OIDC", + "login.form.orcid": "Log in with ORCID", + "login.form.password": "Password", "login.form.shibboleth": "Log in with Shibboleth", From 155db64c63363d7e69fd342a0aabf686697a4291 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 12 Apr 2022 09:36:18 +0200 Subject: [PATCH 008/157] 86526: Added themable AuthNavMenuComponent --- src/app/header/header.component.html | 2 +- .../themed-auth-nav-menu.component.ts | 25 +++++++++++++++++++ src/app/shared/shared.module.ts | 2 ++ .../dspace/app/header/header.component.html | 2 +- .../dspace/app/navbar/navbar.component.html | 2 +- 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/app/shared/auth-nav-menu/themed-auth-nav-menu.component.ts diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index c7b979d266..e5d5f38971 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -8,7 +8,7 @@ From cc08a2829ec05e211d4604173c6fd4a1c37893c0 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Wed, 13 Apr 2022 17:48:26 +0530 Subject: [PATCH 009/157] [CST-5668] ORCID Authorizations added. --- .../data/feature-authorization/feature-id.ts | 1 + .../item-pages/person/person.component.html | 1 + src/app/item-page/item-page-routing-paths.ts | 1 + src/app/item-page/item-page-routing.module.ts | 10 +- src/app/item-page/item-page.module.ts | 6 + .../orcid-auth/orcid-auth.component.html | 71 +++++++ .../orcid-auth/orcid-auth.component.scss | 0 .../orcid-auth/orcid-auth.component.ts | 98 +++++++++ .../orcid-page/orcid-page.component.html | 1 + .../orcid-page/orcid-page.component.scss | 0 .../orcid-page/orcid-page.component.ts | 9 + .../item-page/orcid-page/orcid-page.guard.ts | 31 +++ .../dso-page-orcid-button.component.html | 5 + .../dso-page-orcid-button.component.scss | 0 .../dso-page-orcid-button.component.spec.ts | 76 +++++++ .../dso-page-orcid-button.component.ts | 43 ++++ src/app/shared/shared.module.ts | 2 + src/assets/i18n/en.json5 | 191 ++++++++++++++++++ src/assets/images/orcid.logo.icon.svg | 21 ++ src/styles/_global-styles.scss | 11 + 20 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html create mode 100644 src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.scss create mode 100644 src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts create mode 100644 src/app/item-page/orcid-page/orcid-page.component.html create mode 100644 src/app/item-page/orcid-page/orcid-page.component.scss create mode 100644 src/app/item-page/orcid-page/orcid-page.component.ts create mode 100644 src/app/item-page/orcid-page/orcid-page.guard.ts create mode 100644 src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.html create mode 100644 src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.scss create mode 100644 src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.spec.ts create mode 100644 src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.ts create mode 100644 src/assets/images/orcid.logo.icon.svg diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index 029c75d9cb..1576c37a83 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -28,4 +28,5 @@ export enum FeatureID { CanCreateVersion = 'canCreateVersion', CanViewUsageStatistics = 'canViewUsageStatistics', CanSendFeedback = 'canSendFeedback', + CanSynchronizeWithORCID = 'canSynchronizeWithORCID' } diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index 5c2fd227fd..31ad9b2463 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -3,6 +3,7 @@ {{'person.page.titleprefix' | translate}}
+
diff --git a/src/app/item-page/item-page-routing-paths.ts b/src/app/item-page/item-page-routing-paths.ts index 74ad0aae07..9da2f91431 100644 --- a/src/app/item-page/item-page-routing-paths.ts +++ b/src/app/item-page/item-page-routing-paths.ts @@ -50,3 +50,4 @@ export const ITEM_EDIT_PATH = 'edit'; export const ITEM_EDIT_VERSIONHISTORY_PATH = 'versionhistory'; export const ITEM_VERSION_PATH = 'version'; export const UPLOAD_BITSTREAM_PATH = 'bitstreams/new'; +export const ORCID_PATH = 'orcid'; \ No newline at end of file diff --git a/src/app/item-page/item-page-routing.module.ts b/src/app/item-page/item-page-routing.module.ts index 7d7912bb42..011d7bd83d 100644 --- a/src/app/item-page/item-page-routing.module.ts +++ b/src/app/item-page/item-page-routing.module.ts @@ -7,7 +7,7 @@ import { VersionResolver } from './version-page/version.resolver'; import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component'; -import { ITEM_EDIT_PATH, UPLOAD_BITSTREAM_PATH } from './item-page-routing-paths'; +import { ITEM_EDIT_PATH, ORCID_PATH, UPLOAD_BITSTREAM_PATH } from './item-page-routing-paths'; import { ItemPageAdministratorGuard } from './item-page-administrator.guard'; import { MenuItemType } from '../shared/menu/initial-menus-state'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; @@ -16,6 +16,8 @@ import { ThemedFullItemPageComponent } from './full/themed-full-item-page.compon import { VersionPageComponent } from './version-page/version-page/version-page.component'; import { BitstreamRequestACopyPageComponent } from '../shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component'; import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; +import { OrcidPageComponent } from './orcid-page/orcid-page.component'; +import { OrcidPageGuard } from './orcid-page/orcid-page.guard'; @NgModule({ imports: [ @@ -50,6 +52,11 @@ import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; { path: REQUEST_COPY_MODULE_PATH, component: BitstreamRequestACopyPageComponent, + }, + { + path: ORCID_PATH, + component: OrcidPageComponent, + canActivate: [OrcidPageGuard] } ], data: { @@ -88,6 +95,7 @@ import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; LinkService, ItemPageAdministratorGuard, VersionResolver, + OrcidPageGuard ] }) diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index 80cb1f61a2..f584164c97 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -34,6 +34,9 @@ import { MiradorViewerComponent } from './mirador-viewer/mirador-viewer.componen import { VersionPageComponent } from './version-page/version-page/version-page.component'; import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component'; import { ThemedFileSectionComponent } from './simple/field-components/file-section/themed-file-section.component'; +import { OrcidAuthComponent } from './orcid-page/orcid-auth/orcid-auth.component'; +import { OrcidPageComponent } from './orcid-page/orcid-page.component'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; const ENTRY_COMPONENTS = [ @@ -67,6 +70,8 @@ const DECLARATIONS = [ MediaViewerImageComponent, MiradorViewerComponent, VersionPageComponent, + OrcidPageComponent, + OrcidAuthComponent ]; @NgModule({ @@ -79,6 +84,7 @@ const DECLARATIONS = [ JournalEntitiesModule.withEntryComponents(), ResearchEntitiesModule.withEntryComponents(), NgxGalleryModule, + NgbAccordionModule ], declarations: [ ...DECLARATIONS, diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html new file mode 100644 index 0000000000..8e538b66aa --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html @@ -0,0 +1,71 @@ +
+ + + +
+ +
+
+
+
+ + +
+
+
{{ 'person.page.orcid.granted-authorizations'| translate }}
+
+
+
    +
  • {{getAuthorizationDescription(auth) | translate}}
  • +
+
+
+
+
+
{{ 'person.page.orcid.missing-authorizations'| translate }}
+
+
+
+ {{'person.page.orcid.no-missing-authorizations-message' | translate}} +
+
+ {{'person.page.orcid.missing-authorizations-message' | translate}} +
    +
  • {{getAuthorizationDescription(auth) | translate }}
  • +
+
+
+
+
+
+
+ {{ 'person.page.orcid.remove-orcid-message' | translate}} +
+
+
+ + +
+
+
+ + +
+
orcid-logo
+
{{ getOrcidNotLinkedMessage() | async }}
+
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.scss b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts new file mode 100644 index 0000000000..9651215dc6 --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts @@ -0,0 +1,98 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; +import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; +import { Item } from '../../../core/shared/item.model'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; + +@Component({ + selector: 'ds-orcid-auth', + templateUrl: './orcid-auth.component.html', + styleUrls: ['./orcid-auth.component.scss'] +}) +export class OrcidAuthComponent implements OnInit { + + missingAuthorizations$ = new BehaviorSubject([]); + + unlinkProcessing = false; + + item: Item + + constructor( + private configurationService: ConfigurationDataService, + private researcherProfileService: ResearcherProfileService, + protected translateService: TranslateService, + private notificationsService: NotificationsService, + private itemService: ItemDataService, + private route: ActivatedRoute, + @Inject(NativeWindowService) private _window: NativeWindowRef, + ) { + this.itemService.findById(this.route.snapshot.paramMap.get('id'), true, true).pipe(getFirstCompletedRemoteData()).subscribe((data: RemoteData) => { + this.item = data.payload; + }); + } + + ngOnInit() { + const scopes = this.getOrcidAuthorizations(); + return this.configurationService.findByPropertyName('orcid.scope') + .pipe(getFirstSucceededRemoteDataPayload(), + map((configurationProperty) => configurationProperty.values), + map((allScopes) => allScopes.filter((scope) => !scopes.includes(scope)))) + .subscribe((missingScopes) => this.missingAuthorizations$.next(missingScopes)); + } + + getOrcidAuthorizations(): string[] { + return this.item.allMetadataValues('cris.orcid.scope'); + } + + isLinkedToOrcid(): boolean { + return this.researcherProfileService.isLinkedToOrcid(this.item); + } + + getOrcidNotLinkedMessage(): Observable { + const orcid = this.item.firstMetadataValue('person.identifier.orcid'); + if (orcid) { + return this.translateService.get('person.page.orcid.orcid-not-linked-message', { 'orcid': orcid }); + } else { + return this.translateService.get('person.page.orcid.no-orcid-message'); + } + } + + getAuthorizationDescription(scope: string) { + return 'person.page.orcid.scope.' + scope.substring(1).replace('/', '-'); + } + + onlyAdminCanDisconnectProfileFromOrcid(): Observable { + return this.researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid(); + } + + ownerCanDisconnectProfileFromOrcid(): Observable { + return this.researcherProfileService.ownerCanDisconnectProfileFromOrcid(); + } + + linkOrcid(): void { + this.researcherProfileService.getOrcidAuthorizeUrl(this.item).subscribe((authorizeUrl) => { + this._window.nativeWindow.location.href = authorizeUrl; + }); + } + + unlinkOrcid(): void { + this.unlinkProcessing = true; + this.researcherProfileService.unlinkOrcid(this.item).subscribe((remoteData) => { + this.unlinkProcessing = false; + if (remoteData.isSuccess) { + this.notificationsService.success(this.translateService.get('person.page.orcid.unlink.success')); + } else { + this.notificationsService.error(this.translateService.get('person.page.orcid.unlink.error')); + } + }); + } + +} diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html new file mode 100644 index 0000000000..ba9f445ec2 --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -0,0 +1 @@ + diff --git a/src/app/item-page/orcid-page/orcid-page.component.scss b/src/app/item-page/orcid-page/orcid-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/orcid-page/orcid-page.component.ts b/src/app/item-page/orcid-page/orcid-page.component.ts new file mode 100644 index 0000000000..32b47e069f --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-page.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-orcid-page', + templateUrl: './orcid-page.component.html', + styleUrls: ['./orcid-page.component.scss'] +}) +export class OrcidPageComponent { +} diff --git a/src/app/item-page/orcid-page/orcid-page.guard.ts b/src/app/item-page/orcid-page/orcid-page.guard.ts new file mode 100644 index 0000000000..97c528e9ae --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-page.guard.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable, of as observableOf } from 'rxjs'; +import { AuthService } from '../../core/auth/auth.service'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { Item } from '../../core/shared/item.model'; +import { ItemPageResolver } from '../item-page.resolver'; + +@Injectable({ + providedIn: 'root' +}) +/** + * Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights + */ +export class OrcidPageGuard extends DsoPageSingleFeatureGuard { + constructor(protected resolver: ItemPageResolver, + protected authorizationService: AuthorizationDataService, + protected router: Router, + protected authService: AuthService) { + super(resolver, authorizationService, router, authService); + } + + /** + * Check administrator authorization rights + */ + getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return observableOf(FeatureID.CanSynchronizeWithORCID); + } +} diff --git a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.html b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.html new file mode 100644 index 0000000000..7a3383fd1a --- /dev/null +++ b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.html @@ -0,0 +1,5 @@ +ORCID + diff --git a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.scss b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.spec.ts b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.spec.ts new file mode 100644 index 0000000000..9de3333b7f --- /dev/null +++ b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.spec.ts @@ -0,0 +1,76 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { Item } from '../../../core/shared/item.model'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { of as observableOf } from 'rxjs'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { DsoPageOrcidButtonComponent } from './dso-page-orcid-button.component'; + +describe('DsoPageOrcidButtonComponent', () => { + let component: DsoPageOrcidButtonComponent; + let fixture: ComponentFixture; + + let authorizationService: AuthorizationDataService; + let dso: DSpaceObject; + + beforeEach(waitForAsync(() => { + dso = Object.assign(new Item(), { + id: 'test-item', + _links: { + self: { href: 'test-item-selflink' } + } + }); + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true) + }); + TestBed.configureTestingModule({ + declarations: [DsoPageOrcidButtonComponent], + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule], + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DsoPageOrcidButtonComponent); + component = fixture.componentInstance; + component.dso = dso; + component.pageRoute = 'test'; + fixture.detectChanges(); + }); + + it('should check the authorization of the current user', () => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith(FeatureID.CanEditOrcid, dso.self); + }); + + describe('when the user is authorized', () => { + beforeEach(() => { + (authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(true)); + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should render a link', () => { + const link = fixture.debugElement.query(By.css('a')); + expect(link).not.toBeNull(); + }); + }); + + describe('when the user is not authorized', () => { + beforeEach(() => { + (authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should not render a link', () => { + const link = fixture.debugElement.query(By.css('a')); + expect(link).toBeNull(); + }); + }); +}); diff --git a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.ts b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.ts new file mode 100644 index 0000000000..9f244da7c9 --- /dev/null +++ b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.ts @@ -0,0 +1,43 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id'; +import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; + +@Component({ + selector: 'ds-dso-page-orcid-button', + templateUrl: './dso-page-orcid-button.component.html', + styleUrls: ['./dso-page-orcid-button.component.scss'] +}) +export class DsoPageOrcidButtonComponent implements OnInit { + /** + * The DSpaceObject to display a button to the edit page for + */ + @Input() dso: DSpaceObject; + + /** + * The prefix of the route to the edit page (before the object's UUID, e.g. "items") + */ + @Input() pageRoute: string; + + /** + * A message for the tooltip on the button + * Supports i18n keys + */ + @Input() tooltipMsg: string; + + /** + * Whether or not the current user is authorized to edit the DSpaceObject + */ + isAuthorized: BehaviorSubject = new BehaviorSubject(false); + + constructor(protected authorizationService: AuthorizationDataService) { } + + ngOnInit() { + this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, this.dso.self).pipe(take(1)).subscribe((isAuthorized: boolean) => { + this.isAuthorized.next(isAuthorized); + }); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 01649ee947..fcd80683b7 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -174,6 +174,7 @@ import { DsSelectComponent } from './ds-select/ds-select.component'; import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component'; import { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; import { ClaimItemSelectorComponent } from './dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component'; +import { DsoPageOrcidButtonComponent } from './dso-page/dso-page-orcid-button/dso-page-orcid-button.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -414,6 +415,7 @@ const SHARED_ITEM_PAGE_COMPONENTS = [ GenericItemPageFieldComponent, MetadataRepresentationListComponent, RelatedItemsComponent, + DsoPageOrcidButtonComponent ]; diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index d0ff85ba51..8ef1135910 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4208,4 +4208,195 @@ "researcherprofile.success.claim.body" : "Profile claimed with success", "researcherprofile.success.claim.title" : "Success", + + "person.page.orcid": "ORCID", + + "person.page.orcid.create": "Create an ORCID ID", + + "person.page.orcid.granted-authorizations": "Granted authorizations", + + "person.page.orcid.grant-authorizations" : "Grant authorizations", + + "person.page.orcid.link": "Connect to ORCID ID", + + "person.page.orcid.orcid-not-linked-message": "The ORCID iD of this profile ({{ orcid }}) has not yet been connected to an account on the ORCID registry or the connection is expired.", + + "person.page.orcid.unlink": "Disconnect from ORCID", + + "person.page.orcid.unlink.processing": "Processing...", + + "person.page.orcid.missing-authorizations": "Missing authorizations", + + "person.page.orcid.missing-authorizations-message": "The following authorizations are missing:", + + "person.page.orcid.no-missing-authorizations-message": "Great! This box is empty, so you have granted all access rights to use all functions offers by your institution.", + + "person.page.orcid.no-orcid-message": "No ORCID iD associated yet. By clicking on the button below it is possible to link this profile with an ORCID account.", + + "person.page.orcid.profile-preferences": "Profile preferences", + + "person.page.orcid.funding-preferences": "Funding preferences", + + "person.page.orcid.publications-preferences": "Publication preferences", + + "person.page.orcid.remove-orcid-message": "If you need to remove your ORCID, please contact the repository administrator", + + "person.page.orcid.save.preference.changes": "Update settings", + + "person.page.orcid.sync-profile.affiliation" : "Affiliation", + + "person.page.orcid.sync-profile.biographical" : "Biographical data", + + "person.page.orcid.sync-profile.education" : "Education", + + "person.page.orcid.sync-profile.identifiers" : "Identifiers", + + "person.page.orcid.sync-fundings.all" : "All fundings", + + "person.page.orcid.sync-fundings.mine" : "My fundings", + + "person.page.orcid.sync-fundings.my_selected" : "Selected fundings", + + "person.page.orcid.sync-fundings.disabled" : "Disabled", + + "person.page.orcid.sync-publications.all" : "All publications", + + "person.page.orcid.sync-publications.mine" : "My publications", + + "person.page.orcid.sync-publications.my_selected" : "Selected publications", + + "person.page.orcid.sync-publications.disabled" : "Disabled", + + "person.page.orcid.sync-queue.discard" : "Discard the change and do not synchronize with the ORCID registry", + + "person.page.orcid.sync-queue.discard.error": "The discarding of the ORCID queue record failed", + + "person.page.orcid.sync-queue.discard.success": "The ORCID queue record have been discarded successfully", + + "person.page.orcid.sync-queue.empty-message": "The ORCID queue registry is empty", + + "person.page.orcid.sync-queue.description" : "Description", + + "person.page.orcid.sync-queue.description.affiliation": "Affiliations", + + "person.page.orcid.sync-queue.description.country": "Country", + + "person.page.orcid.sync-queue.description.education": "Educations", + + "person.page.orcid.sync-queue.description.external_ids": "External ids", + + "person.page.orcid.sync-queue.description.other_names": "Other names", + + "person.page.orcid.sync-queue.description.qualification": "Qualifications", + + "person.page.orcid.sync-queue.description.researcher_urls": "Researcher urls", + + "person.page.orcid.sync-queue.description.keywords": "Keywords", + + "person.page.orcid.sync-queue.tooltip.insert": "Add a new entry in the ORCID registry", + + "person.page.orcid.sync-queue.tooltip.update": "Update this entry on the ORCID registry", + + "person.page.orcid.sync-queue.tooltip.delete": "Remove this entry from the ORCID registry", + + "person.page.orcid.sync-queue.tooltip.publication": "Publication", + + "person.page.orcid.sync-queue.tooltip.funding": "Funding", + + "person.page.orcid.sync-queue.tooltip.affiliation": "Affiliation", + + "person.page.orcid.sync-queue.tooltip.education": "Education", + + "person.page.orcid.sync-queue.tooltip.qualification": "Qualification", + + "person.page.orcid.sync-queue.tooltip.other_names": "Other name", + + "person.page.orcid.sync-queue.tooltip.country": "Country", + + "person.page.orcid.sync-queue.tooltip.keywords": "Keyword", + + "person.page.orcid.sync-queue.tooltip.external_ids": "External identifier", + + "person.page.orcid.sync-queue.tooltip.researcher_urls": "Researcher url", + + "person.page.orcid.sync-queue.send" : "Synchronize with ORCID registry", + + "person.page.orcid.sync-queue.send.unauthorized-error.title": "The submission to ORCID failed for missing authorizations.", + + "person.page.orcid.sync-queue.send.unauthorized-error.content": "Click here to grant again the required permissions. If the problem persists, contact the administrator", + + "person.page.orcid.sync-queue.send.bad-request-error": "The submission to ORCID failed because the resource sent to ORCID registry is not valid", + + "person.page.orcid.sync-queue.send.error": "The submission to ORCID failed", + + "person.page.orcid.sync-queue.send.conflict-error": "The submission to ORCID failed because the resource is already present on the ORCID registry", + + "person.page.orcid.sync-queue.send.not-found-warning": "The resource does not exists anymore on the ORCID registry.", + + "person.page.orcid.sync-queue.send.success": "The submission to ORCID was completed successfully", + + "person.page.orcid.sync-queue.send.validation-error": "The data that you want to synchronize with ORCID is not valid", + + "person.page.orcid.sync-queue.send.validation-error.amount-currency.required": "The amount's currency is required", + + "person.page.orcid.sync-queue.send.validation-error.external-id.required": "The resource to be sent requires at least one identifier", + + "person.page.orcid.sync-queue.send.validation-error.title.required": "The title is required", + + "person.page.orcid.sync-queue.send.validation-error.type.required": "The type is required", + + "person.page.orcid.sync-queue.send.validation-error.start-date.required": "The start date is required", + + "person.page.orcid.sync-queue.send.validation-error.funder.required": "The funder is required", + + "person.page.orcid.sync-queue.send.validation-error.organization.required": "The organization is required", + + "person.page.orcid.sync-queue.send.validation-error.organization.name-required": "The organization's name is required", + + "person.page.orcid.sync-queue.send.validation-error.organization.address-required": "The organization to be sent requires an address", + + "person.page.orcid.sync-queue.send.validation-error.organization.city-required": "The address of the organization to be sent requires a city", + + "person.page.orcid.sync-queue.send.validation-error.organization.country-required": "The address of the organization to be sent requires a country", + + "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.required": "An identifier to disambiguate organizations is required. Supported ids are GRID, Ringgold, Legal Entity identifiers (LEIs) and Crossref Funder Registry identifiers", + + "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.value-required": "The organization's identifiers requires a value", + + "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.required": "The organization's identifiers requires a source", + + "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.invalid": "The source of one of the organization identifiers is invalid. Supported sources are RINGGOLD, GRID, LEI and FUNDREF", + + "person.page.orcid.synchronization-mode": "Synchronization mode", + + "person.page.orcid.synchronization-mode.batch": "Batch", + + "person.page.orcid.synchronization-mode.label": "Synchronization mode", + + "person.page.orcid.synchronization-mode-message": "Enable 'Manual' Synchronization mode to disable batch synchronization, so you must send your data to ORCID Registry manually", + + "person.page.orcid.synchronization-settings-update.success": "The synchronization settings have been updated successfully", + + "person.page.orcid.synchronization-settings-update.error": "The update of the synchronization settings failed", + + "person.page.orcid.synchronization-mode.manual": "Manual", + + "person.page.orcid.scope.authenticate": "Get your ORCID iD", + + "person.page.orcid.scope.read-limited": "Read your information with visibility set to Trusted Parties", + + "person.page.orcid.scope.activities-update": "Add/update your research activities", + + "person.page.orcid.scope.person-update": "Add/update other information about you", + + "person.page.orcid.unlink.success": "The disconnection between the profile and the ORCID registry was successful", + + "person.page.orcid.unlink.error": "An error occurred while disconnecting between the profile and the ORCID registry. Try again", + + "person.orcid.sync.setting": "ORCID Synchronization settings", + + "person.orcid.registry.queue": "ORCID Registry Queue", + + "person.orcid.registry.auth": "ORCID Authorizations", + } diff --git a/src/assets/images/orcid.logo.icon.svg b/src/assets/images/orcid.logo.icon.svg new file mode 100644 index 0000000000..8aec5959e5 --- /dev/null +++ b/src/assets/images/orcid.logo.icon.svg @@ -0,0 +1,21 @@ + + + + Orcid logo + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index cf251204e2..7d9550c0fd 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -97,4 +97,15 @@ ngb-modal-backdrop { } .researcher-profile-switch .switch.checked{ color: #fff; +} + +.custom-accordion .card-header button { + -webkit-box-shadow: none!important; + box-shadow: none!important; + width: 100%; +} +.custom-accordion .card:first-of-type { + border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color)!important; + border-bottom-left-radius: var(--bs-card-border-radius)!important; + border-bottom-right-radius: var(--bs-card-border-radius)!important; } \ No newline at end of file From dd4ff5e40c456da430b90d1c868fe5ce8adc81b6 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Wed, 13 Apr 2022 17:54:29 +0530 Subject: [PATCH 010/157] [CST-5338] ORCID Settings added. --- src/app/item-page/item-page.module.ts | 4 +- .../orcid-page/orcid-page.component.html | 1 + .../orcid-sync/orcid-setting.component.html | 82 +++++++++++ .../orcid-sync/orcid-setting.component.scss | 0 .../orcid-sync/orcid-setting.component.ts | 139 ++++++++++++++++++ 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html create mode 100644 src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.scss create mode 100644 src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index f584164c97..2c4b57b249 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -37,6 +37,7 @@ import { ThemedFileSectionComponent } from './simple/field-components/file-secti import { OrcidAuthComponent } from './orcid-page/orcid-auth/orcid-auth.component'; import { OrcidPageComponent } from './orcid-page/orcid-page.component'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { OrcidSettingComponent } from './orcid-page/orcid-sync/orcid-setting.component'; const ENTRY_COMPONENTS = [ @@ -71,7 +72,8 @@ const DECLARATIONS = [ MiradorViewerComponent, VersionPageComponent, OrcidPageComponent, - OrcidAuthComponent + OrcidAuthComponent, + OrcidSettingComponent ]; @NgModule({ diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html index ba9f445ec2..4e62a8d51c 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.html +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -1 +1,2 @@ + diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html new file mode 100644 index 0000000000..793e7570ed --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html @@ -0,0 +1,82 @@ +
+ + + +
+
+
+ {{ 'person.page.orcid.synchronization-mode-message' | translate}} +
+
+
+
{{ 'person.page.orcid.synchronization-mode'| translate }}
+
+
+
+ + +
+
+
+
+
+
+
+
{{ 'person.page.orcid.publications-preferences'| translate }}
+
+
+
+
+ + +
+
+
+
+
+
+
{{ 'person.page.orcid.funding-preferences'| translate }}
+
+
+
+
+ + +
+
+
+
+
+
+
{{ 'person.page.orcid.profile-preferences'| translate }}
+
+
+
+
+ + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.scss b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts new file mode 100644 index 0000000000..87385f0780 --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts @@ -0,0 +1,139 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { Operation } from 'fast-json-patch'; +import { switchMap } from 'rxjs/operators'; +import { AuthService } from '../../../core/auth/auth.service'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; +import { Item } from '../../../core/shared/item.model'; +import { getFinishedRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; + +@Component({ + selector: 'ds-orcid-setting', + templateUrl: './orcid-setting.component.html', + styleUrls: ['./orcid-setting.component.scss'] +}) +export class OrcidSettingComponent implements OnInit { + + messagePrefix = 'person.page.orcid'; + + currentSyncMode: string; + + currentSyncPublications: string; + + currentSyncFundings: string; + + syncModes: { value: string, label: string }[]; + + syncPublicationOptions: { value: string, label: string }[]; + + syncFundingOptions: {value: string, label: string}[]; + + syncProfileOptions: { value: string, label: string, checked: boolean }[]; + + item: Item; + + constructor(private researcherProfileService: ResearcherProfileService, + protected translateService: TranslateService, + private notificationsService: NotificationsService, + public authService: AuthService, + private route: ActivatedRoute, + private itemService: ItemDataService + ) { + this.itemService.findById(this.route.snapshot.paramMap.get('id'), true, true).pipe(getFirstCompletedRemoteData()).subscribe((data: RemoteData) => { + this.item = data.payload; + }); + } + + ngOnInit() { + this.syncModes = [ + { + label: this.messagePrefix + '.synchronization-mode.batch', + value: 'BATCH' + }, + { + label: this.messagePrefix + '.synchronization-mode.manual', + value: 'MANUAL' + } + ]; + + this.syncPublicationOptions = ['DISABLED', 'ALL'] + .map((value) => { + return { + label: this.messagePrefix + '.sync-publications.' + value.toLowerCase(), + value: value, + }; + }); + + this.syncFundingOptions = ['DISABLED', 'ALL'] + .map((value) => { + return { + label: this.messagePrefix + '.sync-fundings.' + value.toLowerCase(), + value: value, + }; + }); + + const syncProfilePreferences = this.item.allMetadataValues('cris.orcid.sync-profile'); + + this.syncProfileOptions = ['AFFILIATION', 'EDUCATION', 'BIOGRAPHICAL', 'IDENTIFIERS'] + .map((value) => { + return { + label: this.messagePrefix + '.sync-profile.' + value.toLowerCase(), + value: value, + checked: syncProfilePreferences.includes(value) + }; + }); + + this.currentSyncMode = this.getCurrentPreference('cris.orcid.sync-mode', ['BATCH, MANUAL'], 'MANUAL'); + this.currentSyncPublications = this.getCurrentPreference('cris.orcid.sync-publications', ['DISABLED', 'ALL'], 'DISABLED'); + this.currentSyncFundings = this.getCurrentPreference('cris.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); + } + + onSubmit(form: FormGroup) { + const operations: Operation[] = []; + this.fillOperationsFor(operations, '/orcid/mode', form.value.syncMode); + this.fillOperationsFor(operations, '/orcid/publications', form.value.syncPublications); + this.fillOperationsFor(operations, '/orcid/fundings', form.value.syncFundings); + + const syncProfileValue = this.syncProfileOptions + .map((syncProfileOption => syncProfileOption.value)) + .filter((value) => form.value['syncProfile_' + value]) + .join(','); + + this.fillOperationsFor(operations, '/orcid/profile', syncProfileValue); + + if (operations.length === 0) { + return; + } + + this.researcherProfileService.findById(this.item.firstMetadata('cris.owner').authority).pipe( + switchMap((profile) => this.researcherProfileService.patch(profile, operations)), + getFinishedRemoteData() + ).subscribe((remoteData) => { + if (remoteData.isSuccess) { + this.notificationsService.success(this.translateService.get(this.messagePrefix + '.synchronization-settings-update.success')); + } else { + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.synchronization-settings-update.error')); + } + }); + } + + fillOperationsFor(operations: Operation[], path: string, currentValue: string) { + operations.push({ + path: path, + op: 'replace', + value: currentValue + }); + } + + getCurrentPreference(metadataField: string, allowedValues: string[], defaultValue: string): string { + const currentPreference = this.item.firstMetadataValue(metadataField); + return (currentPreference && allowedValues.includes(currentPreference)) ? currentPreference : defaultValue; + } + + +} From a9162eb9205c920f1d64857851117514a8c2beca Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Wed, 13 Apr 2022 18:20:16 +0530 Subject: [PATCH 011/157] [CST-5339] ORCID queue added. --- src/app/core/core.module.ts | 12 +- .../core/orcid/model/orcid-history.model.ts | 89 +++++++ .../model/orcid-history.resource-type.ts | 9 + src/app/core/orcid/model/orcid-queue.model.ts | 68 +++++ .../orcid/model/orcid-queue.resource-type.ts | 9 + src/app/core/orcid/orcid-history.service.ts | 95 +++++++ src/app/core/orcid/orcid-queue.service.ts | 124 +++++++++ src/app/item-page/item-page.module.ts | 4 +- .../orcid-page/orcid-page.component.html | 1 + .../orcid-queue/orcid-queue.component.html | 59 +++++ .../orcid-queue/orcid-queue.component.scss | 0 .../orcid-queue/orcid-queue.component.ts | 250 ++++++++++++++++++ 12 files changed, 717 insertions(+), 3 deletions(-) create mode 100644 src/app/core/orcid/model/orcid-history.model.ts create mode 100644 src/app/core/orcid/model/orcid-history.resource-type.ts create mode 100644 src/app/core/orcid/model/orcid-queue.model.ts create mode 100644 src/app/core/orcid/model/orcid-queue.resource-type.ts create mode 100644 src/app/core/orcid/orcid-history.service.ts create mode 100644 src/app/core/orcid/orcid-queue.service.ts create mode 100644 src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.html create mode 100644 src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.scss create mode 100644 src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 7e97c78b3b..3f2737f30d 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -165,6 +165,10 @@ import { SubmissionAccessesModel } from './config/models/config-submission-acces import { ResearcherProfileService } from './profile/researcher-profile.service'; import { ProfileClaimService } from '../profile-page/profile-claim/profile-claim.service'; import { ResearcherProfile } from './profile/model/researcher-profile.model'; +import { OrcidQueueService } from './orcid/orcid-queue.service'; +import { OrcidHistoryService } from './orcid/orcid-history.service'; +import { OrcidQueue } from './orcid/model/orcid-queue.model'; +import { OrcidHistory } from './orcid/model/orcid-history.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -290,7 +294,9 @@ const PROVIDERS = [ GroupDataService, FeedbackDataService, ResearcherProfileService, - ProfileClaimService + ProfileClaimService, + OrcidQueueService, + OrcidHistoryService, ]; /** @@ -351,7 +357,9 @@ export const models = Root, SearchConfig, SubmissionAccessesModel, - ResearcherProfile + ResearcherProfile, + OrcidQueue, + OrcidHistory, ]; @NgModule({ diff --git a/src/app/core/orcid/model/orcid-history.model.ts b/src/app/core/orcid/model/orcid-history.model.ts new file mode 100644 index 0000000000..207a0c4df9 --- /dev/null +++ b/src/app/core/orcid/model/orcid-history.model.ts @@ -0,0 +1,89 @@ +import { autoserialize, deserialize } from 'cerialize'; +import { typedObject } from '../../cache/builders/build-decorators'; +import { CacheableObject } from '../../cache/object-cache.reducer'; +import { HALLink } from '../../shared/hal-link.model'; +import { ResourceType } from '../../shared/resource-type'; +import { excludeFromEquals } from '../../utilities/equals.decorators'; +import { ORCID_HISTORY } from './orcid-history.resource-type'; + +/** + * Class the represents a Orcid History. + */ +@typedObject +export class OrcidHistory extends CacheableObject { + + static type = ORCID_HISTORY; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The identifier of this Orcid History record + */ + @autoserialize + id: number; + + /** + * The name of the related entity + */ + @autoserialize + entityName: string; + + /** + * The identifier of the owner of this Orcid History record. + */ + @autoserialize + ownerId: string; + + /** + * The identifier of the entity related to this Orcid History record. + */ + @autoserialize + entityId: string; + + /** + * The type of the entity related to this Orcid History record. + */ + @autoserialize + entityType: string; + + /** + * The response status coming from ORCID api. + */ + @autoserialize + status: number; + + /** + * The putCode assigned by ORCID to the entity. + */ + @autoserialize + putCode: string; + + /** + * The last send attempt timestamp. + */ + lastAttempt: string; + + /** + * The success send attempt timestamp. + */ + successAttempt: string; + + /** + * The response coming from ORCID. + */ + responseMessage: string; + + /** + * The {@link HALLink}s for this Orcid History record + */ + @deserialize + _links: { + self: HALLink, + }; + +} diff --git a/src/app/core/orcid/model/orcid-history.resource-type.ts b/src/app/core/orcid/model/orcid-history.resource-type.ts new file mode 100644 index 0000000000..45da8cbf68 --- /dev/null +++ b/src/app/core/orcid/model/orcid-history.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../shared/resource-type'; + +/** + * The resource type for OrcidHistory + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const ORCID_HISTORY = new ResourceType('orcidhistory'); diff --git a/src/app/core/orcid/model/orcid-queue.model.ts b/src/app/core/orcid/model/orcid-queue.model.ts new file mode 100644 index 0000000000..52ab8676ad --- /dev/null +++ b/src/app/core/orcid/model/orcid-queue.model.ts @@ -0,0 +1,68 @@ +import { autoserialize, deserialize } from 'cerialize'; +import { typedObject } from '../../cache/builders/build-decorators'; +import { CacheableObject } from '../../cache/object-cache.reducer'; +import { HALLink } from '../../shared/hal-link.model'; +import { ResourceType } from '../../shared/resource-type'; +import { excludeFromEquals } from '../../utilities/equals.decorators'; +import { ORCID_QUEUE } from './orcid-queue.resource-type'; + +/** + * Class the represents a Orcid Queue. + */ +@typedObject +export class OrcidQueue extends CacheableObject { + + static type = ORCID_QUEUE; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The identifier of this Orcid Queue record + */ + @autoserialize + id: number; + + /** + * The record description. + */ + @autoserialize + description: string; + + /** + * The identifier of the owner of this Orcid Queue record. + */ + @autoserialize + ownerId: string; + + /** + * The identifier of the entity related to this Orcid Queue record. + */ + @autoserialize + entityId: string; + + /** + * The type of this Orcid Queue record. + */ + @autoserialize + recordType: string; + + /** + * The operation related to this Orcid Queue record. + */ + @autoserialize + operation: string; + + /** + * The {@link HALLink}s for this Orcid Queue record + */ + @deserialize + _links: { + self: HALLink, + }; + +} diff --git a/src/app/core/orcid/model/orcid-queue.resource-type.ts b/src/app/core/orcid/model/orcid-queue.resource-type.ts new file mode 100644 index 0000000000..a7f40d70ec --- /dev/null +++ b/src/app/core/orcid/model/orcid-queue.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../shared/resource-type'; + +/** + * The resource type for OrcidQueue + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const ORCID_QUEUE = new ResourceType('orcidqueue'); diff --git a/src/app/core/orcid/orcid-history.service.ts b/src/app/core/orcid/orcid-history.service.ts new file mode 100644 index 0000000000..03eb497956 --- /dev/null +++ b/src/app/core/orcid/orcid-history.service.ts @@ -0,0 +1,95 @@ +/* tslint:disable:max-classes-per-file */ + +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'; +import { isNotEmpty } from '../../shared/empty.util'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { createFailedRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { dataService } from '../cache/builders/build-decorators'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RestResponse } from '../cache/response.models'; +import { CoreState } from '../core.reducers'; +import { DataService } from '../data/data.service'; +import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; +import { ItemDataService } from '../data/item-data.service'; +import { RemoteData } from '../data/remote-data'; +import { PostRequest, RestRequest } from '../data/request.models'; +import { RequestService } from '../data/request.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { getFinishedRemoteData, getResponseFromEntry, sendRequest } from '../shared/operators'; +import { OrcidHistory } from './model/orcid-history.model'; +import { ORCID_HISTORY } from './model/orcid-history.resource-type'; +import { OrcidQueue } from './model/orcid-queue.model'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; + +/** + * A private DataService implementation to delegate specific methods to. + */ +class OrcidHistoryServiceImpl extends DataService { + public linkPath = 'orcidhistories'; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer) { + super(); + } + +} + +/** + * A service that provides methods to make REST requests with Orcid History endpoint. + */ +@Injectable() +@dataService(ORCID_HISTORY) +export class OrcidHistoryService { + + dataService: OrcidHistoryServiceImpl; + + responseMsToLive: number = 10 * 1000; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer, + protected itemService: ItemDataService ) { + + this.dataService = new OrcidHistoryServiceImpl(requestService, rdbService, store, objectCache, halService, + notificationsService, http, comparator); + + } + + sendToORCID(orcidQueue: OrcidQueue): Observable> { + const requestId = this.requestService.generateRequestId(); + return this.getEndpoint().pipe( + map((endpointURL: string) => { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + return new PostRequest(requestId, endpointURL, orcidQueue._links.self.href, options); + }), + sendRequest(this.requestService), + switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid) as Observable>) + ); + } + + getEndpoint(): Observable { + return this.halService.getEndpoint(this.dataService.linkPath); + } + +} diff --git a/src/app/core/orcid/orcid-queue.service.ts b/src/app/core/orcid/orcid-queue.service.ts new file mode 100644 index 0000000000..7cf43a9aa0 --- /dev/null +++ b/src/app/core/orcid/orcid-queue.service.ts @@ -0,0 +1,124 @@ +/* tslint:disable:max-classes-per-file */ + +import { DataService } from '../data/data.service'; +import { OrcidQueue } from './model/orcid-queue.model'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { Store } from '@ngrx/store'; +import { CoreState } from '../core.reducers'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; +import { Injectable } from '@angular/core'; +import { dataService } from '../cache/builders/build-decorators'; +import { ORCID_QUEUE } from './model/orcid-queue.resource-type'; +import { ItemDataService } from '../data/item-data.service'; +import { combineLatest, Observable } from 'rxjs'; +import { RemoteData } from '../data/remote-data'; +import { PaginatedList } from '../data/paginated-list.model'; +import { RequestParam } from '../cache/models/request-param.model'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { NoContent } from '../shared/NoContent.model'; +import { ConfigurationDataService } from '../data/configuration-data.service'; +import { map } from 'rxjs/operators'; +import { getFirstSucceededRemoteDataPayload } from '../shared/operators'; +import { environment } from '../../../environments/environment'; +import { Router } from '@angular/router'; + +/** + * A private DataService implementation to delegate specific methods to. + */ +class OrcidQueueServiceImpl extends DataService { + public linkPath = 'orcidqueues'; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer) { + super(); + } + +} + +/** + * A service that provides methods to make REST requests with Orcid Queue endpoint. + */ +@Injectable() +@dataService(ORCID_QUEUE) +export class OrcidQueueService { + + dataService: OrcidQueueServiceImpl; + + responseMsToLive: number = 10 * 1000; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer, + protected configurationService: ConfigurationDataService, + protected router: Router, + protected itemService: ItemDataService ) { + + this.dataService = new OrcidQueueServiceImpl(requestService, rdbService, store, objectCache, halService, + notificationsService, http, comparator); + + } + + /** + * @param itemId It represent a Id of owner + * @param paginationOptions + * @returns { OrcidQueue } + */ + searchByOwnerId(itemId: string, paginationOptions: PaginationComponentOptions): Observable>> { + return this.dataService.searchBy('findByOwner', { + searchParams: [new RequestParam('ownerId', itemId)], + elementsPerPage: paginationOptions.pageSize, + currentPage: paginationOptions.currentPage + },false, + true); + } + + /** + * @param orcidQueueId represents a id of orcid queue + * @returns { NoContent } + */ + deleteById(orcidQueueId: number): Observable> { + return this.dataService.delete(orcidQueueId + ''); + } + + /** + * This method will set linkPath to stale + */ + clearFindByOwnerRequests() { + this.requestService.setStaleByHrefSubstring(this.dataService.linkPath + '/search/findByOwner'); + } + + /** + * @param profileId represent a uuid of that user + * @returns orcid authorized url of that user + */ + getOrcidAuthorizeUrl(profileId: string): Observable { + return combineLatest([ + this.configurationService.findByPropertyName('orcid.authorize-url').pipe(getFirstSucceededRemoteDataPayload()), + this.configurationService.findByPropertyName('orcid.application-client-id').pipe(getFirstSucceededRemoteDataPayload()), + this.configurationService.findByPropertyName('orcid.scope').pipe(getFirstSucceededRemoteDataPayload())] + ).pipe( + map(([authorizeUrl, clientId, scopes]) => { + const redirectUri = environment.rest.baseUrl + '/api/cris/orcid/' + profileId + '/?url=' + encodeURIComponent(this.router.url); + return authorizeUrl.values[0] + '?client_id=' + clientId.values[0] + '&redirect_uri=' + redirectUri + '&response_type=code&scope=' + + scopes.values.join(' '); + })); + } +} diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index 2c4b57b249..9c471a5aa7 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -38,6 +38,7 @@ import { OrcidAuthComponent } from './orcid-page/orcid-auth/orcid-auth.component import { OrcidPageComponent } from './orcid-page/orcid-page.component'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; import { OrcidSettingComponent } from './orcid-page/orcid-sync/orcid-setting.component'; +import { OrcidQueueComponent } from './orcid-page/orcid-queue/orcid-queue.component'; const ENTRY_COMPONENTS = [ @@ -73,7 +74,8 @@ const DECLARATIONS = [ VersionPageComponent, OrcidPageComponent, OrcidAuthComponent, - OrcidSettingComponent + OrcidSettingComponent, + OrcidQueueComponent ]; @NgModule({ diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html index 4e62a8d51c..33c1dd158d 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.html +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -1,2 +1,3 @@ + diff --git a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.html b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.html new file mode 100644 index 0000000000..81e30bc013 --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.html @@ -0,0 +1,59 @@ +
+ + + +
+ + +
+ {{ 'person.page.orcid.sync-queue.empty-message' | translate}} +
+ + +
+ + + + + + + + + + + + + +
{{'person.page.orcid.sync-queue.description' | translate}}
+ {{ entry.description }} + +
+ + + + +
+
+
+
+
+
+
+
+
diff --git a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.scss b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts new file mode 100644 index 0000000000..01639a427c --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts @@ -0,0 +1,250 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; +import { switchMap, tap } from 'rxjs/operators'; +import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { RemoteData } from '../../../core/data/remote-data'; +import { OrcidHistory } from '../../../core/orcid/model/orcid-history.model'; +import { OrcidQueue } from '../../../core/orcid/model/orcid-queue.model'; +import { OrcidHistoryService } from '../../../core/orcid/orcid-history.service'; +import { OrcidQueueService } from '../../../core/orcid/orcid-queue.service'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { getFinishedRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { hasValue } from '../../../shared/empty.util'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; + +@Component({ + selector: 'ds-orcid-queue', + templateUrl: './orcid-queue.component.html', + styleUrls: ['./orcid-queue.component.scss'] +}) +export class OrcidQueueComponent implements OnInit { + /** + * Pagination config used to display the list + */ + public paginationOptions: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + id: 'oqp', + pageSize: 5 + }); + + /** + * A boolean representing if results are loading + */ + public processing$ = new BehaviorSubject(false); + + /** + * A list of orcid queue records + */ + private list$: BehaviorSubject>> = new BehaviorSubject>>({} as any); + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + private subs: Subscription[] = []; + constructor(private orcidQueueService: OrcidQueueService, + protected translateService: TranslateService, + private paginationService: PaginationService, + private route: ActivatedRoute, + private notificationsService: NotificationsService, + private orcidHistoryService: OrcidHistoryService, + ) { } + + ngOnInit(): void { + this.updateList(); + } + + updateList() { + this.subs.push( + this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions).pipe( + tap(() => this.processing$.next(true)), + switchMap((config: PaginationComponentOptions) => this.orcidQueueService.searchByOwnerId(this.route.snapshot.paramMap.get('id'), config)), + getFirstCompletedRemoteData() + ).subscribe((result: RemoteData>) => { + this.processing$.next(false); + this.list$.next(result); + this.orcidQueueService.clearFindByOwnerRequests(); + }) + ); + } + + + /** + * Return the list of orcid queue records + */ + getList(): Observable>> { + return this.list$.asObservable(); + } + + getIconClass(orcidQueue: OrcidQueue): string { + if (!orcidQueue.recordType) { + return 'fa fa-user'; + } + switch (orcidQueue.recordType.toLowerCase()) { + case 'publication': + return 'fa fa-book'; + case 'funding': + return 'fa fa-wallet'; + case 'education': + return 'fa fa-school'; + case 'affiliation': + return 'fa fa-university'; + case 'country': + return 'fas fa-globe-europe'; + case 'external_ids': + case 'researcher_urls': + return 'fas fa-external-link-alt'; + default: + return 'fa fa-user'; + } + } + + getIconTooltip(orcidQueue: OrcidQueue): string { + if (!orcidQueue.recordType) { + return ''; + } + + return 'person.page.orcid.sync-queue.tooltip.' + orcidQueue.recordType.toLowerCase(); + } + + getOperationBadgeClass(orcidQueue: OrcidQueue): string { + + if (!orcidQueue.operation) { + return ''; + } + + switch (orcidQueue.operation.toLowerCase()) { + case 'insert': + return 'badge badge-pill badge-success'; + case 'update': + return 'badge badge-pill badge-primary'; + case 'delete': + return 'badge badge-pill badge-danger'; + default: + return ''; + } + } + + getOperationTooltip(orcidQueue: OrcidQueue): string { + if (!orcidQueue.operation) { + return ''; + } + + return 'person.page.orcid.sync-queue.tooltip.' + orcidQueue.operation.toLowerCase(); + } + + getOperationClass(orcidQueue: OrcidQueue): string { + + if (!orcidQueue.operation) { + return ''; + } + + switch (orcidQueue.operation.toLowerCase()) { + case 'insert': + return 'fas fa-plus'; + case 'update': + return 'fas fa-edit'; + case 'delete': + return 'fas fa-trash-alt'; + default: + return ''; + } + } + + discardEntry(orcidQueue: OrcidQueue) { + this.processing$.next(true); + this.subs.push(this.orcidQueueService.deleteById(orcidQueue.id).pipe( + getFinishedRemoteData() + ).subscribe((remoteData) => { + this.processing$.next(false); + if (remoteData.isSuccess) { + this.notificationsService.success(this.translateService.get('person.page.orcid.sync-queue.discard.success')); + this.updateList(); + } else { + this.notificationsService.error(this.translateService.get('person.page.orcid.sync-queue.discard.error')); + } + })); + } + + send( orcidQueue: OrcidQueue ) { + this.processing$.next(true); + this.subs.push(this.orcidHistoryService.sendToORCID(orcidQueue).pipe( + getFinishedRemoteData() + ).subscribe((remoteData) => { + this.processing$.next(false); + if (remoteData.isSuccess) { + this.handleOrcidHistoryRecordCreation(remoteData.payload); + } else if (remoteData.statusCode === 422) { + this.handleValidationErrors(remoteData); + } else { + this.notificationsService.error(this.translateService.get('person.page.orcid.sync-queue.send.error')); + } + })); + } + + handleOrcidHistoryRecordCreation(orcidHistory: OrcidHistory) { + switch (orcidHistory.status) { + case 200: + case 201: + case 204: + this.notificationsService.success(this.translateService.get('person.page.orcid.sync-queue.send.success')); + this.updateList(); + break; + case 400: + this.notificationsService.error(this.translateService.get('person.page.orcid.sync-queue.send.bad-request-error'), null, { timeOut: -1 }); + break; + case 401: + combineLatest([ + this.translateService.get('person.page.orcid.sync-queue.send.unauthorized-error.title'), + this.getUnauthorizedErrorContent()], + ).subscribe(([title, content]) => { + this.notificationsService.error(title, content, { timeOut: -1}, true); + }); + break; + case 404: + this.notificationsService.warning(this.translateService.get('person.page.orcid.sync-queue.send.not-found-warning')); + break; + case 409: + this.notificationsService.error(this.translateService.get('person.page.orcid.sync-queue.send.conflict-error'), null, { timeOut: -1 }); + break; + default: + this.notificationsService.error(this.translateService.get('person.page.orcid.sync-queue.send.error'), null, { timeOut: -1 }); + } + } + + handleValidationErrors(remoteData: RemoteData) { + const translations = [this.translateService.get('person.page.orcid.sync-queue.send.validation-error')]; + const errorMessage = remoteData.errorMessage; + if (errorMessage && errorMessage.indexOf('Error codes:') > 0) { + errorMessage.substring(errorMessage.indexOf(':') + 1).trim().split(',') + .forEach((error) => translations.push(this.translateService.get('person.page.orcid.sync-queue.send.validation-error.' + error))); + } + combineLatest(translations).subscribe((messages) => { + const title = messages.shift(); + const content = '
    ' + messages.map((message) => '
  • ' + message + '
  • ').join('') + '
'; + this.notificationsService.error(title, content, { timeOut: -1 }, true); + }); + } + + private getUnauthorizedErrorContent(): Observable { + return this.orcidQueueService.getOrcidAuthorizeUrl(this.route.snapshot.paramMap.get('id')).pipe( + switchMap((authorizeUrl) => this.translateService.get('person.page.orcid.sync-queue.send.unauthorized-error.content', { orcid : authorizeUrl})) + ); + } + + /** + * Unsubscribe from all subscriptions + */ + ngOnDestroy(): void { + this.list$ = null; + this.subs.filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); + } + + isProfileRecord(orcidQueue: OrcidQueue): boolean { + return orcidQueue.recordType !== 'Publication' && orcidQueue.recordType !== 'Project'; + } + +} From bcbd8dbd9353967bc600e1c77d4079452170191c Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Thu, 28 Apr 2022 12:28:44 +0200 Subject: [PATCH 012/157] 91070: Fix tests (add missing dependencies to testbed) --- .../journal/journal.component.spec.ts | 24 ++++++++++++++----- .../publication/publication.component.spec.ts | 24 ++++++++++++++----- .../item-types/shared/item.component.spec.ts | 24 ++++++++++++++----- 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts index 0e756b7dc9..3ed73e7891 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts @@ -29,6 +29,11 @@ import { TruncatableService } from '../../../../shared/truncatable/truncatable.s import { TruncatePipe } from '../../../../shared/utils/truncate.pipe'; import { JournalComponent } from './journal.component'; import { RouteService } from '../../../../core/services/route.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service'; +import { VersionDataService } from '../../../../core/data/version-data.service'; +import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service'; +import { SearchService } from '../../../../core/shared/search/search.service'; let comp: JournalComponent; let fixture: ComponentFixture; @@ -65,12 +70,15 @@ describe('JournalComponent', () => { }; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - })], + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + RouterTestingModule, + ], declarations: [JournalComponent, GenericItemPageFieldComponent, TruncatePipe], providers: [ { provide: ItemDataService, useValue: {} }, @@ -86,7 +94,11 @@ describe('JournalComponent', () => { { provide: DSOChangeAnalyzer, useValue: {} }, { provide: NotificationsService, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} }, + { provide: VersionHistoryDataService, useValue: {} }, + { provide: VersionDataService, useValue: {} }, { provide: BitstreamDataService, useValue: mockBitstreamDataService }, + { provide: WorkspaceitemDataService, useValue: {} }, + { provide: SearchService, useValue: {} }, { provide: RouteService, useValue: {} } ], diff --git a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts index 761849f232..404890e36d 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts +++ b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts @@ -33,6 +33,11 @@ import { import { PublicationComponent } from './publication.component'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { RouteService } from '../../../../core/services/route.service'; +import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service'; +import { VersionDataService } from '../../../../core/data/version-data.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service'; +import { SearchService } from '../../../../core/shared/search/search.service'; const iiifEnabledMap: MetadataMap = { 'dspace.iiif.enabled': [iiifEnabled], @@ -64,12 +69,15 @@ describe('PublicationComponent', () => { } }; TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - })], + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + RouterTestingModule, + ], declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe], providers: [ { provide: ItemDataService, useValue: {} }, @@ -85,7 +93,11 @@ describe('PublicationComponent', () => { { provide: HttpClient, useValue: {} }, { provide: DSOChangeAnalyzer, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} }, + { provide: VersionHistoryDataService, useValue: {} }, + { provide: VersionDataService, useValue: {} }, { provide: BitstreamDataService, useValue: mockBitstreamDataService }, + { provide: WorkspaceitemDataService, useValue: {} }, + { provide: SearchService, useValue: {} }, { provide: RouteService, useValue: mockRouteService } ], diff --git a/src/app/item-page/simple/item-types/shared/item.component.spec.ts b/src/app/item-page/simple/item-types/shared/item.component.spec.ts index fc07f60b28..01e3115db1 100644 --- a/src/app/item-page/simple/item-types/shared/item.component.spec.ts +++ b/src/app/item-page/simple/item-types/shared/item.component.spec.ts @@ -32,6 +32,11 @@ import { ItemComponent } from './item.component'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { RouteService } from '../../../../core/services/route.service'; import { MetadataValue } from '../../../../core/shared/metadata.models'; +import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { VersionDataService } from '../../../../core/data/version-data.service'; +import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service'; +import { RouterTestingModule } from '@angular/router/testing'; export const iiifEnabled = Object.assign(new MetadataValue(),{ 'value': 'true', @@ -70,12 +75,15 @@ export function getItemPageFieldsTest(mockItem: Item, component) { } }; TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - })], + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + RouterTestingModule, + ], declarations: [component, GenericItemPageFieldComponent, TruncatePipe], providers: [ { provide: ItemDataService, useValue: {} }, @@ -89,9 +97,13 @@ export function getItemPageFieldsTest(mockItem: Item, component) { { provide: HALEndpointService, useValue: {} }, { provide: HttpClient, useValue: {} }, { provide: DSOChangeAnalyzer, useValue: {} }, + { provide: VersionHistoryDataService, useValue: {} }, + { provide: VersionDataService, useValue: {} }, { provide: NotificationsService, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} }, { provide: BitstreamDataService, useValue: mockBitstreamDataService }, + { provide: WorkspaceitemDataService, useValue: {} }, + { provide: SearchService, useValue: {} }, { provide: RouteService, useValue: {} } ], From 7480a19a656fa954da162ff665b955bc86acecff Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 28 Apr 2022 12:18:14 +0200 Subject: [PATCH 013/157] 89720: Added ThemedItemMetadataComponent --- .../edit-item-template-page.component.html | 2 +- .../edit-item-page/edit-item-page.module.ts | 5 ++- .../themed-item-metadata.component.ts | 34 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 src/app/item-page/edit-item-page/item-metadata/themed-item-metadata.component.ts diff --git a/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.html b/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.html index f8c5c92e96..aa6b71f588 100644 --- a/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.html +++ b/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.html @@ -3,7 +3,7 @@

{{ 'collection.edit.template.head' | translate:{ collection: collection?.name } }}

- +
diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 97901bd7c8..67cf31c6ed 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -15,6 +15,7 @@ import { ItemPrivateComponent } from './item-private/item-private.component'; import { ItemPublicComponent } from './item-public/item-public.component'; import { ItemDeleteComponent } from './item-delete/item-delete.component'; import { ItemMetadataComponent } from './item-metadata/item-metadata.component'; +import { ThemedItemMetadataComponent } from './item-metadata/themed-item-metadata.component'; import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/edit-in-place-field.component'; import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component'; import { ItemEditBitstreamComponent } from './item-bitstreams/item-edit-bitstream/item-edit-bitstream.component'; @@ -62,6 +63,7 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource- ItemDeleteComponent, ItemStatusComponent, ItemMetadataComponent, + ThemedItemMetadataComponent, ItemRelationshipsComponent, ItemBitstreamsComponent, ItemVersionHistoryComponent, @@ -83,7 +85,8 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource- ObjectValuesPipe ], exports: [ - ItemMetadataComponent + EditInPlaceFieldComponent, + ThemedItemMetadataComponent, ] }) export class EditItemPageModule { diff --git a/src/app/item-page/edit-item-page/item-metadata/themed-item-metadata.component.ts b/src/app/item-page/edit-item-page/item-metadata/themed-item-metadata.component.ts new file mode 100644 index 0000000000..53f0120015 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-metadata/themed-item-metadata.component.ts @@ -0,0 +1,34 @@ +import { Component, Input } from '@angular/core'; +import { Item } from '../../../core/shared/item.model'; +import { UpdateDataService } from '../../../core/data/update-data.service'; +import { ItemMetadataComponent } from './item-metadata.component'; +import { ThemedComponent } from '../../../shared/theme-support/themed.component'; + +@Component({ + selector: 'ds-themed-item-metadata', + styleUrls: [], + templateUrl: './../../../shared/theme-support/themed.component.html', +}) +/** + * Component for displaying an item's metadata edit page + */ +export class ThemedItemMetadataComponent extends ThemedComponent { + + @Input() item: Item; + + @Input() updateService: UpdateDataService; + + protected inAndOutputNames: (keyof ItemMetadataComponent & keyof this)[] = ['item', 'updateService']; + + protected getComponentName(): string { + return 'ItemMetadataComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../../themes/${themeName}/app/item-page/edit-item-page/item-metadata/item-metadata.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./item-metadata.component`); + } +} From 7bbce89b40f46b268203d7a30335328f66f1f89c Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 28 Apr 2022 14:26:35 -0700 Subject: [PATCH 014/157] Fixes search passthrough to Mirador and adds tests. --- .../publication/publication.component.spec.ts | 121 ++++++++++++------ .../item-types/shared/item-iiif-utils.ts | 9 +- .../item-types/shared/item.component.spec.ts | 34 ++--- .../untyped-item.component.spec.ts | 86 +++++++++---- 4 files changed, 168 insertions(+), 82 deletions(-) diff --git a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts index 761849f232..7c63fabb47 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts +++ b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Store } from '@ngrx/store'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; @@ -17,7 +17,7 @@ import { RemoteData } from '../../../../core/data/remote-data'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { Item } from '../../../../core/shared/item.model'; -import { MetadataMap } from '../../../../core/shared/metadata.models'; +import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models'; import { UUIDService } from '../../../../core/shared/uuid.service'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; @@ -26,22 +26,12 @@ import { TruncatableService } from '../../../../shared/truncatable/truncatable.s import { TruncatePipe } from '../../../../shared/utils/truncate.pipe'; import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component'; import { - createRelationshipsObservable, - iiifEnabled, - iiifSearchEnabled, mockRouteService + createRelationshipsObservable, getIIIFEnabled, getIIIFSearchEnabled, mockRouteService } from '../shared/item.component.spec'; import { PublicationComponent } from './publication.component'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { RouteService } from '../../../../core/services/route.service'; - -const iiifEnabledMap: MetadataMap = { - 'dspace.iiif.enabled': [iiifEnabled], -}; - -const iiifEnabledWithSearchMap: MetadataMap = { - 'dspace.iiif.enabled': [iiifEnabled], - 'iiif.search.enabled': [iiifSearchEnabled], -}; +import { UntypedItemComponent } from '../untyped-item/untyped-item.component'; const noMetadata = new MetadataMap(); @@ -72,31 +62,32 @@ describe('PublicationComponent', () => { })], declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe], providers: [ - { provide: ItemDataService, useValue: {} }, - { provide: TruncatableService, useValue: {} }, - { provide: RelationshipService, useValue: {} }, - { provide: ObjectCacheService, useValue: {} }, - { provide: UUIDService, useValue: {} }, - { provide: Store, useValue: {} }, - { provide: RemoteDataBuildService, useValue: {} }, - { provide: CommunityDataService, useValue: {} }, - { provide: HALEndpointService, useValue: {} }, - { provide: NotificationsService, useValue: {} }, - { provide: HttpClient, useValue: {} }, - { provide: DSOChangeAnalyzer, useValue: {} }, - { provide: DefaultChangeAnalyzer, useValue: {} }, - { provide: BitstreamDataService, useValue: mockBitstreamDataService }, - { provide: RouteService, useValue: mockRouteService } + {provide: ItemDataService, useValue: {}}, + {provide: TruncatableService, useValue: {}}, + {provide: RelationshipService, useValue: {}}, + {provide: ObjectCacheService, useValue: {}}, + {provide: UUIDService, useValue: {}}, + {provide: Store, useValue: {}}, + {provide: RemoteDataBuildService, useValue: {}}, + {provide: CommunityDataService, useValue: {}}, + {provide: HALEndpointService, useValue: {}}, + {provide: NotificationsService, useValue: {}}, + {provide: HttpClient, useValue: {}}, + {provide: DSOChangeAnalyzer, useValue: {}}, + {provide: DefaultChangeAnalyzer, useValue: {}}, + {provide: BitstreamDataService, useValue: mockBitstreamDataService}, + {provide: RouteService, useValue: mockRouteService} ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(PublicationComponent, { - set: { changeDetection: ChangeDetectionStrategy.Default } - }).compileComponents(); + set: {changeDetection: ChangeDetectionStrategy.Default} + }); })); describe('default view', () => { beforeEach(waitForAsync(() => { + TestBed.compileComponents(); fixture = TestBed.createComponent(PublicationComponent); comp = fixture.componentInstance; comp.object = getItem(noMetadata); @@ -137,6 +128,42 @@ describe('PublicationComponent', () => { describe('with IIIF viewer', () => { beforeEach(waitForAsync(() => { + const iiifEnabledMap: MetadataMap = { + 'dspace.iiif.enabled': [getIIIFEnabled(true)], + 'iiif.search.enabled': [getIIIFSearchEnabled(false)], + }; + TestBed.compileComponents(); + fixture = TestBed.createComponent(PublicationComponent); + comp = fixture.componentInstance; + comp.object = getItem(iiifEnabledMap); + fixture.detectChanges(); + })); + + it('should contain an iiif viewer component', () => { + const fields = fixture.debugElement.queryAll(By.css('ds-mirador-viewer')); + expect(fields.length).toBeGreaterThanOrEqual(1); + }); + it('should not retrieve the query term for previous route', fakeAsync((): void => { + //tick(10) + expect(comp.iiifQuery$).toBeFalsy(); + })); + + }); + + describe('with IIIF viewer and search', () => { + + beforeEach(waitForAsync(() => { + const localMockRouteService = { + getPreviousUrl(): Observable { + return of('/search?query=test') + } + }; + const iiifEnabledMap: MetadataMap = { + 'dspace.iiif.enabled': [getIIIFEnabled(true)], + 'iiif.search.enabled': [getIIIFSearchEnabled(true)], + }; + TestBed.overrideProvider(RouteService, {useValue: localMockRouteService}); + TestBed.compileComponents(); fixture = TestBed.createComponent(PublicationComponent); comp = fixture.componentInstance; comp.object = getItem(iiifEnabledMap); @@ -148,15 +175,30 @@ describe('PublicationComponent', () => { expect(fields.length).toBeGreaterThanOrEqual(1); }); + it('should retrieve the query term for previous route', fakeAsync((): void => { + //tick(10) + expect(comp.iiifQuery$.subscribe(result => expect(result).toEqual('test'))); + })) + }); - describe('with IIIF viewer and search', () => { + describe('with IIIF viewer and search but no previous search query', () => { beforeEach(waitForAsync(() => { - mockRouteService.getPreviousUrl.and.returnValue(of(['/search?q=bird&motivation=painting','/item'])); + const localMockRouteService = { + getPreviousUrl(): Observable { + return of('/item') + } + }; + const iiifEnabledMap: MetadataMap = { + 'dspace.iiif.enabled': [getIIIFEnabled(true)], + 'iiif.search.enabled': [getIIIFSearchEnabled(true)], + }; + TestBed.overrideProvider(RouteService, {useValue: localMockRouteService}); + TestBed.compileComponents(); fixture = TestBed.createComponent(PublicationComponent); comp = fixture.componentInstance; - comp.object = getItem(iiifEnabledWithSearchMap); + comp.object = getItem(iiifEnabledMap); fixture.detectChanges(); })); @@ -165,11 +207,12 @@ describe('PublicationComponent', () => { expect(fields.length).toBeGreaterThanOrEqual(1); }); - it('should call the RouteService getHistory method', () => { - expect(mockRouteService.getPreviousUrl).toHaveBeenCalled(); - }); + it('should not retrieve the query term for previous route', fakeAsync( () => { + let emitted; + comp.iiifQuery$.subscribe(result => emitted = result); + tick(10) + expect(emitted).toBeUndefined(); + })); }); - }); - diff --git a/src/app/item-page/simple/item-types/shared/item-iiif-utils.ts b/src/app/item-page/simple/item-types/shared/item-iiif-utils.ts index eb7b30eb56..45bba1e569 100644 --- a/src/app/item-page/simple/item-types/shared/item-iiif-utils.ts +++ b/src/app/item-page/simple/item-types/shared/item-iiif-utils.ts @@ -1,6 +1,6 @@ import { Item } from '../../../../core/shared/item.model'; import { Observable } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; +import { filter, map, take } from 'rxjs/operators'; import { RouteService } from '../../../../core/services/route.service'; export const isIiifEnabled = (item: Item) => { @@ -26,10 +26,9 @@ export const getDSpaceQuery = (item: Item, routeService: RouteService): Observab return r.includes('/search'); }), map(r => { - const arr = r.split('&'); - const q = arr[1]; - const v = q.split('='); + const v = r.split('query='); return v[1]; - }) + }), + take(1) ); }; diff --git a/src/app/item-page/simple/item-types/shared/item.component.spec.ts b/src/app/item-page/simple/item-types/shared/item.component.spec.ts index fc07f60b28..96c5bd5106 100644 --- a/src/app/item-page/simple/item-types/shared/item.component.spec.ts +++ b/src/app/item-page/simple/item-types/shared/item.component.spec.ts @@ -4,7 +4,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Store } from '@ngrx/store'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; @@ -33,21 +33,25 @@ import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { RouteService } from '../../../../core/services/route.service'; import { MetadataValue } from '../../../../core/shared/metadata.models'; -export const iiifEnabled = Object.assign(new MetadataValue(),{ - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 -}); +export function getIIIFSearchEnabled(enabled: boolean): MetadataValue { + return Object.assign(new MetadataValue(), { + 'value': enabled, + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }); +} -export const iiifSearchEnabled = Object.assign(new MetadataValue(), { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 -}); +export function getIIIFEnabled(enabled: boolean): MetadataValue { + return Object.assign(new MetadataValue(), { + 'value': enabled, + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }); +} export const mockRouteService = jasmine.createSpyObj('RouteService', ['getPreviousUrl']); diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts index efbb4672a5..b5ac779086 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts @@ -1,10 +1,10 @@ import { HttpClient } from '@angular/common/http'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Store } from '@ngrx/store'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; @@ -26,13 +26,10 @@ import { TruncatableService } from '../../../../shared/truncatable/truncatable.s import { TruncatePipe } from '../../../../shared/utils/truncate.pipe'; import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component'; import { - createRelationshipsObservable, - iiifEnabled, - iiifSearchEnabled, mockRouteService + createRelationshipsObservable, getIIIFEnabled, getIIIFSearchEnabled, mockRouteService } from '../shared/item.component.spec'; import { UntypedItemComponent } from './untyped-item.component'; import { RouteService } from '../../../../core/services/route.service'; -import { of } from 'rxjs'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service'; import { VersionDataService } from '../../../../core/data/version-data.service'; @@ -41,16 +38,6 @@ import { WorkspaceitemDataService } from '../../../../core/submission/workspacei import { SearchService } from '../../../../core/shared/search/search.service'; import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service'; - -const iiifEnabledMap: MetadataMap = { - 'dspace.iiif.enabled': [iiifEnabled], -}; - -const iiifEnabledWithSearchMap: MetadataMap = { - 'dspace.iiif.enabled': [iiifEnabled], - 'iiif.search.enabled': [iiifSearchEnabled], -}; - const noMetadata = new MetadataMap(); function getItem(metadata: MetadataMap) { @@ -108,11 +95,12 @@ describe('UntypedItemComponent', () => { schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(UntypedItemComponent, { set: {changeDetection: ChangeDetectionStrategy.Default} - }).compileComponents(); + }); })); describe('default view', () => { beforeEach(waitForAsync(() => { + TestBed.compileComponents(); fixture = TestBed.createComponent(UntypedItemComponent); comp = fixture.componentInstance; comp.object = getItem(noMetadata); @@ -159,6 +147,41 @@ describe('UntypedItemComponent', () => { describe('with IIIF viewer', () => { beforeEach(waitForAsync(() => { + const iiifEnabledMap: MetadataMap = { + 'dspace.iiif.enabled': [getIIIFEnabled(true)], + 'iiif.search.enabled': [getIIIFSearchEnabled(false)], + }; + TestBed.compileComponents(); + fixture = TestBed.createComponent(UntypedItemComponent); + comp = fixture.componentInstance; + comp.object = getItem(iiifEnabledMap); + fixture.detectChanges(); + })); + + it('should contain an iiif viewer component', () => { + const fields = fixture.debugElement.queryAll(By.css('ds-mirador-viewer')); + expect(fields.length).toBeGreaterThanOrEqual(1); + }); + it('should not retrieve the query term for previous route', (): void => { + expect(comp.iiifQuery$).toBeFalsy(); + }); + + }); + + describe('with IIIF viewer and search', () => { + + beforeEach(waitForAsync(() => { + const localMockRouteService = { + getPreviousUrl(): Observable { + return of('/search?query=test') + } + }; + const iiifEnabledMap: MetadataMap = { + 'dspace.iiif.enabled': [getIIIFEnabled(true)], + 'iiif.search.enabled': [getIIIFSearchEnabled(true)], + }; + TestBed.overrideProvider(RouteService, {useValue: localMockRouteService}); + TestBed.compileComponents(); fixture = TestBed.createComponent(UntypedItemComponent); comp = fixture.componentInstance; comp.object = getItem(iiifEnabledMap); @@ -170,15 +193,29 @@ describe('UntypedItemComponent', () => { expect(fields.length).toBeGreaterThanOrEqual(1); }); + it('should retrieve the query term for previous route', (): void => { + expect(comp.iiifQuery$.subscribe(result => expect(result).toEqual('test'))); + }); + }); - describe('with IIIF viewer and search', () => { + describe('with IIIF viewer and search but no previous search query', () => { beforeEach(waitForAsync(() => { - mockRouteService.getPreviousUrl.and.returnValue(of(['/search?q=bird&motivation=painting','/item'])); + const localMockRouteService = { + getPreviousUrl(): Observable { + return of('/item') + } + }; + const iiifEnabledMap: MetadataMap = { + 'dspace.iiif.enabled': [getIIIFEnabled(true)], + 'iiif.search.enabled': [getIIIFSearchEnabled(true)], + }; + TestBed.overrideProvider(RouteService, {useValue: localMockRouteService}); + TestBed.compileComponents(); fixture = TestBed.createComponent(UntypedItemComponent); comp = fixture.componentInstance; - comp.object = getItem(iiifEnabledWithSearchMap); + comp.object = getItem(iiifEnabledMap); fixture.detectChanges(); })); @@ -187,9 +224,12 @@ describe('UntypedItemComponent', () => { expect(fields.length).toBeGreaterThanOrEqual(1); }); - it('should call the RouteService getHistory method', () => { - expect(mockRouteService.getPreviousUrl).toHaveBeenCalled(); - }); + it('should not retrieve the query term for previous route', fakeAsync( () => { + let emitted; + comp.iiifQuery$.subscribe(result => emitted = result); + tick(10) + expect(emitted).toBeUndefined() + })); }); From 58b8ba3aa7a5a8274e9731fea6cc158373d93d2e Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 28 Apr 2022 17:01:59 -0700 Subject: [PATCH 015/157] Lint errors. --- .../item-types/publication/publication.component.spec.ts | 8 ++++---- .../untyped-item/untyped-item.component.spec.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts index 7c63fabb47..772a83d93e 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts +++ b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts @@ -155,7 +155,7 @@ describe('PublicationComponent', () => { beforeEach(waitForAsync(() => { const localMockRouteService = { getPreviousUrl(): Observable { - return of('/search?query=test') + return of('/search?query=test'); } }; const iiifEnabledMap: MetadataMap = { @@ -178,7 +178,7 @@ describe('PublicationComponent', () => { it('should retrieve the query term for previous route', fakeAsync((): void => { //tick(10) expect(comp.iiifQuery$.subscribe(result => expect(result).toEqual('test'))); - })) + })); }); @@ -187,7 +187,7 @@ describe('PublicationComponent', () => { beforeEach(waitForAsync(() => { const localMockRouteService = { getPreviousUrl(): Observable { - return of('/item') + return of('/item'); } }; const iiifEnabledMap: MetadataMap = { @@ -210,7 +210,7 @@ describe('PublicationComponent', () => { it('should not retrieve the query term for previous route', fakeAsync( () => { let emitted; comp.iiifQuery$.subscribe(result => emitted = result); - tick(10) + tick(10); expect(emitted).toBeUndefined(); })); diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts index b5ac779086..308129892a 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts @@ -173,7 +173,7 @@ describe('UntypedItemComponent', () => { beforeEach(waitForAsync(() => { const localMockRouteService = { getPreviousUrl(): Observable { - return of('/search?query=test') + return of('/search?query=test'); } }; const iiifEnabledMap: MetadataMap = { @@ -204,7 +204,7 @@ describe('UntypedItemComponent', () => { beforeEach(waitForAsync(() => { const localMockRouteService = { getPreviousUrl(): Observable { - return of('/item') + return of('/item'); } }; const iiifEnabledMap: MetadataMap = { @@ -227,8 +227,8 @@ describe('UntypedItemComponent', () => { it('should not retrieve the query term for previous route', fakeAsync( () => { let emitted; comp.iiifQuery$.subscribe(result => emitted = result); - tick(10) - expect(emitted).toBeUndefined() + tick(10); + expect(emitted).toBeUndefined(); })); }); From 2f84d0294be4582fdc5c6db8c0fe059b42eeddfa Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 29 Apr 2022 10:27:26 +0200 Subject: [PATCH 016/157] 89720: Added ThemedEditItemTemplatePageComponent --- .../collection-page-routing.module.ts | 4 +-- .../collection-page/collection-page.module.ts | 2 ++ ...hemed-edit-item-template-page.component.ts | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 src/app/collection-page/edit-item-template-page/themed-edit-item-template-page.component.ts diff --git a/src/app/collection-page/collection-page-routing.module.ts b/src/app/collection-page/collection-page-routing.module.ts index 5879e523af..0574958037 100644 --- a/src/app/collection-page/collection-page-routing.module.ts +++ b/src/app/collection-page/collection-page-routing.module.ts @@ -6,7 +6,7 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard'; import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; -import { EditItemTemplatePageComponent } from './edit-item-template-page/edit-item-template-page.component'; +import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component'; import { ItemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver'; import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver'; import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; @@ -52,7 +52,7 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen }, { path: ITEMTEMPLATE_PATH, - component: EditItemTemplatePageComponent, + component: ThemedEditItemTemplatePageComponent, canActivate: [AuthenticatedGuard], resolve: { item: ItemTemplatePageResolver, diff --git a/src/app/collection-page/collection-page.module.ts b/src/app/collection-page/collection-page.module.ts index 3652823200..c35ebf9021 100644 --- a/src/app/collection-page/collection-page.module.ts +++ b/src/app/collection-page/collection-page.module.ts @@ -8,6 +8,7 @@ import { CollectionPageRoutingModule } from './collection-page-routing.module'; import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component'; import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; import { EditItemTemplatePageComponent } from './edit-item-template-page/edit-item-template-page.component'; +import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component'; import { EditItemPageModule } from '../item-page/edit-item-page/edit-item-page.module'; import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component'; import { SearchService } from '../core/shared/search/search.service'; @@ -32,6 +33,7 @@ import { ComcolModule } from '../shared/comcol/comcol.module'; CreateCollectionPageComponent, DeleteCollectionPageComponent, EditItemTemplatePageComponent, + ThemedEditItemTemplatePageComponent, CollectionItemMapperComponent ], providers: [ diff --git a/src/app/collection-page/edit-item-template-page/themed-edit-item-template-page.component.ts b/src/app/collection-page/edit-item-template-page/themed-edit-item-template-page.component.ts new file mode 100644 index 0000000000..b53f4e6c45 --- /dev/null +++ b/src/app/collection-page/edit-item-template-page/themed-edit-item-template-page.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { EditItemTemplatePageComponent } from './edit-item-template-page.component'; + +@Component({ + selector: 'ds-themed-edit-item-template-page', + styleUrls: [], + templateUrl: '../../shared/theme-support/themed.component.html', +}) +/** + * Component for editing the item template of a collection + */ +export class ThemedEditItemTemplatePageComponent extends ThemedComponent { + protected getComponentName(): string { + return 'EditItemTemplatePageComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/collection-page/edit-item-template-page/edit-item-template-page.component`); + } + + protected importUnthemedComponent(): Promise { + return import('./edit-item-template-page.component'); + } +} From b9b5b50999a3e3173a97709e403c4773839116a5 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 3 May 2022 08:56:37 +0200 Subject: [PATCH 017/157] 89741: Added ThemedExpandableNavbarSectionComponent --- ...med-expandable-navbar-section.component.ts | 29 +++++++++++++++++++ src/app/navbar/navbar.module.ts | 11 +++---- 2 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts diff --git a/src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts b/src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts new file mode 100644 index 0000000000..744eeb5a02 --- /dev/null +++ b/src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { ExpandableNavbarSectionComponent } from './expandable-navbar-section.component'; +import { rendersSectionForMenu } from '../../shared/menu/menu-section.decorator'; +import { MenuID } from '../../shared/menu/initial-menus-state'; + +/** + * Themed wrapper for ExpandableNavbarSectionComponent + */ +@Component({ + /* tslint:disable:component-selector */ + selector: 'li[ds-themed-expandable-navbar-section]', + styleUrls: [], + templateUrl: '../../shared/theme-support/themed.component.html', +}) +@rendersSectionForMenu(MenuID.PUBLIC, true) +export class ThemedExpandableNavbarSectionComponent extends ThemedComponent { + protected getComponentName(): string { + return 'ExpandableNavbarSectionComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/navbar/expandable-navbar-section/expandable-navbar-section.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./expandable-navbar-section.component`); + } +} diff --git a/src/app/navbar/navbar.module.ts b/src/app/navbar/navbar.module.ts index c84e732fd5..95641de41b 100644 --- a/src/app/navbar/navbar.module.ts +++ b/src/app/navbar/navbar.module.ts @@ -7,6 +7,7 @@ import { CoreModule } from '../core/core.module'; import { NavbarEffects } from './navbar.effects'; import { NavbarSectionComponent } from './navbar-section/navbar-section.component'; import { ExpandableNavbarSectionComponent } from './expandable-navbar-section/expandable-navbar-section.component'; +import { ThemedExpandableNavbarSectionComponent } from './expandable-navbar-section/themed-expandable-navbar-section.component'; import { NavbarComponent } from './navbar.component'; import { MenuModule } from '../shared/menu/menu.module'; import { SharedModule } from '../shared/shared.module'; @@ -20,7 +21,7 @@ const effects = [ const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator NavbarSectionComponent, - ExpandableNavbarSectionComponent, + ThemedExpandableNavbarSectionComponent, ]; @NgModule({ @@ -36,19 +37,19 @@ const ENTRY_COMPONENTS = [ NavbarComponent, ThemedNavbarComponent, NavbarSectionComponent, - ExpandableNavbarSectionComponent + ExpandableNavbarSectionComponent, + ThemedExpandableNavbarSectionComponent, ], providers: [ ], entryComponents: [ - NavbarSectionComponent, - ExpandableNavbarSectionComponent + ...ENTRY_COMPONENTS, ], exports: [ ThemedNavbarComponent, NavbarSectionComponent, - ExpandableNavbarSectionComponent + ThemedExpandableNavbarSectionComponent, ] }) From da1fb5506b4edec2fac0c04eff624ada4e86de0f Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 6 May 2022 16:50:22 +0200 Subject: [PATCH 018/157] [CST-5668] Fixed orcid page --- .../profile/researcher-profile.service.ts | 6 ++-- .../orcid-auth/orcid-auth.component.ts | 6 ++-- .../orcid-page/orcid-page.component.html | 2 +- .../orcid-page/orcid-page.component.ts | 30 ++++++++++++++++++- .../orcid-sync/orcid-setting.component.ts | 12 ++++---- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index 100fa459a1..b7035a921f 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -162,7 +162,7 @@ export class ResearcherProfileService { * @returns the check result */ isLinkedToOrcid(item: Item): boolean { - return item.hasMetadata('cris.orcid.authenticated'); + return item.hasMetadata('dspace.orcid.authenticated'); } /** @@ -214,7 +214,7 @@ export class ResearcherProfileService { op:'remove' }]; - return this.findById(item.firstMetadata('cris.owner').authority).pipe( + return this.findById(item.firstMetadata('dspace.object.owner').authority).pipe( switchMap((profile) => this.patch(profile, operations)), getFinishedRemoteData() ); @@ -227,7 +227,7 @@ export class ResearcherProfileService { this.configurationService.findByPropertyName('orcid.scope').pipe(getFirstSucceededRemoteDataPayload())] ).pipe( map(([authorizeUrl, clientId, scopes]) => { - const redirectUri = environment.rest.baseUrl + '/api/cris/orcid/' + profile.id + '/?url=' + encodeURIComponent(this.router.url); + const redirectUri = environment.rest.baseUrl + '/api/eperson/orcid/' + profile.id + '/?url=' + encodeURIComponent(this.router.url); return authorizeUrl.values[0] + '?client_id=' + clientId.values[0] + '&redirect_uri=' + redirectUri + '&response_type=code&scope=' + scopes.values.join(' '); })); diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts index 9651215dc6..8594cba9f5 100644 --- a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts @@ -33,7 +33,7 @@ export class OrcidAuthComponent implements OnInit { private itemService: ItemDataService, private route: ActivatedRoute, @Inject(NativeWindowService) private _window: NativeWindowRef, - ) { + ) { this.itemService.findById(this.route.snapshot.paramMap.get('id'), true, true).pipe(getFirstCompletedRemoteData()).subscribe((data: RemoteData) => { this.item = data.payload; }); @@ -49,10 +49,10 @@ export class OrcidAuthComponent implements OnInit { } getOrcidAuthorizations(): string[] { - return this.item.allMetadataValues('cris.orcid.scope'); + return this.item.allMetadataValues('dspace.orcid.scope'); } - isLinkedToOrcid(): boolean { + isLinkedToOrcid(): boolean { return this.researcherProfileService.isLinkedToOrcid(this.item); } diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html index 4e62a8d51c..e0cbfe95e2 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.html +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -1,2 +1,2 @@ - + diff --git a/src/app/item-page/orcid-page/orcid-page.component.ts b/src/app/item-page/orcid-page/orcid-page.component.ts index 32b47e069f..eed5ebc276 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.ts +++ b/src/app/item-page/orcid-page/orcid-page.component.ts @@ -1,4 +1,14 @@ -import { Component } from '@angular/core'; +import {Component, Inject} from '@angular/core'; +import {ConfigurationDataService} from '../../core/data/configuration-data.service'; +import {ResearcherProfileService} from '../../core/profile/researcher-profile.service'; +import {TranslateService} from '@ngx-translate/core'; +import {NotificationsService} from '../../shared/notifications/notifications.service'; +import {ItemDataService} from '../../core/data/item-data.service'; +import {ActivatedRoute} from '@angular/router'; +import {NativeWindowRef, NativeWindowService} from '../../core/services/window.service'; +import {getFirstCompletedRemoteData} from '../../core/shared/operators'; +import {RemoteData} from '../../core/data/remote-data'; +import {Item} from '../../core/shared/item.model'; @Component({ selector: 'ds-orcid-page', @@ -6,4 +16,22 @@ import { Component } from '@angular/core'; styleUrls: ['./orcid-page.component.scss'] }) export class OrcidPageComponent { + + item: Item; + + constructor( + private itemService: ItemDataService, + private researcherProfileService: ResearcherProfileService, + private route: ActivatedRoute, + @Inject(NativeWindowService) private _window: NativeWindowRef, + ) { + this.itemService.findById(this.route.snapshot.paramMap.get('id'), true, true).pipe(getFirstCompletedRemoteData()).subscribe((data: RemoteData) => { + this.item = data.payload; + }); + } + + isLinkedToOrcid(): boolean { + return this.researcherProfileService.isLinkedToOrcid(this.item); + } + } diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts index 87385f0780..45ec48e788 100644 --- a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts @@ -77,9 +77,9 @@ export class OrcidSettingComponent implements OnInit { }; }); - const syncProfilePreferences = this.item.allMetadataValues('cris.orcid.sync-profile'); + const syncProfilePreferences = this.item.allMetadataValues('dspace.orcid.sync-profile'); - this.syncProfileOptions = ['AFFILIATION', 'EDUCATION', 'BIOGRAPHICAL', 'IDENTIFIERS'] + this.syncProfileOptions = ['BIOGRAPHICAL', 'IDENTIFIERS'] .map((value) => { return { label: this.messagePrefix + '.sync-profile.' + value.toLowerCase(), @@ -88,9 +88,9 @@ export class OrcidSettingComponent implements OnInit { }; }); - this.currentSyncMode = this.getCurrentPreference('cris.orcid.sync-mode', ['BATCH, MANUAL'], 'MANUAL'); - this.currentSyncPublications = this.getCurrentPreference('cris.orcid.sync-publications', ['DISABLED', 'ALL'], 'DISABLED'); - this.currentSyncFundings = this.getCurrentPreference('cris.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); + this.currentSyncMode = this.getCurrentPreference('dspace.orcid.sync-mode', ['BATCH, MANUAL'], 'MANUAL'); + this.currentSyncPublications = this.getCurrentPreference('dspace.orcid.sync-publications', ['DISABLED', 'ALL'], 'DISABLED'); + this.currentSyncFundings = this.getCurrentPreference('dspace.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); } onSubmit(form: FormGroup) { @@ -110,7 +110,7 @@ export class OrcidSettingComponent implements OnInit { return; } - this.researcherProfileService.findById(this.item.firstMetadata('cris.owner').authority).pipe( + this.researcherProfileService.findById(this.item.firstMetadata('dspace.object.owner').authority).pipe( switchMap((profile) => this.researcherProfileService.patch(profile, operations)), getFinishedRemoteData() ).subscribe((remoteData) => { From 870a36180c4836108c23f790e5e49c04460a99b4 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 25 Apr 2022 17:26:12 +0200 Subject: [PATCH 019/157] 89984: Add ThemedLoadingComponent --- .../epeople-registry.component.html | 2 +- .../eperson-form/eperson-form.component.html | 4 +-- .../groups-registry.component.html | 2 +- .../edit-bitstream-page.component.html | 4 +-- .../browse-by-metadata-page.component.html | 2 +- .../collection-page.component.html | 8 ++--- .../collection-source.component.html | 2 +- .../edit-item-template-page.component.html | 2 +- .../community-list.component.html | 6 ++-- .../community-page.component.html | 2 +- ...ty-page-sub-collection-list.component.html | 2 +- ...ity-page-sub-community-list.component.html | 2 +- .../top-level-community-list.component.html | 2 +- .../item-bitstreams.component.html | 2 +- ...rag-and-drop-bitstream-list.component.html | 2 +- .../edit-relationship-list.component.html | 2 +- .../item-relationships.component.html | 2 +- .../full/full-item-page.component.html | 2 +- .../media-viewer/media-viewer.component.html | 4 +-- .../media-viewer.component.spec.ts | 2 +- .../file-section/file-section.component.html | 2 +- .../file-section.component.spec.ts | 2 +- .../item-page/simple/item-page.component.html | 2 +- .../simple/item-page.component.spec.ts | 2 +- ...etadata-representation-list.component.html | 2 +- .../related-items.component.html | 2 +- .../detail/process-detail.component.html | 2 +- .../deny-request-copy.component.html | 2 +- .../grant-deny-request-copy.component.html | 2 +- .../grant-request-copy.component.html | 2 +- src/app/root/root.component.html | 4 +-- .../user-menu/user-menu.component.html | 2 +- .../shared/browse-by/browse-by.component.html | 2 +- .../browse-by/browse-by.component.spec.ts | 2 +- .../collection-dropdown.component.html | 4 +-- .../comcol-role/comcol-role.component.html | 2 +- .../dso-selector/dso-selector.component.html | 2 +- .../entity-dropdown.component.html | 4 +-- ...sting-metadata-list-element.component.html | 2 +- ...sting-relation-list-element.component.html | 2 +- .../dynamic-relation-group.component.html | 2 +- ...namic-lookup-relation-modal.component.html | 2 +- ...elation-external-source-tab.component.html | 4 +-- ...tion-external-source-tab.component.spec.ts | 4 +-- .../loading/themed-loading.component.ts | 31 +++++++++++++++++++ src/app/shared/log-in/log-in.component.html | 2 +- .../object-detail.component.html | 2 +- .../object-grid/object-grid.component.html | 2 +- ...-search-result-list-element.component.html | 4 +-- ...-search-result-list-element.component.html | 4 +-- .../collection-select.component.html | 2 +- .../item-select/item-select.component.html | 2 +- .../search-results.component.html | 2 +- src/app/shared/shared.module.ts | 2 ++ .../vocabulary-treeview.component.html | 2 +- .../statistics-page.component.html | 2 +- .../form/submission-form.component.html | 2 +- ...-import-external-collection.component.html | 2 +- .../submission-import-external.component.html | 4 +-- ...mission-section-cc-licenses.component.html | 4 +-- .../sections/form/section-form.component.html | 2 +- src/app/thumbnail/thumbnail.component.html | 4 +-- src/app/thumbnail/thumbnail.component.spec.ts | 2 +- 63 files changed, 112 insertions(+), 79 deletions(-) create mode 100644 src/app/shared/loading/themed-loading.component.ts diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html index 7ef02a76cf..2d87f21d26 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/access-control/epeople-registry/epeople-registry.component.html @@ -45,7 +45,7 @@
- + - +
{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}
- + - +
- + diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html index 2321da0204..c0741891eb 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html @@ -35,7 +35,7 @@ (prev)="goPrev()" (next)="goNext()"> - + diff --git a/src/app/collection-page/collection-page.component.html b/src/app/collection-page/collection-page.component.html index 9d598a3b69..4f5422e300 100644 --- a/src/app/collection-page/collection-page.component.html +++ b/src/app/collection-page/collection-page.component.html @@ -57,8 +57,8 @@ - + @@ -75,7 +75,7 @@ - + diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html index b67ee9a1bd..847ccefdda 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html @@ -25,7 +25,7 @@ - +

{{ 'collection.edit.tabs.source.form.head' | translate }}

diff --git a/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.html b/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.html index f8c5c92e96..37258cd057 100644 --- a/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.html +++ b/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.html @@ -6,7 +6,7 @@ - +
diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index f441dfa36e..5d53633c4e 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -1,4 +1,4 @@ - + {{ 'communityList.showMore' | translate }} - +
@@ -57,7 +57,7 @@ - +
diff --git a/src/app/community-page/community-page.component.html b/src/app/community-page/community-page.component.html index cf7282eb4b..95755fdaf1 100644 --- a/src/app/community-page/community-page.component.html +++ b/src/app/community-page/community-page.component.html @@ -41,5 +41,5 @@ - + diff --git a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.html b/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.html index 9928ebd18a..69f16ee3ac 100644 --- a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.html +++ b/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.html @@ -9,5 +9,5 @@ - +
diff --git a/src/app/community-page/sub-community-list/community-page-sub-community-list.component.html b/src/app/community-page/sub-community-list/community-page-sub-community-list.component.html index 2d14dce60a..be2788a9f4 100644 --- a/src/app/community-page/sub-community-list/community-page-sub-community-list.component.html +++ b/src/app/community-page/sub-community-list/community-page-sub-community-list.component.html @@ -9,5 +9,5 @@ - + diff --git a/src/app/home-page/top-level-community-list/top-level-community-list.component.html b/src/app/home-page/top-level-community-list/top-level-community-list.component.html index dbea87a175..788db6ea4f 100644 --- a/src/app/home-page/top-level-community-list/top-level-community-list.component.html +++ b/src/app/home-page/top-level-community-list/top-level-community-list.component.html @@ -10,4 +10,4 @@ - + diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index 315498378a..0d6a609e74 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -44,7 +44,7 @@ class="alert alert-info w-100 d-inline-block mt-4" role="alert"> {{'item.edit.bitstreams.empty' | translate}} - +
diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html index f7904f6cc8..f54aa73597 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html @@ -29,5 +29,5 @@
- + diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html index 86ab93662e..7cdc903f24 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html @@ -31,5 +31,5 @@
{{"item.edit.relationships.no-relationships" | translate}}
- + diff --git a/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.html index 4a53445a6d..f19ab21871 100644 --- a/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.html +++ b/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.html @@ -32,7 +32,7 @@ >
- +
diff --git a/src/app/item-page/full/full-item-page.component.html b/src/app/item-page/full/full-item-page.component.html index d2655c4ad0..ea97a3957c 100644 --- a/src/app/item-page/full/full-item-page.component.html +++ b/src/app/item-page/full/full-item-page.component.html @@ -37,5 +37,5 @@
- + diff --git a/src/app/item-page/media-viewer/media-viewer.component.html b/src/app/item-page/media-viewer/media-viewer.component.html index b79b91629f..4259af5250 100644 --- a/src/app/item-page/media-viewer/media-viewer.component.html +++ b/src/app/item-page/media-viewer/media-viewer.component.html @@ -1,9 +1,9 @@ - + >
diff --git a/src/app/item-page/media-viewer/media-viewer.component.spec.ts b/src/app/item-page/media-viewer/media-viewer.component.spec.ts index cfd72bf416..39a35ebe61 100644 --- a/src/app/item-page/media-viewer/media-viewer.component.spec.ts +++ b/src/app/item-page/media-viewer/media-viewer.component.spec.ts @@ -110,7 +110,7 @@ describe('MediaViewerComponent', () => { }); it('should display a loading component', () => { - const loading = fixture.debugElement.query(By.css('ds-loading')); + const loading = fixture.debugElement.query(By.css('ds-themed-loading')); expect(loading.nativeElement).toBeDefined(); }); }); diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.html b/src/app/item-page/simple/field-components/file-section/file-section.component.html index 3d093f83c9..5fa5e5096f 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.html +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.html @@ -6,7 +6,7 @@ ({{(file?.sizeBytes) | dsFileSize }}) - + diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts b/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts index fdd6d14e7d..2d185aef9c 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts @@ -86,7 +86,7 @@ describe('FileSectionComponent', () => { }); it('should display a loading component', () => { - const loading = fixture.debugElement.query(By.css('ds-loading')); + const loading = fixture.debugElement.query(By.css('ds-themed-loading')); expect(loading.nativeElement).toBeDefined(); }); }); diff --git a/src/app/item-page/simple/item-page.component.html b/src/app/item-page/simple/item-page.component.html index 74b61fd976..d91f4b501d 100644 --- a/src/app/item-page/simple/item-page.component.html +++ b/src/app/item-page/simple/item-page.component.html @@ -9,5 +9,5 @@
- + diff --git a/src/app/item-page/simple/item-page.component.spec.ts b/src/app/item-page/simple/item-page.component.spec.ts index ff5a1e38d5..0b0f1e1724 100644 --- a/src/app/item-page/simple/item-page.component.spec.ts +++ b/src/app/item-page/simple/item-page.component.spec.ts @@ -85,7 +85,7 @@ describe('ItemPageComponent', () => { }); it('should display a loading component', () => { - const loading = fixture.debugElement.query(By.css('ds-loading')); + const loading = fixture.debugElement.query(By.css('ds-themed-loading')); expect(loading.nativeElement).toBeDefined(); }); }); diff --git a/src/app/item-page/simple/metadata-representation-list/metadata-representation-list.component.html b/src/app/item-page/simple/metadata-representation-list/metadata-representation-list.component.html index d1281f450a..e1559cb129 100644 --- a/src/app/item-page/simple/metadata-representation-list/metadata-representation-list.component.html +++ b/src/app/item-page/simple/metadata-representation-list/metadata-representation-list.component.html @@ -4,7 +4,7 @@ - + - + diff --git a/src/app/request-copy/grant-request-copy/grant-request-copy.component.html b/src/app/request-copy/grant-request-copy/grant-request-copy.component.html index d2c2cfc3c8..5cb4dbac36 100644 --- a/src/app/request-copy/grant-request-copy/grant-request-copy.component.html +++ b/src/app/request-copy/grant-request-copy/grant-request-copy.component.html @@ -13,5 +13,5 @@ - + diff --git a/src/app/root/root.component.html b/src/app/root/root.component.html index 898074352c..d0465af69f 100644 --- a/src/app/root/root.component.html +++ b/src/app/root/root.component.html @@ -13,7 +13,7 @@
- +
@@ -25,6 +25,6 @@
- +
diff --git a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html index ac55a211e9..736d39d318 100644 --- a/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html +++ b/src/app/shared/auth-nav-menu/user-menu/user-menu.component.html @@ -1,4 +1,4 @@ - +
{{(user$ | async)?.name}} ({{(user$ | async)?.email}}) {{'nav.profile' | translate}} diff --git a/src/app/shared/browse-by/browse-by.component.html b/src/app/shared/browse-by/browse-by.component.html index 6d1422293d..645a987abe 100644 --- a/src/app/shared/browse-by/browse-by.component.html +++ b/src/app/shared/browse-by/browse-by.component.html @@ -34,7 +34,7 @@
- + diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html index b0a319b4ff..440785b89b 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html @@ -13,7 +13,7 @@
- +
{{'comcol-role.edit.no-group' | translate}}
diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.html b/src/app/shared/dso-selector/dso-selector/dso-selector.component.html index 4b46d3bc3f..d4283246d8 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.html +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.html @@ -31,7 +31,7 @@
diff --git a/src/app/shared/entity-dropdown/entity-dropdown.component.html b/src/app/shared/entity-dropdown/entity-dropdown.component.html index 59c242ef97..a4d539625f 100644 --- a/src/app/shared/entity-dropdown/entity-dropdown.component.html +++ b/src/app/shared/entity-dropdown/entity-dropdown.component.html @@ -21,8 +21,8 @@ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html index 07ea131a00..62d34a8625 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html @@ -1,7 +1,7 @@
- + diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html index 15087d2553..ff91b18e1c 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html @@ -1,7 +1,7 @@
- + diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.html index 84bc0f4ffe..05f443aad5 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.html @@ -57,7 +57,7 @@
- +
- + diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html index 74fc5fd06d..f26de733b2 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html @@ -6,6 +6,6 @@ - + [showMessage]="false"> diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.html index 41d95b87af..2f0b04ef12 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.html @@ -6,6 +6,6 @@ - + [showMessage]="false"> diff --git a/src/app/shared/object-select/collection-select/collection-select.component.html b/src/app/shared/object-select/collection-select/collection-select.component.html index 27ebcd9643..8ed41c5226 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.html +++ b/src/app/shared/object-select/collection-select/collection-select.component.html @@ -28,7 +28,7 @@ {{'collection.select.empty' | translate}}
- +
- + diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 715ee66a99..f9391afdb4 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -177,6 +177,7 @@ import { ScopeSelectorModalComponent } from './search-form/scope-selector-modal/ import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-page/bitstream-request-a-copy-page.component'; import { DsSelectComponent } from './ds-select/ds-select.component'; import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component'; +import { ThemedLoadingComponent } from './loading/themed-loading.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -234,6 +235,7 @@ const COMPONENTS = [ FileSectionComponent, LangSwitchComponent, LoadingComponent, + ThemedLoadingComponent, LogInComponent, LogOutComponent, NumberPickerComponent, diff --git a/src/app/shared/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/vocabulary-treeview/vocabulary-treeview.component.html index 3f57e6a4c3..39c62d6e53 100644 --- a/src/app/shared/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/vocabulary-treeview/vocabulary-treeview.component.html @@ -23,7 +23,7 @@
- +

{{'vocabulary-treeview.search.no-result' | translate}}

diff --git a/src/app/statistics-page/statistics-page/statistics-page.component.html b/src/app/statistics-page/statistics-page/statistics-page.component.html index 5cf1e9c8b5..c6938c7582 100644 --- a/src/app/statistics-page/statistics-page/statistics-page.component.html +++ b/src/app/statistics-page/statistics-page/statistics-page.component.html @@ -11,7 +11,7 @@ - + diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index c86d4e0195..a80fe35f4e 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -22,7 +22,7 @@
- +
- \ No newline at end of file + diff --git a/src/app/shared/menu/menu-section.decorator.ts b/src/app/shared/menu/menu-section.decorator.ts index 9374ab3c0d..18eedd1cf4 100644 --- a/src/app/shared/menu/menu-section.decorator.ts +++ b/src/app/shared/menu/menu-section.decorator.ts @@ -1,6 +1,6 @@ import { DEFAULT_THEME } from '../object-collection/shared/listable-object/listable-object.decorator'; import { MenuID } from './initial-menus-state'; -import { hasNoValue, hasValue } from '../empty.util'; +import { hasValue } from '../empty.util'; const menuComponentMap = new Map(); diff --git a/src/app/shared/theme-support/themed.component.ts b/src/app/shared/theme-support/themed.component.ts index 2ff0713f46..79ed914c5d 100644 --- a/src/app/shared/theme-support/themed.component.ts +++ b/src/app/shared/theme-support/themed.component.ts @@ -70,6 +70,7 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges this.lazyLoadSub = this.resolveThemedComponent(this.themeService.getThemeName()).pipe( switchMap((themedFile: any) => { + console.log(themedFile); if (hasValue(themedFile) && hasValue(themedFile[this.getComponentName()])) { // if the file is not null, and exports a component with the specified name, // return that component diff --git a/src/themes/custom/app/shared/loading/loading.component.html b/src/themes/custom/app/shared/loading/loading.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/loading/loading.component.scss b/src/themes/custom/app/shared/loading/loading.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/loading/loading.component.ts b/src/themes/custom/app/shared/loading/loading.component.ts new file mode 100644 index 0000000000..43249a4624 --- /dev/null +++ b/src/themes/custom/app/shared/loading/loading.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { LoadingComponent as BaseComponent } from '../../../../../app/shared/loading/loading.component'; + +@Component({ + selector: 'ds-loading', + styleUrls: ['../../../../../app/shared/loading/loading.component.scss'], + // styleUrls: ['./loading.component.scss'], + templateUrl: '../../../../app/shared/loading/loading.component.html' + // templateUrl: './loading.component.html' +}) +export class LoadingComponent extends BaseComponent { + +} diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index e2e97b9087..67ad6028ce 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -84,6 +84,7 @@ import { SearchModule } from '../../app/shared/search/search.module'; import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../app/shared/comcol/comcol.module'; import { FeedbackComponent } from './app/info/feedback/feedback.component'; +import { LoadingComponent } from './app/shared/loading/loading.component'; const DECLARATIONS = [ FileSectionComponent, @@ -126,7 +127,8 @@ const DECLARATIONS = [ NavbarComponent, HeaderNavbarWrapperComponent, BreadcrumbsComponent, - FeedbackComponent + FeedbackComponent, + LoadingComponent ]; @NgModule({ From 0ce709ee68c6de3aef8ac6c79f8cdd3cb7943f68 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Mon, 9 May 2022 18:19:53 +0200 Subject: [PATCH 028/157] 90945: Use *-relationships discovery configs on single-item pages --- .../item-pages/journal/journal.component.html | 3 ++- .../item-pages/org-unit/org-unit.component.html | 4 ++-- .../research-entities/item-pages/person/person.component.html | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index b1b35db724..79c66cccc1 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -47,7 +47,8 @@ diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 7c8ce67b88..6703634b6b 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -57,12 +57,12 @@ [relationTypes]="[{ label: 'isOrgUnitOfPerson', filter: 'isOrgUnitOfPerson', - configuration: 'person' + configuration: 'person-relationships' }, { label: 'isOrgUnitOfProject', filter: 'isOrgUnitOfProject', - configuration: 'project' + configuration: 'project-relationships' }]"> diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index 6e71f775d6..4b7b3bed32 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -68,7 +68,8 @@ From 166a163c24fd0e6b05aa6a49fc8474c10c9a1600 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 11 May 2022 12:34:44 +0200 Subject: [PATCH 029/157] Fix custom loading components template url --- src/themes/custom/app/shared/loading/loading.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/custom/app/shared/loading/loading.component.ts b/src/themes/custom/app/shared/loading/loading.component.ts index 43249a4624..fb1a291dc0 100644 --- a/src/themes/custom/app/shared/loading/loading.component.ts +++ b/src/themes/custom/app/shared/loading/loading.component.ts @@ -5,7 +5,7 @@ import { LoadingComponent as BaseComponent } from '../../../../../app/shared/loa selector: 'ds-loading', styleUrls: ['../../../../../app/shared/loading/loading.component.scss'], // styleUrls: ['./loading.component.scss'], - templateUrl: '../../../../app/shared/loading/loading.component.html' + templateUrl: '../../../../../app/shared/loading/loading.component.html' // templateUrl: './loading.component.html' }) export class LoadingComponent extends BaseComponent { From 67ffa89c472bc7f86283fd8e0bcd735f481d4bc6 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 11 May 2022 13:30:11 +0200 Subject: [PATCH 030/157] Remove console.log --- src/app/shared/theme-support/themed.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/shared/theme-support/themed.component.ts b/src/app/shared/theme-support/themed.component.ts index 79ed914c5d..2ff0713f46 100644 --- a/src/app/shared/theme-support/themed.component.ts +++ b/src/app/shared/theme-support/themed.component.ts @@ -70,7 +70,6 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges this.lazyLoadSub = this.resolveThemedComponent(this.themeService.getThemeName()).pipe( switchMap((themedFile: any) => { - console.log(themedFile); if (hasValue(themedFile) && hasValue(themedFile[this.getComponentName()])) { // if the file is not null, and exports a component with the specified name, // return that component From 5b4ecb9fc23633aad3224a9002cb56d85fc028c8 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 11 May 2022 13:46:03 +0200 Subject: [PATCH 031/157] 89720: Themed ItemMetadataComponent --- .../item-metadata/item-metadata.component.html | 0 .../item-metadata/item-metadata.component.scss | 0 .../item-metadata/item-metadata.component.ts | 17 +++++++++++++++++ src/themes/custom/theme.module.ts | 4 +++- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.html create mode 100644 src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.scss create mode 100644 src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.ts diff --git a/src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.html b/src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.scss b/src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.ts new file mode 100644 index 0000000000..d6d7c4b8fb --- /dev/null +++ b/src/themes/custom/app/item-page/edit-item-page/item-metadata/item-metadata.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { + ItemMetadataComponent as BaseComponent +} from '../../../../../../app/item-page/edit-item-page/item-metadata/item-metadata.component'; + +@Component({ + selector: 'ds-item-metadata', + // styleUrls: ['./item-metadata.component.scss'], + styleUrls: ['../../../../../../app/item-page/edit-item-page/item-metadata/item-metadata.component.scss'], + // templateUrl: './item-metadata.component.html', + templateUrl: '../../../../../../app/item-page/edit-item-page/item-metadata/item-metadata.component.html', +}) +/** + * Component for displaying an item's metadata edit page + */ +export class ItemMetadataComponent extends BaseComponent { +} diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index e2e97b9087..ce259b5841 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -84,6 +84,7 @@ import { SearchModule } from '../../app/shared/search/search.module'; import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../app/shared/comcol/comcol.module'; import { FeedbackComponent } from './app/info/feedback/feedback.component'; +import { ItemMetadataComponent } from './app/item-page/edit-item-page/item-metadata/item-metadata.component'; const DECLARATIONS = [ FileSectionComponent, @@ -126,7 +127,8 @@ const DECLARATIONS = [ NavbarComponent, HeaderNavbarWrapperComponent, BreadcrumbsComponent, - FeedbackComponent + FeedbackComponent, + ItemMetadataComponent, ]; @NgModule({ From a89375f67babaad5db95d9e661c8a89210940806 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 11 May 2022 14:28:16 +0200 Subject: [PATCH 032/157] 89720: Themed EditItemTemplatePageComponent --- .../edit-item-template-page.component.html | 0 .../edit-item-template-page.component.scss | 0 .../edit-item-template-page.component.ts | 16 ++++++++++++++++ src/themes/custom/theme.module.ts | 4 ++++ 4 files changed, 20 insertions(+) create mode 100644 src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.html create mode 100644 src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.scss create mode 100644 src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.ts diff --git a/src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.html b/src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.scss b/src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.ts b/src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.ts new file mode 100644 index 0000000000..ad9f515dcf --- /dev/null +++ b/src/themes/custom/app/collection-page/edit-item-template-page/edit-item-template-page.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { + EditItemTemplatePageComponent as BaseComponent +} from '../../../../../app/collection-page/edit-item-template-page/edit-item-template-page.component'; + +@Component({ + selector: 'ds-edit-item-template-page', + styleUrls: ['./edit-item-template-page.component.scss'], + // templateUrl: './edit-item-template-page.component.html', + templateUrl: '../../../../../app/collection-page/edit-item-template-page/edit-item-template-page.component.html', +}) +/** + * Component for editing the item template of a collection + */ +export class EditItemTemplatePageComponent extends BaseComponent { +} diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index ce259b5841..af3dee50c0 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -85,6 +85,9 @@ import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resou import { ComcolModule } from '../../app/shared/comcol/comcol.module'; import { FeedbackComponent } from './app/info/feedback/feedback.component'; import { ItemMetadataComponent } from './app/item-page/edit-item-page/item-metadata/item-metadata.component'; +import { + EditItemTemplatePageComponent +} from './app/collection-page/edit-item-template-page/edit-item-template-page.component'; const DECLARATIONS = [ FileSectionComponent, @@ -129,6 +132,7 @@ const DECLARATIONS = [ BreadcrumbsComponent, FeedbackComponent, ItemMetadataComponent, + EditItemTemplatePageComponent, ]; @NgModule({ From 3abf83a5e9479148d7aea72c0c177f7469deb02c Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 11 May 2022 17:54:41 +0200 Subject: [PATCH 033/157] 90945: Add missing translations for new discovery configs --- src/assets/i18n/en.json5 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f742273edb..13e5171f62 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2278,6 +2278,8 @@ "journal.search.results.head": "Journal Search Results", + "journal-relationships.search.results.head": "Journal Search Results", + "journal.search.title": "Journal Search", @@ -2755,6 +2757,8 @@ "person.search.results.head": "Person Search Results", + "person-relationships.search.results.head": "Person Search Results", + "person.search.title": "Person Search", @@ -2930,6 +2934,8 @@ "project.search.results.head": "Project Search Results", + "project-relationships.search.results.head": "Project Search Results", + "publication.listelement.badge": "Publication", @@ -2950,6 +2956,8 @@ "publication.search.results.head": "Publication Search Results", + "publication-relationships.search.results.head": "Publication Search Results", + "publication.search.title": "Publication Search", @@ -3410,6 +3418,8 @@ "default.search.results.head": "Search Results", + "default-relationships.search.results.head": "Search Results", + "search.sidebar.close": "Back to results", From db375b8f4702acc19c14480d3f27a26ae023957c Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 12 May 2022 12:05:17 +0200 Subject: [PATCH 034/157] 91131: Added parent constructor to ThemedLoadingComponent --- src/app/shared/loading/themed-loading.component.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/app/shared/loading/themed-loading.component.ts b/src/app/shared/loading/themed-loading.component.ts index 2b6aa6212c..0f887a025f 100644 --- a/src/app/shared/loading/themed-loading.component.ts +++ b/src/app/shared/loading/themed-loading.component.ts @@ -1,6 +1,7 @@ -import { Component, Input } from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { Component, Input, ComponentFactoryResolver, ChangeDetectorRef } from '@angular/core'; +import { ThemedComponent } from '../theme-support/themed.component'; import { LoadingComponent } from './loading.component'; +import { ThemeService } from '../theme-support/theme.service'; /** * Themed wrapper for LoadingComponent @@ -17,6 +18,14 @@ export class ThemedLoadingComponent extends ThemedComponent { protected inAndOutputNames: (keyof LoadingComponent & keyof this)[] = ['message', 'showMessage']; + constructor( + protected resolver: ComponentFactoryResolver, + protected cdr: ChangeDetectorRef, + protected themeService: ThemeService + ) { + super(resolver, cdr, themeService); + } + protected getComponentName(): string { return 'LoadingComponent'; } From 5682c81217c5da5090ad6cbb4cb561cbca5cd84c Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 13 May 2022 13:45:49 +0200 Subject: [PATCH 035/157] 91272: Fixed import statements and position of expandable navbar button --- .../themed-expandable-navbar-section.component.ts | 4 ++-- src/app/navbar/navbar.component.html | 4 ++-- .../expandable-navbar-section.component.ts | 4 ++-- src/themes/dspace/app/navbar/navbar.component.html | 4 ++-- src/themes/dspace/styles/_global-styles.scss | 3 +-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts b/src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts index 744eeb5a02..e33dca4104 100644 --- a/src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts +++ b/src/app/navbar/expandable-navbar-section/themed-expandable-navbar-section.component.ts @@ -2,13 +2,13 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { ExpandableNavbarSectionComponent } from './expandable-navbar-section.component'; import { rendersSectionForMenu } from '../../shared/menu/menu-section.decorator'; -import { MenuID } from '../../shared/menu/initial-menus-state'; +import { MenuID } from '../../shared/menu/menu-id.model'; /** * Themed wrapper for ExpandableNavbarSectionComponent */ @Component({ - /* tslint:disable:component-selector */ + /* eslint-disable @angular-eslint/component-selector */ selector: 'li[ds-themed-expandable-navbar-section]', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', diff --git a/src/app/navbar/navbar.component.html b/src/app/navbar/navbar.component.html index 63f308fb32..b41f2ea706 100644 --- a/src/app/navbar/navbar.component.html +++ b/src/app/navbar/navbar.component.html @@ -4,7 +4,7 @@
-
- \ No newline at end of file + diff --git a/src/themes/custom/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts b/src/themes/custom/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts index 283b9ca6f6..f7efd1fdca 100644 --- a/src/themes/custom/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts +++ b/src/themes/custom/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts @@ -4,13 +4,13 @@ import { } from '../../../../../app/navbar/expandable-navbar-section/expandable-navbar-section.component'; import { slide } from '../../../../../app/shared/animations/slide'; import { rendersSectionForMenu } from '../../../../../app/shared/menu/menu-section.decorator'; -import { MenuID } from '../../../../../app/shared/menu/initial-menus-state'; +import { MenuID } from '../../../../../app/shared/menu/menu-id.model'; /** * Represents an expandable section in the navbar */ @Component({ - /* tslint:disable:component-selector */ + /* eslint-disable @angular-eslint/component-selector */ selector: 'li[ds-expandable-navbar-section]', // templateUrl: './expandable-navbar-section.component.html', templateUrl: '../../../../../app/navbar/expandable-navbar-section/expandable-navbar-section.component.html', diff --git a/src/themes/dspace/app/navbar/navbar.component.html b/src/themes/dspace/app/navbar/navbar.component.html index 218fc368ba..6b71728494 100644 --- a/src/themes/dspace/app/navbar/navbar.component.html +++ b/src/themes/dspace/app/navbar/navbar.component.html @@ -6,7 +6,7 @@
-
- \ No newline at end of file + diff --git a/src/themes/dspace/styles/_global-styles.scss b/src/themes/dspace/styles/_global-styles.scss index 8682e3dcdf..3271f15bf2 100644 --- a/src/themes/dspace/styles/_global-styles.scss +++ b/src/themes/dspace/styles/_global-styles.scss @@ -23,8 +23,7 @@ } header { - li > .navbar-section, - li > .expandable-navbar-section { + .navbar-navigation > li { display: flex; flex-direction: column; justify-content: center; From e197e496b51aad5d05e4e6684a84cf435a1e15d4 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 13 May 2022 17:19:42 +0200 Subject: [PATCH 036/157] 91272: Fixed accessibility violation --- .../admin-sidebar-section/admin-sidebar-section.component.ts | 2 ++ .../expandable-admin-sidebar-section.component.ts | 2 ++ .../expandable-navbar-section.component.ts | 3 +-- src/app/navbar/navbar-section/navbar-section.component.ts | 2 ++ .../expandable-navbar-section.component.ts | 3 +-- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts index 47f693cb99..422431d2c5 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts @@ -14,6 +14,8 @@ import { Router } from '@angular/router'; @Component({ /* eslint-disable @angular-eslint/component-selector */ selector: 'li[ds-admin-sidebar-section]', + // To theme this element remove the surrounding li[] in this component but leave it in the new + // ThemedAdminSidebarSectionComponent templateUrl: './admin-sidebar-section.component.html', styleUrls: ['./admin-sidebar-section.component.scss'], diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts index 7cd20b15d2..28cadcac5f 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts @@ -17,6 +17,8 @@ import { Router } from '@angular/router'; @Component({ /* eslint-disable @angular-eslint/component-selector */ selector: 'li[ds-expandable-admin-sidebar-section]', + // To theme this element remove the surrounding li[] in this component but leave it in the new + // ThemedExpandableAdminSidebarSectionComponent templateUrl: './expandable-admin-sidebar-section.component.html', styleUrls: ['./expandable-admin-sidebar-section.component.scss'], animations: [rotate, slide, bgColor] diff --git a/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts b/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts index a6d3a52cd2..5bc69bcbb4 100644 --- a/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts +++ b/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts @@ -11,8 +11,7 @@ import { MenuID } from '../../shared/menu/menu-id.model'; * Represents an expandable section in the navbar */ @Component({ - /* eslint-disable @angular-eslint/component-selector */ - selector: 'li[ds-expandable-navbar-section]', + selector: 'ds-expandable-navbar-section', templateUrl: './expandable-navbar-section.component.html', styleUrls: ['./expandable-navbar-section.component.scss'], animations: [slide] diff --git a/src/app/navbar/navbar-section/navbar-section.component.ts b/src/app/navbar/navbar-section/navbar-section.component.ts index 9f75a96f6e..20d5299584 100644 --- a/src/app/navbar/navbar-section/navbar-section.component.ts +++ b/src/app/navbar/navbar-section/navbar-section.component.ts @@ -10,6 +10,8 @@ import { MenuID } from '../../shared/menu/menu-id.model'; @Component({ /* eslint-disable @angular-eslint/component-selector */ selector: 'li[ds-navbar-section]', + // To theme this element remove the surrounding li[] in this component but leave it in the new + // ThemedNavbarSectionComponent templateUrl: './navbar-section.component.html', styleUrls: ['./navbar-section.component.scss'] }) diff --git a/src/themes/custom/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts b/src/themes/custom/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts index f7efd1fdca..3e11271bf0 100644 --- a/src/themes/custom/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts +++ b/src/themes/custom/app/navbar/expandable-navbar-section/expandable-navbar-section.component.ts @@ -10,8 +10,7 @@ import { MenuID } from '../../../../../app/shared/menu/menu-id.model'; * Represents an expandable section in the navbar */ @Component({ - /* eslint-disable @angular-eslint/component-selector */ - selector: 'li[ds-expandable-navbar-section]', + selector: 'ds-expandable-navbar-section', // templateUrl: './expandable-navbar-section.component.html', templateUrl: '../../../../../app/navbar/expandable-navbar-section/expandable-navbar-section.component.html', // styleUrls: ['./expandable-navbar-section.component.scss'], From 460efa42c732dc80979e8733793c439282141c1e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 13 May 2022 19:33:40 +0200 Subject: [PATCH 037/157] 91272: Fix merge request --- src/app/admin/admin-sidebar/admin-sidebar.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index 5febf93deb..d4071b91ed 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -41,7 +41,6 @@ import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { MenuID } from '../../shared/menu/menu-id.model'; import { MenuItemType } from '../../shared/menu/menu-item-type.model'; import { ActivatedRoute } from '@angular/router'; -import { Router, ActivatedRoute } from '@angular/router'; import { ThemeService } from '../../shared/theme-support/theme.service'; /** From 743513cf848a7db3c5ec1fc97817a07fd6cdc134 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 13 May 2022 20:35:08 +0200 Subject: [PATCH 038/157] 89676: Added empty SearchResultsComponent files --- .../shared/search/search-results/search-results.component.html | 0 .../shared/search/search-results/search-results.component.scss | 0 .../app/shared/search/search-results/search-results.component.ts | 1 + 3 files changed, 1 insertion(+) create mode 100644 src/themes/custom/app/shared/search/search-results/search-results.component.html create mode 100644 src/themes/custom/app/shared/search/search-results/search-results.component.scss diff --git a/src/themes/custom/app/shared/search/search-results/search-results.component.html b/src/themes/custom/app/shared/search/search-results/search-results.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/search/search-results/search-results.component.scss b/src/themes/custom/app/shared/search/search-results/search-results.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/search/search-results/search-results.component.ts b/src/themes/custom/app/shared/search/search-results/search-results.component.ts index bdfeea15a5..5084d4d405 100644 --- a/src/themes/custom/app/shared/search/search-results/search-results.component.ts +++ b/src/themes/custom/app/shared/search/search-results/search-results.component.ts @@ -6,6 +6,7 @@ import { fadeIn, fadeInOut } from '../../../../../../app/shared/animations/fade' selector: 'ds-search-results', // templateUrl: './search-results.component.html', templateUrl: '../../../../../../app/shared/search/search-results/search-results.component.html', + // styleUrls: ['./search-results.component.scss'], animations: [ fadeIn, fadeInOut From 06091e39ca8f970e7166a6266ecf95ee0c39517f Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 20 May 2022 17:16:28 +0200 Subject: [PATCH 039/157] [CST-5668] Improved orcid page adding back button and fixing synchronization mode setting --- .../item-pages/person/person.component.html | 2 +- src/app/item-page/orcid-page/orcid-page.component.html | 8 ++++++++ src/app/item-page/orcid-page/orcid-page.component.ts | 5 +++++ .../orcid-page/orcid-sync/orcid-setting.component.ts | 2 +- src/assets/i18n/en.json5 | 1 + 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index d8325d8077..bcff32c553 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -3,7 +3,7 @@ {{'person.page.titleprefix' | translate}}
- +
diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html index e0cbfe95e2..296d5468c5 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.html +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -1,2 +1,10 @@ + + diff --git a/src/app/item-page/orcid-page/orcid-page.component.ts b/src/app/item-page/orcid-page/orcid-page.component.ts index eed5ebc276..506b5c7e49 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.ts +++ b/src/app/item-page/orcid-page/orcid-page.component.ts @@ -9,6 +9,7 @@ import {NativeWindowRef, NativeWindowService} from '../../core/services/window.s import {getFirstCompletedRemoteData} from '../../core/shared/operators'; import {RemoteData} from '../../core/data/remote-data'; import {Item} from '../../core/shared/item.model'; +import {getItemPageRoute} from '../item-page-routing-paths'; @Component({ selector: 'ds-orcid-page', @@ -34,4 +35,8 @@ export class OrcidPageComponent { return this.researcherProfileService.isLinkedToOrcid(this.item); } + getItemPage(): string { + return getItemPageRoute(this.item); + } + } diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts index 45ec48e788..00a6018892 100644 --- a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts @@ -88,7 +88,7 @@ export class OrcidSettingComponent implements OnInit { }; }); - this.currentSyncMode = this.getCurrentPreference('dspace.orcid.sync-mode', ['BATCH, MANUAL'], 'MANUAL'); + this.currentSyncMode = this.getCurrentPreference('dspace.orcid.sync-mode', ['BATCH', 'MANUAL'], 'MANUAL'); this.currentSyncPublications = this.getCurrentPreference('dspace.orcid.sync-publications', ['DISABLED', 'ALL'], 'DISABLED'); this.currentSyncFundings = this.getCurrentPreference('dspace.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0f240899cd..79714d78a6 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2067,6 +2067,7 @@ "item.edit.withdraw.success": "The item was withdrawn successfully", + "item.orcid.return": "Back", "item.listelement.badge": "Item", From 857a3c56b749e93b596f9ab9125e9ad506b5dd9f Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 20 May 2022 17:31:54 +0200 Subject: [PATCH 040/157] [CST-5668] Fixed lint warnings --- src/app/item-page/item-page-routing-paths.ts | 2 +- src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts | 2 +- src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/item-page/item-page-routing-paths.ts b/src/app/item-page/item-page-routing-paths.ts index 9da2f91431..90a4a54b1e 100644 --- a/src/app/item-page/item-page-routing-paths.ts +++ b/src/app/item-page/item-page-routing-paths.ts @@ -50,4 +50,4 @@ export const ITEM_EDIT_PATH = 'edit'; export const ITEM_EDIT_VERSIONHISTORY_PATH = 'versionhistory'; export const ITEM_VERSION_PATH = 'version'; export const UPLOAD_BITSTREAM_PATH = 'bitstreams/new'; -export const ORCID_PATH = 'orcid'; \ No newline at end of file +export const ORCID_PATH = 'orcid'; diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts index 8594cba9f5..0f368ada2f 100644 --- a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts @@ -23,7 +23,7 @@ export class OrcidAuthComponent implements OnInit { unlinkProcessing = false; - item: Item + item: Item; constructor( private configurationService: ConfigurationDataService, diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts index 0bc9e18520..2181f3db20 100644 --- a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts +++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts @@ -14,7 +14,7 @@ import { AuthService } from '../../../../core/auth/auth.service'; import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; import { take } from 'rxjs/operators'; import { URLCombiner } from '../../../../core/url-combiner/url-combiner'; -import {CoreState} from "../../../../core/core-state.model"; +import {CoreState} from '../../../../core/core-state.model'; @Component({ selector: 'ds-log-in-orcid', From 5ec460d236fe3f0bcb832e0929fe71de16714dc4 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 23 May 2022 08:44:10 -0700 Subject: [PATCH 041/157] Url parsing Lint fix --- .../item-types/publication/publication.component.spec.ts | 8 +++----- .../item-page/simple/item-types/shared/item-iiif-utils.ts | 8 +++++--- .../untyped-item/untyped-item.component.spec.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts index 772a83d93e..9f20c81aa8 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts +++ b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts @@ -17,7 +17,7 @@ import { RemoteData } from '../../../../core/data/remote-data'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { Item } from '../../../../core/shared/item.model'; -import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models'; +import { MetadataMap } from '../../../../core/shared/metadata.models'; import { UUIDService } from '../../../../core/shared/uuid.service'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; @@ -31,7 +31,6 @@ import { import { PublicationComponent } from './publication.component'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { RouteService } from '../../../../core/services/route.service'; -import { UntypedItemComponent } from '../untyped-item/untyped-item.component'; const noMetadata = new MetadataMap(); @@ -155,7 +154,7 @@ describe('PublicationComponent', () => { beforeEach(waitForAsync(() => { const localMockRouteService = { getPreviousUrl(): Observable { - return of('/search?query=test'); + return of('/search?query=test%20query&fakeParam=true'); } }; const iiifEnabledMap: MetadataMap = { @@ -176,8 +175,7 @@ describe('PublicationComponent', () => { }); it('should retrieve the query term for previous route', fakeAsync((): void => { - //tick(10) - expect(comp.iiifQuery$.subscribe(result => expect(result).toEqual('test'))); + expect(comp.iiifQuery$.subscribe(result => expect(result).toEqual('test query'))); })); }); diff --git a/src/app/item-page/simple/item-types/shared/item-iiif-utils.ts b/src/app/item-page/simple/item-types/shared/item-iiif-utils.ts index 45bba1e569..fe1070400e 100644 --- a/src/app/item-page/simple/item-types/shared/item-iiif-utils.ts +++ b/src/app/item-page/simple/item-types/shared/item-iiif-utils.ts @@ -2,6 +2,7 @@ import { Item } from '../../../../core/shared/item.model'; import { Observable } from 'rxjs'; import { filter, map, take } from 'rxjs/operators'; import { RouteService } from '../../../../core/services/route.service'; +import { DefaultUrlSerializer, UrlTree } from '@angular/router'; export const isIiifEnabled = (item: Item) => { return !!item.firstMetadataValue('dspace.iiif.enabled'); @@ -21,13 +22,14 @@ export const isIiifSearchEnabled = (item: Item) => { * @param routeService */ export const getDSpaceQuery = (item: Item, routeService: RouteService): Observable => { + return routeService.getPreviousUrl().pipe( filter(r => { return r.includes('/search'); }), - map(r => { - const v = r.split('query='); - return v[1]; + map((r: string) => { + const url: UrlTree = new DefaultUrlSerializer().parse(r); + return url.queryParamMap.get('query'); }), take(1) ); diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts index 308129892a..736916c940 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts @@ -173,7 +173,7 @@ describe('UntypedItemComponent', () => { beforeEach(waitForAsync(() => { const localMockRouteService = { getPreviousUrl(): Observable { - return of('/search?query=test'); + return of('/search?query=test%20query&fakeParam=true'); } }; const iiifEnabledMap: MetadataMap = { @@ -194,7 +194,7 @@ describe('UntypedItemComponent', () => { }); it('should retrieve the query term for previous route', (): void => { - expect(comp.iiifQuery$.subscribe(result => expect(result).toEqual('test'))); + expect(comp.iiifQuery$.subscribe(result => expect(result).toEqual('test query'))); }); }); From 3a483c393ddeeb94c90113bbd36320bf8abb3800 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 27 May 2022 16:32:23 +0200 Subject: [PATCH 042/157] 89721: Themed UntypedItemComponent --- .../untyped-item/untyped-item.component.html | 0 .../untyped-item/untyped-item.component.scss | 0 .../untyped-item/untyped-item.component.ts | 25 +++++++++++++++++++ src/themes/custom/entry-components.ts | 4 ++- src/themes/custom/theme.module.ts | 22 ++++++++++++---- 5 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.html create mode 100644 src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.scss create mode 100644 src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts diff --git a/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.scss b/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts b/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts new file mode 100644 index 0000000000..1d1676e92f --- /dev/null +++ b/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts @@ -0,0 +1,25 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Item } from '../../../../../../../app/core/shared/item.model'; +import { ViewMode } from '../../../../../../../app/core/shared/view-mode.model'; +import { + listableObjectComponent +} from '../../../../../../../app/shared/object-collection/shared/listable-object/listable-object.decorator'; +import { Context } from '../../../../../../../app/core/shared/context.model'; +import { + UntypedItemComponent as BaseComponent +} from '../../../../../../../app/item-page/simple/item-types/untyped-item/untyped-item.component'; + +/** + * Component that represents an untyped Item page + */ +@listableObjectComponent(Item, ViewMode.StandalonePage, Context.Any, 'custom') +@Component({ + selector: 'ds-untyped-item', + // styleUrls: ['./untyped-item.component.scss'], + styleUrls: ['../../../../../../../app/item-page/simple/item-types/untyped-item/untyped-item.component.scss'], + // templateUrl: './untyped-item.component.html', + templateUrl: '../../../../../../../app/item-page/simple/item-types/untyped-item/untyped-item.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UntypedItemComponent extends BaseComponent { +} diff --git a/src/themes/custom/entry-components.ts b/src/themes/custom/entry-components.ts index b518e4cc45..9a9c0624a4 100644 --- a/src/themes/custom/entry-components.ts +++ b/src/themes/custom/entry-components.ts @@ -1,5 +1,7 @@ import { PublicationComponent } from './app/item-page/simple/item-types/publication/publication.component'; +import { UntypedItemComponent } from './app/item-page/simple/item-types/untyped-item/untyped-item.component'; export const ENTRY_COMPONENTS = [ - PublicationComponent + PublicationComponent, + UntypedItemComponent, ]; diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index e2e97b9087..c5d5e5a1c4 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -34,6 +34,7 @@ import { HomePageModule } from '../../app/home-page/home-page.module'; import { RootComponent } from './app/root/root.component'; import { AppModule } from '../../app/app.module'; import { PublicationComponent } from './app/item-page/simple/item-types/publication/publication.component'; +import { UntypedItemComponent } from './app/item-page/simple/item-types/untyped-item/untyped-item.component'; import { ItemPageModule } from '../../app/item-page/item-page.module'; import { RouterModule } from '@angular/router'; import { AccessControlModule } from '../../app/access-control/access-control.module'; @@ -47,8 +48,12 @@ import { PageNotFoundComponent } from './app/pagenotfound/pagenotfound.component import { ObjectNotFoundComponent } from './app/lookup-by-id/objectnotfound/objectnotfound.component'; import { ForbiddenComponent } from './app/forbidden/forbidden.component'; import { PrivacyComponent } from './app/info/privacy/privacy.component'; -import { CollectionStatisticsPageComponent } from './app/statistics-page/collection-statistics-page/collection-statistics-page.component'; -import { CommunityStatisticsPageComponent } from './app/statistics-page/community-statistics-page/community-statistics-page.component'; +import { + CollectionStatisticsPageComponent +} from './app/statistics-page/collection-statistics-page/collection-statistics-page.component'; +import { + CommunityStatisticsPageComponent +} from './app/statistics-page/community-statistics-page/community-statistics-page.component'; import { StatisticsPageModule } from '../../app/statistics-page/statistics-page.module'; import { ItemStatisticsPageComponent } from './app/statistics-page/item-statistics-page/item-statistics-page.component'; import { SiteStatisticsPageComponent } from './app/statistics-page/site-statistics-page/site-statistics-page.component'; @@ -67,11 +72,17 @@ import { ForgotPasswordFormComponent } from './app/forgot-password/forgot-passwo import { ProfilePageComponent } from './app/profile-page/profile-page.component'; import { RegisterEmailComponent } from './app/register-page/register-email/register-email.component'; import { SubmissionEditComponent } from './app/submission/edit/submission-edit.component'; -import { SubmissionImportExternalComponent } from './app/submission/import-external/submission-import-external.component'; +import { + SubmissionImportExternalComponent +} from './app/submission/import-external/submission-import-external.component'; import { SubmissionSubmitComponent } from './app/submission/submit/submission-submit.component'; import { MyDSpacePageComponent } from './app/my-dspace-page/my-dspace-page.component'; -import { WorkflowItemSendBackComponent } from './app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component'; -import { WorkflowItemDeleteComponent } from './app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component'; +import { + WorkflowItemSendBackComponent +} from './app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component'; +import { + WorkflowItemDeleteComponent +} from './app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component'; import { SubmissionModule } from '../../app/submission/submission.module'; import { MyDSpacePageModule } from '../../app/my-dspace-page/my-dspace-page.module'; import { NavbarComponent } from './app/navbar/navbar.component'; @@ -91,6 +102,7 @@ const DECLARATIONS = [ HomeNewsComponent, RootComponent, PublicationComponent, + UntypedItemComponent, BrowseBySwitcherComponent, CommunityListPageComponent, SearchPageComponent, From 533e4c982f43f2859951479c1302b3343226941d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 2 Jun 2022 10:22:46 +0200 Subject: [PATCH 043/157] 91272: Small fixes --- .../listable-object-component-loader.component.ts | 2 ++ .../app/info/end-user-agreement/end-user-agreement.component.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 8adbcbeec3..9c7ad5f659 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -128,6 +128,8 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges 'context', 'viewMode', 'value', + 'hideBadges', + 'contentChange', ]; constructor( diff --git a/src/themes/custom/app/info/end-user-agreement/end-user-agreement.component.ts b/src/themes/custom/app/info/end-user-agreement/end-user-agreement.component.ts index e3e5ac8d19..50e196bfaf 100644 --- a/src/themes/custom/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/themes/custom/app/info/end-user-agreement/end-user-agreement.component.ts @@ -2,7 +2,7 @@ import { Component } from '@angular/core'; import { EndUserAgreementComponent as BaseComponent } from '../../../../../app/info/end-user-agreement/end-user-agreement.component'; @Component({ - selector: 'ds-home-news', + selector: 'ds-end-user-agreement', // styleUrls: ['./end-user-agreement.component.scss'], styleUrls: ['../../../../../app/info/end-user-agreement/end-user-agreement.component.scss'], // templateUrl: './end-user-agreement.component.html' From 2441db16e93a500f8ed4849537be918d86696086 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 27 May 2022 18:53:56 +0200 Subject: [PATCH 044/157] 89721: Themed JournalIssueComponent --- .../journal-issue.component.html | 0 .../journal-issue.component.scss | 0 .../journal-issue/journal-issue.component.ts | 23 +++++++++++++++++++ src/themes/custom/entry-components.ts | 4 ++++ src/themes/custom/theme.module.ts | 6 ++++- 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts new file mode 100644 index 0000000000..a9f23c25f6 --- /dev/null +++ b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { ViewMode } from '../../../../../../../app/core/shared/view-mode.model'; +import { + listableObjectComponent +} from '../../../../../../../app/shared/object-collection/shared/listable-object/listable-object.decorator'; +import { + JournalIssueComponent as BaseComponent +} from '../../../../../../../app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component'; +import { Context } from '../../../../../../../app/core/shared/context.model'; + +@listableObjectComponent('JournalIssue', ViewMode.StandalonePage, Context.Any, 'custom') +@Component({ + selector: 'ds-journal-issue', + // styleUrls: ['./journal-issue.component.scss'], + styleUrls: ['../../../../../../../app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss'], + // templateUrl: './journal-issue.component.html', + templateUrl: '../../../../../../../app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html', +}) +/** + * The component for displaying metadata and relations of an item of the type Journal Issue + */ +export class JournalIssueComponent extends BaseComponent { +} diff --git a/src/themes/custom/entry-components.ts b/src/themes/custom/entry-components.ts index 9a9c0624a4..b5c4469718 100644 --- a/src/themes/custom/entry-components.ts +++ b/src/themes/custom/entry-components.ts @@ -1,7 +1,11 @@ +import { + JournalIssueComponent +} from './app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component'; import { PublicationComponent } from './app/item-page/simple/item-types/publication/publication.component'; import { UntypedItemComponent } from './app/item-page/simple/item-types/untyped-item/untyped-item.component'; export const ENTRY_COMPONENTS = [ + JournalIssueComponent, PublicationComponent, UntypedItemComponent, ]; diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index c5d5e5a1c4..5fb1ce580b 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -95,6 +95,9 @@ import { SearchModule } from '../../app/shared/search/search.module'; import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../app/shared/comcol/comcol.module'; import { FeedbackComponent } from './app/info/feedback/feedback.component'; +import { + JournalIssueComponent +} from './app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component'; const DECLARATIONS = [ FileSectionComponent, @@ -138,7 +141,8 @@ const DECLARATIONS = [ NavbarComponent, HeaderNavbarWrapperComponent, BreadcrumbsComponent, - FeedbackComponent + FeedbackComponent, + JournalIssueComponent, ]; @NgModule({ From 2264067240e3d7302878e59fae02e27fc65d096e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 27 May 2022 19:12:03 +0200 Subject: [PATCH 045/157] 89721: Themed JournalVolumeComponent --- .../journal-volume.component.html | 0 .../journal-volume.component.scss | 0 .../journal-volume.component.ts | 23 +++++++++++++++++++ src/themes/custom/entry-components.ts | 4 ++++ src/themes/custom/theme.module.ts | 4 ++++ 5 files changed, 31 insertions(+) create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts new file mode 100644 index 0000000000..1a190dc2e8 --- /dev/null +++ b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { ViewMode } from '../../../../../../../app/core/shared/view-mode.model'; +import { + listableObjectComponent +} from '../../../../../../../app/shared/object-collection/shared/listable-object/listable-object.decorator'; +import { + JournalVolumeComponent as BaseComponent +} from '../../../../../../../app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component'; +import { Context } from '../../../../../../../app/core/shared/context.model'; + +@listableObjectComponent('JournalVolume', ViewMode.StandalonePage, Context.Any, 'custom') +@Component({ + selector: 'ds-journal-volume', + // styleUrls: ['./journal-volume.component.scss'], + styleUrls: ['../../../../../../../app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss'], + // templateUrl: './journal-volume.component.html', + templateUrl: '../../../../../../../app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html', +}) +/** + * The component for displaying metadata and relations of an item of the type Journal Volume + */ +export class JournalVolumeComponent extends BaseComponent { +} diff --git a/src/themes/custom/entry-components.ts b/src/themes/custom/entry-components.ts index b5c4469718..8f24a3b7e6 100644 --- a/src/themes/custom/entry-components.ts +++ b/src/themes/custom/entry-components.ts @@ -1,11 +1,15 @@ import { JournalIssueComponent } from './app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component'; +import { + JournalVolumeComponent +} from './app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component'; import { PublicationComponent } from './app/item-page/simple/item-types/publication/publication.component'; import { UntypedItemComponent } from './app/item-page/simple/item-types/untyped-item/untyped-item.component'; export const ENTRY_COMPONENTS = [ JournalIssueComponent, + JournalVolumeComponent, PublicationComponent, UntypedItemComponent, ]; diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index 5fb1ce580b..3193b200af 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -98,6 +98,9 @@ import { FeedbackComponent } from './app/info/feedback/feedback.component'; import { JournalIssueComponent } from './app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component'; +import { + JournalVolumeComponent +} from './app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component'; const DECLARATIONS = [ FileSectionComponent, @@ -143,6 +146,7 @@ const DECLARATIONS = [ BreadcrumbsComponent, FeedbackComponent, JournalIssueComponent, + JournalVolumeComponent, ]; @NgModule({ From bca332a23aed34b5e96ea6cfae8cfccd92173fc3 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 30 May 2022 10:12:04 +0200 Subject: [PATCH 046/157] 89721: Themed JournalComponent --- .../item-pages/journal/journal.component.html | 0 .../item-pages/journal/journal.component.scss | 0 .../item-pages/journal/journal.component.ts | 23 +++++++++++++++++++ src/themes/custom/entry-components.ts | 2 ++ src/themes/custom/theme.module.ts | 6 ++++- 5 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.html create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss create mode 100644 src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts new file mode 100644 index 0000000000..7b64c1a35d --- /dev/null +++ b/src/themes/custom/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { ViewMode } from '../../../../../../../app/core/shared/view-mode.model'; +import { + listableObjectComponent +} from '../../../../../../../app/shared/object-collection/shared/listable-object/listable-object.decorator'; +import { + JournalComponent as BaseComponent +} from '../../../../../../../app/entity-groups/journal-entities/item-pages/journal/journal.component'; +import { Context } from '../../../../../../../app/core/shared/context.model'; + +@listableObjectComponent('Journal', ViewMode.StandalonePage, Context.Any, 'custom') +@Component({ + selector: 'ds-journal', + // styleUrls: ['./journal.component.scss'], + styleUrls: ['../../../../../../../app/entity-groups/journal-entities/item-pages/journal/journal.component.scss'], + // templateUrl: './journal.component.html', + templateUrl: '../../../../../../../app/entity-groups/journal-entities/item-pages/journal/journal.component.html', +}) +/** + * The component for displaying metadata and relations of an item of the type Journal + */ +export class JournalComponent extends BaseComponent { +} diff --git a/src/themes/custom/entry-components.ts b/src/themes/custom/entry-components.ts index 8f24a3b7e6..ca4c40f523 100644 --- a/src/themes/custom/entry-components.ts +++ b/src/themes/custom/entry-components.ts @@ -1,3 +1,4 @@ +import { JournalComponent } from './app/entity-groups/journal-entities/item-pages/journal/journal.component'; import { JournalIssueComponent } from './app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component'; @@ -8,6 +9,7 @@ import { PublicationComponent } from './app/item-page/simple/item-types/publicat import { UntypedItemComponent } from './app/item-page/simple/item-types/untyped-item/untyped-item.component'; export const ENTRY_COMPONENTS = [ + JournalComponent, JournalIssueComponent, JournalVolumeComponent, PublicationComponent, diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index 3193b200af..b42dfa80ac 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -95,12 +95,14 @@ import { SearchModule } from '../../app/shared/search/search.module'; import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../app/shared/comcol/comcol.module'; import { FeedbackComponent } from './app/info/feedback/feedback.component'; +import { JournalComponent } from './app/entity-groups/journal-entities/item-pages/journal/journal.component'; import { JournalIssueComponent } from './app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component'; import { JournalVolumeComponent } from './app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component'; +import { ItemSharedModule } from '../../app/item-page/item-shared.module'; const DECLARATIONS = [ FileSectionComponent, @@ -145,6 +147,7 @@ const DECLARATIONS = [ HeaderNavbarWrapperComponent, BreadcrumbsComponent, FeedbackComponent, + JournalComponent, JournalIssueComponent, JournalVolumeComponent, ]; @@ -196,7 +199,8 @@ const DECLARATIONS = [ SearchModule, FormsModule, ResourcePoliciesModule, - ComcolModule + ComcolModule, + ItemSharedModule, ], declarations: DECLARATIONS }) From dc5d46993e33f31d5c35b52777fc544c1d372d84 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 27 May 2022 11:14:28 +0200 Subject: [PATCH 047/157] 89721: Themed AdminSidebarComponent --- .../admin-sidebar/admin-sidebar.component.html | 0 .../admin-sidebar/admin-sidebar.component.scss | 0 .../admin-sidebar/admin-sidebar.component.ts | 15 +++++++++++++++ src/themes/custom/theme.module.ts | 2 ++ 4 files changed, 17 insertions(+) create mode 100644 src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.html create mode 100644 src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.scss create mode 100644 src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.ts diff --git a/src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.html b/src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.scss b/src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.ts new file mode 100644 index 0000000000..6485ad98e6 --- /dev/null +++ b/src/themes/custom/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { AdminSidebarComponent as BaseComponent } from '../../../../../app/admin/admin-sidebar/admin-sidebar.component'; + +/** + * Component representing the admin sidebar + */ +@Component({ + selector: 'ds-admin-sidebar', + // templateUrl: './admin-sidebar.component.html', + templateUrl: '../../../../../app/admin/admin-sidebar/admin-sidebar.component.html', + // styleUrls: ['./admin-sidebar.component.scss'] + styleUrls: ['../../../../../app/admin/admin-sidebar/admin-sidebar.component.scss'] +}) +export class AdminSidebarComponent extends BaseComponent { +} diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index b42dfa80ac..95203336dd 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -103,6 +103,7 @@ import { JournalVolumeComponent } from './app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component'; import { ItemSharedModule } from '../../app/item-page/item-shared.module'; +import { AdminSidebarComponent } from './app/admin/admin-sidebar/admin-sidebar.component'; const DECLARATIONS = [ FileSectionComponent, @@ -150,6 +151,7 @@ const DECLARATIONS = [ JournalComponent, JournalIssueComponent, JournalVolumeComponent, + AdminSidebarComponent, ]; @NgModule({ From fc1e6b6b852da9fc635144caf84da08ddaaa5f8e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 3 Jun 2022 11:47:46 +0200 Subject: [PATCH 048/157] Removed comments for how to theme li[Component] --- .../admin-sidebar-section/admin-sidebar-section.component.ts | 2 -- .../expandable-admin-sidebar-section.component.ts | 2 -- src/app/navbar/navbar-section/navbar-section.component.ts | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts index 422431d2c5..47f693cb99 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts @@ -14,8 +14,6 @@ import { Router } from '@angular/router'; @Component({ /* eslint-disable @angular-eslint/component-selector */ selector: 'li[ds-admin-sidebar-section]', - // To theme this element remove the surrounding li[] in this component but leave it in the new - // ThemedAdminSidebarSectionComponent templateUrl: './admin-sidebar-section.component.html', styleUrls: ['./admin-sidebar-section.component.scss'], diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts index 28cadcac5f..7cd20b15d2 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts @@ -17,8 +17,6 @@ import { Router } from '@angular/router'; @Component({ /* eslint-disable @angular-eslint/component-selector */ selector: 'li[ds-expandable-admin-sidebar-section]', - // To theme this element remove the surrounding li[] in this component but leave it in the new - // ThemedExpandableAdminSidebarSectionComponent templateUrl: './expandable-admin-sidebar-section.component.html', styleUrls: ['./expandable-admin-sidebar-section.component.scss'], animations: [rotate, slide, bgColor] diff --git a/src/app/navbar/navbar-section/navbar-section.component.ts b/src/app/navbar/navbar-section/navbar-section.component.ts index 20d5299584..9f75a96f6e 100644 --- a/src/app/navbar/navbar-section/navbar-section.component.ts +++ b/src/app/navbar/navbar-section/navbar-section.component.ts @@ -10,8 +10,6 @@ import { MenuID } from '../../shared/menu/menu-id.model'; @Component({ /* eslint-disable @angular-eslint/component-selector */ selector: 'li[ds-navbar-section]', - // To theme this element remove the surrounding li[] in this component but leave it in the new - // ThemedNavbarSectionComponent templateUrl: './navbar-section.component.html', styleUrls: ['./navbar-section.component.scss'] }) From 7320d1cc66dd85b4771798c9af0a3b63a38c16cf Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 7 Jun 2022 14:59:04 +0200 Subject: [PATCH 049/157] [CST-5668] Removed unused imports --- src/app/item-page/orcid-page/orcid-page.component.ts | 3 --- src/app/item-page/simple/item-page.component.ts | 11 +++-------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/app/item-page/orcid-page/orcid-page.component.ts b/src/app/item-page/orcid-page/orcid-page.component.ts index 506b5c7e49..3c464596b1 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.ts +++ b/src/app/item-page/orcid-page/orcid-page.component.ts @@ -1,8 +1,5 @@ import {Component, Inject} from '@angular/core'; -import {ConfigurationDataService} from '../../core/data/configuration-data.service'; import {ResearcherProfileService} from '../../core/profile/researcher-profile.service'; -import {TranslateService} from '@ngx-translate/core'; -import {NotificationsService} from '../../shared/notifications/notifications.service'; import {ItemDataService} from '../../core/data/item-data.service'; import {ActivatedRoute} from '@angular/router'; import {NativeWindowRef, NativeWindowService} from '../../core/services/window.service'; diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index b3660eb9d7..e75ea9a843 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -1,26 +1,21 @@ -import { map, mergeMap, take, tap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { ItemDataService } from '../../core/data/item-data.service'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { fadeInOut } from '../../shared/animations/fade'; -import { getAllSucceededRemoteDataPayload, getFirstSucceededRemoteData } from '../../core/shared/operators'; +import { getAllSucceededRemoteDataPayload } from '../../core/shared/operators'; import { ViewMode } from '../../core/shared/view-mode.model'; import { AuthService } from '../../core/auth/auth.service'; import { getItemPageRoute } from '../item-page-routing-paths'; import { redirectOn4xx } from '../../core/shared/authorized.operators'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { TranslateService } from '@ngx-translate/core'; -import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; -import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model'; -import { isNotUndefined } from '../../shared/empty.util'; -import { NotificationsService } from '../../shared/notifications/notifications.service'; /** From 43f4ff7cdeea6d797e5e98d48e3976a25b38e601 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 7 Jun 2022 15:15:51 +0200 Subject: [PATCH 050/157] [CST-5668] Fixed DsoPageOrcidButtonComponent test --- .../dso-page-orcid-button.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.spec.ts b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.spec.ts index 9de3333b7f..c70ec4b808 100644 --- a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.spec.ts +++ b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.spec.ts @@ -45,7 +45,7 @@ describe('DsoPageOrcidButtonComponent', () => { }); it('should check the authorization of the current user', () => { - expect(authorizationService.isAuthorized).toHaveBeenCalledWith(FeatureID.CanEditOrcid, dso.self); + expect(authorizationService.isAuthorized).toHaveBeenCalledWith(FeatureID.CanSynchronizeWithORCID, dso.self); }); describe('when the user is authorized', () => { From a9fcdce9605d3f4f7ce5c55795ca570adcc9b495 Mon Sep 17 00:00:00 2001 From: nikunj59 Date: Tue, 7 Jun 2022 15:34:47 +0530 Subject: [PATCH 051/157] CST-5309 changes for error page component --- src/app/app-routing-paths.ts | 2 ++ src/app/app-routing.module.ts | 3 +++ src/app/page-error/page-error.component.html | 10 +++++++ src/app/page-error/page-error.component.scss | 0 src/app/page-error/page-error.component.ts | 27 +++++++++++++++++++ .../page-error/themed-page-error.component.ts | 26 ++++++++++++++++++ src/app/root.module.ts | 6 ++++- src/assets/i18n/en.json5 | 10 +++++++ 8 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/app/page-error/page-error.component.html create mode 100644 src/app/page-error/page-error.component.scss create mode 100644 src/app/page-error/page-error.component.ts create mode 100644 src/app/page-error/themed-page-error.component.ts diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index 6524edef77..866957f70d 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -107,6 +107,8 @@ export function getPageInternalServerErrorRoute() { return `/${INTERNAL_SERVER_ERROR}`; } +export const ERROR_PAGE = 'error' + export const INFO_MODULE_PATH = 'info'; export function getInfoModulePath() { return `/${INFO_MODULE_PATH}`; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index f0869d9fb6..1815e0ec60 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import { ACCESS_CONTROL_MODULE_PATH, ADMIN_MODULE_PATH, BITSTREAM_MODULE_PATH, + ERROR_PAGE, FORBIDDEN_PATH, FORGOT_PASSWORD_PATH, INFO_MODULE_PATH, @@ -31,11 +32,13 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component'; import { ServerCheckGuard } from './core/server-check/server-check.guard'; import { MenuResolver } from './menu.resolver'; +import { ThemedPageErrorComponent } from './page-error/themed-page-error.component'; @NgModule({ imports: [ RouterModule.forRoot([ { path: INTERNAL_SERVER_ERROR, component: ThemedPageInternalServerErrorComponent }, + { path: ERROR_PAGE , component: ThemedPageErrorComponent }, { path: '', canActivate: [AuthBlockingGuard], diff --git a/src/app/page-error/page-error.component.html b/src/app/page-error/page-error.component.html new file mode 100644 index 0000000000..d0571e420d --- /dev/null +++ b/src/app/page-error/page-error.component.html @@ -0,0 +1,10 @@ +
+

{{status}}

+

{{"error-page.description." + status | translate}}

+
+

{{"error-page." + code | translate}}

+
+

+ {{ status + ".link.home-page" | translate}} +

+
diff --git a/src/app/page-error/page-error.component.scss b/src/app/page-error/page-error.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/page-error/page-error.component.ts b/src/app/page-error/page-error.component.ts new file mode 100644 index 0000000000..18f0b34c47 --- /dev/null +++ b/src/app/page-error/page-error.component.ts @@ -0,0 +1,27 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +/** + * This component representing the `PageError` DSpace page. + */ +@Component({ + selector: 'ds-page-error', + styleUrls: ['./page-error.component.scss'], + templateUrl: './page-error.component.html', + changeDetection: ChangeDetectionStrategy.Default +}) +export class PageErrorComponent { + status: number; + code: string; + /** + * Initialize instance variables + * + * @param {ActivatedRoute} activatedRoute + */ + constructor(private activatedRoute: ActivatedRoute) { + this.activatedRoute.queryParams.subscribe((params) => { + this.status = params['status']; + this.code = params['code']; + }); + } +} diff --git a/src/app/page-error/themed-page-error.component.ts b/src/app/page-error/themed-page-error.component.ts new file mode 100644 index 0000000000..34d29fb2a9 --- /dev/null +++ b/src/app/page-error/themed-page-error.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { ThemedComponent } from '../shared/theme-support/themed.component'; +import { PageErrorComponent } from './page-error.component'; + +/** + * Themed wrapper for PageErrorComponent + */ +@Component({ + selector: 'ds-themed-search-page', + styleUrls: [], + templateUrl: '../shared/theme-support/themed.component.html', +}) +export class ThemedPageErrorComponent extends ThemedComponent { + + protected getComponentName(): string { + return 'PageErrorComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../themes/${themeName}/app/page-error/page-error.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`src/app/page-error/page-error.component`); + } +} diff --git a/src/app/root.module.ts b/src/app/root.module.ts index e5a8aad949..8577f0d728 100644 --- a/src/app/root.module.ts +++ b/src/app/root.module.ts @@ -40,6 +40,8 @@ import { import { PageInternalServerErrorComponent } from './page-internal-server-error/page-internal-server-error.component'; +import { ThemedPageErrorComponent } from './page-error/themed-page-error.component'; +import { PageErrorComponent } from './page-error/page-error.component'; const IMPORTS = [ CommonModule, @@ -74,7 +76,9 @@ const DECLARATIONS = [ ThemedForbiddenComponent, IdleModalComponent, ThemedPageInternalServerErrorComponent, - PageInternalServerErrorComponent + PageInternalServerErrorComponent, + ThemedPageErrorComponent, + PageErrorComponent ]; const EXPORTS = [ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c81ab66516..7ea671a0bc 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -27,6 +27,16 @@ "404.page-not-found": "page not found", + "error-page.description.401": "unauthorized", + + "error-page.description.403": "forbidden", + + "error-page.description.500": "Service Unavailable", + + "error-page.description.404": "page not found", + + "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", + "access-status.embargo.listelement.badge": "Embargo", "access-status.metadata.only.listelement.badge": "Metadata only", From 1e9e4d5b12c4136adafe68b08dc7b066c1342acd Mon Sep 17 00:00:00 2001 From: nikunj59 Date: Tue, 7 Jun 2022 17:42:36 +0530 Subject: [PATCH 052/157] CST-5309 added test cases --- src/app/page-error/page-error.component.html | 2 +- .../page-error/page-error.component.spec.ts | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/app/page-error/page-error.component.spec.ts diff --git a/src/app/page-error/page-error.component.html b/src/app/page-error/page-error.component.html index d0571e420d..9a5f02600a 100644 --- a/src/app/page-error/page-error.component.html +++ b/src/app/page-error/page-error.component.html @@ -1,5 +1,5 @@
-

{{status}}

+

{{status}}

{{"error-page.description." + status | translate}}


{{"error-page." + code | translate}}

diff --git a/src/app/page-error/page-error.component.spec.ts b/src/app/page-error/page-error.component.spec.ts new file mode 100644 index 0000000000..0f876f3196 --- /dev/null +++ b/src/app/page-error/page-error.component.spec.ts @@ -0,0 +1,48 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { PageErrorComponent } from './page-error.component'; +import { ActivatedRoute } from '@angular/router'; +import { ActivatedRouteStub } from '../shared/testing/active-router.stub'; +import { of as observableOf } from 'rxjs'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { By } from '@angular/platform-browser'; +import { TranslateLoaderMock } from '../shared/testing/translate-loader.mock'; + +describe('PageErrorComponent', () => { + let component: PageErrorComponent; + let fixture: ComponentFixture; + const activatedRouteStub = Object.assign(new ActivatedRouteStub(), { + queryParams: observableOf({ + status: 401, + code: 'orcid.generic-error' + }) + }); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ PageErrorComponent ], + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], + providers: [ + { provide: ActivatedRoute, useValue: activatedRouteStub }, + ] + }).compileComponents(); + + fixture = TestBed.createComponent(PageErrorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show error for 401 unauthorized', () => { + const statusElement = fixture.debugElement.query(By.css('[data-test="status"]')).nativeElement; + expect(statusElement.innerHTML).toEqual('401'); + }); +}); From 7ea208e314d2510e1c96506f0b066c5cb10c3e6c Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 7 Jun 2022 17:47:20 +0200 Subject: [PATCH 053/157] [CST-5668] Fixed lint warnings --- src/app/app-routing-paths.ts | 2 +- src/app/page-error/page-error.component.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index 866957f70d..b230bdc1ac 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -107,7 +107,7 @@ export function getPageInternalServerErrorRoute() { return `/${INTERNAL_SERVER_ERROR}`; } -export const ERROR_PAGE = 'error' +export const ERROR_PAGE = 'error'; export const INFO_MODULE_PATH = 'info'; export function getInfoModulePath() { diff --git a/src/app/page-error/page-error.component.ts b/src/app/page-error/page-error.component.ts index 18f0b34c47..dea1b68407 100644 --- a/src/app/page-error/page-error.component.ts +++ b/src/app/page-error/page-error.component.ts @@ -20,8 +20,8 @@ export class PageErrorComponent { */ constructor(private activatedRoute: ActivatedRoute) { this.activatedRoute.queryParams.subscribe((params) => { - this.status = params['status']; - this.code = params['code']; + this.status = params.status; + this.code = params.code; }); } } From c3ececdde714ab880b1918d41adb718c74d1fc8a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 9 Jun 2022 19:22:23 +0200 Subject: [PATCH 054/157] [CST-5668] Retrieve item from route data --- .../orcid-auth/orcid-auth.component.html | 140 +++++++++--------- .../orcid-auth/orcid-auth.component.ts | 12 +- .../orcid-page/orcid-page.component.html | 4 +- .../orcid-page/orcid-page.component.ts | 55 +++++-- .../orcid-sync/orcid-setting.component.ts | 40 +++-- 5 files changed, 146 insertions(+), 105 deletions(-) diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html index 8e538b66aa..12b13c433c 100644 --- a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html @@ -1,71 +1,77 @@
- - - -
- -
-
-
-
- - -
-
-
{{ 'person.page.orcid.granted-authorizations'| translate }}
-
-
-
    -
  • {{getAuthorizationDescription(auth) | translate}}
  • -
+ + + +
+ +
+
+
+
+ + +
+
+
{{ 'person.page.orcid.granted-authorizations'| translate }}
+
+
+
    +
  • {{getAuthorizationDescription(auth) | translate}}
  • +
+
+
+
+
+
{{ 'person.page.orcid.missing-authorizations'| translate }}
+
+
+
+ {{'person.page.orcid.no-missing-authorizations-message' | translate}}
+
+ {{'person.page.orcid.missing-authorizations-message' | translate}} +
    +
  • {{getAuthorizationDescription(auth) | translate }}
  • +
+
-
-
{{ 'person.page.orcid.missing-authorizations'| translate }}
-
-
-
- {{'person.page.orcid.no-missing-authorizations-message' | translate}} -
-
- {{'person.page.orcid.missing-authorizations-message' | translate}} -
    -
  • {{getAuthorizationDescription(auth) | translate }}
  • -
-
-
-
-
-
-
- {{ 'person.page.orcid.remove-orcid-message' | translate}} -
-
-
- - -
-
- - - -
-
orcid-logo
-
{{ getOrcidNotLinkedMessage() | async }}
-
-
-
- -
-
-
-
\ No newline at end of file +
+
+
+ {{ 'person.page.orcid.remove-orcid-message' | translate}} +
+
+
+ + +
+
+ + + +
+
orcid-logo
+
{{ getOrcidNotLinkedMessage() | async }}
+
+
+
+ +
+
+
+
diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts index 0f368ada2f..aa234648f0 100644 --- a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, Input, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, Observable } from 'rxjs'; @@ -19,12 +19,15 @@ import { NotificationsService } from '../../../shared/notifications/notification }) export class OrcidAuthComponent implements OnInit { + /** + * The item for which showing the orcid settings + */ + @Input() item: Item; + missingAuthorizations$ = new BehaviorSubject([]); unlinkProcessing = false; - item: Item; - constructor( private configurationService: ConfigurationDataService, private researcherProfileService: ResearcherProfileService, @@ -34,9 +37,6 @@ export class OrcidAuthComponent implements OnInit { private route: ActivatedRoute, @Inject(NativeWindowService) private _window: NativeWindowRef, ) { - this.itemService.findById(this.route.snapshot.paramMap.get('id'), true, true).pipe(getFirstCompletedRemoteData()).subscribe((data: RemoteData) => { - this.item = data.payload; - }); } ngOnInit() { diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html index 296d5468c5..c105ba6127 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.html +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -6,5 +6,5 @@
- - + + diff --git a/src/app/item-page/orcid-page/orcid-page.component.ts b/src/app/item-page/orcid-page/orcid-page.component.ts index 3c464596b1..122c199f61 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.ts +++ b/src/app/item-page/orcid-page/orcid-page.component.ts @@ -1,37 +1,64 @@ -import {Component, Inject} from '@angular/core'; -import {ResearcherProfileService} from '../../core/profile/researcher-profile.service'; -import {ItemDataService} from '../../core/data/item-data.service'; -import {ActivatedRoute} from '@angular/router'; -import {NativeWindowRef, NativeWindowService} from '../../core/services/window.service'; -import {getFirstCompletedRemoteData} from '../../core/shared/operators'; -import {RemoteData} from '../../core/data/remote-data'; -import {Item} from '../../core/shared/item.model'; -import {getItemPageRoute} from '../item-page-routing-paths'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { map } from 'rxjs/operators'; + +import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; +import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; +import { Item } from '../../core/shared/item.model'; +import { getItemPageRoute } from '../item-page-routing-paths'; +import { AuthService } from '../../core/auth/auth.service'; +import { redirectOn4xx } from '../../core/shared/authorized.operators'; + +/** + * A component that represents the orcid settings page + */ @Component({ selector: 'ds-orcid-page', templateUrl: './orcid-page.component.html', styleUrls: ['./orcid-page.component.scss'] }) -export class OrcidPageComponent { +export class OrcidPageComponent implements OnInit { + /** + * The item for which showing the orcid settings + */ item: Item; constructor( - private itemService: ItemDataService, + private authService: AuthService, private researcherProfileService: ResearcherProfileService, private route: ActivatedRoute, - @Inject(NativeWindowService) private _window: NativeWindowRef, + private router: Router ) { - this.itemService.findById(this.route.snapshot.paramMap.get('id'), true, true).pipe(getFirstCompletedRemoteData()).subscribe((data: RemoteData) => { - this.item = data.payload; + } + + /** + * Retrieve the item for which showing the orcid settings + */ + ngOnInit(): void { + this.route.data.pipe( + map((data) => data.dso as RemoteData), + redirectOn4xx(this.router, this.authService), + getFirstSucceededRemoteDataPayload() + ).subscribe((item) => { + this.item = item; }); } + /** + * Check if the current item is linked to an ORCID profile. + * + * @returns the check result + */ isLinkedToOrcid(): boolean { return this.researcherProfileService.isLinkedToOrcid(this.item); } + /** + * Get the route to an item's page + */ getItemPage(): string { return getItemPageRoute(this.item); } diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts index 00a6018892..2429ac2e43 100644 --- a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts @@ -1,16 +1,18 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { ActivatedRoute } from '@angular/router'; + import { TranslateService } from '@ngx-translate/core'; import { Operation } from 'fast-json-patch'; +import { of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; + import { AuthService } from '../../../core/auth/auth.service'; -import { ItemDataService } from '../../../core/data/item-data.service'; import { RemoteData } from '../../../core/data/remote-data'; import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; import { Item } from '../../../core/shared/item.model'; -import { getFinishedRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model'; @Component({ selector: 'ds-orcid-setting', @@ -19,6 +21,11 @@ import { NotificationsService } from '../../../shared/notifications/notification }) export class OrcidSettingComponent implements OnInit { + /** + * The item for which showing the orcid settings + */ + @Input() item: Item; + messagePrefix = 'person.page.orcid'; currentSyncMode: string; @@ -35,18 +42,12 @@ export class OrcidSettingComponent implements OnInit { syncProfileOptions: { value: string, label: string, checked: boolean }[]; - item: Item; constructor(private researcherProfileService: ResearcherProfileService, protected translateService: TranslateService, private notificationsService: NotificationsService, - public authService: AuthService, - private route: ActivatedRoute, - private itemService: ItemDataService + public authService: AuthService ) { - this.itemService.findById(this.route.snapshot.paramMap.get('id'), true, true).pipe(getFirstCompletedRemoteData()).subscribe((data: RemoteData) => { - this.item = data.payload; - }); } ngOnInit() { @@ -110,10 +111,18 @@ export class OrcidSettingComponent implements OnInit { return; } - this.researcherProfileService.findById(this.item.firstMetadata('dspace.object.owner').authority).pipe( - switchMap((profile) => this.researcherProfileService.patch(profile, operations)), - getFinishedRemoteData() - ).subscribe((remoteData) => { + this.researcherProfileService.findByRelatedItem(this.item).pipe( + getFirstCompletedRemoteData(), + switchMap((profileRD: RemoteData) => { + if (profileRD.hasSucceeded) { + return this.researcherProfileService.updateByOrcidOperations(profileRD.payload, operations).pipe( + getFirstCompletedRemoteData() + ); + } else { + return of(profileRD); + } + }), + ).subscribe((remoteData: RemoteData) => { if (remoteData.isSuccess) { this.notificationsService.success(this.translateService.get(this.messagePrefix + '.synchronization-settings-update.success')); } else { @@ -135,5 +144,4 @@ export class OrcidSettingComponent implements OnInit { return (currentPreference && allowedValues.includes(currentPreference)) ? currentPreference : defaultValue; } - } From 17cc3078df6e6acce4447f474bae460c452f1901 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 9 Jun 2022 19:23:19 +0200 Subject: [PATCH 055/157] [CST-5668] Fix after merge --- .../researcher-profile.service.spec.ts | 14 +++++++- .../profile/researcher-profile.service.ts | 33 ++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/app/core/profile/researcher-profile.service.spec.ts b/src/app/core/profile/researcher-profile.service.spec.ts index eb5ff776fe..0dada6e5e3 100644 --- a/src/app/core/profile/researcher-profile.service.spec.ts +++ b/src/app/core/profile/researcher-profile.service.spec.ts @@ -26,6 +26,8 @@ import { ReplaceOperation } from 'fast-json-patch'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { PostRequest } from '../data/request.models'; import { followLink } from '../../shared/utils/follow-link-config.model'; +import { ConfigurationProperty } from '../shared/configuration-property.model'; +import { ConfigurationDataService } from '../data/configuration-data.service'; describe('ResearcherProfileService', () => { let scheduler: TestScheduler; @@ -36,6 +38,7 @@ describe('ResearcherProfileService', () => { let objectCache: ObjectCacheService; let halService: HALEndpointService; let responseCacheEntry: RequestEntry; + let configurationDataService: ConfigurationDataService; const researcherProfileId = 'beef9946-rt56-479e-8f11-b90cbe9f7241'; const itemId = 'beef9946-rt56-479e-8f11-b90cbe9f7241'; @@ -136,6 +139,14 @@ describe('ResearcherProfileService', () => { const itemService = jasmine.createSpyObj('ItemService', { findByHref: jasmine.createSpy('findByHref') }); + configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { + name: 'test', + values: [ + 'org.dspace.ctask.general.ProfileFormats = test' + ] + })) + }); service = new ResearcherProfileService( requestService, @@ -146,7 +157,8 @@ describe('ResearcherProfileService', () => { http, routerStub, comparator, - itemService + itemService, + configurationDataService ); serviceAsAny = service; diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index a3ed6d22af..702c168539 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -4,7 +4,7 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; -import { RemoveOperation, ReplaceOperation } from 'fast-json-patch'; +import { Operation, RemoveOperation, ReplaceOperation } from 'fast-json-patch'; import { combineLatest, Observable } from 'rxjs'; import { find, map, switchMap } from 'rxjs/operators'; @@ -32,10 +32,11 @@ import { ResearcherProfile } from './model/researcher-profile.model'; import { RESEARCHER_PROFILE } from './model/researcher-profile.resource-type'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { PostRequest } from '../data/request.models'; -import { hasValue } from '../../shared/empty.util'; +import { hasValue, isEmpty } from '../../shared/empty.util'; import { CoreState } from '../core-state.model'; import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { Item } from '../shared/item.model'; +import { createFailedRemoteDataObject$ } from '../../shared/remote-data.utils'; /** * A private DataService implementation to delegate specific methods to. @@ -64,9 +65,9 @@ class ResearcherProfileServiceImpl extends DataService { @dataService(RESEARCHER_PROFILE) export class ResearcherProfileService { - dataService: ResearcherProfileServiceImpl; + protected dataService: ResearcherProfileServiceImpl; - responseMsToLive: number = 10 * 1000; + protected responseMsToLive: number = 10 * 1000; constructor( protected requestService: RequestService, @@ -121,6 +122,20 @@ export class ResearcherProfileService { ); } + /** + * Find a researcher profile by its own related item + * + * @param item + */ + public findByRelatedItem(item: Item): Observable> { + const profileId = item.firstMetadata('dspace.object.owner')?.authority; + if (isEmpty(profileId)) { + return createFailedRemoteDataObject$(); + } else { + return this.findById(profileId); + } + } + /** * Find the item id related to the given researcher profile. * @@ -253,6 +268,16 @@ export class ResearcherProfileService { return this.rdbService.buildFromRequestUUID(requestId, followLink('item')); } + /** + * Update researcher profile by patch orcid operation + * + * @param researcherProfile + * @param operations + */ + public updateByOrcidOperations(researcherProfile: ResearcherProfile, operations: Operation[]): Observable> { + return this.dataService.patch(researcherProfile, operations); + } + private getOrcidDisconnectionAllowedUsersConfiguration(): Observable { return this.configurationService.findByPropertyName('orcid.disconnection.allowed-users').pipe( getFirstSucceededRemoteDataPayload() From 3cb5e0c22662656eae4a1e1460c97cf7414bcce9 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 10 Jun 2022 11:32:48 +0200 Subject: [PATCH 056/157] [CST-5668] Fix button style and tooltip messages --- .../dso-page-edit-button.component.scss | 4 +--- .../dso-page-orcid-button.component.html | 3 ++- .../dso-page-orcid-button.component.ts | 14 +++++--------- .../person-page-claim-button.component.html | 8 +++++++- src/assets/i18n/en.json5 | 6 ++++++ src/styles/_global-styles.scss | 5 +++++ 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/app/shared/dso-page/dso-page-edit-button/dso-page-edit-button.component.scss b/src/app/shared/dso-page/dso-page-edit-button/dso-page-edit-button.component.scss index e8b7d689a3..8b13789179 100644 --- a/src/app/shared/dso-page/dso-page-edit-button/dso-page-edit-button.component.scss +++ b/src/app/shared/dso-page/dso-page-edit-button/dso-page-edit-button.component.scss @@ -1,3 +1 @@ -.btn-dark { - background-color: var(--ds-admin-sidebar-bg); -} + diff --git a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.html b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.html index 7a3383fd1a..03a07beb8a 100644 --- a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.html +++ b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.html @@ -1,5 +1,6 @@ ORCID + role="button" >{{'item.page.orcid.title' | translate}} diff --git a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.ts b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.ts index 9f244da7c9..c345d8cbdc 100644 --- a/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.ts +++ b/src/app/shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component.ts @@ -1,9 +1,11 @@ import { Component, Input, OnInit } from '@angular/core'; + import { BehaviorSubject } from 'rxjs'; import { take } from 'rxjs/operators'; -import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; -import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id'; -import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; + +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; @Component({ selector: 'ds-dso-page-orcid-button', @@ -21,12 +23,6 @@ export class DsoPageOrcidButtonComponent implements OnInit { */ @Input() pageRoute: string; - /** - * A message for the tooltip on the button - * Supports i18n keys - */ - @Input() tooltipMsg: string; - /** * Whether or not the current user is authorized to edit the DSpaceObject */ diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html index 12d5d2b47d..c4bba286bf 100644 --- a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html +++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html @@ -1 +1,7 @@ - + diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0449e9fda7..c87c76dc9c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2186,6 +2186,10 @@ "item.page.link.simple": "Simple item page", + "item.page.orcid.title": "ORCID", + + "item.page.orcid.tooltip": "Open ORCID setting page", + "item.page.person.search.title": "Articles by this author", "item.page.related-items.view-more": "Show {{ amount }} more", @@ -2220,6 +2224,8 @@ "item.page.claim.button": "Claim", + "item.page.claim.tooltip": "Claim this item as profile", + "item.preview.dc.identifier.uri": "Identifier:", "item.preview.dc.contributor.author": "Authors:", diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index 1bd2ec7b0a..89d1d76e9a 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -133,3 +133,8 @@ ds-dynamic-form-control-container.d-none { */ visibility: collapse; } + +/* Used for dso administrative functionality */ +.btn-dark { + background-color: var(--ds-admin-sidebar-bg); +} From b372a27513961613a9435e651aaf6e9de38a88f8 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 10 Jun 2022 12:30:15 +0200 Subject: [PATCH 057/157] [CST-5668] Add test for orcid-page.component --- .../orcid-page/orcid-page.component.html | 6 +- .../orcid-page/orcid-page.component.spec.ts | 113 ++++++++++++++++++ 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/app/item-page/orcid-page/orcid-page.component.spec.ts diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html index c105ba6127..91c49596b6 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.html +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -1,7 +1,9 @@ -
+ diff --git a/src/app/item-page/orcid-page/orcid-page.component.spec.ts b/src/app/item-page/orcid-page/orcid-page.component.spec.ts new file mode 100644 index 0000000000..1d61f18c3e --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-page.component.spec.ts @@ -0,0 +1,113 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; + +import { of as observableOf } from 'rxjs'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; + +import { AuthService } from '../../core/auth/auth.service'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; +import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; +import { OrcidPageComponent } from './orcid-page.component'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { Item } from '../../core/shared/item.model'; +import { createPaginatedList } from '../../shared/testing/utils.test'; +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; + +fdescribe('OrcidPageComponent test suite', () => { + let comp: OrcidPageComponent; + let fixture: ComponentFixture; + + let authService: jasmine.SpyObj; + let routeStub: jasmine.SpyObj; + let routeData: any; + let researcherProfileService: jasmine.SpyObj; + + const mockItem: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'test item' + } + ] + } + }); + const mockItemLinkedToOrcid: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: { + 'dc.title': [ + { + value: 'test item' + } + ], + 'dspace.orcid.authenticated': [ + { + value: 'true' + } + ] + } + }); + + beforeEach(waitForAsync(() => { + authService = jasmine.createSpyObj('authService', { + isAuthenticated: jasmine.createSpy('isAuthenticated'), + navigateByUrl: jasmine.createSpy('navigateByUrl') + }); + + routeData = { + dso: createSuccessfulRemoteDataObject(mockItem), + }; + + routeStub = Object.assign(new ActivatedRouteStub(), { + data: observableOf(routeData) + }); + + researcherProfileService = jasmine.createSpyObj('researcherProfileService', { + isLinkedToOrcid: jasmine.createSpy('isLinkedToOrcid') + }); + + void TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + RouterTestingModule.withRoutes([]) + ], + declarations: [OrcidPageComponent], + providers: [ + { provide: ActivatedRoute, useValue: routeStub }, + { provide: ResearcherProfileService, useValue: researcherProfileService }, + { provide: AuthService, useValue: authService }, + ], + + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(waitForAsync(() => { + fixture = TestBed.createComponent(OrcidPageComponent); + comp = fixture.componentInstance; + authService.isAuthenticated.and.returnValue(observableOf(true)); + fixture.detectChanges(); + })); + + it('should create', () => { + const btn = fixture.debugElement.queryAll(By.css('[data-test="back-button"]')); + expect(comp).toBeTruthy(); + expect(btn.length).toBe(1); + }); + + it('should call isLinkedToOrcid', () => { + comp.isLinkedToOrcid(); + + expect(researcherProfileService.isLinkedToOrcid).toHaveBeenCalledWith(comp.item); + }); + +}); From 5bf34b868cabce3d87d2bf64a704a1617b6b3e7b Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 10 Jun 2022 13:08:36 +0200 Subject: [PATCH 058/157] added missing keys workspace.search.results.head and workflow.search.results.head --- src/assets/i18n/de.json5 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 23ab3a29e5..526da437b0 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5358,10 +5358,14 @@ "virtual-metadata.delete-relationship.modal-head": "Wählen Sie die Items aus, deren virtuelle Metadaten Sie als echte Metadaten speichern möchten.", - + // "workspace.search.results.head": "Your submissions", + "workspace.search.results.head": "Deine Einreichungen", + // "workflowAdmin.search.results.head": "Administer Workflow", "workflowAdmin.search.results.head": "Workflow verwalten", - + + // "workflow.search.results.head": "Workflow tasks", + "workflow.search.results.head": "Workflow-Aufgaben", // "workflow-item.delete.notification.success.title": "Deleted", From afe798942bf39f39ef055bd4246d6e4be6692b7f Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 10 Jun 2022 13:15:53 +0200 Subject: [PATCH 059/157] =?UTF-8?q?harmonized=20german=20translations=20("?= =?UTF-8?q?Ihre=20Ver=C3=B6ffentlichungen")?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/i18n/de.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 526da437b0..42e0d083e7 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5359,7 +5359,7 @@ // "workspace.search.results.head": "Your submissions", - "workspace.search.results.head": "Deine Einreichungen", + "workspace.search.results.head": "Ihre Veröffentlichungen", // "workflowAdmin.search.results.head": "Administer Workflow", "workflowAdmin.search.results.head": "Workflow verwalten", From 68d162432b114425d3d96da1b238a986e3414d6d Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 10 Jun 2022 14:22:52 +0200 Subject: [PATCH 060/157] added two missing keys --- src/assets/i18n/de.json5 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 42e0d083e7..effe6932fd 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5279,6 +5279,13 @@ // "submission.workflow.tasks.pool.show-detail": "Show detail", "submission.workflow.tasks.pool.show-detail": "Details anzeigen", + // "submission.workspace.generic.view": "View", + "submission.workspace.generic.view": "Anzeige", + + // "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", + "submission.workspace.generic.view-help": "Wählen Sie diese Option, um die Metadaten des Items anzuzeigen.", + + // "thumbnail.default.alt": "Thumbnail Image", "thumbnail.default.alt": "Vorschaubild", From 03369b8e10cfec86294a96d00e7279f7cf300393 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 10 Jun 2022 19:03:31 +0200 Subject: [PATCH 061/157] [CST-5668] Address feedback and add test for orcid-auth.component --- .../researcher-profile.service.spec.ts | 250 ++++++++++++- .../profile/researcher-profile.service.ts | 64 ++-- .../orcid-auth/orcid-auth.component.html | 96 ++--- .../orcid-auth/orcid-auth.component.spec.ts | 336 ++++++++++++++++++ .../orcid-auth/orcid-auth.component.ts | 176 +++++++-- .../orcid-page/orcid-page.component.html | 6 +- .../orcid-page/orcid-page.component.spec.ts | 26 +- .../orcid-page/orcid-page.component.ts | 26 +- 8 files changed, 863 insertions(+), 117 deletions(-) create mode 100644 src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.spec.ts diff --git a/src/app/core/profile/researcher-profile.service.spec.ts b/src/app/core/profile/researcher-profile.service.spec.ts index 0dada6e5e3..9a6b0477d5 100644 --- a/src/app/core/profile/researcher-profile.service.spec.ts +++ b/src/app/core/profile/researcher-profile.service.spec.ts @@ -12,6 +12,7 @@ import { RequestService } from '../data/request.service'; import { PageInfo } from '../shared/page-info.model'; import { buildPaginatedList } from '../data/paginated-list.model'; import { + createFailedRemoteDataObject$, createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ @@ -22,12 +23,14 @@ import { ResearcherProfileService } from './researcher-profile.service'; import { RouterMock } from '../../shared/mocks/router.mock'; import { ResearcherProfile } from './model/researcher-profile.model'; import { Item } from '../shared/item.model'; -import { ReplaceOperation } from 'fast-json-patch'; +import { RemoveOperation, ReplaceOperation } from 'fast-json-patch'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { PostRequest } from '../data/request.models'; import { followLink } from '../../shared/utils/follow-link-config.model'; import { ConfigurationProperty } from '../shared/configuration-property.model'; import { ConfigurationDataService } from '../data/configuration-data.service'; +import { createPaginatedList } from '../../shared/testing/utils.test'; +import { environment } from '../../../environments/environment'; describe('ResearcherProfileService', () => { let scheduler: TestScheduler; @@ -89,6 +92,106 @@ describe('ResearcherProfileService', () => { }, } }); + + const mockItemUnlinkedToOrcid: Item = Object.assign(new Item(), { + id: 'mockItemUnlinkedToOrcid', + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: { + 'dc.title': [{ + value: 'test person' + }], + 'dspace.entity.type': [{ + 'value': 'Person' + }] + } + }); + + const mockItemLinkedToOrcid: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: { + 'dc.title': [{ + value: 'test person' + }], + 'dspace.entity.type': [{ + 'value': 'Person' + }], + 'dspace.object.owner': [{ + 'value': 'test person', + 'language': null, + 'authority': 'researcher-profile-id', + 'confidence': 600, + 'place': 0 + }], + 'dspace.orcid.authenticated': [{ + 'value': '2022-06-10T15:15:12.952872', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }], + 'dspace.orcid.scope': [{ + 'value': '/authenticate', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, { + 'value': '/read-limited', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + }, { + 'value': '/activities/update', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 2 + }, { + 'value': '/person/update', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 3 + }], + 'person.identifier.orcid': [{ + 'value': 'orcid-id', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }] + } + }); + + const disconnectionAllowAdmin = { + uuid: 'orcid.disconnection.allowed-users', + name: 'orcid.disconnection.allowed-users', + values: ['only_admin'] + } as ConfigurationProperty; + + const disconnectionAllowAdminOwner = { + uuid: 'orcid.disconnection.allowed-users', + name: 'orcid.disconnection.allowed-users', + values: ['admin_and_owner'] + } as ConfigurationProperty; + + const authorizeUrl = { + uuid: 'orcid.authorize-url', + name: 'orcid.authorize-url', + values: ['orcid.authorize-url'] + } as ConfigurationProperty; + const appClientId = { + uuid: 'orcid.application-client-id', + name: 'orcid.application-client-id', + values: ['orcid.application-client-id'] + } as ConfigurationProperty; + const orcidScope = { + uuid: 'orcid.scope', + name: 'orcid.scope', + values: ['/authenticate', '/read-limited'] + } as ConfigurationProperty; + const endpointURL = `https://rest.api/rest/api/profiles`; const endpointURLWithEmbed = 'https://rest.api/rest/api/profiles?embed=item'; const sourceUri = `https://rest.api/rest/api/external-source/profile`; @@ -140,12 +243,7 @@ describe('ResearcherProfileService', () => { findByHref: jasmine.createSpy('findByHref') }); configurationDataService = jasmine.createSpyObj('configurationDataService', { - findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { - name: 'test', - values: [ - 'org.dspace.ctask.general.ProfileFormats = test' - ] - })) + findByPropertyName: jasmine.createSpy('findByPropertyName') }); service = new ResearcherProfileService( @@ -283,7 +381,7 @@ describe('ResearcherProfileService', () => { }); describe('createFromExternalSource', () => { - let patchSpy; + beforeEach(() => { spyOn((service as any).dataService, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); @@ -305,4 +403,140 @@ describe('ResearcherProfileService', () => { }); }); + describe('isLinkedToOrcid', () => { + it('should return true when item has metadata', () => { + const result = service.isLinkedToOrcid(mockItemLinkedToOrcid); + expect(result).toBeTrue(); + }); + + it('should return true when item has no metadata', () => { + const result = service.isLinkedToOrcid(mockItemUnlinkedToOrcid); + expect(result).toBeFalse(); + }); + }); + + describe('onlyAdminCanDisconnectProfileFromOrcid', () => { + it('should return true when property is only_admin', () => { + spyOn((service as any), 'getOrcidDisconnectionAllowedUsersConfiguration').and.returnValue(createSuccessfulRemoteDataObject$(disconnectionAllowAdmin)); + const result = service.onlyAdminCanDisconnectProfileFromOrcid(); + const expected = cold('(a|)', { + a: true + }); + expect(result).toBeObservable(expected); + }); + + it('should return false on faild', () => { + spyOn((service as any), 'getOrcidDisconnectionAllowedUsersConfiguration').and.returnValue(createFailedRemoteDataObject$()); + const result = service.onlyAdminCanDisconnectProfileFromOrcid(); + const expected = cold('(a|)', { + a: false + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('ownerCanDisconnectProfileFromOrcid', () => { + it('should return true when property is admin_and_owner', () => { + spyOn((service as any), 'getOrcidDisconnectionAllowedUsersConfiguration').and.returnValue(createSuccessfulRemoteDataObject$(disconnectionAllowAdminOwner)); + const result = service.ownerCanDisconnectProfileFromOrcid(); + const expected = cold('(a|)', { + a: true + }); + expect(result).toBeObservable(expected); + }); + + it('should return false on faild', () => { + spyOn((service as any), 'getOrcidDisconnectionAllowedUsersConfiguration').and.returnValue(createFailedRemoteDataObject$()); + const result = service.ownerCanDisconnectProfileFromOrcid(); + const expected = cold('(a|)', { + a: false + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('unlinkOrcid', () => { + beforeEach(() => { + scheduler = getTestScheduler(); + spyOn((service as any).dataService, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfile)); + }); + + it('should call patch method properly', () => { + const operations: RemoveOperation[] = [{ + path: '/orcid', + op: 'remove' + }]; + + scheduler.schedule(() => service.unlinkOrcid(mockItemLinkedToOrcid).subscribe()); + scheduler.flush(); + + expect((service as any).dataService.patch).toHaveBeenCalledWith(researcherProfile, operations); + }); + }); + + describe('getOrcidAuthorizeUrl', () => { + beforeEach(() => { + (service as any).configurationService.findByPropertyName.and.returnValues( + createSuccessfulRemoteDataObject$(authorizeUrl), + createSuccessfulRemoteDataObject$(appClientId), + createSuccessfulRemoteDataObject$(orcidScope) + ); + }); + + it('should build the url properly', () => { + const result = service.getOrcidAuthorizeUrl(mockItemUnlinkedToOrcid); + const redirectUri = environment.rest.baseUrl + '/api/eperson/orcid/' + mockItemUnlinkedToOrcid.id + '/?url=undefined'; + const url = 'orcid.authorize-url?client_id=orcid.application-client-id&redirect_uri=' + redirectUri + '&response_type=code&scope=/authenticate /read-limited'; + + const expected = cold('(a|)', { + a: url + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('updateByOrcidOperations', () => { + beforeEach(() => { + scheduler = getTestScheduler(); + spyOn((service as any).dataService, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + }); + + it('should call patch method properly', () => { + scheduler.schedule(() => service.updateByOrcidOperations(researcherProfile, []).subscribe()); + scheduler.flush(); + + expect((service as any).dataService.patch).toHaveBeenCalledWith(researcherProfile, []); + }); + }); + + describe('getOrcidAuthorizationScopesByItem', () => { + it('should return list of scopes saved in the item', () => { + const orcidScopes = [ + '/authenticate', + '/read-limited', + '/activities/update', + '/person/update' + ]; + const result = service.getOrcidAuthorizationScopesByItem(mockItemLinkedToOrcid); + expect(result).toEqual(orcidScopes); + }); + }); + + describe('getOrcidAuthorizationScopes', () => { + it('should return list of scopes by configuration', () => { + (service as any).configurationService.findByPropertyName.and.returnValue( + createSuccessfulRemoteDataObject$(orcidScope) + ); + const orcidScopes = [ + '/authenticate', + '/read-limited' + ]; + const expected = cold('(a|)', { + a: orcidScopes + }); + const result = service.getOrcidAuthorizationScopes(); + expect(result).toBeObservable(expected); + }); + }); }); diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index 702c168539..f944d53b46 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -32,7 +32,7 @@ import { ResearcherProfile } from './model/researcher-profile.model'; import { RESEARCHER_PROFILE } from './model/researcher-profile.resource-type'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { PostRequest } from '../data/request.models'; -import { hasValue, isEmpty } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { CoreState } from '../core-state.model'; import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { Item } from '../shared/item.model'; @@ -171,7 +171,7 @@ export class ResearcherProfileService { * @param item the item to check * @returns the check result */ - isLinkedToOrcid(item: Item): boolean { + public isLinkedToOrcid(item: Item): boolean { return item.hasMetadata('dspace.orcid.authenticated'); } @@ -180,9 +180,11 @@ export class ResearcherProfileService { * * @returns the check result */ - onlyAdminCanDisconnectProfileFromOrcid(): Observable { + public onlyAdminCanDisconnectProfileFromOrcid(): Observable { return this.getOrcidDisconnectionAllowedUsersConfiguration().pipe( - map((property) => property.values.map( (value) => value.toLowerCase()).includes('only_admin')) + map((propertyRD: RemoteData) => { + return propertyRD.hasSucceeded && propertyRD.payload.values.map((value) => value.toLowerCase()).includes('only_admin'); + }) ); } @@ -191,25 +193,10 @@ export class ResearcherProfileService { * * @returns the check result */ - ownerCanDisconnectProfileFromOrcid(): Observable { + public ownerCanDisconnectProfileFromOrcid(): Observable { return this.getOrcidDisconnectionAllowedUsersConfiguration().pipe( - map((property) => { - const values = property.values.map( (value) => value.toLowerCase()); - return values.includes('only_owner') || values.includes('admin_and_owner'); - }) - ); - } - - /** - * Returns true if the admin users can disconnect a researcher profile from ORCID. - * - * @returns the check result - */ - adminCanDisconnectProfileFromOrcid(): Observable { - return this.getOrcidDisconnectionAllowedUsersConfiguration().pipe( - map((property) => { - const values = property.values.map( (value) => value.toLowerCase()); - return values.includes('only_admin') || values.includes('admin_and_owner'); + map((propertyRD: RemoteData) => { + return propertyRD.hasSucceeded && propertyRD.payload.values.map( (value) => value.toLowerCase()).includes('admin_and_owner'); }) ); } @@ -217,8 +204,7 @@ export class ResearcherProfileService { /** * If the given item represents a profile unlink it from ORCID. */ - unlinkOrcid(item: Item): Observable> { - + public unlinkOrcid(item: Item): Observable> { const operations: RemoveOperation[] = [{ path:'/orcid', op:'remove' @@ -231,7 +217,12 @@ export class ResearcherProfileService { ); } - getOrcidAuthorizeUrl(profile: Item): Observable { + /** + * Build and return the url to authenticate with orcid + * + * @param profile + */ + public getOrcidAuthorizeUrl(profile: Item): Observable { return combineLatest([ this.configurationService.findByPropertyName('orcid.authorize-url').pipe(getFirstSucceededRemoteDataPayload()), this.configurationService.findByPropertyName('orcid.application-client-id').pipe(getFirstSucceededRemoteDataPayload()), @@ -278,9 +269,28 @@ export class ResearcherProfileService { return this.dataService.patch(researcherProfile, operations); } - private getOrcidDisconnectionAllowedUsersConfiguration(): Observable { + /** + * Return all orcid authorization scopes saved in the given item + * + * @param item + */ + public getOrcidAuthorizationScopesByItem(item: Item): string[] { + return isNotEmpty(item) ? item.allMetadataValues('dspace.orcid.scope') : []; + } + + /** + * Return all orcid authorization scopes available by configuration + */ + public getOrcidAuthorizationScopes(): Observable { + return this.configurationService.findByPropertyName('orcid.scope').pipe( + getFirstCompletedRemoteData(), + map((propertyRD: RemoteData) => propertyRD.hasSucceeded ? propertyRD.payload.values : []) + ); + } + + private getOrcidDisconnectionAllowedUsersConfiguration(): Observable> { return this.configurationService.findByPropertyName('orcid.disconnection.allowed-users').pipe( - getFirstSucceededRemoteDataPayload() + getFirstCompletedRemoteData() ); } diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html index 12b13c433c..b15b23d1e0 100644 --- a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.html @@ -3,75 +3,89 @@
- +
+
- -
-
-
{{ 'person.page.orcid.granted-authorizations'| translate }}
-
-
-
    -
  • {{getAuthorizationDescription(auth) | translate}}
  • -
-
-
-
-
-
{{ 'person.page.orcid.missing-authorizations'| translate }}
-
-
-
- {{'person.page.orcid.no-missing-authorizations-message' | translate}} -
-
- {{'person.page.orcid.missing-authorizations-message' | translate}} + +
+
+
+
+
{{ 'person.page.orcid.granted-authorizations'| translate }}
+
+
    -
  • {{getAuthorizationDescription(auth) | translate }}
  • +
  • + {{getAuthorizationDescription(auth) | translate}} +
+
+
+
{{ 'person.page.orcid.missing-authorizations'| translate }}
+
+
+ + {{'person.page.orcid.no-missing-authorizations-message' | translate}} + + + {{'person.page.orcid.missing-authorizations-message' | translate}} +
    +
  • + {{getAuthorizationDescription(auth) | translate }} +
  • +
+
+
+
+
+
-
+ {{ 'person.page.orcid.remove-orcid-message' | translate}} -
-
-
- -
- +
+
- + +
orcid-logo
-
{{ getOrcidNotLinkedMessage() | async }}
+
+ {{ getOrcidNotLinkedMessage() | async }} +
-
+
- -
+
+
+ diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.spec.ts b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.spec.ts new file mode 100644 index 0000000000..a2ec1cf9b1 --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.spec.ts @@ -0,0 +1,336 @@ +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; + +import { getTestScheduler } from 'jasmine-marbles'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; + +import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { createPaginatedList } from '../../../shared/testing/utils.test'; +import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; +import { OrcidAuthComponent } from './orcid-auth.component'; +import { NativeWindowService } from '../../../core/services/window.service'; +import { NativeWindowMockFactory } from '../../../shared/mocks/mock-native-window-ref'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; +import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model'; + +describe('OrcidAuthComponent test suite', () => { + let comp: OrcidAuthComponent; + let fixture: ComponentFixture; + let scheduler: TestScheduler; + let researcherProfileService: jasmine.SpyObj; + let nativeWindowRef; + let notificationsService; + + const orcidScopes = [ + '/authenticate', + '/read-limited', + '/activities/update', + '/person/update' + ]; + + const partialOrcidScopes = [ + '/authenticate', + '/read-limited', + ]; + + const mockItemUnlinkedToOrcid: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: { + 'dc.title': [{ + value: 'test person' + }], + 'dspace.entity.type': [{ + 'value': 'Person' + }] + } + }); + + const mockItemLinkedToOrcid: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: { + 'dc.title': [{ + value: 'test person' + }], + 'dspace.entity.type': [{ + 'value': 'Person' + }], + 'dspace.object.owner': [{ + 'value': 'test person', + 'language': null, + 'authority': 'deced3e7-68e2-495d-bf98-7c44fc33b8ff', + 'confidence': 600, + 'place': 0 + }], + 'dspace.orcid.authenticated': [{ + 'value': '2022-06-10T15:15:12.952872', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }], + 'dspace.orcid.scope': [{ + 'value': '/authenticate', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, { + 'value': '/read-limited', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + }, { + 'value': '/activities/update', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 2 + }, { + 'value': '/person/update', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 3 + }], + 'person.identifier.orcid': [{ + 'value': 'orcid-id', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }] + } + }); + + beforeEach(waitForAsync(() => { + researcherProfileService = jasmine.createSpyObj('researcherProfileService', { + getOrcidAuthorizationScopes: jasmine.createSpy('getOrcidAuthorizationScopes'), + getOrcidAuthorizationScopesByItem: jasmine.createSpy('getOrcidAuthorizationScopesByItem'), + getOrcidAuthorizeUrl: jasmine.createSpy('getOrcidAuthorizeUrl'), + isLinkedToOrcid: jasmine.createSpy('isLinkedToOrcid'), + onlyAdminCanDisconnectProfileFromOrcid: jasmine.createSpy('onlyAdminCanDisconnectProfileFromOrcid'), + ownerCanDisconnectProfileFromOrcid: jasmine.createSpy('ownerCanDisconnectProfileFromOrcid'), + unlinkOrcid: jasmine.createSpy('unlinkOrcid') + }); + + void TestBed.configureTestingModule({ + imports: [ + NgbAccordionModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + RouterTestingModule.withRoutes([]) + ], + declarations: [OrcidAuthComponent], + providers: [ + { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, + { provide: NotificationsService, useClass: NotificationsServiceStub }, + { provide: ResearcherProfileService, useValue: researcherProfileService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(OrcidAuthComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(waitForAsync(() => { + scheduler = getTestScheduler(); + fixture = TestBed.createComponent(OrcidAuthComponent); + comp = fixture.componentInstance; + researcherProfileService.getOrcidAuthorizationScopes.and.returnValue(of(orcidScopes)); + })); + + describe('when orcid profile is not linked', () => { + beforeEach(waitForAsync(() => { + comp.item = mockItemUnlinkedToOrcid; + researcherProfileService.getOrcidAuthorizationScopesByItem.and.returnValue([]); + researcherProfileService.isLinkedToOrcid.and.returnValue(false); + researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid.and.returnValue(of(false)); + researcherProfileService.ownerCanDisconnectProfileFromOrcid.and.returnValue(of(true)); + researcherProfileService.getOrcidAuthorizeUrl.and.returnValue(of('oarcidUrl')); + fixture.detectChanges(); + })); + + it('should create', fakeAsync(() => { + const orcidLinked = fixture.debugElement.query(By.css('[data-test="orcidLinked"]')); + const orcidNotLinked = fixture.debugElement.query(By.css('[data-test="orcidNotLinked"]')); + expect(orcidLinked).toBeFalsy(); + expect(orcidNotLinked).toBeTruthy(); + })); + + it('should change location on link', () => { + nativeWindowRef = (comp as any)._window; + scheduler.schedule(() => comp.linkOrcid()); + scheduler.flush(); + + expect(nativeWindowRef.nativeWindow.location.href).toBe('oarcidUrl'); + }); + + }); + + describe('when orcid profile is linked', () => { + beforeEach(waitForAsync(() => { + comp.item = mockItemLinkedToOrcid; + researcherProfileService.isLinkedToOrcid.and.returnValue(true); + })); + + describe('', () => { + + beforeEach(waitForAsync(() => { + comp.item = mockItemLinkedToOrcid; + notificationsService = (comp as any).notificationsService; + researcherProfileService.getOrcidAuthorizationScopesByItem.and.returnValue([...orcidScopes]); + researcherProfileService.isLinkedToOrcid.and.returnValue(true); + researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid.and.returnValue(of(false)); + researcherProfileService.ownerCanDisconnectProfileFromOrcid.and.returnValue(of(true)); + })); + + describe('and unlink is successfully', () => { + beforeEach(waitForAsync(() => { + comp.item = mockItemLinkedToOrcid; + researcherProfileService.unlinkOrcid.and.returnValue(createSuccessfulRemoteDataObject$(new ResearcherProfile())); + spyOn(comp.unlink, 'emit'); + fixture.detectChanges(); + })); + + it('should show success notification', () => { + scheduler.schedule(() => comp.unlinkOrcid()); + scheduler.flush(); + + expect(notificationsService.success).toHaveBeenCalled(); + expect(comp.unlink.emit).toHaveBeenCalled(); + }); + }); + + describe('and unlink is failed', () => { + beforeEach(waitForAsync(() => { + comp.item = mockItemLinkedToOrcid; + researcherProfileService.unlinkOrcid.and.returnValue(createFailedRemoteDataObject$()); + fixture.detectChanges(); + })); + + it('should show success notification', () => { + scheduler.schedule(() => comp.unlinkOrcid()); + scheduler.flush(); + + expect(notificationsService.error).toHaveBeenCalled(); + }); + }); + }); + + describe('and has orcid authorization scopes', () => { + + beforeEach(waitForAsync(() => { + comp.item = mockItemLinkedToOrcid; + researcherProfileService.getOrcidAuthorizationScopesByItem.and.returnValue([...orcidScopes]); + researcherProfileService.isLinkedToOrcid.and.returnValue(true); + researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid.and.returnValue(of(false)); + researcherProfileService.ownerCanDisconnectProfileFromOrcid.and.returnValue(of(true)); + fixture.detectChanges(); + })); + + it('should create', fakeAsync(() => { + const orcidLinked = fixture.debugElement.query(By.css('[data-test="orcidLinked"]')); + const orcidNotLinked = fixture.debugElement.query(By.css('[data-test="orcidNotLinked"]')); + expect(orcidLinked).toBeTruthy(); + expect(orcidNotLinked).toBeFalsy(); + })); + + it('should display orcid authorizations', fakeAsync(() => { + const orcidAuthorizations = fixture.debugElement.query(By.css('[data-test="hasOrcidAuthorizations"]')); + const noMissingOrcidAuthorizations = fixture.debugElement.query(By.css('[data-test="noMissingOrcidAuthorizations"]')); + const orcidAuthorizationsList = fixture.debugElement.queryAll(By.css('[data-test="orcidAuthorization"]')); + + expect(orcidAuthorizations).toBeTruthy(); + expect(noMissingOrcidAuthorizations).toBeTruthy(); + expect(orcidAuthorizationsList.length).toBe(4); + })); + }); + + describe('and has missing orcid authorization scopes', () => { + + beforeEach(waitForAsync(() => { + comp.item = mockItemLinkedToOrcid; + researcherProfileService.getOrcidAuthorizationScopesByItem.and.returnValue([...partialOrcidScopes]); + researcherProfileService.isLinkedToOrcid.and.returnValue(true); + researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid.and.returnValue(of(false)); + researcherProfileService.ownerCanDisconnectProfileFromOrcid.and.returnValue(of(true)); + fixture.detectChanges(); + })); + + it('should create', fakeAsync(() => { + const orcidLinked = fixture.debugElement.query(By.css('[data-test="orcidLinked"]')); + const orcidNotLinked = fixture.debugElement.query(By.css('[data-test="orcidNotLinked"]')); + expect(orcidLinked).toBeTruthy(); + expect(orcidNotLinked).toBeFalsy(); + })); + + it('should display orcid authorizations', fakeAsync(() => { + const orcidAuthorizations = fixture.debugElement.query(By.css('[data-test="hasOrcidAuthorizations"]')); + const missingOrcidAuthorizations = fixture.debugElement.query(By.css('[data-test="missingOrcidAuthorizations"]')); + const orcidAuthorizationsList = fixture.debugElement.queryAll(By.css('[data-test="orcidAuthorization"]')); + const missingOrcidAuthorizationsList = fixture.debugElement.queryAll(By.css('[data-test="missingOrcidAuthorization"]')); + + expect(orcidAuthorizations).toBeTruthy(); + expect(missingOrcidAuthorizations).toBeTruthy(); + expect(orcidAuthorizationsList.length).toBe(2); + expect(missingOrcidAuthorizationsList.length).toBe(2); + })); + }); + + describe('and only admin can unlink scopes', () => { + + beforeEach(waitForAsync(() => { + comp.item = mockItemLinkedToOrcid; + researcherProfileService.getOrcidAuthorizationScopesByItem.and.returnValue([...orcidScopes]); + researcherProfileService.isLinkedToOrcid.and.returnValue(true); + researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid.and.returnValue(of(true)); + researcherProfileService.ownerCanDisconnectProfileFromOrcid.and.returnValue(of(false)); + fixture.detectChanges(); + })); + + it('should display warning panel', fakeAsync(() => { + const unlinkOnlyAdmin = fixture.debugElement.query(By.css('[data-test="unlinkOnlyAdmin"]')); + const unlinkOwner = fixture.debugElement.query(By.css('[data-test="unlinkOwner"]')); + expect(unlinkOnlyAdmin).toBeTruthy(); + expect(unlinkOwner).toBeFalsy(); + })); + + }); + + describe('and owner can unlink scopes', () => { + + beforeEach(waitForAsync(() => { + comp.item = mockItemLinkedToOrcid; + researcherProfileService.getOrcidAuthorizationScopesByItem.and.returnValue([...orcidScopes]); + researcherProfileService.isLinkedToOrcid.and.returnValue(true); + researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid.and.returnValue(of(true)); + researcherProfileService.ownerCanDisconnectProfileFromOrcid.and.returnValue(of(true)); + fixture.detectChanges(); + })); + + it('should display warning panel', fakeAsync(() => { + const unlinkOnlyAdmin = fixture.debugElement.query(By.css('[data-test="unlinkOnlyAdmin"]')); + const unlinkOwner = fixture.debugElement.query(By.css('[data-test="unlinkOwner"]')); + expect(unlinkOnlyAdmin).toBeFalsy(); + expect(unlinkOwner).toBeTruthy(); + })); + + }); + + }); + + +}); diff --git a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts index aa234648f0..ed8b1fd3d9 100644 --- a/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts +++ b/src/app/item-page/orcid-page/orcid-auth/orcid-auth.component.ts @@ -1,59 +1,126 @@ -import { Component, Inject, Input, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { Component, EventEmitter, Inject, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; + import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; -import { ItemDataService } from '../../../core/data/item-data.service'; -import { RemoteData } from '../../../core/data/remote-data'; + import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; import { Item } from '../../../core/shared/item.model'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model'; @Component({ selector: 'ds-orcid-auth', templateUrl: './orcid-auth.component.html', styleUrls: ['./orcid-auth.component.scss'] }) -export class OrcidAuthComponent implements OnInit { +export class OrcidAuthComponent implements OnInit, OnChanges { /** * The item for which showing the orcid settings */ @Input() item: Item; - missingAuthorizations$ = new BehaviorSubject([]); + /** + * The list of exposed orcid authorization scopes for the orcid profile + */ + profileAuthorizationScopes: BehaviorSubject = new BehaviorSubject([]); - unlinkProcessing = false; + /** + * The list of all orcid authorization scopes missing in the orcid profile + */ + missingAuthorizationScopes: BehaviorSubject = new BehaviorSubject([]); + + /** + * The list of all orcid authorization scopes available + */ + orcidAuthorizationScopes: BehaviorSubject = new BehaviorSubject([]); + + /** + * A boolean representing if unlink operation is processing + */ + unlinkProcessing: BehaviorSubject = new BehaviorSubject(false); + + /** + * A boolean representing if orcid profile is linked + */ + private isOrcidLinked$: BehaviorSubject = new BehaviorSubject(false); + + /** + * A boolean representing if only admin can disconnect orcid profile + */ + private onlyAdminCanDisconnectProfileFromOrcid$: BehaviorSubject = new BehaviorSubject(false); + + /** + * A boolean representing if owner can disconnect orcid profile + */ + private ownerCanDisconnectProfileFromOrcid$: BehaviorSubject = new BehaviorSubject(false); + + /** + * An event emitted when orcid profile is unliked successfully + */ + @Output() unlink: EventEmitter = new EventEmitter(); constructor( - private configurationService: ConfigurationDataService, private researcherProfileService: ResearcherProfileService, - protected translateService: TranslateService, + private translateService: TranslateService, private notificationsService: NotificationsService, - private itemService: ItemDataService, - private route: ActivatedRoute, @Inject(NativeWindowService) private _window: NativeWindowRef, - ) { + ) { } ngOnInit() { - const scopes = this.getOrcidAuthorizations(); - return this.configurationService.findByPropertyName('orcid.scope') - .pipe(getFirstSucceededRemoteDataPayload(), - map((configurationProperty) => configurationProperty.values), - map((allScopes) => allScopes.filter((scope) => !scopes.includes(scope)))) - .subscribe((missingScopes) => this.missingAuthorizations$.next(missingScopes)); + this.researcherProfileService.getOrcidAuthorizationScopes().subscribe((scopes: string[]) => { + this.orcidAuthorizationScopes.next(scopes); + this.initOrcidAuthSettings(); + }); } - getOrcidAuthorizations(): string[] { - return this.item.allMetadataValues('dspace.orcid.scope'); + ngOnChanges(changes: SimpleChanges): void { + if (!changes.item.isFirstChange() && changes.item.currentValue !== changes.item.previousValue) { + this.initOrcidAuthSettings(); + } } - isLinkedToOrcid(): boolean { - return this.researcherProfileService.isLinkedToOrcid(this.item); + /** + * Check if the list of exposed orcid authorization scopes for the orcid profile has values + */ + hasOrcidAuthorizations(): Observable { + return this.profileAuthorizationScopes.asObservable().pipe( + map((scopes: string[]) => scopes.length > 0) + ); + } + + /** + * Return the list of exposed orcid authorization scopes for the orcid profile + */ + getOrcidAuthorizations(): Observable { + return this.profileAuthorizationScopes.asObservable(); + } + + /** + * Check if the list of exposed orcid authorization scopes for the orcid profile has values + */ + hasMissingOrcidAuthorizations(): Observable { + return this.missingAuthorizationScopes.asObservable().pipe( + map((scopes: string[]) => scopes.length > 0) + ); + } + + /** + * Return the list of exposed orcid authorization scopes for the orcid profile + */ + getMissingOrcidAuthorizations(): Observable { + return this.profileAuthorizationScopes.asObservable(); + } + + /** + * Return a boolean representing if orcid profile is linked + */ + isLinkedToOrcid(): Observable { + return this.isOrcidLinked$.asObservable(); } getOrcidNotLinkedMessage(): Observable { @@ -65,34 +132,85 @@ export class OrcidAuthComponent implements OnInit { } } + /** + * Get label for a given orcid authorization scope + * + * @param scope + */ getAuthorizationDescription(scope: string) { return 'person.page.orcid.scope.' + scope.substring(1).replace('/', '-'); } + /** + * Return a boolean representing if only admin can disconnect orcid profile + */ onlyAdminCanDisconnectProfileFromOrcid(): Observable { - return this.researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid(); + return this.onlyAdminCanDisconnectProfileFromOrcid$.asObservable(); } + /** + * Return a boolean representing if owner can disconnect orcid profile + */ ownerCanDisconnectProfileFromOrcid(): Observable { - return this.researcherProfileService.ownerCanDisconnectProfileFromOrcid(); + return this.ownerCanDisconnectProfileFromOrcid$.asObservable(); } + /** + * Link existing person profile with orcid + */ linkOrcid(): void { this.researcherProfileService.getOrcidAuthorizeUrl(this.item).subscribe((authorizeUrl) => { this._window.nativeWindow.location.href = authorizeUrl; }); } + /** + * Unlink existing person profile from orcid + */ unlinkOrcid(): void { - this.unlinkProcessing = true; - this.researcherProfileService.unlinkOrcid(this.item).subscribe((remoteData) => { - this.unlinkProcessing = false; + this.unlinkProcessing.next(true); + this.researcherProfileService.unlinkOrcid(this.item).subscribe((remoteData: RemoteData) => { + this.unlinkProcessing.next(false); if (remoteData.isSuccess) { this.notificationsService.success(this.translateService.get('person.page.orcid.unlink.success')); + this.unlink.emit(); } else { this.notificationsService.error(this.translateService.get('person.page.orcid.unlink.error')); } }); } + /** + * initialize all Orcid authentication settings + * @private + */ + private initOrcidAuthSettings(): void { + + this.setOrcidAuthorizationsFromItem(); + + this.setMissingOrcidAuthorizations(); + + this.researcherProfileService.onlyAdminCanDisconnectProfileFromOrcid().subscribe((result) => { + this.onlyAdminCanDisconnectProfileFromOrcid$.next(result); + }); + + this.researcherProfileService.ownerCanDisconnectProfileFromOrcid().subscribe((result) => { + this.ownerCanDisconnectProfileFromOrcid$.next(result); + }); + + this.isOrcidLinked$.next(this.researcherProfileService.isLinkedToOrcid(this.item)); + } + + private setMissingOrcidAuthorizations(): void { + const profileScopes = this.researcherProfileService.getOrcidAuthorizationScopesByItem(this.item); + const orcidScopes = this.orcidAuthorizationScopes.value; + const missingScopes = orcidScopes.filter((scope) => !profileScopes.includes(scope)); + + this.missingAuthorizationScopes.next(missingScopes); + } + + private setOrcidAuthorizationsFromItem(): void { + this.profileAuthorizationScopes.next(this.researcherProfileService.getOrcidAuthorizationScopesByItem(this.item)); + } + } diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html index 91c49596b6..1921db3e92 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.html +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -1,4 +1,4 @@ -
+
- - + + diff --git a/src/app/item-page/orcid-page/orcid-page.component.spec.ts b/src/app/item-page/orcid-page/orcid-page.component.spec.ts index 1d61f18c3e..9210d56865 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.spec.ts +++ b/src/app/item-page/orcid-page/orcid-page.component.spec.ts @@ -1,11 +1,13 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { By } from '@angular/platform-browser'; import { of as observableOf } from 'rxjs'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TestScheduler } from 'rxjs/testing'; +import { getTestScheduler } from 'jasmine-marbles'; import { AuthService } from '../../core/auth/auth.service'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; @@ -15,14 +17,16 @@ import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } f import { Item } from '../../core/shared/item.model'; import { createPaginatedList } from '../../shared/testing/utils.test'; import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; +import { ItemDataService } from '../../core/data/item-data.service'; -fdescribe('OrcidPageComponent test suite', () => { +describe('OrcidPageComponent test suite', () => { let comp: OrcidPageComponent; let fixture: ComponentFixture; - + let scheduler: TestScheduler; let authService: jasmine.SpyObj; let routeStub: jasmine.SpyObj; let routeData: any; + let itemDataService: jasmine.SpyObj; let researcherProfileService: jasmine.SpyObj; const mockItem: Item = Object.assign(new Item(), { @@ -70,6 +74,10 @@ fdescribe('OrcidPageComponent test suite', () => { isLinkedToOrcid: jasmine.createSpy('isLinkedToOrcid') }); + itemDataService = jasmine.createSpyObj('ItemDataService', { + findById: jasmine.createSpy('findById') + }); + void TestBed.configureTestingModule({ imports: [ TranslateModule.forRoot({ @@ -85,6 +93,7 @@ fdescribe('OrcidPageComponent test suite', () => { { provide: ActivatedRoute, useValue: routeStub }, { provide: ResearcherProfileService, useValue: researcherProfileService }, { provide: AuthService, useValue: authService }, + { provide: ItemDataService, useValue: itemDataService }, ], schemas: [NO_ERRORS_SCHEMA] @@ -92,6 +101,7 @@ fdescribe('OrcidPageComponent test suite', () => { })); beforeEach(waitForAsync(() => { + scheduler = getTestScheduler(); fixture = TestBed.createComponent(OrcidPageComponent); comp = fixture.componentInstance; authService.isAuthenticated.and.returnValue(observableOf(true)); @@ -107,7 +117,15 @@ fdescribe('OrcidPageComponent test suite', () => { it('should call isLinkedToOrcid', () => { comp.isLinkedToOrcid(); - expect(researcherProfileService.isLinkedToOrcid).toHaveBeenCalledWith(comp.item); + expect(researcherProfileService.isLinkedToOrcid).toHaveBeenCalledWith(comp.item.value); }); + it('should update item', fakeAsync(() => { + itemDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockItemLinkedToOrcid)); + scheduler.schedule(() => comp.updateItem()); + scheduler.flush(); + + expect(comp.item.value).toEqual(mockItemLinkedToOrcid); + })); + }); diff --git a/src/app/item-page/orcid-page/orcid-page.component.ts b/src/app/item-page/orcid-page/orcid-page.component.ts index 122c199f61..be4e0e7945 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.ts +++ b/src/app/item-page/orcid-page/orcid-page.component.ts @@ -1,15 +1,17 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; +import { BehaviorSubject } from 'rxjs'; import { map } from 'rxjs/operators'; import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; -import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { getItemPageRoute } from '../item-page-routing-paths'; import { AuthService } from '../../core/auth/auth.service'; import { redirectOn4xx } from '../../core/shared/authorized.operators'; +import { ItemDataService } from '../../core/data/item-data.service'; /** * A component that represents the orcid settings page @@ -24,10 +26,11 @@ export class OrcidPageComponent implements OnInit { /** * The item for which showing the orcid settings */ - item: Item; + item: BehaviorSubject = new BehaviorSubject(null); constructor( private authService: AuthService, + private itemService: ItemDataService, private researcherProfileService: ResearcherProfileService, private route: ActivatedRoute, private router: Router @@ -43,7 +46,7 @@ export class OrcidPageComponent implements OnInit { redirectOn4xx(this.router, this.authService), getFirstSucceededRemoteDataPayload() ).subscribe((item) => { - this.item = item; + this.item.next(item); }); } @@ -53,14 +56,27 @@ export class OrcidPageComponent implements OnInit { * @returns the check result */ isLinkedToOrcid(): boolean { - return this.researcherProfileService.isLinkedToOrcid(this.item); + return this.researcherProfileService.isLinkedToOrcid(this.item.value); } /** * Get the route to an item's page */ getItemPage(): string { - return getItemPageRoute(this.item); + return getItemPageRoute(this.item.value); + } + + /** + * Retrieve the updated profile item + */ + updateItem(): void { + this.itemService.findById(this.item.value.id, false).pipe( + getFirstCompletedRemoteData() + ).subscribe((itemRD: RemoteData) => { + if (itemRD.hasSucceeded) { + this.item.next(itemRD.payload); + } + }); } } From 10f4f80f0de7e33afc3c80824f52849193ba7593 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 10 Jun 2022 19:27:40 +0200 Subject: [PATCH 062/157] [CST-5668] Create and abstract component for authentication with external providers --- .../log-in-external-provider.component.ts | 110 ++++++++++++++++++ .../methods/oidc/log-in-oidc.component.ts | 99 +--------------- .../methods/orcid/log-in-orcid.component.ts | 99 +--------------- .../shibboleth/log-in-shibboleth.component.ts | 102 +--------------- 4 files changed, 125 insertions(+), 285 deletions(-) create mode 100644 src/app/shared/log-in/methods/log-in-external-provider.component.ts diff --git a/src/app/shared/log-in/methods/log-in-external-provider.component.ts b/src/app/shared/log-in/methods/log-in-external-provider.component.ts new file mode 100644 index 0000000000..037fc40e90 --- /dev/null +++ b/src/app/shared/log-in/methods/log-in-external-provider.component.ts @@ -0,0 +1,110 @@ +import { Component, Inject, OnInit, } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { select, Store } from '@ngrx/store'; + +import { AuthMethod } from '../../../core/auth/models/auth.method'; + +import { isAuthenticated, isAuthenticationLoading } from '../../../core/auth/selectors'; +import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; +import { isEmpty, isNotNull } from '../../empty.util'; +import { AuthService } from '../../../core/auth/auth.service'; +import { HardRedirectService } from '../../../core/services/hard-redirect.service'; +import { URLCombiner } from '../../../core/url-combiner/url-combiner'; +import { CoreState } from '../../../core/core-state.model'; + +@Component({ + selector: 'ds-log-in-external-provider', + template: '' + +}) +export abstract class LogInExternalProviderComponent implements OnInit { + + /** + * The authentication method data. + * @type {AuthMethod} + */ + public authMethod: AuthMethod; + + /** + * True if the authentication is loading. + * @type {boolean} + */ + public loading: Observable; + + /** + * The shibboleth authentication location url. + * @type {string} + */ + public location: string; + + /** + * Whether user is authenticated. + * @type {Observable} + */ + public isAuthenticated: Observable; + + /** + * @constructor + * @param {AuthMethod} injectedAuthMethodModel + * @param {boolean} isStandalonePage + * @param {NativeWindowRef} _window + * @param {AuthService} authService + * @param {HardRedirectService} hardRedirectService + * @param {Store} store + */ + constructor( + @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod, + @Inject('isStandalonePage') public isStandalonePage: boolean, + @Inject(NativeWindowService) protected _window: NativeWindowRef, + private authService: AuthService, + private hardRedirectService: HardRedirectService, + private store: Store + ) { + this.authMethod = injectedAuthMethodModel; + } + + ngOnInit(): void { + // set isAuthenticated + this.isAuthenticated = this.store.pipe(select(isAuthenticated)); + + // set loading + this.loading = this.store.pipe(select(isAuthenticationLoading)); + + // set location + this.location = decodeURIComponent(this.injectedAuthMethodModel.location); + + } + + /** + * Redirect to the external provider url for login + */ + redirectToExternalProvider() { + this.authService.getRedirectUrl().pipe(take(1)).subscribe((redirectRoute) => { + if (!this.isStandalonePage) { + redirectRoute = this.hardRedirectService.getCurrentRoute(); + } else if (isEmpty(redirectRoute)) { + redirectRoute = '/'; + } + const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString(); + + let externalServerUrl = this.location; + const myRegexp = /\?redirectUrl=(.*)/g; + const match = myRegexp.exec(this.location); + const redirectUrlFromServer = (match && match[1]) ? match[1] : null; + + // Check whether the current page is different from the redirect url received from rest + if (isNotNull(redirectUrlFromServer) && redirectUrlFromServer !== correctRedirectUrl) { + // change the redirect url with the current page url + const newRedirectUrl = `?redirectUrl=${correctRedirectUrl}`; + externalServerUrl = this.location.replace(/\?redirectUrl=(.*)/g, newRedirectUrl); + } + + // redirect to shibboleth authentication url + this.hardRedirectService.redirect(externalServerUrl); + }); + + } + +} diff --git a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.ts b/src/app/shared/log-in/methods/oidc/log-in-oidc.component.ts index 38cedf91ec..882996b207 100644 --- a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.ts +++ b/src/app/shared/log-in/methods/oidc/log-in-oidc.component.ts @@ -1,110 +1,21 @@ -import { Component, Inject, OnInit, } from '@angular/core'; - -import { Observable } from 'rxjs'; -import { select, Store } from '@ngrx/store'; +import { Component, } from '@angular/core'; import { renderAuthMethodFor } from '../log-in.methods-decorator'; import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { AuthMethod } from '../../../../core/auth/models/auth.method'; - -import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors'; -import { NativeWindowRef, NativeWindowService } from '../../../../core/services/window.service'; -import { isNotNull, isEmpty } from '../../../empty.util'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; -import { take } from 'rxjs/operators'; -import { URLCombiner } from '../../../../core/url-combiner/url-combiner'; -import { CoreState } from '../../../../core/core-state.model'; +import { LogInExternalProviderComponent } from '../log-in-external-provider.component'; @Component({ selector: 'ds-log-in-oidc', templateUrl: './log-in-oidc.component.html', }) @renderAuthMethodFor(AuthMethodType.Oidc) -export class LogInOidcComponent implements OnInit { +export class LogInOidcComponent extends LogInExternalProviderComponent { /** - * The authentication method data. - * @type {AuthMethod} + * Redirect to orcid authentication url */ - public authMethod: AuthMethod; - - /** - * True if the authentication is loading. - * @type {boolean} - */ - public loading: Observable; - - /** - * The oidc authentication location url. - * @type {string} - */ - public location: string; - - /** - * Whether user is authenticated. - * @type {Observable} - */ - public isAuthenticated: Observable; - - /** - * @constructor - * @param {AuthMethod} injectedAuthMethodModel - * @param {boolean} isStandalonePage - * @param {NativeWindowRef} _window - * @param {AuthService} authService - * @param {HardRedirectService} hardRedirectService - * @param {Store} store - */ - constructor( - @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod, - @Inject('isStandalonePage') public isStandalonePage: boolean, - @Inject(NativeWindowService) protected _window: NativeWindowRef, - private authService: AuthService, - private hardRedirectService: HardRedirectService, - private store: Store - ) { - this.authMethod = injectedAuthMethodModel; - } - - ngOnInit(): void { - // set isAuthenticated - this.isAuthenticated = this.store.pipe(select(isAuthenticated)); - - // set loading - this.loading = this.store.pipe(select(isAuthenticationLoading)); - - // set location - this.location = decodeURIComponent(this.injectedAuthMethodModel.location); - - } - redirectToOidc() { - - this.authService.getRedirectUrl().pipe(take(1)).subscribe((redirectRoute) => { - if (!this.isStandalonePage) { - redirectRoute = this.hardRedirectService.getCurrentRoute(); - } else if (isEmpty(redirectRoute)) { - redirectRoute = '/'; - } - const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString(); - - let oidcServerUrl = this.location; - const myRegexp = /\?redirectUrl=(.*)/g; - const match = myRegexp.exec(this.location); - const redirectUrlFromServer = (match && match[1]) ? match[1] : null; - - // Check whether the current page is different from the redirect url received from rest - if (isNotNull(redirectUrlFromServer) && redirectUrlFromServer !== correctRedirectUrl) { - // change the redirect url with the current page url - const newRedirectUrl = `?redirectUrl=${correctRedirectUrl}`; - oidcServerUrl = this.location.replace(/\?redirectUrl=(.*)/g, newRedirectUrl); - } - - // redirect to oidc authentication url - this.hardRedirectService.redirect(oidcServerUrl); - }); - + this.redirectToExternalProvider(); } } diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts index 2181f3db20..e0b1da3db5 100644 --- a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts +++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts @@ -1,110 +1,21 @@ -import { Component, Inject, OnInit, } from '@angular/core'; - -import { Observable } from 'rxjs'; -import { select, Store } from '@ngrx/store'; +import { Component, } from '@angular/core'; import { renderAuthMethodFor } from '../log-in.methods-decorator'; import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { AuthMethod } from '../../../../core/auth/models/auth.method'; - -import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors'; -import { NativeWindowRef, NativeWindowService } from '../../../../core/services/window.service'; -import { isNotNull, isEmpty } from '../../../empty.util'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; -import { take } from 'rxjs/operators'; -import { URLCombiner } from '../../../../core/url-combiner/url-combiner'; -import {CoreState} from '../../../../core/core-state.model'; +import { LogInExternalProviderComponent } from '../log-in-external-provider.component'; @Component({ selector: 'ds-log-in-orcid', templateUrl: './log-in-orcid.component.html', }) @renderAuthMethodFor(AuthMethodType.Orcid) -export class LogInOrcidComponent implements OnInit { +export class LogInOrcidComponent extends LogInExternalProviderComponent { /** - * The authentication method data. - * @type {AuthMethod} + * Redirect to orcid authentication url */ - public authMethod: AuthMethod; - - /** - * True if the authentication is loading. - * @type {boolean} - */ - public loading: Observable; - - /** - * The orcid authentication location url. - * @type {string} - */ - public location: string; - - /** - * Whether user is authenticated. - * @type {Observable} - */ - public isAuthenticated: Observable; - - /** - * @constructor - * @param {AuthMethod} injectedAuthMethodModel - * @param {boolean} isStandalonePage - * @param {NativeWindowRef} _window - * @param {AuthService} authService - * @param {HardRedirectService} hardRedirectService - * @param {Store} store - */ - constructor( - @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod, - @Inject('isStandalonePage') public isStandalonePage: boolean, - @Inject(NativeWindowService) protected _window: NativeWindowRef, - private authService: AuthService, - private hardRedirectService: HardRedirectService, - private store: Store - ) { - this.authMethod = injectedAuthMethodModel; - } - - ngOnInit(): void { - // set isAuthenticated - this.isAuthenticated = this.store.pipe(select(isAuthenticated)); - - // set loading - this.loading = this.store.pipe(select(isAuthenticationLoading)); - - // set location - this.location = decodeURIComponent(this.injectedAuthMethodModel.location); - - } - redirectToOrcid() { - - this.authService.getRedirectUrl().pipe(take(1)).subscribe((redirectRoute) => { - if (!this.isStandalonePage) { - redirectRoute = this.hardRedirectService.getCurrentRoute(); - } else if (isEmpty(redirectRoute)) { - redirectRoute = '/'; - } - const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString(); - - let orcidServerUrl = this.location; - const myRegexp = /\?redirectUrl=(.*)/g; - const match = myRegexp.exec(this.location); - const redirectUrlFromServer = (match && match[1]) ? match[1] : null; - - // Check whether the current page is different from the redirect url received from rest - if (isNotNull(redirectUrlFromServer) && redirectUrlFromServer !== correctRedirectUrl) { - // change the redirect url with the current page url - const newRedirectUrl = `?redirectUrl=${correctRedirectUrl}`; - orcidServerUrl = this.location.replace(/\?redirectUrl=(.*)/g, newRedirectUrl); - } - - // redirect to orcid authentication url - this.hardRedirectService.redirect(orcidServerUrl); - }); - + this.redirectToExternalProvider(); } } diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts index d218a7ca4e..dcfb3ccfc3 100644 --- a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts +++ b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts @@ -1,21 +1,8 @@ -import { Component, Inject, OnInit, } from '@angular/core'; - -import { Observable } from 'rxjs'; -import { select, Store } from '@ngrx/store'; +import { Component, } from '@angular/core'; import { renderAuthMethodFor } from '../log-in.methods-decorator'; import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { AuthMethod } from '../../../../core/auth/models/auth.method'; - -import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors'; -import { RouteService } from '../../../../core/services/route.service'; -import { NativeWindowRef, NativeWindowService } from '../../../../core/services/window.service'; -import { isNotNull, isEmpty } from '../../../empty.util'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; -import { take } from 'rxjs/operators'; -import { URLCombiner } from '../../../../core/url-combiner/url-combiner'; -import { CoreState } from '../../../../core/core-state.model'; +import { LogInExternalProviderComponent } from '../log-in-external-provider.component'; @Component({ selector: 'ds-log-in-shibboleth', @@ -24,92 +11,13 @@ import { CoreState } from '../../../../core/core-state.model'; }) @renderAuthMethodFor(AuthMethodType.Shibboleth) -export class LogInShibbolethComponent implements OnInit { +export class LogInShibbolethComponent extends LogInExternalProviderComponent { /** - * The authentication method data. - * @type {AuthMethod} + * Redirect to shibboleth authentication url */ - public authMethod: AuthMethod; - - /** - * True if the authentication is loading. - * @type {boolean} - */ - public loading: Observable; - - /** - * The shibboleth authentication location url. - * @type {string} - */ - public location: string; - - /** - * Whether user is authenticated. - * @type {Observable} - */ - public isAuthenticated: Observable; - - /** - * @constructor - * @param {AuthMethod} injectedAuthMethodModel - * @param {boolean} isStandalonePage - * @param {NativeWindowRef} _window - * @param {RouteService} route - * @param {AuthService} authService - * @param {HardRedirectService} hardRedirectService - * @param {Store} store - */ - constructor( - @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod, - @Inject('isStandalonePage') public isStandalonePage: boolean, - @Inject(NativeWindowService) protected _window: NativeWindowRef, - private route: RouteService, - private authService: AuthService, - private hardRedirectService: HardRedirectService, - private store: Store - ) { - this.authMethod = injectedAuthMethodModel; - } - - ngOnInit(): void { - // set isAuthenticated - this.isAuthenticated = this.store.pipe(select(isAuthenticated)); - - // set loading - this.loading = this.store.pipe(select(isAuthenticationLoading)); - - // set location - this.location = decodeURIComponent(this.injectedAuthMethodModel.location); - - } - redirectToShibboleth() { - - this.authService.getRedirectUrl().pipe(take(1)).subscribe((redirectRoute) => { - if (!this.isStandalonePage) { - redirectRoute = this.hardRedirectService.getCurrentRoute(); - } else if (isEmpty(redirectRoute)) { - redirectRoute = '/'; - } - const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString(); - - let shibbolethServerUrl = this.location; - const myRegexp = /\?redirectUrl=(.*)/g; - const match = myRegexp.exec(this.location); - const redirectUrlFromServer = (match && match[1]) ? match[1] : null; - - // Check whether the current page is different from the redirect url received from rest - if (isNotNull(redirectUrlFromServer) && redirectUrlFromServer !== correctRedirectUrl) { - // change the redirect url with the current page url - const newRedirectUrl = `?redirectUrl=${correctRedirectUrl}`; - shibbolethServerUrl = this.location.replace(/\?redirectUrl=(.*)/g, newRedirectUrl); - } - - // redirect to shibboleth authentication url - this.hardRedirectService.redirect(shibbolethServerUrl); - }); - + this.redirectToExternalProvider(); } } From 0b0fae45fa3417094f56ede7a4286abeeba6d726 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 13 Jun 2022 09:27:47 +0200 Subject: [PATCH 063/157] [CST-5668] Fix layout and add typedoc --- .../orcid-sync/orcid-setting.component.html | 144 ++++++++++-------- .../orcid-sync/orcid-setting.component.ts | 73 +++++++-- 2 files changed, 139 insertions(+), 78 deletions(-) diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html index 793e7570ed..563ddba699 100644 --- a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html +++ b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html @@ -4,71 +4,85 @@
-
+ {{ 'person.page.orcid.synchronization-mode-message' | translate}} -
-
-
-
{{ 'person.page.orcid.synchronization-mode'| translate }}
-
-
-
- - -
-
-
-
-
-
-
-
{{ 'person.page.orcid.publications-preferences'| translate }}
-
-
-
-
- - -
-
-
-
-
-
-
{{ 'person.page.orcid.funding-preferences'| translate }}
-
-
-
-
- - -
-
-
-
-
-
-
{{ 'person.page.orcid.profile-preferences'| translate }}
-
-
-
-
- - -
-
-
-
-
-
-
+ +
-
+
+
+
+
+
{{ 'person.page.orcid.publications-preferences'| translate }}
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
{{ 'person.page.orcid.funding-preferences'| translate }}
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
{{ 'person.page.orcid.profile-preferences'| translate }}
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ @@ -78,5 +92,5 @@
- -
\ No newline at end of file + +
diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts index 2429ac2e43..74b96f8859 100644 --- a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts @@ -26,30 +26,57 @@ export class OrcidSettingComponent implements OnInit { */ @Input() item: Item; + /** + * The prefix used for i18n keys + */ messagePrefix = 'person.page.orcid'; + /** + * The current synchronization mode + */ currentSyncMode: string; + /** + * The current synchronization mode for publications + */ currentSyncPublications: string; - currentSyncFundings: string; + /** + * The current synchronization mode for funding + */ + currentSyncFunding: string; + /** + * The synchronization options + */ syncModes: { value: string, label: string }[]; + /** + * The synchronization options for publications + */ syncPublicationOptions: { value: string, label: string }[]; - syncFundingOptions: {value: string, label: string}[]; + /** + * The synchronization options for funding + */ + syncFundingOptions: { value: string, label: string }[]; + /** + * The profile synchronization options + */ syncProfileOptions: { value: string, label: string, checked: boolean }[]; constructor(private researcherProfileService: ResearcherProfileService, - protected translateService: TranslateService, - private notificationsService: NotificationsService, - public authService: AuthService + protected translateService: TranslateService, + private notificationsService: NotificationsService, + public authService: AuthService ) { } + /** + * Init orcid settings form + */ ngOnInit() { this.syncModes = [ { @@ -91,10 +118,15 @@ export class OrcidSettingComponent implements OnInit { this.currentSyncMode = this.getCurrentPreference('dspace.orcid.sync-mode', ['BATCH', 'MANUAL'], 'MANUAL'); this.currentSyncPublications = this.getCurrentPreference('dspace.orcid.sync-publications', ['DISABLED', 'ALL'], 'DISABLED'); - this.currentSyncFundings = this.getCurrentPreference('dspace.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); + this.currentSyncFunding = this.getCurrentPreference('dspace.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); } - onSubmit(form: FormGroup) { + /** + * Generate path operations to save orcid synchronization preferences + * + * @param form The form group + */ + onSubmit(form: FormGroup): void { const operations: Operation[] = []; this.fillOperationsFor(operations, '/orcid/mode', form.value.syncMode); this.fillOperationsFor(operations, '/orcid/publications', form.value.syncPublications); @@ -131,7 +163,27 @@ export class OrcidSettingComponent implements OnInit { }); } - fillOperationsFor(operations: Operation[], path: string, currentValue: string) { + /** + * Retrieve setting saved in the item's metadata + * + * @param metadataField The metadata name that contains setting + * @param allowedValues The allowed values + * @param defaultValue The default value + * @private + */ + private getCurrentPreference(metadataField: string, allowedValues: string[], defaultValue: string): string { + const currentPreference = this.item.firstMetadataValue(metadataField); + return (currentPreference && allowedValues.includes(currentPreference)) ? currentPreference : defaultValue; + } + + /** + * Generate a replace patch operation + * + * @param operations + * @param path + * @param currentValue + */ + private fillOperationsFor(operations: Operation[], path: string, currentValue: string): void { operations.push({ path: path, op: 'replace', @@ -139,9 +191,4 @@ export class OrcidSettingComponent implements OnInit { }); } - getCurrentPreference(metadataField: string, allowedValues: string[], defaultValue: string): string { - const currentPreference = this.item.firstMetadataValue(metadataField); - return (currentPreference && allowedValues.includes(currentPreference)) ? currentPreference : defaultValue; - } - } From 98f1baea2fb894dbdacbaf3450363c68d09153b0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 13 Jun 2022 09:32:24 +0200 Subject: [PATCH 064/157] [CST-5668] Rename component --- src/app/item-page/item-page.module.ts | 24 +++++++++++++------ .../orcid-page/orcid-page.component.html | 2 +- .../orcid-sync-settings.component.html} | 0 .../orcid-sync-settings.component.scss} | 0 .../orcid-sync-settings.component.ts} | 8 +++---- 5 files changed, 22 insertions(+), 12 deletions(-) rename src/app/item-page/orcid-page/{orcid-sync/orcid-setting.component.html => orcid-sync-settings/orcid-sync-settings.component.html} (100%) rename src/app/item-page/orcid-page/{orcid-sync/orcid-setting.component.scss => orcid-sync-settings/orcid-sync-settings.component.scss} (100%) rename src/app/item-page/orcid-page/{orcid-sync/orcid-setting.component.ts => orcid-sync-settings/orcid-sync-settings.component.ts} (96%) diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index 2c4b57b249..d1be5a9cd8 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -6,11 +6,19 @@ import { SharedModule } from '../shared/shared.module'; import { ItemPageComponent } from './simple/item-page.component'; import { ItemPageRoutingModule } from './item-page-routing.module'; import { MetadataUriValuesComponent } from './field-components/metadata-uri-values/metadata-uri-values.component'; -import { ItemPageAuthorFieldComponent } from './simple/field-components/specific-field/author/item-page-author-field.component'; -import { ItemPageDateFieldComponent } from './simple/field-components/specific-field/date/item-page-date-field.component'; -import { ItemPageAbstractFieldComponent } from './simple/field-components/specific-field/abstract/item-page-abstract-field.component'; +import { + ItemPageAuthorFieldComponent +} from './simple/field-components/specific-field/author/item-page-author-field.component'; +import { + ItemPageDateFieldComponent +} from './simple/field-components/specific-field/date/item-page-date-field.component'; +import { + ItemPageAbstractFieldComponent +} from './simple/field-components/specific-field/abstract/item-page-abstract-field.component'; import { ItemPageUriFieldComponent } from './simple/field-components/specific-field/uri/item-page-uri-field.component'; -import { ItemPageTitleFieldComponent } from './simple/field-components/specific-field/title/item-page-title-field.component'; +import { + ItemPageTitleFieldComponent +} from './simple/field-components/specific-field/title/item-page-title-field.component'; import { ItemPageFieldComponent } from './simple/field-components/specific-field/item-page-field.component'; import { CollectionsComponent } from './field-components/collections/collections.component'; import { FullItemPageComponent } from './full/full-item-page.component'; @@ -20,7 +28,9 @@ import { ItemComponent } from './simple/item-types/shared/item.component'; import { EditItemPageModule } from './edit-item-page/edit-item-page.module'; import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component'; import { StatisticsModule } from '../statistics/statistics.module'; -import { AbstractIncrementalListComponent } from './simple/abstract-incremental-list/abstract-incremental-list.component'; +import { + AbstractIncrementalListComponent +} from './simple/abstract-incremental-list/abstract-incremental-list.component'; import { UntypedItemComponent } from './simple/item-types/untyped-item/untyped-item.component'; import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module'; @@ -37,7 +47,7 @@ import { ThemedFileSectionComponent } from './simple/field-components/file-secti import { OrcidAuthComponent } from './orcid-page/orcid-auth/orcid-auth.component'; import { OrcidPageComponent } from './orcid-page/orcid-page.component'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; -import { OrcidSettingComponent } from './orcid-page/orcid-sync/orcid-setting.component'; +import { OrcidSyncSettingsComponent } from './orcid-page/orcid-sync-settings/orcid-sync-settings.component'; const ENTRY_COMPONENTS = [ @@ -73,7 +83,7 @@ const DECLARATIONS = [ VersionPageComponent, OrcidPageComponent, OrcidAuthComponent, - OrcidSettingComponent + OrcidSyncSettingsComponent ]; @NgModule({ diff --git a/src/app/item-page/orcid-page/orcid-page.component.html b/src/app/item-page/orcid-page/orcid-page.component.html index 1921db3e92..6b49c4d224 100644 --- a/src/app/item-page/orcid-page/orcid-page.component.html +++ b/src/app/item-page/orcid-page/orcid-page.component.html @@ -9,4 +9,4 @@
- + diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html similarity index 100% rename from src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.html rename to src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.scss b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.scss similarity index 100% rename from src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.scss rename to src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.scss diff --git a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts similarity index 96% rename from src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts rename to src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts index 74b96f8859..111c7a08df 100644 --- a/src/app/item-page/orcid-page/orcid-sync/orcid-setting.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts @@ -15,11 +15,11 @@ import { NotificationsService } from '../../../shared/notifications/notification import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model'; @Component({ - selector: 'ds-orcid-setting', - templateUrl: './orcid-setting.component.html', - styleUrls: ['./orcid-setting.component.scss'] + selector: 'ds-orcid-sync-setting', + templateUrl: './orcid-sync-settings.component.html', + styleUrls: ['./orcid-sync-settings.component.scss'] }) -export class OrcidSettingComponent implements OnInit { +export class OrcidSyncSettingsComponent implements OnInit { /** * The item for which showing the orcid settings From a915659cc962fa553ab1fd718743954a69a16b51 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 13 Jun 2022 10:39:44 +0200 Subject: [PATCH 065/157] [CST-5668] Add test --- .../orcid-sync-settings.component.html | 8 +- .../orcid-sync-settings.component.spec.ts | 257 ++++++++++++++++++ .../orcid-sync-settings.component.ts | 5 +- 3 files changed, 262 insertions(+), 8 deletions(-) create mode 100644 src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html index 563ddba699..6487e78476 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html @@ -9,7 +9,7 @@
-
+
{{ 'person.page.orcid.synchronization-mode'| translate }}
@@ -28,7 +28,7 @@
-
+
{{ 'person.page.orcid.publications-preferences'| translate }}
@@ -46,7 +46,7 @@
-
+
{{ 'person.page.orcid.funding-preferences'| translate }}
@@ -63,7 +63,7 @@
-
+
{{ 'person.page.orcid.profile-preferences'| translate }}
diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts new file mode 100644 index 0000000000..ccc00178cb --- /dev/null +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts @@ -0,0 +1,257 @@ +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; + +import { getTestScheduler } from 'jasmine-marbles'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { TestScheduler } from 'rxjs/testing'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { Operation } from 'fast-json-patch'; + +import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { createPaginatedList } from '../../../shared/testing/utils.test'; +import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; +import { OrcidSyncSettingsComponent } from './orcid-sync-settings.component'; +import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model'; + +describe('OrcidAuthComponent test suite', () => { + let comp: OrcidSyncSettingsComponent; + let fixture: ComponentFixture; + let scheduler: TestScheduler; + let researcherProfileService: jasmine.SpyObj; + let notificationsService; + let formGroup: FormGroup; + + const mockResearcherProfile: ResearcherProfile = Object.assign(new ResearcherProfile(), { + id: 'test-id', + visible: true, + type: 'profile', + _links: { + item: { + href: 'https://rest.api/rest/api/profiles/test-id/item' + }, + self: { + href: 'https://rest.api/rest/api/profiles/test-id' + }, + } + }); + + const mockItemLinkedToOrcid: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: { + 'dc.title': [{ + value: 'test person' + }], + 'dspace.entity.type': [{ + 'value': 'Person' + }], + 'dspace.object.owner': [{ + 'value': 'test person', + 'language': null, + 'authority': 'deced3e7-68e2-495d-bf98-7c44fc33b8ff', + 'confidence': 600, + 'place': 0 + }], + 'dspace.orcid.authenticated': [{ + 'value': '2022-06-10T15:15:12.952872', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }], + 'dspace.orcid.scope': [{ + 'value': '/authenticate', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, { + 'value': '/read-limited', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + }, { + 'value': '/activities/update', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 2 + }, { + 'value': '/person/update', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 3 + }], + 'dspace.orcid.sync-mode': [{ + 'value': 'MANUAL', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }], + 'dspace.orcid.sync-profile': [{ + 'value': 'BIOGRAPHICAL', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }, { + 'value': 'IDENTIFIERS', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 1 + }], + 'dspace.orcid.sync-publications': [{ + 'value': 'ALL', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }], + 'person.identifier.orcid': [{ + 'value': 'orcid-id', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }] + } + }); + + beforeEach(waitForAsync(() => { + researcherProfileService = jasmine.createSpyObj('researcherProfileService', { + findByRelatedItem: jasmine.createSpy('findByRelatedItem'), + updateByOrcidOperations: jasmine.createSpy('updateByOrcidOperations') + }); + + void TestBed.configureTestingModule({ + imports: [ + FormsModule, + NgbAccordionModule, + ReactiveFormsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + RouterTestingModule.withRoutes([]) + ], + declarations: [OrcidSyncSettingsComponent], + providers: [ + { provide: NotificationsService, useClass: NotificationsServiceStub }, + { provide: ResearcherProfileService, useValue: researcherProfileService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(OrcidSyncSettingsComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(waitForAsync(() => { + scheduler = getTestScheduler(); + fixture = TestBed.createComponent(OrcidSyncSettingsComponent); + comp = fixture.componentInstance; + comp.item = mockItemLinkedToOrcid; + fixture.detectChanges(); + })); + + it('should create cards properly', () => { + const modes = fixture.debugElement.query(By.css('[data-test="sync-mode"]')); + const publication = fixture.debugElement.query(By.css('[data-test="sync-mode-publication"]')); + const funding = fixture.debugElement.query(By.css('[data-test="sync-mode-funding"]')); + const preferences = fixture.debugElement.query(By.css('[data-test="profile-preferences"]')); + expect(modes).toBeTruthy(); + expect(publication).toBeTruthy(); + expect(funding).toBeTruthy(); + expect(preferences).toBeTruthy(); + }); + + it('should init sync modes properly', () => { + expect(comp.currentSyncMode).toBe('MANUAL'); + expect(comp.currentSyncPublications).toBe('ALL'); + expect(comp.currentSyncFunding).toBe('DISABLED'); + }); + + describe('form submit', () => { + beforeEach(() => { + scheduler = getTestScheduler(); + notificationsService = (comp as any).notificationsService; + formGroup = new FormGroup({ + syncMode: new FormControl('MANUAL'), + syncFundings: new FormControl('ALL'), + syncPublications: new FormControl('ALL'), + syncProfile_BIOGRAPHICAL: new FormControl(true), + syncProfile_IDENTIFIERS: new FormControl(true), + }); + }); + + it('should call updateByOrcidOperations properly', () => { + researcherProfileService.findByRelatedItem.and.returnValue(createSuccessfulRemoteDataObject$(mockResearcherProfile)); + researcherProfileService.updateByOrcidOperations.and.returnValue(createSuccessfulRemoteDataObject$(mockResearcherProfile)); + const expectedOps: Operation[] = [ + { + path: '/orcid/mode', + op: 'replace', + value: 'MANUAL' + }, { + path: '/orcid/publications', + op: 'replace', + value: 'ALL' + }, { + path: '/orcid/fundings', + op: 'replace', + value: 'ALL' + }, { + path: '/orcid/profile', + op: 'replace', + value: 'BIOGRAPHICAL,IDENTIFIERS' + } + ]; + + scheduler.schedule(() => comp.onSubmit(formGroup)); + scheduler.flush(); + + expect(researcherProfileService.updateByOrcidOperations).toHaveBeenCalledWith(mockResearcherProfile, expectedOps); + }); + + it('should show notification on success', () => { + researcherProfileService.findByRelatedItem.and.returnValue(createSuccessfulRemoteDataObject$(mockResearcherProfile)); + researcherProfileService.updateByOrcidOperations.and.returnValue(createSuccessfulRemoteDataObject$(mockResearcherProfile)); + + scheduler.schedule(() => comp.onSubmit(formGroup)); + scheduler.flush(); + + expect(notificationsService.success).toHaveBeenCalled(); + }); + + it('should show notification on error', () => { + researcherProfileService.findByRelatedItem.and.returnValue(createFailedRemoteDataObject$()); + + scheduler.schedule(() => comp.onSubmit(formGroup)); + scheduler.flush(); + + expect(notificationsService.error).toHaveBeenCalled(); + }); + + it('should show notification on error', () => { + researcherProfileService.findByRelatedItem.and.returnValue(createSuccessfulRemoteDataObject$(mockResearcherProfile)); + researcherProfileService.updateByOrcidOperations.and.returnValue(createFailedRemoteDataObject$()); + + scheduler.schedule(() => comp.onSubmit(formGroup)); + scheduler.flush(); + + expect(notificationsService.error).toHaveBeenCalled(); + }); + }); + +}); diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts index 111c7a08df..5b5e13a1aa 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts @@ -6,7 +6,6 @@ import { Operation } from 'fast-json-patch'; import { of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; -import { AuthService } from '../../../core/auth/auth.service'; import { RemoteData } from '../../../core/data/remote-data'; import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; import { Item } from '../../../core/shared/item.model'; @@ -68,10 +67,8 @@ export class OrcidSyncSettingsComponent implements OnInit { constructor(private researcherProfileService: ResearcherProfileService, - protected translateService: TranslateService, private notificationsService: NotificationsService, - public authService: AuthService - ) { + private translateService: TranslateService) { } /** From 9b6aa9f324d1631381c6102445581d7eee0d07dc Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 13 Jun 2022 14:55:17 +0200 Subject: [PATCH 066/157] [CST-5668] Improve synchronization description --- .../orcid-sync-settings.component.html | 8 +++++--- src/assets/i18n/en.json5 | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html index 6487e78476..75038d5973 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html @@ -4,15 +4,17 @@
- - {{ 'person.page.orcid.synchronization-mode-message' | translate}} -
{{ 'person.page.orcid.synchronization-mode'| translate }}
+
+ + {{ 'person.page.orcid.synchronization-mode-message' | translate}} + +
-
+
{{ 'person.page.orcid.funding-preferences'| translate }}
+
+ + {{ 'person.page.orcid.synchronization-mode-funding-message' | translate}} + +
-
+
{{ 'person.page.orcid.profile-preferences'| translate }}
+
+ + {{ 'person.page.orcid.synchronization-mode-profile-message' | translate}} + +
Date: Tue, 14 Jun 2022 10:05:29 +0200 Subject: [PATCH 072/157] [CST-5668] Fix issue with notification css that override min-width for all alert boxes --- .../notifications/notification/notification.component.html | 2 +- .../notifications/notification/notification.component.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/notifications/notification/notification.component.html b/src/app/shared/notifications/notification/notification.component.html index e8b3d37b5f..befa84cfc0 100644 --- a/src/app/shared/notifications/notification/notification.component.html +++ b/src/app/shared/notifications/notification/notification.component.html @@ -1,4 +1,4 @@ -