mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-11 20:13:07 +00:00
95632: Refactor orcid/claim person buttons to dso-menu-resolver
This commit is contained in:
@@ -3,8 +3,8 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { Operation, ReplaceOperation } from 'fast-json-patch';
|
import { Operation, ReplaceOperation } from 'fast-json-patch';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { find, map } from 'rxjs/operators';
|
import { find, map, mergeMap } from 'rxjs/operators';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
@@ -130,6 +130,24 @@ export class ResearcherProfileDataService extends IdentifiableDataService<Resear
|
|||||||
return this.rdbService.buildFromRequestUUID(requestId, followLink('item'));
|
return this.rdbService.buildFromRequestUUID(requestId, followLink('item'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a researcher profile starting from an external source URI and returns the related item's ID
|
||||||
|
* Emits null if the researcher profile doesn't exist after sending out the request
|
||||||
|
* @param sourceUri
|
||||||
|
*/
|
||||||
|
createFromExternalSourceAndReturnRelatedItemId(sourceUri: string): Observable<string> {
|
||||||
|
return this.createFromExternalSource(sourceUri).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
mergeMap((rd: RemoteData<ResearcherProfile>) => {
|
||||||
|
if (rd.hasSucceeded) {
|
||||||
|
return this.findRelatedItemId(rd.payload);
|
||||||
|
} else {
|
||||||
|
return observableOf(null);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new object on the server, and store the response in the object cache
|
* Create a new object on the server, and store the response in the object cache
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { MenuServiceStub } from '../testing/menu-service.stub';
|
import { MenuServiceStub } from '../testing/menu-service.stub';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { AdminSidebarComponent } from '../../admin/admin-sidebar/admin-sidebar.component';
|
import { AdminSidebarComponent } from '../../admin/admin-sidebar/admin-sidebar.component';
|
||||||
@@ -18,6 +18,8 @@ import { MenuID } from '../menu/menu-id.model';
|
|||||||
import { MenuItemType } from '../menu/menu-item-type.model';
|
import { MenuItemType } from '../menu/menu-item-type.model';
|
||||||
import { TextMenuItemModel } from '../menu/menu-item/models/text.model';
|
import { TextMenuItemModel } from '../menu/menu-item/models/text.model';
|
||||||
import { LinkMenuItemModel } from '../menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../menu/menu-item/models/link.model';
|
||||||
|
import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||||
|
import { NotificationsService } from '../notifications/notifications.service';
|
||||||
|
|
||||||
describe('DSOEditMenuResolver', () => {
|
describe('DSOEditMenuResolver', () => {
|
||||||
|
|
||||||
@@ -31,6 +33,9 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
let menuService;
|
let menuService;
|
||||||
let authorizationService;
|
let authorizationService;
|
||||||
let dsoVersioningModalService;
|
let dsoVersioningModalService;
|
||||||
|
let researcherProfileService;
|
||||||
|
let notificationsService;
|
||||||
|
let translate;
|
||||||
|
|
||||||
const route = {
|
const route = {
|
||||||
data: {
|
data: {
|
||||||
@@ -99,6 +104,16 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
getVersioningTooltipMessage: observableOf('message'),
|
getVersioningTooltipMessage: observableOf('message'),
|
||||||
openCreateVersionModal: {}
|
openCreateVersionModal: {}
|
||||||
});
|
});
|
||||||
|
researcherProfileService = jasmine.createSpyObj('researcherProfileService', {
|
||||||
|
createFromExternalSourceAndReturnRelatedItemId: observableOf('mock-id'),
|
||||||
|
});
|
||||||
|
translate = jasmine.createSpyObj('translate', {
|
||||||
|
get: observableOf('translated-message'),
|
||||||
|
});
|
||||||
|
notificationsService = jasmine.createSpyObj('notificationsService', {
|
||||||
|
success: {},
|
||||||
|
error: {},
|
||||||
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
|
||||||
@@ -108,6 +123,9 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
{provide: MenuService, useValue: menuService},
|
{provide: MenuService, useValue: menuService},
|
||||||
{provide: AuthorizationDataService, useValue: authorizationService},
|
{provide: AuthorizationDataService, useValue: authorizationService},
|
||||||
{provide: DsoVersioningModalService, useValue: dsoVersioningModalService},
|
{provide: DsoVersioningModalService, useValue: dsoVersioningModalService},
|
||||||
|
{provide: ResearcherProfileDataService, useValue: researcherProfileService},
|
||||||
|
{provide: TranslateService, useValue: translate},
|
||||||
|
{provide: NotificationsService, useValue: notificationsService},
|
||||||
{
|
{
|
||||||
provide: NgbModal, useValue: {
|
provide: NgbModal, useValue: {
|
||||||
open: () => {/*comment*/
|
open: () => {/*comment*/
|
||||||
@@ -194,17 +212,30 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('getDsoMenus', () => {
|
describe('getDsoMenus', () => {
|
||||||
it('should return as first part the item version list ', (done) => {
|
it('should return as first part the item version, orcid and claim list ', (done) => {
|
||||||
const result = resolver.getDsoMenus(testObject, route, state);
|
const result = resolver.getDsoMenus(testObject, route, state);
|
||||||
result[0].subscribe((menuList) => {
|
result[0].subscribe((menuList) => {
|
||||||
expect(menuList.length).toEqual(1);
|
expect(menuList.length).toEqual(3);
|
||||||
expect(menuList[0].id).toEqual('version-dso');
|
expect(menuList[0].id).toEqual('orcid-dso');
|
||||||
expect(menuList[0].active).toEqual(false);
|
expect(menuList[0].active).toEqual(false);
|
||||||
expect(menuList[0].visible).toEqual(true);
|
// Visible should be false due to the item not being of type person
|
||||||
expect(menuList[0].model.type).toEqual(MenuItemType.ONCLICK);
|
expect(menuList[0].visible).toEqual(false);
|
||||||
expect((menuList[0].model as TextMenuItemModel).text).toEqual('message');
|
expect(menuList[0].model.type).toEqual(MenuItemType.LINK);
|
||||||
expect(menuList[0].model.disabled).toEqual(false);
|
|
||||||
expect(menuList[0].icon).toEqual('code-branch');
|
expect(menuList[1].id).toEqual('version-dso');
|
||||||
|
expect(menuList[1].active).toEqual(false);
|
||||||
|
expect(menuList[1].visible).toEqual(true);
|
||||||
|
expect(menuList[1].model.type).toEqual(MenuItemType.ONCLICK);
|
||||||
|
expect((menuList[1].model as TextMenuItemModel).text).toEqual('message');
|
||||||
|
expect(menuList[1].model.disabled).toEqual(false);
|
||||||
|
expect(menuList[1].icon).toEqual('code-branch');
|
||||||
|
|
||||||
|
expect(menuList[2].id).toEqual('claim-dso');
|
||||||
|
expect(menuList[2].active).toEqual(false);
|
||||||
|
// Visible should be false due to the item not being of type person
|
||||||
|
expect(menuList[2].visible).toEqual(false);
|
||||||
|
expect(menuList[2].model.type).toEqual(MenuItemType.ONCLICK);
|
||||||
|
expect((menuList[2].model as TextMenuItemModel).text).toEqual('item.page.claim.button');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -9,15 +9,18 @@ import { Item } from '../../core/shared/item.model';
|
|||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model';
|
import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model';
|
||||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, mergeMap, switchMap, take } from 'rxjs/operators';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||||
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
|
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
|
||||||
import { hasNoValue, hasValue } from '../empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from '../empty.util';
|
||||||
import { MenuID } from '../menu/menu-id.model';
|
import { MenuID } from '../menu/menu-id.model';
|
||||||
import { MenuItemType } from '../menu/menu-item-type.model';
|
import { MenuItemType } from '../menu/menu-item-type.model';
|
||||||
import { MenuSection } from '../menu/menu-section.model';
|
import { MenuSection } from '../menu/menu-section.model';
|
||||||
import { getDSORoute } from '../../app-routing-paths';
|
import { getDSORoute } from '../../app-routing-paths';
|
||||||
|
import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||||
|
import { NotificationsService } from '../notifications/notifications.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the menus for the dspace object pages
|
* Creates the menus for the dspace object pages
|
||||||
@@ -33,6 +36,9 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
|||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected modalService: NgbModal,
|
protected modalService: NgbModal,
|
||||||
protected dsoVersioningModalService: DsoVersioningModalService,
|
protected dsoVersioningModalService: DsoVersioningModalService,
|
||||||
|
protected researcherProfileService: ResearcherProfileDataService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected translate: TranslateService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +102,7 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
|||||||
link: new URLCombiner(getDSORoute(dso), 'edit', 'metadata').toString()
|
link: new URLCombiner(getDSORoute(dso), 'edit', 'metadata').toString()
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
icon: 'pencil-alt',
|
icon: 'pencil-alt',
|
||||||
index: 1
|
index: 2
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
@@ -104,17 +110,32 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get item sepcific menus
|
* Get item specific menus
|
||||||
*/
|
*/
|
||||||
protected getItemMenu(dso): Observable<MenuSection[]> {
|
protected getItemMenu(dso): Observable<MenuSection[]> {
|
||||||
if (dso instanceof Item) {
|
if (dso instanceof Item) {
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
|
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
|
||||||
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
|
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
|
||||||
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create')
|
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create'),
|
||||||
|
this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, dso.self),
|
||||||
|
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, dso.self),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([canCreateVersion, disableVersioning, versionTooltip]) => {
|
map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem]) => {
|
||||||
|
const isPerson = this.getDsoType(dso) === 'person';
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
id: 'orcid-dso',
|
||||||
|
active: false,
|
||||||
|
visible: isPerson && canSynchronizeWithOrcid,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.LINK,
|
||||||
|
text: 'item.page.orcid.tooltip',
|
||||||
|
link: new URLCombiner(getDSORoute(dso), 'orcid').toString()
|
||||||
|
} as LinkMenuItemModel,
|
||||||
|
icon: 'orcid fab fa-lg',
|
||||||
|
index: 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'version-dso',
|
id: 'version-dso',
|
||||||
active: false,
|
active: false,
|
||||||
@@ -128,7 +149,21 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
|||||||
}
|
}
|
||||||
} as OnClickMenuItemModel,
|
} as OnClickMenuItemModel,
|
||||||
icon: 'code-branch',
|
icon: 'code-branch',
|
||||||
index: 0
|
index: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'claim-dso',
|
||||||
|
active: false,
|
||||||
|
visible: isPerson && canClaimItem,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.ONCLICK,
|
||||||
|
text: 'item.page.claim.button',
|
||||||
|
function: () => {
|
||||||
|
this.claimResearcher(dso);
|
||||||
|
}
|
||||||
|
} as OnClickMenuItemModel,
|
||||||
|
icon: 'hand-paper',
|
||||||
|
index: 3
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
@@ -138,6 +173,26 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claim a researcher by creating a profile
|
||||||
|
* Shows notifications and/or hides the menu section on success/error
|
||||||
|
*/
|
||||||
|
protected claimResearcher(dso) {
|
||||||
|
this.researcherProfileService.createFromExternalSourceAndReturnRelatedItemId(dso.self)
|
||||||
|
.subscribe((id: string) => {
|
||||||
|
if (isNotEmpty(id)) {
|
||||||
|
this.notificationsService.success(this.translate.get('researcherprofile.success.claim.title'),
|
||||||
|
this.translate.get('researcherprofile.success.claim.body'));
|
||||||
|
this.authorizationService.invalidateAuthorizationsRequestCache();
|
||||||
|
this.menuService.hideMenuSection(MenuID.DSO_EDIT, 'claim-dso-' + dso.uuid);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(
|
||||||
|
this.translate.get('researcherprofile.error.claim.title'),
|
||||||
|
this.translate.get('researcherprofile.error.claim.body'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the dso or entity type for an object to be used in generic messages
|
* Retrieve the dso or entity type for an object to be used in generic messages
|
||||||
*/
|
*/
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
<a *ngIf="isAuthorized | async"
|
|
||||||
[ngbTooltip]="'item.page.orcid.tooltip' | translate"
|
|
||||||
[routerLink]="[pageRoute, 'orcid']"
|
|
||||||
class="btn btn-dark btn-sm"
|
|
||||||
role="button" ><i class="fab fa-orcid fa-lg"></i>
|
|
||||||
</a>
|
|
@@ -1,76 +0,0 @@
|
|||||||
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<DsoPageOrcidButtonComponent>;
|
|
||||||
|
|
||||||
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.CanSynchronizeWithORCID, 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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,39 +0,0 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { take } from 'rxjs/operators';
|
|
||||||
|
|
||||||
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',
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the current user is authorized to edit the DSpaceObject
|
|
||||||
*/
|
|
||||||
isAuthorized: BehaviorSubject<boolean> = 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
<button *ngIf="(isClaimable() | async)"
|
|
||||||
[ngbTooltip]="'item.page.claim.tooltip' | translate"
|
|
||||||
class="edit-button btn btn-dark btn-sm"
|
|
||||||
data-test="item-claim"
|
|
||||||
(click)="claim()">
|
|
||||||
{{'item.page.claim.button' | translate }}
|
|
||||||
</button>
|
|
@@ -1,186 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
import { PersonPageClaimButtonComponent } from './person-page-claim-button.component';
|
|
||||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
|
||||||
import { NotificationsService } from '../../notifications/notifications.service';
|
|
||||||
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
|
||||||
import { TranslateLoaderMock } from '../../mocks/translate-loader.mock';
|
|
||||||
import { ResearcherProfileDataService } from '../../../core/profile/researcher-profile-data.service';
|
|
||||||
import { RouteService } from '../../../core/services/route.service';
|
|
||||||
import { routeServiceStub } from '../../testing/route-service.stub';
|
|
||||||
import { Item } from '../../../core/shared/item.model';
|
|
||||||
import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model';
|
|
||||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
|
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
|
||||||
|
|
||||||
describe('PersonPageClaimButtonComponent', () => {
|
|
||||||
let scheduler: TestScheduler;
|
|
||||||
let component: PersonPageClaimButtonComponent;
|
|
||||||
let fixture: ComponentFixture<PersonPageClaimButtonComponent>;
|
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
|
||||||
metadata: {
|
|
||||||
'person.email': [
|
|
||||||
{
|
|
||||||
language: 'en_US',
|
|
||||||
value: 'fake@email.com'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'person.birthDate': [
|
|
||||||
{
|
|
||||||
language: 'en_US',
|
|
||||||
value: '1993'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'person.jobTitle': [
|
|
||||||
{
|
|
||||||
language: 'en_US',
|
|
||||||
value: 'Developer'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'person.familyName': [
|
|
||||||
{
|
|
||||||
language: 'en_US',
|
|
||||||
value: 'Doe'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'person.givenName': [
|
|
||||||
{
|
|
||||||
language: 'en_US',
|
|
||||||
value: 'John'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
_links: {
|
|
||||||
self: {
|
|
||||||
href: 'item-href'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 notificationsService = new NotificationsServiceStub();
|
|
||||||
|
|
||||||
const authorizationDataService = jasmine.createSpyObj('authorizationDataService', {
|
|
||||||
isAuthorized: jasmine.createSpy('isAuthorized')
|
|
||||||
});
|
|
||||||
|
|
||||||
const researcherProfileService = jasmine.createSpyObj('researcherProfileService', {
|
|
||||||
createFromExternalSource: jasmine.createSpy('createFromExternalSource'),
|
|
||||||
findRelatedItemId: jasmine.createSpy('findRelatedItemId'),
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
TranslateModule.forRoot({
|
|
||||||
loader: {
|
|
||||||
provide: TranslateLoader,
|
|
||||||
useClass: TranslateLoaderMock
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
declarations: [PersonPageClaimButtonComponent],
|
|
||||||
providers: [
|
|
||||||
{ provide: AuthorizationDataService, useValue: authorizationDataService },
|
|
||||||
{ provide: NotificationsService, useValue: notificationsService },
|
|
||||||
{ provide: ResearcherProfileDataService, useValue: researcherProfileService },
|
|
||||||
{ provide: RouteService, useValue: routeServiceStub },
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(PersonPageClaimButtonComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
component.object = mockItem;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when item can be claimed', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
authorizationDataService.isAuthorized.and.returnValue(observableOf(true));
|
|
||||||
researcherProfileService.createFromExternalSource.calls.reset();
|
|
||||||
researcherProfileService.findRelatedItemId.calls.reset();
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create claim button', () => {
|
|
||||||
const btn = fixture.debugElement.query(By.css('[data-test="item-claim"]'));
|
|
||||||
expect(btn).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('claim', () => {
|
|
||||||
describe('when successfully', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
scheduler = getTestScheduler();
|
|
||||||
researcherProfileService.createFromExternalSource.and.returnValue(createSuccessfulRemoteDataObject$(mockResearcherProfile));
|
|
||||||
researcherProfileService.findRelatedItemId.and.returnValue(observableOf('test-id'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display success notification', () => {
|
|
||||||
scheduler.schedule(() => component.claim());
|
|
||||||
scheduler.flush();
|
|
||||||
|
|
||||||
expect(researcherProfileService.findRelatedItemId).toHaveBeenCalled();
|
|
||||||
expect(notificationsService.success).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when not successfully', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
scheduler = getTestScheduler();
|
|
||||||
researcherProfileService.createFromExternalSource.and.returnValue(createFailedRemoteDataObject$());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display success notification', () => {
|
|
||||||
scheduler.schedule(() => component.claim());
|
|
||||||
scheduler.flush();
|
|
||||||
|
|
||||||
expect(researcherProfileService.findRelatedItemId).not.toHaveBeenCalled();
|
|
||||||
expect(notificationsService.error).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when item cannot be claimed', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
authorizationDataService.isAuthorized.and.returnValue(observableOf(false));
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create claim button', () => {
|
|
||||||
const btn = fixture.debugElement.query(By.css('[data-test="item-claim"]'));
|
|
||||||
expect(btn).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,84 +0,0 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
|
||||||
|
|
||||||
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
|
||||||
import { mergeMap, take } from 'rxjs/operators';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
import { RouteService } from '../../../core/services/route.service';
|
|
||||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
|
||||||
import { NotificationsService } from '../../notifications/notifications.service';
|
|
||||||
import { ResearcherProfileDataService } from '../../../core/profile/researcher-profile-data.service';
|
|
||||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
|
||||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
|
||||||
import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model';
|
|
||||||
import { isNotEmpty } from '../../empty.util';
|
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-person-page-claim-button',
|
|
||||||
templateUrl: './person-page-claim-button.component.html',
|
|
||||||
styleUrls: ['./person-page-claim-button.component.scss']
|
|
||||||
})
|
|
||||||
export class PersonPageClaimButtonComponent implements OnInit {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The target person item to claim
|
|
||||||
*/
|
|
||||||
@Input() object: DSpaceObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A boolean representing if item can be claimed or not
|
|
||||||
*/
|
|
||||||
claimable$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
||||||
|
|
||||||
constructor(protected routeService: RouteService,
|
|
||||||
protected authorizationService: AuthorizationDataService,
|
|
||||||
protected notificationsService: NotificationsService,
|
|
||||||
protected translate: TranslateService,
|
|
||||||
protected researcherProfileService: ResearcherProfileDataService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, this.object._links.self.href, null, false).pipe(
|
|
||||||
take(1)
|
|
||||||
).subscribe((isAuthorized: boolean) => {
|
|
||||||
this.claimable$.next(isAuthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new researcher profile claiming the current item.
|
|
||||||
*/
|
|
||||||
claim() {
|
|
||||||
this.researcherProfileService.createFromExternalSource(this.object._links.self.href).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
mergeMap((rd: RemoteData<ResearcherProfile>) => {
|
|
||||||
if (rd.hasSucceeded) {
|
|
||||||
return this.researcherProfileService.findRelatedItemId(rd.payload);
|
|
||||||
} else {
|
|
||||||
return observableOf(null);
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.subscribe((id: string) => {
|
|
||||||
if (isNotEmpty(id)) {
|
|
||||||
this.notificationsService.success(this.translate.get('researcherprofile.success.claim.title'),
|
|
||||||
this.translate.get('researcherprofile.success.claim.body'));
|
|
||||||
this.claimable$.next(false);
|
|
||||||
} else {
|
|
||||||
this.notificationsService.error(
|
|
||||||
this.translate.get('researcherprofile.error.claim.title'),
|
|
||||||
this.translate.get('researcherprofile.error.claim.body'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the item is claimable, false otherwise.
|
|
||||||
*/
|
|
||||||
isClaimable(): Observable<boolean> {
|
|
||||||
return this.claimable$;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -11,9 +11,9 @@ import {
|
|||||||
DeactivateMenuSectionAction,
|
DeactivateMenuSectionAction,
|
||||||
ExpandMenuAction,
|
ExpandMenuAction,
|
||||||
ExpandMenuPreviewAction,
|
ExpandMenuPreviewAction,
|
||||||
HideMenuAction,
|
HideMenuAction, HideMenuSectionAction,
|
||||||
RemoveMenuSectionAction,
|
RemoveMenuSectionAction,
|
||||||
ShowMenuAction,
|
ShowMenuAction, ShowMenuSectionAction,
|
||||||
ToggleActiveMenuSectionAction,
|
ToggleActiveMenuSectionAction,
|
||||||
ToggleMenuAction,
|
ToggleMenuAction,
|
||||||
} from './menu.actions';
|
} from './menu.actions';
|
||||||
@@ -240,6 +240,15 @@ export class MenuService {
|
|||||||
this.store.dispatch(new ShowMenuAction(menuID));
|
this.store.dispatch(new ShowMenuAction(menuID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a given menu section
|
||||||
|
* @param {MenuID} menuID The ID of the menu
|
||||||
|
* @param id The ID of the section
|
||||||
|
*/
|
||||||
|
showMenuSection(menuID: MenuID, id: string): void {
|
||||||
|
this.store.dispatch(new ShowMenuSectionAction(menuID, id));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide a given menu
|
* Hide a given menu
|
||||||
* @param {MenuID} menuID The ID of the menu
|
* @param {MenuID} menuID The ID of the menu
|
||||||
@@ -248,6 +257,15 @@ export class MenuService {
|
|||||||
this.store.dispatch(new HideMenuAction(menuID));
|
this.store.dispatch(new HideMenuAction(menuID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide a given menu section
|
||||||
|
* @param {MenuID} menuID The ID of the menu
|
||||||
|
* @param id The ID of the section
|
||||||
|
*/
|
||||||
|
hideMenuSection(menuID: MenuID, id: string): void {
|
||||||
|
this.store.dispatch(new HideMenuSectionAction(menuID, id));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate a given menu section when it's currently inactive or deactivate it when it's currently active
|
* Activate a given menu section when it's currently inactive or deactivate it when it's currently active
|
||||||
* @param {MenuID} menuID The ID of the menu
|
* @param {MenuID} menuID The ID of the menu
|
||||||
|
@@ -305,11 +305,9 @@ 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 { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component';
|
||||||
import { RSSComponent } from './rss-feed/rss.component';
|
import { RSSComponent } from './rss-feed/rss.component';
|
||||||
import { ExternalLinkMenuItemComponent } from './menu/menu-item/external-link-menu-item.component';
|
import { ExternalLinkMenuItemComponent } from './menu/menu-item/external-link-menu-item.component';
|
||||||
import { DsoPageOrcidButtonComponent } from './dso-page/dso-page-orcid-button/dso-page-orcid-button.component';
|
|
||||||
import { LogInOrcidComponent } from './log-in/methods/orcid/log-in-orcid.component';
|
import { LogInOrcidComponent } from './log-in/methods/orcid/log-in-orcid.component';
|
||||||
import { BrowserOnlyPipe } from './utils/browser-only.pipe';
|
import { BrowserOnlyPipe } from './utils/browser-only.pipe';
|
||||||
import { ThemedLoadingComponent } from './loading/themed-loading.component';
|
import { ThemedLoadingComponent } from './loading/themed-loading.component';
|
||||||
import { PersonPageClaimButtonComponent } from './dso-page/person-page-claim-button/person-page-claim-button.component';
|
|
||||||
import { SearchExportCsvComponent } from './search/search-export-csv/search-export-csv.component';
|
import { SearchExportCsvComponent } from './search/search-export-csv/search-export-csv.component';
|
||||||
import {
|
import {
|
||||||
ItemPageTitleFieldComponent
|
ItemPageTitleFieldComponent
|
||||||
@@ -578,12 +576,10 @@ const ENTRY_COMPONENTS = [
|
|||||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||||
MetadataFieldWrapperComponent,
|
MetadataFieldWrapperComponent,
|
||||||
MetadataValuesComponent,
|
MetadataValuesComponent,
|
||||||
PersonPageClaimButtonComponent,
|
|
||||||
ItemAlertsComponent,
|
ItemAlertsComponent,
|
||||||
GenericItemPageFieldComponent,
|
GenericItemPageFieldComponent,
|
||||||
MetadataRepresentationListComponent,
|
MetadataRepresentationListComponent,
|
||||||
RelatedItemsComponent,
|
RelatedItemsComponent,
|
||||||
DsoPageOrcidButtonComponent,
|
|
||||||
DsoEditMenuSectionComponent,
|
DsoEditMenuSectionComponent,
|
||||||
DsoEditMenuComponent,
|
DsoEditMenuComponent,
|
||||||
DsoEditMenuExpandableSectionComponent,
|
DsoEditMenuExpandableSectionComponent,
|
||||||
|
Reference in New Issue
Block a user