diff --git a/src/app/core/profile/researcher-profile.service.spec.ts b/src/app/core/profile/researcher-profile.service.spec.ts index 9a6b0477d5..11f6e1b13b 100644 --- a/src/app/core/profile/researcher-profile.service.spec.ts +++ b/src/app/core/profile/researcher-profile.service.spec.ts @@ -23,14 +23,15 @@ 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 { RemoveOperation, ReplaceOperation } from 'fast-json-patch'; +import { AddOperation, 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'; +import { NativeWindowRefMock } from '../../shared/mocks/mock-native-window-ref'; +import { URLCombiner } from '../url-combiner/url-combiner'; describe('ResearcherProfileService', () => { let scheduler: TestScheduler; @@ -42,6 +43,8 @@ describe('ResearcherProfileService', () => { let halService: HALEndpointService; let responseCacheEntry: RequestEntry; let configurationDataService: ConfigurationDataService; + let nativeWindowService: NativeWindowRefMock; + let routerStub: any; const researcherProfileId = 'beef9946-rt56-479e-8f11-b90cbe9f7241'; const itemId = 'beef9946-rt56-479e-8f11-b90cbe9f7241'; @@ -102,7 +105,14 @@ describe('ResearcherProfileService', () => { }], 'dspace.entity.type': [{ 'value': 'Person' - }] + }], + 'dspace.object.owner': [{ + 'value': 'test person', + 'language': null, + 'authority': 'researcher-profile-id', + 'confidence': 600, + 'place': 0 + }], } }); @@ -238,15 +248,17 @@ describe('ResearcherProfileService', () => { const notificationsService = {} as NotificationsService; const http = {} as HttpClient; const comparator = {} as any; - const routerStub: any = new RouterMock(); + routerStub = new RouterMock(); const itemService = jasmine.createSpyObj('ItemService', { findByHref: jasmine.createSpy('findByHref') }); configurationDataService = jasmine.createSpyObj('configurationDataService', { findByPropertyName: jasmine.createSpy('findByPropertyName') }); + nativeWindowService = new NativeWindowRefMock(); service = new ResearcherProfileService( + nativeWindowService, requestService, rdbService, objectCache, @@ -455,7 +467,28 @@ describe('ResearcherProfileService', () => { }); }); - describe('unlinkOrcid', () => { + describe('linkOrcidByItem', () => { + 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: AddOperation[] = [{ + path: '/orcid', + op: 'add', + value: 'test-code' + }]; + + scheduler.schedule(() => service.linkOrcidByItem(mockItemUnlinkedToOrcid, 'test-code').subscribe()); + scheduler.flush(); + + expect((service as any).dataService.patch).toHaveBeenCalledWith(researcherProfile, operations); + }); + }); + + describe('unlinkOrcidByItem', () => { beforeEach(() => { scheduler = getTestScheduler(); spyOn((service as any).dataService, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); @@ -468,7 +501,7 @@ describe('ResearcherProfileService', () => { op: 'remove' }]; - scheduler.schedule(() => service.unlinkOrcid(mockItemLinkedToOrcid).subscribe()); + scheduler.schedule(() => service.unlinkOrcidByItem(mockItemLinkedToOrcid).subscribe()); scheduler.flush(); expect((service as any).dataService.patch).toHaveBeenCalledWith(researcherProfile, operations); @@ -477,6 +510,7 @@ describe('ResearcherProfileService', () => { describe('getOrcidAuthorizeUrl', () => { beforeEach(() => { + routerStub.setRoute('/entities/person/uuid/orcid'); (service as any).configurationService.findByPropertyName.and.returnValues( createSuccessfulRemoteDataObject$(authorizeUrl), createSuccessfulRemoteDataObject$(appClientId), @@ -486,7 +520,7 @@ describe('ResearcherProfileService', () => { it('should build the url properly', () => { const result = service.getOrcidAuthorizeUrl(mockItemUnlinkedToOrcid); - const redirectUri = environment.rest.baseUrl + '/api/eperson/orcid/' + mockItemUnlinkedToOrcid.id + '/?url=undefined'; + const redirectUri: string = new URLCombiner(nativeWindowService.nativeWindow.origin, encodeURIComponent(routerStub.url.split('?')[0])).toString(); 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|)', { diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index f944d53b46..0ceb851e2c 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -1,14 +1,12 @@ /* eslint-disable max-classes-per-file */ import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; -import { Operation, RemoveOperation, ReplaceOperation } from 'fast-json-patch'; +import { AddOperation, Operation, RemoveOperation, ReplaceOperation } from 'fast-json-patch'; import { combineLatest, Observable } from 'rxjs'; import { find, map, switchMap } from 'rxjs/operators'; - -import { environment } from '../../../environments/environment'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { dataService } from '../cache/builders/build-decorators'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -24,7 +22,6 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NoContent } from '../shared/NoContent.model'; import { getAllCompletedRemoteData, - getFinishedRemoteData, getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../shared/operators'; @@ -37,6 +34,8 @@ 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'; +import { NativeWindowRef, NativeWindowService } from '../services/window.service'; +import { URLCombiner } from '../url-combiner/url-combiner'; /** * A private DataService implementation to delegate specific methods to. @@ -70,6 +69,7 @@ export class ResearcherProfileService { protected responseMsToLive: number = 10 * 1000; constructor( + @Inject(NativeWindowService) protected _window: NativeWindowRef, protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected objectCache: ObjectCacheService, @@ -202,18 +202,38 @@ export class ResearcherProfileService { } /** - * If the given item represents a profile unlink it from ORCID. + * Perform a link operation to ORCID profile. + * + * @param person The person item related to the researcher profile + * @param code The auth-code received from orcid */ - public unlinkOrcid(item: Item): Observable> { + public linkOrcidByItem(person: Item, code: string): Observable> { + const operations: AddOperation[] = [{ + path: '/orcid', + op: 'add', + value: code + }]; + + return this.findById(person.firstMetadata('dspace.object.owner').authority).pipe( + getFirstCompletedRemoteData(), + switchMap((profileRD) => this.updateByOrcidOperations(profileRD.payload, operations)) + ); + } + + /** + * Perform unlink operation from ORCID profile. + * + * @param person The person item related to the researcher profile + */ + public unlinkOrcidByItem(person: Item): Observable> { const operations: RemoveOperation[] = [{ path:'/orcid', op:'remove' }]; - return this.findById(item.firstMetadata('dspace.object.owner').authority).pipe( + return this.findById(person.firstMetadata('dspace.object.owner').authority).pipe( getFirstCompletedRemoteData(), - switchMap((profileRD) => this.dataService.patch(profileRD.payload, operations)), - getFinishedRemoteData() + switchMap((profileRD) => this.updateByOrcidOperations(profileRD.payload, operations)) ); } @@ -229,7 +249,9 @@ export class ResearcherProfileService { this.configurationService.findByPropertyName('orcid.scope').pipe(getFirstSucceededRemoteDataPayload())] ).pipe( map(([authorizeUrl, clientId, scopes]) => { - const redirectUri = environment.rest.baseUrl + '/api/eperson/orcid/' + profile.id + '/?url=' + encodeURIComponent(this.router.url); + console.log(this._window.nativeWindow.origin, this.router.url); + const redirectUri = new URLCombiner(this._window.nativeWindow.origin, encodeURIComponent(this.router.url.split('?')[0])); + console.log(redirectUri.toString()); 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-routing.module.ts b/src/app/item-page/item-page-routing.module.ts index cf75ebfcd8..add2c3d768 100644 --- a/src/app/item-page/item-page-routing.module.ts +++ b/src/app/item-page/item-page-routing.module.ts @@ -14,7 +14,9 @@ import { ThemedItemPageComponent } from './simple/themed-item-page.component'; import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component'; import { MenuItemType } from '../shared/menu/menu-item-type.model'; 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 { + 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'; @@ -56,7 +58,7 @@ import { OrcidPageGuard } from './orcid-page/orcid-page.guard'; { path: ORCID_PATH, component: OrcidPageComponent, - canActivate: [OrcidPageGuard] + canActivate: [AuthenticatedGuard, OrcidPageGuard] } ], data: { 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 b15b23d1e0..e57ce33008 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,19 +1,12 @@ -
- - - -
- -
-
-
-
+
+

{{'person.orcid.registry.auth' | translate}}

+
-
-
+
+
{{ 'person.page.orcid.granted-authorizations'| translate }}
@@ -27,7 +20,7 @@
-
+
{{ 'person.page.orcid.missing-authorizations'| translate }}
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 index a2ec1cf9b1..6b5f2d8593 100644 --- 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 @@ -119,7 +119,7 @@ describe('OrcidAuthComponent test suite', () => { isLinkedToOrcid: jasmine.createSpy('isLinkedToOrcid'), onlyAdminCanDisconnectProfileFromOrcid: jasmine.createSpy('onlyAdminCanDisconnectProfileFromOrcid'), ownerCanDisconnectProfileFromOrcid: jasmine.createSpy('ownerCanDisconnectProfileFromOrcid'), - unlinkOrcid: jasmine.createSpy('unlinkOrcid') + unlinkOrcidByItem: jasmine.createSpy('unlinkOrcidByItem') }); void TestBed.configureTestingModule({ @@ -200,7 +200,7 @@ describe('OrcidAuthComponent test suite', () => { describe('and unlink is successfully', () => { beforeEach(waitForAsync(() => { comp.item = mockItemLinkedToOrcid; - researcherProfileService.unlinkOrcid.and.returnValue(createSuccessfulRemoteDataObject$(new ResearcherProfile())); + researcherProfileService.unlinkOrcidByItem.and.returnValue(createSuccessfulRemoteDataObject$(new ResearcherProfile())); spyOn(comp.unlink, 'emit'); fixture.detectChanges(); })); @@ -217,7 +217,7 @@ describe('OrcidAuthComponent test suite', () => { describe('and unlink is failed', () => { beforeEach(waitForAsync(() => { comp.item = mockItemLinkedToOrcid; - researcherProfileService.unlinkOrcid.and.returnValue(createFailedRemoteDataObject$()); + researcherProfileService.unlinkOrcidByItem.and.returnValue(createFailedRemoteDataObject$()); fixture.detectChanges(); })); 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 ed8b1fd3d9..b052fdedd8 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 @@ -10,6 +10,7 @@ import { Item } from '../../../core/shared/item.model'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { RemoteData } from '../../../core/data/remote-data'; import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; @Component({ selector: 'ds-orcid-auth', @@ -169,7 +170,9 @@ export class OrcidAuthComponent implements OnInit, OnChanges { */ unlinkOrcid(): void { this.unlinkProcessing.next(true); - this.researcherProfileService.unlinkOrcid(this.item).subscribe((remoteData: RemoteData) => { + this.researcherProfileService.unlinkOrcidByItem(this.item).pipe( + getFirstCompletedRemoteData() + ).subscribe((remoteData: RemoteData) => { this.unlinkProcessing.next(false); if (remoteData.isSuccess) { this.notificationsService.success(this.translateService.get('person.page.orcid.unlink.success')); 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 7fb1ae3a71..3928642423 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 @@ -
+
- - - + +
+ {{'person.page.orcid.link.error.message' | translate}} +
+ + + + + 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 9210d56865..a4af5edf5c 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,4 +1,4 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; @@ -13,11 +13,16 @@ 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 { + createFailedRemoteDataObject$, + 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'; import { ItemDataService } from '../../core/data/item-data.service'; +import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model'; describe('OrcidPageComponent test suite', () => { let comp: OrcidPageComponent; @@ -29,7 +34,21 @@ describe('OrcidPageComponent test suite', () => { let itemDataService: jasmine.SpyObj; let researcherProfileService: jasmine.SpyObj; + 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 mockItem: Item = Object.assign(new Item(), { + id: 'test-id', bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), metadata: { 'dc.title': [ @@ -41,6 +60,7 @@ describe('OrcidPageComponent test suite', () => { } }); const mockItemLinkedToOrcid: Item = Object.assign(new Item(), { + id: 'test-id', bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), metadata: { 'dc.title': [ @@ -66,12 +86,11 @@ describe('OrcidPageComponent test suite', () => { dso: createSuccessfulRemoteDataObject(mockItem), }; - routeStub = Object.assign(new ActivatedRouteStub(), { - data: observableOf(routeData) - }); + routeStub = new ActivatedRouteStub({}, routeData); researcherProfileService = jasmine.createSpyObj('researcherProfileService', { - isLinkedToOrcid: jasmine.createSpy('isLinkedToOrcid') + isLinkedToOrcid: jasmine.createSpy('isLinkedToOrcid'), + linkOrcidByItem: jasmine.createSpy('linkOrcidByItem'), }); itemDataService = jasmine.createSpyObj('ItemDataService', { @@ -94,6 +113,7 @@ describe('OrcidPageComponent test suite', () => { { provide: ResearcherProfileService, useValue: researcherProfileService }, { provide: AuthService, useValue: authService }, { provide: ItemDataService, useValue: itemDataService }, + { provide: PLATFORM_ID, useValue: 'browser' }, ], schemas: [NO_ERRORS_SCHEMA] @@ -105,27 +125,96 @@ describe('OrcidPageComponent test suite', () => { 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); + describe('whn has no query param', () => { + beforeEach(waitForAsync(() => { + fixture.detectChanges(); + })); + + it('should create', () => { + const btn = fixture.debugElement.queryAll(By.css('[data-test="back-button"]')); + const auth = fixture.debugElement.query(By.css('[data-test="orcid-auth"]')); + const settings = fixture.debugElement.query(By.css('[data-test="orcid-sync-setting"]')); + expect(comp).toBeTruthy(); + expect(btn.length).toBe(1); + expect(auth).toBeTruthy(); + expect(settings).toBeTruthy(); + expect(comp.itemId).toBe('test-id'); + }); + + it('should call isLinkedToOrcid', () => { + comp.isLinkedToOrcid(); + + 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); + })); }); - it('should call isLinkedToOrcid', () => { - comp.isLinkedToOrcid(); + describe('when query param contains orcid code', () => { + beforeEach(waitForAsync(() => { + spyOn(comp, 'updateItem').and.callThrough(); + routeStub.testParams = { + code: 'orcid-code' + }; + })); - expect(researcherProfileService.isLinkedToOrcid).toHaveBeenCalledWith(comp.item.value); + describe('and linking to orcid profile is successfully', () => { + beforeEach(waitForAsync(() => { + researcherProfileService.linkOrcidByItem.and.returnValue(createSuccessfulRemoteDataObject$(mockResearcherProfile)); + itemDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockItemLinkedToOrcid)); + fixture.detectChanges(); + })); + + it('should call linkOrcidByItem', () => { + expect(researcherProfileService.linkOrcidByItem).toHaveBeenCalledWith(mockItem, 'orcid-code'); + expect(comp.updateItem).toHaveBeenCalled(); + }); + + it('should create', () => { + const btn = fixture.debugElement.queryAll(By.css('[data-test="back-button"]')); + const auth = fixture.debugElement.query(By.css('[data-test="orcid-auth"]')); + const settings = fixture.debugElement.query(By.css('[data-test="orcid-sync-setting"]')); + expect(comp).toBeTruthy(); + expect(btn.length).toBe(1); + expect(auth).toBeTruthy(); + expect(settings).toBeTruthy(); + expect(comp.itemId).toBe('test-id'); + }); + + }); + + describe('and linking to orcid profile is failed', () => { + beforeEach(waitForAsync(() => { + researcherProfileService.linkOrcidByItem.and.returnValue(createFailedRemoteDataObject$()); + itemDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockItemLinkedToOrcid)); + fixture.detectChanges(); + })); + + it('should call linkOrcidByItem', () => { + expect(researcherProfileService.linkOrcidByItem).toHaveBeenCalledWith(mockItem, 'orcid-code'); + expect(comp.updateItem).not.toHaveBeenCalled(); + }); + + it('should create', () => { + const btn = fixture.debugElement.queryAll(By.css('[data-test="back-button"]')); + const auth = fixture.debugElement.query(By.css('[data-test="orcid-auth"]')); + const settings = fixture.debugElement.query(By.css('[data-test="orcid-sync-setting"]')); + const error = fixture.debugElement.query(By.css('[data-test="error-box"]')); + expect(comp).toBeTruthy(); + expect(btn.length).toBe(1); + expect(error).toBeTruthy(); + expect(auth).toBeFalsy(); + expect(settings).toBeFalsy(); + }); + + }); }); - - 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 be4e0e7945..6c1de3d100 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,8 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core'; +import { ActivatedRoute, ParamMap, Router } from '@angular/router'; -import { BehaviorSubject } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest } from 'rxjs'; +import { map, take } from 'rxjs/operators'; import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; @@ -12,6 +12,9 @@ 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'; +import { isNotEmpty } from '../../shared/empty.util'; +import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model'; +import { isPlatformBrowser } from '@angular/common'; /** * A component that represents the orcid settings page @@ -23,12 +26,28 @@ import { ItemDataService } from '../../core/data/item-data.service'; }) export class OrcidPageComponent implements OnInit { + /** + * A boolean representing if the connection operation with orcid profile is in progress + */ + connectionStatus: BehaviorSubject = new BehaviorSubject(false); + /** * The item for which showing the orcid settings */ item: BehaviorSubject = new BehaviorSubject(null); + /** + * The item id for which showing the orcid settings + */ + itemId: string; + + /** + * A boolean representing if the connection operation with orcid profile is in progress + */ + processingConnection: BehaviorSubject = new BehaviorSubject(true); + constructor( + @Inject(PLATFORM_ID) private platformId: any, private authService: AuthService, private itemService: ItemDataService, private researcherProfileService: ResearcherProfileService, @@ -41,13 +60,33 @@ export class OrcidPageComponent implements OnInit { * 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.next(item); - }); + if (isPlatformBrowser(this.platformId)) { + const codeParam$ = this.route.queryParamMap.pipe( + take(1), + map((paramMap: ParamMap) => paramMap.get('code')), + ); + + const item$ = this.route.data.pipe( + map((data) => data.dso as RemoteData), + redirectOn4xx(this.router, this.authService), + getFirstSucceededRemoteDataPayload() + ); + + combineLatest([codeParam$, item$]).subscribe(([codeParam, item]) => { + this.itemId = item.id; + /** + * Check if code is present in the query param. If so it means this page is loaded after attempting to + * link the person to the ORCID profile, otherwise the person is already linked to ORCID profile + */ + if (isNotEmpty(codeParam)) { + this.linkProfileToOrcid(item, codeParam); + } else { + this.item.next(item); + this.processingConnection.next(false); + this.connectionStatus.next(true); + } + }); + } } /** @@ -70,7 +109,7 @@ export class OrcidPageComponent implements OnInit { * Retrieve the updated profile item */ updateItem(): void { - this.itemService.findById(this.item.value.id, false).pipe( + this.itemService.findById(this.itemId, false).pipe( getFirstCompletedRemoteData() ).subscribe((itemRD: RemoteData) => { if (itemRD.hasSucceeded) { @@ -79,4 +118,28 @@ export class OrcidPageComponent implements OnInit { }); } + /** + * Link person item to ORCID profile by using the code received after redirect from ORCID. + * + * @param person The person item to link to ORCID profile + * @param code The auth-code received from ORCID + */ + private linkProfileToOrcid(person: Item, code: string) { + this.researcherProfileService.linkOrcidByItem(person, code).pipe( + getFirstCompletedRemoteData() + ).subscribe((profileRD: RemoteData) => { + this.processingConnection.next(false); + if (profileRD.hasSucceeded) { + this.connectionStatus.next(true); + this.updateItem(); + } else { + this.item.next(person); + this.connectionStatus.next(false); + } + + // update route removing the code from query params + const redirectUrl = this.router.url.split('?')[0]; + this.router.navigate([redirectUrl]); + }); + } } 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 75038d5973..ee9a15268a 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 @@ -1,98 +1,106 @@ -
- - - -
-
-
-
-
-
{{ 'person.page.orcid.synchronization-mode'| translate }}
-
-
-
- - {{ 'person.page.orcid.synchronization-mode-message' | translate}} - -
-
- - -
-
-
-
+
+

{{'person.orcid.sync.setting' | translate}}

+ +
+
+
+
{{ 'person.page.orcid.synchronization-mode'| translate }}
+
+
+
+ + {{ 'person.page.orcid.synchronization-mode-message' | 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.synchronization-mode-publication-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}} + +
+
+
+ + +
+
+
+
+
+
+
+
+
+ +
+
+
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 03a07beb8a..305900ae33 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 @@ -2,5 +2,5 @@ [ngbTooltip]="'item.page.orcid.tooltip' | translate" [routerLink]="[pageRoute, 'orcid']" class="btn btn-dark btn-sm" - role="button" >{{'item.page.orcid.title' | translate}} + role="button" >
diff --git a/src/app/shared/mocks/router.mock.ts b/src/app/shared/mocks/router.mock.ts index eb260af6b4..98a63363b6 100644 --- a/src/app/shared/mocks/router.mock.ts +++ b/src/app/shared/mocks/router.mock.ts @@ -29,4 +29,8 @@ export class RouterMock { createUrlTree(commands, navExtras = {}) { return {}; } + + get url() { + return this.routerState.snapshot.url; + } } 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 @@ -