[CST-5668] Implement orcid linking by redirect on front-end

This commit is contained in:
Giuseppe Digilio
2022-06-14 17:51:37 +02:00
parent a0ea69aafa
commit eda4797034
10 changed files with 289 additions and 62 deletions

View File

@@ -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<string>[] = [{
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|)', {

View File

@@ -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<RemoteData<ResearcherProfile>> {
public linkOrcidByItem(person: Item, code: string): Observable<RemoteData<ResearcherProfile>> {
const operations: AddOperation<string>[] = [{
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<RemoteData<ResearcherProfile>> {
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(' ');
}));

View File

@@ -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: {

View File

@@ -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();
}));

View File

@@ -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<ResearcherProfile>) => {
this.researcherProfileService.unlinkOrcidByItem(this.item).pipe(
getFirstCompletedRemoteData()
).subscribe((remoteData: RemoteData<ResearcherProfile>) => {
this.unlinkProcessing.next(false);
if (remoteData.isSuccess) {
this.notificationsService.success(this.translateService.get('person.page.orcid.unlink.success'));

View File

@@ -1,4 +1,4 @@
<div *ngIf="(item | async)" class="container">
<div *ngIf="!(processingConnection | async) && (item | async)" class="container">
<div class="button-row bottom mb-3">
<div class="text-right">
<a [routerLink]="getItemPage()" role="button" class="btn btn-outline-secondary" data-test="back-button">
@@ -8,5 +8,11 @@
</div>
</div>
<ds-orcid-auth *ngIf="(item | async)" [item]="(item | async)" (unlink)="updateItem()"></ds-orcid-auth>
<ds-orcid-sync-setting *ngIf="(item | async) && isLinkedToOrcid()" [item]="(item | async)"></ds-orcid-sync-setting>
<ds-loading *ngIf="(processingConnection | async)" [message]="'person.page.orcid.link.processing' | translate"></ds-loading>
<div class="container" *ngIf="!(processingConnection | async) && !(connectionStatus | async)" data-test="error-box">
<ds-alert [type]="'alert-danger'">{{'person.page.orcid.link.error.message' | translate}}</ds-alert>
</div>
<ng-container *ngIf="!(processingConnection | async) && (item | async) && (connectionStatus | async)" >
<ds-orcid-auth [item]="(item | async)" (unlink)="updateItem()" data-test="orcid-auth"></ds-orcid-auth>
<ds-orcid-sync-setting *ngIf="isLinkedToOrcid()" [item]="(item | async)" data-test="orcid-sync-setting"></ds-orcid-sync-setting>
</ng-container>

View File

@@ -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<ItemDataService>;
let researcherProfileService: jasmine.SpyObj<ResearcherProfileService>;
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,13 +125,22 @@ describe('OrcidPageComponent test suite', () => {
fixture = TestBed.createComponent(OrcidPageComponent);
comp = fixture.componentInstance;
authService.isAuthenticated.and.returnValue(observableOf(true));
}));
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', () => {
@@ -127,5 +156,65 @@ describe('OrcidPageComponent test suite', () => {
expect(comp.item.value).toEqual(mockItemLinkedToOrcid);
}));
});
describe('when query param contains orcid code', () => {
beforeEach(waitForAsync(() => {
spyOn(comp, 'updateItem').and.callThrough();
routeStub.testParams = {
code: 'orcid-code'
};
}));
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();
});
});
});
});

View File

@@ -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<boolean> = new BehaviorSubject<boolean>(false);
/**
* The item for which showing the orcid settings
*/
item: BehaviorSubject<Item> = new BehaviorSubject<Item>(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<boolean> = new BehaviorSubject<boolean>(true);
constructor(
@Inject(PLATFORM_ID) private platformId: any,
private authService: AuthService,
private itemService: ItemDataService,
private researcherProfileService: ResearcherProfileService,
@@ -41,14 +60,34 @@ export class OrcidPageComponent implements OnInit {
* Retrieve the item for which showing the orcid settings
*/
ngOnInit(): void {
this.route.data.pipe(
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<Item>),
redirectOn4xx(this.router, this.authService),
getFirstSucceededRemoteDataPayload()
).subscribe((item) => {
);
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);
}
});
}
}
/**
* Check if the current item is linked to an ORCID profile.
@@ -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<Item>) => {
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<ResearcherProfile>) => {
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]);
});
}
}

View File

@@ -29,4 +29,8 @@ export class RouterMock {
createUrlTree(commands, navExtras = {}) {
return {};
}
get url() {
return this.routerState.snapshot.url;
}
}

View File

@@ -4491,6 +4491,10 @@
"person.page.orcid.link": "Connect to ORCID ID",
"person.page.orcid.link.processing": "Linking profile to ORCID...",
"person.page.orcid.link.error.message": "Something went wrong while connecting the profile with ORCID. If the problem persists, contact the administrator.",
"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",