diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts
index 46f83c964b..6893ac2437 100644
--- a/src/app/profile-page/profile-page.component.spec.ts
+++ b/src/app/profile-page/profile-page.component.spec.ts
@@ -11,16 +11,18 @@ import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model';
import { EPersonDataService } from '../core/eperson/eperson-data.service';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { authReducer } from '../core/auth/auth.reducer';
-import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
+import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
import { createPaginatedList } from '../shared/testing/utils.test';
import { BehaviorSubject, of as observableOf } from 'rxjs';
import { AuthService } from '../core/auth/auth.service';
import { RestResponse } from '../core/cache/response.models';
import { provideMockStore } from '@ngrx/store/testing';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
-import { getTestScheduler } from 'jasmine-marbles';
+import { cold, getTestScheduler } from 'jasmine-marbles';
import { By } from '@angular/platform-browser';
import { EmptySpecialGroupDataMock$, SpecialGroupDataMock$ } from '../shared/testing/special-group.mock';
+import { ConfigurationDataService } from '../core/data/configuration-data.service';
+import { ConfigurationProperty } from '../core/shared/configuration-property.model';
describe('ProfilePageComponent', () => {
let component: ProfilePageComponent;
@@ -29,16 +31,28 @@ describe('ProfilePageComponent', () => {
let initialState: any;
let authService;
+ let authorizationService;
let epersonService;
let notificationsService;
+ let configurationService;
const canChangePassword = new BehaviorSubject(true);
+ const validConfiguration = Object.assign(new ConfigurationProperty(), {
+ name: 'researcher-profile.entity-type',
+ values: [
+ 'Person'
+ ]
+ });
+ const emptyConfiguration = Object.assign(new ConfigurationProperty(), {
+ name: 'researcher-profile.entity-type',
+ values: []
+ });
function init() {
user = Object.assign(new EPerson(), {
id: 'userId',
groups: createSuccessfulRemoteDataObject$(createPaginatedList([])),
- _links: {self: {href: 'test.com/uuid/1234567654321'}}
+ _links: { self: { href: 'test.com/uuid/1234567654321' } }
});
initialState = {
core: {
@@ -53,7 +67,7 @@ describe('ProfilePageComponent', () => {
}
}
};
-
+ authorizationService = jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword });
authService = jasmine.createSpyObj('authService', {
getAuthenticatedUserFromStore: observableOf(user),
getSpecialGroupsFromAuthStatus: SpecialGroupDataMock$
@@ -67,6 +81,9 @@ describe('ProfilePageComponent', () => {
error: {},
warning: {}
});
+ configurationService = jasmine.createSpyObj('configurationDataService', {
+ findByPropertyName: jasmine.createSpy('findByPropertyName')
+ });
}
beforeEach(waitForAsync(() => {
@@ -82,7 +99,8 @@ describe('ProfilePageComponent', () => {
{ provide: EPersonDataService, useValue: epersonService },
{ provide: NotificationsService, useValue: notificationsService },
{ provide: AuthService, useValue: authService },
- { provide: AuthorizationDataService, useValue: jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword }) },
+ { provide: ConfigurationDataService, useValue: configurationService },
+ { provide: AuthorizationDataService, useValue: authorizationService },
provideMockStore({ initialState }),
],
schemas: [NO_ERRORS_SCHEMA]
@@ -92,151 +110,157 @@ describe('ProfilePageComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ProfilePageComponent);
component = fixture.componentInstance;
- fixture.detectChanges();
});
- describe('updateProfile', () => {
- describe('when the metadata form returns false and the security form returns true', () => {
- beforeEach(() => {
- component.metadataForm = jasmine.createSpyObj('metadataForm', {
- updateProfile: false
+ describe('', () => {
+
+ beforeEach(() => {
+ configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(validConfiguration));
+ fixture.detectChanges();
+ });
+
+ describe('updateProfile', () => {
+ describe('when the metadata form returns false and the security form returns true', () => {
+ beforeEach(() => {
+ component.metadataForm = jasmine.createSpyObj('metadataForm', {
+ updateProfile: false
+ });
+ spyOn(component, 'updateSecurity').and.returnValue(true);
+ component.updateProfile();
});
- spyOn(component, 'updateSecurity').and.returnValue(true);
- component.updateProfile();
- });
- it('should not display a warning', () => {
- expect(notificationsService.warning).not.toHaveBeenCalled();
- });
- });
-
- describe('when the metadata form returns true and the security form returns false', () => {
- beforeEach(() => {
- component.metadataForm = jasmine.createSpyObj('metadataForm', {
- updateProfile: true
+ it('should not display a warning', () => {
+ expect(notificationsService.warning).not.toHaveBeenCalled();
});
- component.updateProfile();
});
- it('should not display a warning', () => {
- expect(notificationsService.warning).not.toHaveBeenCalled();
- });
- });
-
- describe('when the metadata form returns true and the security form returns true', () => {
- beforeEach(() => {
- component.metadataForm = jasmine.createSpyObj('metadataForm', {
- updateProfile: true
+ describe('when the metadata form returns true and the security form returns false', () => {
+ beforeEach(() => {
+ component.metadataForm = jasmine.createSpyObj('metadataForm', {
+ updateProfile: true
+ });
+ component.updateProfile();
});
- component.updateProfile();
- });
- it('should not display a warning', () => {
- expect(notificationsService.warning).not.toHaveBeenCalled();
- });
- });
-
- describe('when the metadata form returns false and the security form returns false', () => {
- beforeEach(() => {
- component.metadataForm = jasmine.createSpyObj('metadataForm', {
- updateProfile: false
+ it('should not display a warning', () => {
+ expect(notificationsService.warning).not.toHaveBeenCalled();
});
- component.updateProfile();
});
- it('should display a warning', () => {
- expect(notificationsService.warning).toHaveBeenCalled();
- });
- });
- });
+ describe('when the metadata form returns true and the security form returns true', () => {
+ beforeEach(() => {
+ component.metadataForm = jasmine.createSpyObj('metadataForm', {
+ updateProfile: true
+ });
+ component.updateProfile();
+ });
- describe('updateSecurity', () => {
- describe('when no password value present', () => {
- let result;
-
- beforeEach(() => {
- component.setPasswordValue('');
-
- result = component.updateSecurity();
+ it('should not display a warning', () => {
+ expect(notificationsService.warning).not.toHaveBeenCalled();
+ });
});
- it('should return false', () => {
- expect(result).toEqual(false);
- });
+ describe('when the metadata form returns false and the security form returns false', () => {
+ beforeEach(() => {
+ component.metadataForm = jasmine.createSpyObj('metadataForm', {
+ updateProfile: false
+ });
+ component.updateProfile();
+ });
- it('should not call epersonService.patch', () => {
- expect(epersonService.patch).not.toHaveBeenCalled();
+ it('should display a warning', () => {
+ expect(notificationsService.warning).toHaveBeenCalled();
+ });
});
});
- describe('when password is filled in, but the password is invalid', () => {
- let result;
+ describe('updateSecurity', () => {
+ describe('when no password value present', () => {
+ let result;
- beforeEach(() => {
- component.setPasswordValue('test');
- component.setInvalid(true);
- result = component.updateSecurity();
+ beforeEach(() => {
+ component.setPasswordValue('');
+
+ result = component.updateSecurity();
+ });
+
+ it('should return false', () => {
+ expect(result).toEqual(false);
+ });
+
+ it('should not call epersonService.patch', () => {
+ expect(epersonService.patch).not.toHaveBeenCalled();
+ });
});
- it('should return true', () => {
- expect(result).toEqual(true);
- expect(epersonService.patch).not.toHaveBeenCalled();
+ describe('when password is filled in, but the password is invalid', () => {
+ let result;
+
+ beforeEach(() => {
+ component.setPasswordValue('test');
+ component.setInvalid(true);
+ result = component.updateSecurity();
+ });
+
+ it('should return true', () => {
+ expect(result).toEqual(true);
+ expect(epersonService.patch).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when password is filled in, and is valid', () => {
+ let result;
+ let operations;
+
+ beforeEach(() => {
+ component.setPasswordValue('testest');
+ component.setInvalid(false);
+
+ operations = [{ op: 'add', path: '/password', value: 'testest' }];
+ result = component.updateSecurity();
+ });
+
+ it('should return true', () => {
+ expect(result).toEqual(true);
+ });
+
+ it('should return call epersonService.patch', () => {
+ expect(epersonService.patch).toHaveBeenCalledWith(user, operations);
+ });
});
});
- describe('when password is filled in, and is valid', () => {
- let result;
- let operations;
+ describe('canChangePassword$', () => {
+ describe('when the user is allowed to change their password', () => {
+ beforeEach(() => {
+ canChangePassword.next(true);
+ });
- beforeEach(() => {
- component.setPasswordValue('testest');
- component.setInvalid(false);
+ it('should contain true', () => {
+ getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true });
+ });
- operations = [{ op: 'add', path: '/password', value: 'testest' }];
- result = component.updateSecurity();
+ it('should show the security section on the page', () => {
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull();
+ });
});
- it('should return true', () => {
- expect(result).toEqual(true);
- });
+ describe('when the user is not allowed to change their password', () => {
+ beforeEach(() => {
+ canChangePassword.next(false);
+ });
- it('should return call epersonService.patch', () => {
- expect(epersonService.patch).toHaveBeenCalledWith(user, operations);
+ it('should contain false', () => {
+ getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false });
+ });
+
+ it('should not show the security section on the page', () => {
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull();
+ });
});
});
- });
-
- describe('canChangePassword$', () => {
- describe('when the user is allowed to change their password', () => {
- beforeEach(() => {
- canChangePassword.next(true);
- });
-
- it('should contain true', () => {
- getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true });
- });
-
- it('should show the security section on the page', () => {
- fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull();
- });
- });
-
- describe('when the user is not allowed to change their password', () => {
- beforeEach(() => {
- canChangePassword.next(false);
- });
-
- it('should contain false', () => {
- getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false });
- });
-
- it('should not show the security section on the page', () => {
- fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull();
- });
- });
- });
describe('check for specialGroups', () => {
it('should contains specialGroups list', () => {
@@ -258,4 +282,56 @@ describe('ProfilePageComponent', () => {
expect(specialGroupsEle).toBeFalsy();
});
});
+ });
+
+ describe('isResearcherProfileEnabled', () => {
+
+ describe('when configuration service return values', () => {
+
+ beforeEach(() => {
+ configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(validConfiguration));
+ fixture.detectChanges();
+ });
+
+ it('should return true', () => {
+ const result = component.isResearcherProfileEnabled();
+ const expected = cold('a', {
+ a: true
+ });
+ expect(result).toBeObservable(expected);
+ });
+ });
+
+ describe('when configuration service return no values', () => {
+
+ beforeEach(() => {
+ configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(emptyConfiguration));
+ fixture.detectChanges();
+ });
+
+ it('should return false', () => {
+ const result = component.isResearcherProfileEnabled();
+ const expected = cold('a', {
+ a: false
+ });
+ expect(result).toBeObservable(expected);
+ });
+ });
+
+ describe('when configuration service return an error', () => {
+
+ beforeEach(() => {
+ configurationService.findByPropertyName.and.returnValue(createFailedRemoteDataObject$());
+ fixture.detectChanges();
+ });
+
+ it('should return false', () => {
+ const result = component.isResearcherProfileEnabled();
+ const expected = cold('a', {
+ a: false
+ });
+ expect(result).toBeObservable(expected);
+ });
+ });
+ });
});
diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts
index 7623e9e6ea..5629a1ae18 100644
--- a/src/app/profile-page/profile-page.component.ts
+++ b/src/app/profile-page/profile-page.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit, ViewChild } from '@angular/core';
-import { Observable } from 'rxjs';
+import { BehaviorSubject, Observable } from 'rxjs';
import { EPerson } from '../core/eperson/models/eperson.model';
import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component';
import { NotificationsService } from '../shared/notifications/notifications.service';
@@ -16,6 +16,8 @@ import { AuthService } from '../core/auth/auth.service';
import { Operation } from 'fast-json-patch';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../core/data/feature-authorization/feature-id';
+import { ConfigurationDataService } from '../core/data/configuration-data.service';
+import { ConfigurationProperty } from '../core/shared/configuration-property.model';
@Component({
selector: 'ds-profile-page',
@@ -72,11 +74,14 @@ export class ProfilePageComponent implements OnInit {
private currentUser: EPerson;
canChangePassword$: Observable
;
+ isResearcherProfileEnabled$: BehaviorSubject = new BehaviorSubject(false);
+
constructor(private authService: AuthService,
private notificationsService: NotificationsService,
private translate: TranslateService,
private epersonService: EPersonDataService,
- private authorizationService: AuthorizationDataService) {
+ private authorizationService: AuthorizationDataService,
+ private configurationService: ConfigurationDataService) {
}
ngOnInit(): void {
@@ -90,6 +95,12 @@ export class ProfilePageComponent implements OnInit {
this.groupsRD$ = this.user$.pipe(switchMap((user: EPerson) => user.groups));
this.canChangePassword$ = this.user$.pipe(switchMap((user: EPerson) => this.authorizationService.isAuthorized(FeatureID.CanChangePassword, user._links.self.href)));
this.specialGroupsRD$ = this.authService.getSpecialGroupsFromAuthStatus();
+
+ this.configurationService.findByPropertyName('researcher-profile.entity-type').pipe(
+ getFirstCompletedRemoteData()
+ ).subscribe((configRD: RemoteData) => {
+ this.isResearcherProfileEnabled$.next(configRD.hasSucceeded && configRD.payload.values.length > 0);
+ });
}
/**
@@ -165,4 +176,12 @@ export class ProfilePageComponent implements OnInit {
submit() {
this.updateProfile();
}
+
+ /**
+ * Returns true if the researcher profile feature is enabled, false otherwise.
+ */
+ isResearcherProfileEnabled(): Observable {
+ return this.isResearcherProfileEnabled$.asObservable();
+ }
+
}
diff --git a/src/app/profile-page/profile-page.module.ts b/src/app/profile-page/profile-page.module.ts
index dc9595140b..0e2902de33 100644
--- a/src/app/profile-page/profile-page.module.ts
+++ b/src/app/profile-page/profile-page.module.ts
@@ -5,25 +5,37 @@ import { ProfilePageRoutingModule } from './profile-page-routing.module';
import { ProfilePageComponent } from './profile-page.component';
import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component';
import { ProfilePageSecurityFormComponent } from './profile-page-security-form/profile-page-security-form.component';
+import {
+ ProfilePageResearcherFormComponent
+} from './profile-page-researcher-form/profile-page-researcher-form.component';
import { ThemedProfilePageComponent } from './themed-profile-page.component';
import { FormModule } from '../shared/form/form.module';
+import { UiSwitchModule } from 'ngx-ui-switch';
+import { ProfileClaimItemModalComponent } from './profile-claim-item-modal/profile-claim-item-modal.component';
+
@NgModule({
imports: [
ProfilePageRoutingModule,
CommonModule,
SharedModule,
- FormModule
+ FormModule,
+ UiSwitchModule
],
exports: [
+ ProfilePageComponent,
+ ThemedProfilePageComponent,
+ ProfilePageMetadataFormComponent,
ProfilePageSecurityFormComponent,
- ProfilePageMetadataFormComponent
+ ProfilePageResearcherFormComponent
],
declarations: [
ProfilePageComponent,
ThemedProfilePageComponent,
+ ProfileClaimItemModalComponent,
ProfilePageMetadataFormComponent,
- ProfilePageSecurityFormComponent
+ ProfilePageSecurityFormComponent,
+ ProfilePageResearcherFormComponent
]
})
export class ProfilePageModule {
diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts
index dc146e1fd7..054c161da6 100644
--- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts
+++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts
@@ -55,6 +55,9 @@ describe('ComColFormComponent', () => {
})
];
+ const logo = {
+ id: 'logo'
+ };
const logoEndpoint = 'rest/api/logo/endpoint';
const dsoService = Object.assign({
getLogoEndpoint: () => observableOf(logoEndpoint),
@@ -207,7 +210,7 @@ describe('ComColFormComponent', () => {
beforeEach(() => {
initComponent(Object.assign(new Community(), {
id: 'community-id',
- logo: createSuccessfulRemoteDataObject$({}),
+ logo: createSuccessfulRemoteDataObject$(logo),
_links: {
self: { href: 'community-self' },
logo: { href: 'community-logo' },
@@ -225,28 +228,31 @@ describe('ComColFormComponent', () => {
describe('submit with logo marked for deletion', () => {
beforeEach(() => {
+ spyOn(dsoService, 'deleteLogo').and.callThrough();
comp.markLogoForDeletion = true;
});
+ it('should call dsoService.deleteLogo on the DSO', () => {
+ comp.onSubmit();
+ fixture.detectChanges();
+
+ expect(dsoService.deleteLogo).toHaveBeenCalledWith(comp.dso);
+ });
+
describe('when dsoService.deleteLogo returns a successful response', () => {
beforeEach(() => {
- spyOn(dsoService, 'deleteLogo').and.returnValue(createSuccessfulRemoteDataObject$({}));
+ dsoService.deleteLogo.and.returnValue(createSuccessfulRemoteDataObject$({}));
comp.onSubmit();
});
it('should display a success notification', () => {
expect(notificationsService.success).toHaveBeenCalled();
});
-
- it('should remove the object\'s cache', () => {
- expect(requestServiceStub.removeByHrefSubstring).toHaveBeenCalled();
- expect(objectCacheStub.remove).toHaveBeenCalled();
- });
});
describe('when dsoService.deleteLogo returns an error response', () => {
beforeEach(() => {
- spyOn(dsoService, 'deleteLogo').and.returnValue(createFailedRemoteDataObject$('Error', 500));
+ dsoService.deleteLogo.and.returnValue(createFailedRemoteDataObject$('Error', 500));
comp.onSubmit();
});
diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts
index c3ba9dcaee..29be240753 100644
--- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts
+++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts
@@ -184,7 +184,6 @@ export class ComColFormComponent implements On
}
this.dso.logo = undefined;
this.uploadFilesOptions.method = RestRequestMethod.POST;
- this.refreshCache();
this.finish.emit();
});
}
diff --git a/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts b/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts
index bdc2637886..bc73e4134b 100644
--- a/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts
+++ b/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts
@@ -68,7 +68,6 @@ describe('DeleteComColPageComponent', () => {
{
delete: createNoContentRemoteDataObject$(),
findByHref: jasmine.createSpy('findByHref'),
- refreshCache: jasmine.createSpy('refreshCache')
});
routerStub = {
@@ -79,10 +78,6 @@ describe('DeleteComColPageComponent', () => {
data: observableOf(community)
};
- requestServiceStub = jasmine.createSpyObj('RequestService', {
- removeByHrefSubstring: jasmine.createSpy('removeByHrefSubstring')
- });
-
translateServiceStub = jasmine.createSpyObj('TranslateService', {
instant: jasmine.createSpy('instant')
});
@@ -99,7 +94,6 @@ describe('DeleteComColPageComponent', () => {
{ provide: ActivatedRoute, useValue: routeStub },
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
{ provide: TranslateService, useValue: translateServiceStub },
- { provide: RequestService, useValue: requestServiceStub }
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
@@ -159,7 +153,6 @@ describe('DeleteComColPageComponent', () => {
scheduler.flush();
fixture.detectChanges();
expect(notificationsService.error).toHaveBeenCalled();
- expect(dsoDataService.refreshCache).not.toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalled();
});
@@ -169,7 +162,6 @@ describe('DeleteComColPageComponent', () => {
scheduler.flush();
fixture.detectChanges();
expect(notificationsService.success).toHaveBeenCalled();
- expect(dsoDataService.refreshCache).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalled();
});
diff --git a/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts b/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts
index f781b71f3f..94f46088aa 100644
--- a/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts
+++ b/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts
@@ -7,7 +7,6 @@ import { NotificationsService } from '../../../notifications/notifications.servi
import { TranslateService } from '@ngx-translate/core';
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
import { NoContent } from '../../../../core/shared/NoContent.model';
-import { RequestService } from '../../../../core/data/request.service';
import { ComColDataService } from '../../../../core/data/comcol-data.service';
import { Community } from '../../../../core/shared/community.model';
import { Collection } from '../../../../core/shared/collection.model';
@@ -41,7 +40,6 @@ export class DeleteComColPageComponent i
protected route: ActivatedRoute,
protected notifications: NotificationsService,
protected translate: TranslateService,
- protected requestService: RequestService
) {
}
@@ -61,7 +59,6 @@ export class DeleteComColPageComponent i
if (response.hasSucceeded) {
const successMessage = this.translate.instant((dso as any).type + '.delete.notification.success');
this.notifications.success(successMessage);
- this.dsoDataService.refreshCache(dso);
} else {
const errorMessage = this.translate.instant((dso as any).type + '.delete.notification.fail');
this.notifications.error(errorMessage);
diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html
index b0a319b4ff..905186a232 100644
--- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html
+++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html
@@ -4,7 +4,7 @@
*ngVar="group$ | async as group">
- {{'comcol-role.edit.' + (comcolRole$ | async)?.name + '.name' | translate}}
+ {{ roleName$ | async }}
diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts
index 175abe48e4..59fc95b67f 100644
--- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts
+++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts
@@ -10,6 +10,8 @@ import { RouterTestingModule } from '@angular/router/testing';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ComcolModule } from '../../../comcol.module';
+import { NotificationsService } from '../../../../notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../../testing/notifications-service.stub';
describe('ComcolRoleComponent', () => {
@@ -20,6 +22,7 @@ describe('ComcolRoleComponent', () => {
let group;
let statusCode;
let comcolRole;
+ let notificationsService;
const requestService = { hasByHref$: () => observableOf(true) };
@@ -40,6 +43,7 @@ describe('ComcolRoleComponent', () => {
providers: [
{ provide: GroupDataService, useValue: groupService },
{ provide: RequestService, useValue: requestService },
+ { provide: NotificationsService, useClass: NotificationsServiceStub }
], schemas: [
NO_ERRORS_SCHEMA
]
@@ -59,12 +63,14 @@ describe('ComcolRoleComponent', () => {
fixture = TestBed.createComponent(ComcolRoleComponent);
comp = fixture.componentInstance;
de = fixture.debugElement;
+ notificationsService = TestBed.inject(NotificationsService);
comcolRole = {
name: 'test role name',
href: 'test role link',
};
comp.comcolRole = comcolRole;
+ comp.roleName$ = observableOf(comcolRole.name);
fixture.detectChanges();
});
@@ -101,6 +107,18 @@ describe('ComcolRoleComponent', () => {
done();
});
});
+
+ describe('when a group cannot be created', () => {
+ beforeEach(() => {
+ groupService.createComcolGroup.and.returnValue(createFailedRemoteDataObject$());
+ de.query(By.css('.btn.create')).nativeElement.click();
+ });
+
+ it('should show an error notification', (done) => {
+ expect(notificationsService.error).toHaveBeenCalled();
+ done();
+ });
+ });
});
describe('when the related group is the Anonymous group', () => {
@@ -169,5 +187,17 @@ describe('ComcolRoleComponent', () => {
done();
});
});
+
+ describe('when a group cannot be deleted', () => {
+ beforeEach(() => {
+ groupService.deleteComcolGroup.and.returnValue(createFailedRemoteDataObject$());
+ de.query(By.css('.btn.delete')).nativeElement.click();
+ });
+
+ it('should show an error notification', (done) => {
+ expect(notificationsService.error).toHaveBeenCalled();
+ done();
+ });
+ });
});
});
diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts
index 7ed88fae1c..3091dd0cf0 100644
--- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts
+++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts
@@ -12,6 +12,8 @@ import { HALLink } from '../../../../../core/shared/hal-link.model';
import { getGroupEditRoute } from '../../../../../access-control/access-control-routing-paths';
import { hasNoValue, hasValue } from '../../../../empty.util';
import { NoContent } from '../../../../../core/shared/NoContent.model';
+import { NotificationsService } from '../../../../notifications/notifications.service';
+import { TranslateService } from '@ngx-translate/core';
/**
* Component for managing a community or collection role.
@@ -64,9 +66,16 @@ export class ComcolRoleComponent implements OnInit {
*/
hasCustomGroup$: Observable
;
+ /**
+ * The human-readable name of this role
+ */
+ roleName$: Observable;
+
constructor(
protected requestService: RequestService,
protected groupService: GroupDataService,
+ protected notificationsService: NotificationsService,
+ protected translateService: TranslateService,
) {
}
@@ -101,7 +110,12 @@ export class ComcolRoleComponent implements OnInit {
this.groupService.clearGroupsRequests();
this.requestService.setStaleByHrefSubstring(this.comcolRole.href);
} else {
- // TODO show error notification
+ this.notificationsService.error(
+ this.roleName$.pipe(
+ switchMap(role => this.translateService.get('comcol-role.edit.create.error.title', { role }))
+ ),
+ `${rd.statusCode} ${rd.errorMessage}`
+ );
}
});
}
@@ -117,7 +131,12 @@ export class ComcolRoleComponent implements OnInit {
this.groupService.clearGroupsRequests();
this.requestService.setStaleByHrefSubstring(this.comcolRole.href);
} else {
- // TODO show error notification
+ this.notificationsService.error(
+ this.roleName$.pipe(
+ switchMap(role => this.translateService.get('comcol-role.edit.delete.error.title', { role }))
+ ),
+ rd.errorMessage
+ );
}
});
}
@@ -154,5 +173,7 @@ export class ComcolRoleComponent implements OnInit {
this.hasCustomGroup$ = this.group$.pipe(
map((group: Group) => hasValue(group) && group.name !== 'Anonymous'),
);
+
+ this.roleName$ = this.translateService.get(`comcol-role.edit.${this.comcolRole.name}.name`);
}
}
diff --git a/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts b/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts
index b2ef7cec99..d899dd8ef8 100644
--- a/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts
+++ b/src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts
@@ -46,27 +46,27 @@ describe('ConfirmationModalComponent', () => {
describe('confirmPressed', () => {
beforeEach(() => {
- spyOn(component.response, 'next');
+ spyOn(component.response, 'emit');
component.confirmPressed();
});
it('should call the close method on the active modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
- it('behaviour subject should have true as next', () => {
- expect(component.response.next).toHaveBeenCalledWith(true);
+ it('behaviour subject should emit true', () => {
+ expect(component.response.emit).toHaveBeenCalledWith(true);
});
});
describe('cancelPressed', () => {
beforeEach(() => {
- spyOn(component.response, 'next');
+ spyOn(component.response, 'emit');
component.cancelPressed();
});
it('should call the close method on the active modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
- it('behaviour subject should have false as next', () => {
- expect(component.response.next).toHaveBeenCalledWith(false);
+ it('behaviour subject should emit false', () => {
+ expect(component.response.emit).toHaveBeenCalledWith(false);
});
});
@@ -88,7 +88,7 @@ describe('ConfirmationModalComponent', () => {
describe('when the click method emits on cancel button', () => {
beforeEach(fakeAsync(() => {
spyOn(component, 'close');
- spyOn(component.response, 'next');
+ spyOn(component.response, 'emit');
debugElement.query(By.css('button.cancel')).triggerEventHandler('click', {
preventDefault: () => {/**/
}
@@ -99,15 +99,15 @@ describe('ConfirmationModalComponent', () => {
it('should call the close method on the component', () => {
expect(component.close).toHaveBeenCalled();
});
- it('behaviour subject should have false as next', () => {
- expect(component.response.next).toHaveBeenCalledWith(false);
+ it('behaviour subject should emit false', () => {
+ expect(component.response.emit).toHaveBeenCalledWith(false);
});
});
describe('when the click method emits on confirm button', () => {
beforeEach(fakeAsync(() => {
spyOn(component, 'close');
- spyOn(component.response, 'next');
+ spyOn(component.response, 'emit');
debugElement.query(By.css('button.confirm')).triggerEventHandler('click', {
preventDefault: () => {/**/
}
@@ -118,8 +118,8 @@ describe('ConfirmationModalComponent', () => {
it('should call the close method on the component', () => {
expect(component.close).toHaveBeenCalled();
});
- it('behaviour subject should have true as next', () => {
- expect(component.response.next).toHaveBeenCalledWith(true);
+ it('behaviour subject should emit false', () => {
+ expect(component.response.emit).toHaveBeenCalledWith(true);
});
});
diff --git a/src/app/shared/confirmation-modal/confirmation-modal.component.ts b/src/app/shared/confirmation-modal/confirmation-modal.component.ts
index c18025427a..4fa4858600 100644
--- a/src/app/shared/confirmation-modal/confirmation-modal.component.ts
+++ b/src/app/shared/confirmation-modal/confirmation-modal.component.ts
@@ -1,6 +1,5 @@
-import { Component, Input, Output } from '@angular/core';
+import { Component, EventEmitter, Input, Output } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
-import { Subject } from 'rxjs';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
@Component({
@@ -24,7 +23,7 @@ export class ConfirmationModalComponent {
* An event fired when the cancel or confirm button is clicked, with respectively false or true
*/
@Output()
- response: Subject = new Subject();
+ response = new EventEmitter();
constructor(protected activeModal: NgbActiveModal) {
}
@@ -33,7 +32,7 @@ export class ConfirmationModalComponent {
* Confirm the action that led to the modal
*/
confirmPressed() {
- this.response.next(true);
+ this.response.emit(true);
this.close();
}
@@ -41,7 +40,7 @@ export class ConfirmationModalComponent {
* Cancel the action that led to the modal and close modal
*/
cancelPressed() {
- this.response.next(false);
+ this.response.emit(false);
this.close();
}
diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html
new file mode 100644
index 0000000000..12d5d2b47d
--- /dev/null
+++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html
@@ -0,0 +1 @@
+
diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.scss b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.spec.ts b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.spec.ts
new file mode 100644
index 0000000000..168517b47a
--- /dev/null
+++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.spec.ts
@@ -0,0 +1,186 @@
+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 { ResearcherProfileService } from '../../../core/profile/researcher-profile.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;
+
+ 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: ResearcherProfileService, 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();
+ });
+
+ });
+});
diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts
new file mode 100644
index 0000000000..903b9d3679
--- /dev/null
+++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts
@@ -0,0 +1,84 @@
+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 { ResearcherProfileService } from '../../../core/profile/researcher-profile.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 = new BehaviorSubject(false);
+
+ constructor(protected routeService: RouteService,
+ protected authorizationService: AuthorizationDataService,
+ protected notificationsService: NotificationsService,
+ protected translate: TranslateService,
+ protected researcherProfileService: ResearcherProfileService) {
+ }
+
+ 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) => {
+ 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 {
+ return this.claimable$;
+ }
+
+}
diff --git a/src/app/shared/idle-modal/idle-modal.component.spec.ts b/src/app/shared/idle-modal/idle-modal.component.spec.ts
index 847bf6ac4f..7ea0b96d5b 100644
--- a/src/app/shared/idle-modal/idle-modal.component.spec.ts
+++ b/src/app/shared/idle-modal/idle-modal.component.spec.ts
@@ -46,7 +46,7 @@ describe('IdleModalComponent', () => {
describe('extendSessionPressed', () => {
beforeEach(fakeAsync(() => {
- spyOn(component.response, 'next');
+ spyOn(component.response, 'emit');
component.extendSessionPressed();
}));
it('should set idle to false', () => {
@@ -55,8 +55,8 @@ describe('IdleModalComponent', () => {
it('should close the modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
- it('response \'closed\' should have true as next', () => {
- expect(component.response.next).toHaveBeenCalledWith(true);
+ it('response \'closed\' should emit true', () => {
+ expect(component.response.emit).toHaveBeenCalledWith(true);
});
});
@@ -74,7 +74,7 @@ describe('IdleModalComponent', () => {
describe('closePressed', () => {
beforeEach(fakeAsync(() => {
- spyOn(component.response, 'next');
+ spyOn(component.response, 'emit');
component.closePressed();
}));
it('should set idle to false', () => {
@@ -83,8 +83,8 @@ describe('IdleModalComponent', () => {
it('should close the modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
- it('response \'closed\' should have true as next', () => {
- expect(component.response.next).toHaveBeenCalledWith(true);
+ it('response \'closed\' should emit true', () => {
+ expect(component.response.emit).toHaveBeenCalledWith(true);
});
});
diff --git a/src/app/shared/idle-modal/idle-modal.component.ts b/src/app/shared/idle-modal/idle-modal.component.ts
index 35fafcf5cf..4873137ff1 100644
--- a/src/app/shared/idle-modal/idle-modal.component.ts
+++ b/src/app/shared/idle-modal/idle-modal.component.ts
@@ -1,8 +1,7 @@
-import { Component, OnInit, Output } from '@angular/core';
+import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { environment } from '../../../environments/environment';
import { AuthService } from '../../core/auth/auth.service';
-import { Subject } from 'rxjs';
import { hasValue } from '../empty.util';
import { Store } from '@ngrx/store';
import { AppState } from '../../app.reducer';
@@ -29,7 +28,7 @@ export class IdleModalComponent implements OnInit {
* An event fired when the modal is closed
*/
@Output()
- response: Subject = new Subject();
+ response = new EventEmitter();
constructor(private activeModal: NgbActiveModal,
private authService: AuthService,
@@ -84,6 +83,6 @@ export class IdleModalComponent implements OnInit {
*/
closeModal() {
this.activeModal.close();
- this.response.next(true);
+ this.response.emit(true);
}
}
diff --git a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html
index 0c0b72272f..6c54a15162 100644
--- a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html
+++ b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html
@@ -8,12 +8,12 @@
{{ "item.version.delete.modal.text" | translate : {version: versionNumber} }}