mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
[CST-5668] Implement orcid linking by redirect on front-end
This commit is contained in:
@@ -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|)', {
|
||||
|
@@ -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(' ');
|
||||
}));
|
||||
|
@@ -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: {
|
||||
|
@@ -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();
|
||||
}));
|
||||
|
||||
|
@@ -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'));
|
||||
|
@@ -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>
|
||||
|
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -29,4 +29,8 @@ export class RouterMock {
|
||||
createUrlTree(commands, navExtras = {}) {
|
||||
return {};
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this.routerState.snapshot.url;
|
||||
}
|
||||
}
|
||||
|
@@ -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",
|
||||
|
Reference in New Issue
Block a user