From d9237acb701491106acb6873e599d2adbd762e57 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Fri, 24 Sep 2021 17:16:53 +0200 Subject: [PATCH 1/5] [CST-4634] Added custom validator and messages and show error message for not valid email, lint fixes & unit testing --- .../access-control/access-control.module.ts | 1 + .../eperson-form/eperson-form.component.ts | 14 +++++++++-- .../validators/email-taken.validator.ts | 23 +++++++++++++++++++ src/assets/i18n/en.json5 | 5 ++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/app/access-control/epeople-registry/eperson-form/validators/email-taken.validator.ts diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts index 0e872458bd..6968bff63d 100644 --- a/src/app/access-control/access-control.module.ts +++ b/src/app/access-control/access-control.module.ts @@ -10,6 +10,7 @@ import { MembersListComponent } from './group-registry/group-form/members-list/m import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; + @NgModule({ imports: [ CommonModule, diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index b1d0c5bed3..f4eeaf468a 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; -import { FormGroup } from '@angular/forms'; +import { FormGroup, Validators } from '@angular/forms'; import { DynamicCheckboxModel, DynamicFormControlModel, @@ -33,6 +33,7 @@ import { RequestService } from '../../../core/data/request.service'; import { NoContent } from '../../../core/shared/NoContent.model'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { followLink } from '../../../shared/utils/follow-link-config.model'; +import { ValidateEmailNotTaken } from './validators/email-taken.validator'; @Component({ selector: 'ds-eperson-form', @@ -187,6 +188,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * This method will initialise the page */ initialisePage() { + observableCombineLatest( this.translateService.get(`${this.messagePrefix}.firstName`), this.translateService.get(`${this.messagePrefix}.lastName`), @@ -219,9 +221,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy { name: 'email', validators: { required: null, - pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$' + pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$', }, required: true, + errorMessages: { + emailTaken: 'error.validation.emailTaken', + pattern: 'error.validation.NotValidEmail' + }, hint: emailHint }); this.canLogIn = new DynamicCheckboxModel( @@ -262,6 +268,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy { }); })); + if (!!this.formGroup.controls.email) { + this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService)); + } + const activeEPerson$ = this.epersonService.getActiveEPerson(); this.groups = activeEPerson$.pipe( diff --git a/src/app/access-control/epeople-registry/eperson-form/validators/email-taken.validator.ts b/src/app/access-control/epeople-registry/eperson-form/validators/email-taken.validator.ts new file mode 100644 index 0000000000..8e5303c00c --- /dev/null +++ b/src/app/access-control/epeople-registry/eperson-form/validators/email-taken.validator.ts @@ -0,0 +1,23 @@ +import { AbstractControl, ValidationErrors } from '@angular/forms'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; +import { getFirstSucceededRemoteData, } from '../../../../core/shared/operators'; + +export class ValidateEmailNotTaken { + + /** + * This method will create the validator with the ePersonDataService requested from component + * @param ePersonDataService the service with DI in the component that this validator is being utilized. + */ + static createValidator(ePersonDataService: EPersonDataService) { + return (control: AbstractControl): Promise | Observable => { + return ePersonDataService.getEPersonByEmail(control.value) + .pipe( + getFirstSucceededRemoteData(), + map(res => { return !!res.payload ? { emailTaken: true } : null; }) + ); + }; + } +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index aa76d2e422..373e25d2bd 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1260,6 +1260,11 @@ "error.validation.filerequired": "The file upload is mandatory", + "error.validation.required": "This field is required", + + "error.validation.NotValidEmail": "This E-mail is not a valid email", + + "error.validation.emailTaken": "This E-mail is already taken", "file-section.error.header": "Error obtaining files for this item", From 7543af2a38cc52022899083e6a3509e3b0082184 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Mon, 27 Sep 2021 16:06:29 +0200 Subject: [PATCH 2/5] [CST-4634] Worked on unit testing formgroup input validation and custom validations --- .../eperson-form.component.spec.ts | 201 +++++++++++++++++- 1 file changed, 199 insertions(+), 2 deletions(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index 832f4f6ce5..485cd7668c 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -28,6 +28,9 @@ import { createPaginatedList } from '../../../shared/testing/utils.test'; import { RequestService } from '../../../core/data/request.service'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms'; +import { ValidateEmailNotTaken } from './validators/email-taken.validator'; + describe('EPersonFormComponent', () => { let component: EPersonFormComponent; @@ -99,12 +102,78 @@ describe('EPersonFormComponent', () => { } }); return createSuccessfulRemoteDataObject$(ePerson); + }, + getEPersonByEmail(email): Observable> { + return createSuccessfulRemoteDataObject$(null); } }; - builderService = getMockFormBuilderService(); + builderService = Object.assign(getMockFormBuilderService(),{ + createFormGroup(formModel, options = null) { + const controls = {}; + formModel.forEach( model => { + model.parent = parent; + const controlModel = model; + const controlState = { value: controlModel.value, disabled: controlModel.disabled }; + const controlOptions = this.createAbstractControlOptions(controlModel.validators, controlModel.asyncValidators, controlModel.updateOn); + controls[model.id] = new FormControl(controlState, controlOptions); + }); + return new FormGroup(controls, options); + }, + createAbstractControlOptions(validatorsConfig = null, asyncValidatorsConfig = null, updateOn = null) { + return { + validators: validatorsConfig !== null ? this.getValidators(validatorsConfig) : null, + }; + }, + getValidators(validatorsConfig) { + return this.getValidatorFns(validatorsConfig); + }, + getValidatorFns(validatorsConfig, validatorsToken = this._NG_VALIDATORS) { + let validatorFns = []; + if (this.isObject(validatorsConfig)) { + validatorFns = Object.keys(validatorsConfig).map(validatorConfigKey => { + const validatorConfigValue = validatorsConfig[validatorConfigKey]; + if (this.isValidatorDescriptor(validatorConfigValue)) { + const descriptor = validatorConfigValue; + return this.getValidatorFn(descriptor.name, descriptor.args, validatorsToken); + } + return this.getValidatorFn(validatorConfigKey, validatorConfigValue, validatorsToken); + }); + } + return validatorFns; + }, + getValidatorFn(validatorName, validatorArgs = null, validatorsToken = this._NG_VALIDATORS) { + let validatorFn; + if (Validators.hasOwnProperty(validatorName)) { // Built-in Angular Validators + validatorFn = Validators[validatorName]; + } else { // Custom Validators + if (this._DYNAMIC_VALIDATORS && this._DYNAMIC_VALIDATORS.has(validatorName)) { + validatorFn = this._DYNAMIC_VALIDATORS.get(validatorName); + } else if (validatorsToken) { + validatorFn = validatorsToken.find(validator => validator.name === validatorName); + } + } + if (validatorFn === undefined) { // throw when no validator could be resolved + throw new Error(`validator '${validatorName}' is not provided via NG_VALIDATORS, NG_ASYNC_VALIDATORS or DYNAMIC_FORM_VALIDATORS`); + } + if (validatorArgs !== null) { + return validatorFn(validatorArgs); + } + return validatorFn; + }, + isValidatorDescriptor(value) { + if (this.isObject(value)) { + return value.hasOwnProperty('name') && value.hasOwnProperty('args'); + } + return false; + }, + isObject(value) { + return typeof value === 'object' && value !== null; + } + }); authService = new AuthServiceStub(); authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true) + isAuthorized: observableOf(true), + }); groupsDataService = jasmine.createSpyObj('groupsDataService', { findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), @@ -146,6 +215,134 @@ describe('EPersonFormComponent', () => { expect(component).toBeDefined(); }); + describe('check form validation', () => { + let firstName; + let lastName; + let email; + let canLogIn; + let requireCertificate; + + let expected; + beforeEach(() => { + firstName = 'testName'; + lastName = 'testLastName'; + email = 'testEmail@test.com'; + canLogIn = false; + requireCertificate = false; + + expected = Object.assign(new EPerson(), { + metadata: { + 'eperson.firstname': [ + { + value: firstName + } + ], + 'eperson.lastname': [ + { + value: lastName + }, + ], + }, + email: email, + canLogIn: canLogIn, + requireCertificate: requireCertificate, + }); + spyOn(component.submitForm, 'emit'); + // component.firstName.value = firstName; + // component.lastName.value = lastName; + // component.email.value = email; + component.canLogIn.value = canLogIn; + component.requireCertificate.value = requireCertificate; + + fixture.detectChanges(); + component.initialisePage(); + fixture.detectChanges(); + }); + describe('firstName, lastName and email should be required', () => { + it('form should be invalid because the firstName is required', waitForAsync(() => { + fixture.whenStable().then(() => { + expect(component.formGroup.controls.firstName.valid).toBeFalse(); + expect(component.formGroup.controls.firstName.errors.required).toBeTrue(); + }); + })); + it('form should be invalid because the lastName is required', waitForAsync(() => { + fixture.whenStable().then(() => { + expect(component.formGroup.controls.lastName.valid).toBeFalse(); + expect(component.formGroup.controls.lastName.errors.required).toBeTrue(); + }); + })); + it('form should be invalid because the email is required', waitForAsync(() => { + fixture.whenStable().then(() => { + expect(component.formGroup.controls.email.valid).toBeFalse(); + expect(component.formGroup.controls.email.errors.required).toBeTrue(); + }); + })); + }); + + describe('after inserting information firstName,lastName and email not required', () => { + beforeEach(() => { + component.formGroup.controls.firstName.setValue('test'); + component.formGroup.controls.lastName.setValue('test'); + component.formGroup.controls.email.setValue('test@test.com'); + fixture.detectChanges(); + }); + it('firstName should be valid because the firstName is set', waitForAsync(() => { + fixture.whenStable().then(() => { + expect(component.formGroup.controls.firstName.valid).toBeTrue(); + expect(component.formGroup.controls.firstName.errors).toBeNull(); + }); + })); + it('lastName should be valid because the lastName is set', waitForAsync(() => { + fixture.whenStable().then(() => { + expect(component.formGroup.controls.lastName.valid).toBeTrue(); + expect(component.formGroup.controls.lastName.errors).toBeNull(); + }); + })); + it('email should be valid because the email is set', waitForAsync(() => { + fixture.whenStable().then(() => { + expect(component.formGroup.controls.email.valid).toBeTrue(); + expect(component.formGroup.controls.email.errors).toBeNull(); + }); + })); + }); + + + describe('after inserting email wrong should show pattern validation error', () => { + beforeEach(() => { + component.formGroup.controls.email.setValue('test@test'); + fixture.detectChanges(); + }); + it('email should not be valid because the email pattern', waitForAsync(() => { + fixture.whenStable().then(() => { + expect(component.formGroup.controls.email.valid).toBeFalse(); + expect(component.formGroup.controls.email.errors.pattern).toBeTruthy(); + }); + })); + }); + + describe('after already utilized email', () => { + beforeEach(() => { + const ePersonServiceWithEperson = Object.assign(ePersonDataServiceStub,{ + getEPersonByEmail(): Observable> { + return createSuccessfulRemoteDataObject$(EPersonMock); + } + }); + component.formGroup.controls.email.setValue('test@test.com'); + component.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(ePersonServiceWithEperson)); + fixture.detectChanges(); + }); + + it('email should not be valid because email is already taken', waitForAsync(() => { + fixture.whenStable().then(() => { + expect(component.formGroup.controls.email.valid).toBeFalse(); + expect(component.formGroup.controls.email.errors.emailTaken).toBeTruthy(); + }); + })); + }); + + + + }); describe('when submitting the form', () => { let firstName; let lastName; From 91218bb5341fe3fb1babc79b3ddcf08e3910b229 Mon Sep 17 00:00:00 2001 From: Corrado Lombardi Date: Wed, 6 Oct 2021 18:00:19 +0200 Subject: [PATCH 3/5] removed unnecessary new lines and commented out code --- src/app/access-control/access-control.module.ts | 1 - .../eperson-form/eperson-form.component.spec.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts index 6968bff63d..0e872458bd 100644 --- a/src/app/access-control/access-control.module.ts +++ b/src/app/access-control/access-control.module.ts @@ -10,7 +10,6 @@ import { MembersListComponent } from './group-registry/group-form/members-list/m import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; - @NgModule({ imports: [ CommonModule, diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index 485cd7668c..1f4a106bfa 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -248,9 +248,6 @@ describe('EPersonFormComponent', () => { requireCertificate: requireCertificate, }); spyOn(component.submitForm, 'emit'); - // component.firstName.value = firstName; - // component.lastName.value = lastName; - // component.email.value = email; component.canLogIn.value = canLogIn; component.requireCertificate.value = requireCertificate; From 9c47583a0af9f80302363971c0dd16b4c76b654f Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 13 Oct 2021 23:13:39 +0200 Subject: [PATCH 4/5] [CST-4634] Change email validator in order to show error also on focus --- .../eperson-form/eperson-form.component.ts | 200 ++++++++++-------- .../validators/email-taken.validator.ts | 20 +- src/app/app.module.ts | 19 +- 3 files changed, 135 insertions(+), 104 deletions(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index f4eeaf468a..aa5ecdff37 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -1,5 +1,5 @@ -import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; -import { FormGroup, Validators } from '@angular/forms'; +import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; +import { FormGroup } from '@angular/forms'; import { DynamicCheckboxModel, DynamicFormControlModel, @@ -8,7 +8,7 @@ import { } from '@ng-dynamic-forms/core'; import { TranslateService } from '@ngx-translate/core'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; -import { switchMap, take } from 'rxjs/operators'; +import { debounceTime, switchMap, take } from 'rxjs/operators'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; @@ -37,7 +37,7 @@ import { ValidateEmailNotTaken } from './validators/email-taken.validator'; @Component({ selector: 'ds-eperson-form', - templateUrl: './eperson-form.component.html' + templateUrl: './eperson-form.component.html', }) /** * A form used for creating and editing EPeople @@ -162,7 +162,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy { */ isImpersonated = false; - constructor(public epersonService: EPersonDataService, + /** + * Subscription to email field value change + */ + emailValueChangeSubscribe: Subscription; + + constructor(protected changeDetectorRef: ChangeDetectorRef, + public epersonService: EPersonDataService, public groupsDataService: GroupDataService, private formBuilderService: FormBuilderService, private translateService: TranslateService, @@ -225,8 +231,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy { }, required: true, errorMessages: { - emailTaken: 'error.validation.emailTaken', - pattern: 'error.validation.NotValidEmail' + emailTaken: 'error.validation.emailTaken', + pattern: 'error.validation.NotValidEmail' }, hint: emailHint }); @@ -266,15 +272,18 @@ export class EPersonFormComponent implements OnInit, OnDestroy { canLogIn: eperson != null ? eperson.canLogIn : true, requireCertificate: eperson != null ? eperson.requireCertificate : false }); - })); - if (!!this.formGroup.controls.email) { - this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService)); - } + if (eperson === null && !!this.formGroup.controls.email) { + this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService)); + this.emailValueChangeSubscribe = this.email.valueChanges.pipe(debounceTime(300)).subscribe(() => { + this.changeDetectorRef.detectChanges(); + }); + } + })); const activeEPerson$ = this.epersonService.getActiveEPerson(); - this.groups = activeEPerson$.pipe( + this.groups = activeEPerson$.pipe( switchMap((eperson) => { return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, { currentPage: 1, @@ -353,10 +362,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy { getFirstCompletedRemoteData() ).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', {name: ePersonToCreate.name})); + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: ePersonToCreate.name })); this.submitForm.emit(ePersonToCreate); } else { - this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', {name: ePersonToCreate.name})); + this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', { name: ePersonToCreate.name })); this.cancelForm.emit(); } }); @@ -392,10 +401,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy { const response = this.epersonService.updateEPerson(editedEperson); response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', {name: editedEperson.name})); + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name })); this.submitForm.emit(editedEperson); } else { - this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', {name: editedEperson.name})); + this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', { name: editedEperson.name })); this.cancelForm.emit(); } }); @@ -405,6 +414,87 @@ export class EPersonFormComponent implements OnInit, OnDestroy { } } + /** + * Event triggered when the user changes page + * @param event + */ + onPageChange(event) { + this.updateGroups({ + currentPage: event, + elementsPerPage: this.config.pageSize + }); + } + + /** + * Start impersonating the EPerson + */ + impersonate() { + this.authService.impersonate(this.epersonInitial.id); + this.isImpersonated = true; + } + + /** + * Deletes the EPerson from the Repository. The EPerson will be the only that this form is showing. + * It'll either show a success or error message depending on whether the delete was successful or not. + */ + delete() { + this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = eperson; + modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info'; + modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel'; + modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { + if (confirm) { + if (hasValue(eperson.id)) { + this.epersonService.deleteEPerson(eperson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData) => { + if (restResponse.hasSucceeded) { + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: eperson.name })); + this.submitForm.emit(); + } else { + this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage); + } + this.cancelForm.emit(); + }); + } + } + }); + }); + } + + /** + * Stop impersonating the EPerson + */ + stopImpersonating() { + this.authService.stopImpersonatingAndRefresh(); + this.isImpersonated = false; + } + + /** + * Cancel the current edit when component is destroyed & unsub all subscriptions + */ + ngOnDestroy(): void { + this.onCancel(); + this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + this.paginationService.clearPagination(this.config.id); + if (hasValue(this.emailValueChangeSubscribe)) { + this.emailValueChangeSubscribe.unsubscribe(); + } + } + + /** + * This method will ensure that the page gets reset and that the cache is cleared + */ + reset() { + this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => { + this.requestService.removeByHrefSubstring(eperson.self); + }); + this.initialisePage(); + } + /** * Checks for the given ePerson if there is already an ePerson in the system with that email * and shows notification if this is the case @@ -427,17 +517,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy { })); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - this.updateGroups({ - currentPage: event, - elementsPerPage: this.config.pageSize - }); - } - /** * Update the list of groups by fetching it from the rest api or cache */ @@ -446,71 +525,4 @@ export class EPersonFormComponent implements OnInit, OnDestroy { this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options); })); } - - /** - * Start impersonating the EPerson - */ - impersonate() { - this.authService.impersonate(this.epersonInitial.id); - this.isImpersonated = true; - } - - /** - * Deletes the EPerson from the Repository. The EPerson will be the only that this form is showing. - * It'll either show a success or error message depending on whether the delete was successful or not. - */ - delete() { - this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => { - const modalRef = this.modalService.open(ConfirmationModalComponent); - modalRef.componentInstance.dso = eperson; - modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header'; - modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info'; - modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel'; - modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm'; - modalRef.componentInstance.brandColor = 'danger'; - modalRef.componentInstance.confirmIcon = 'fas fa-trash'; - modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { - if (confirm) { - if (hasValue(eperson.id)) { - this.epersonService.deleteEPerson(eperson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData) => { - if (restResponse.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: eperson.name })); - this.submitForm.emit(); - } else { - this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage); - } - this.cancelForm.emit(); - }); - }} - }); - }); - } - - /** - * Stop impersonating the EPerson - */ - stopImpersonating() { - this.authService.stopImpersonatingAndRefresh(); - this.isImpersonated = false; - } - - /** - * Cancel the current edit when component is destroyed & unsub all subscriptions - */ - ngOnDestroy(): void { - this.onCancel(); - this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); - this.paginationService.clearPagination(this.config.id); - } - - - /** - * This method will ensure that the page gets reset and that the cache is cleared - */ - reset() { - this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => { - this.requestService.removeByHrefSubstring(eperson.self); - }); - this.initialisePage(); - } } diff --git a/src/app/access-control/epeople-registry/eperson-form/validators/email-taken.validator.ts b/src/app/access-control/epeople-registry/eperson-form/validators/email-taken.validator.ts index 8e5303c00c..5153abae7c 100644 --- a/src/app/access-control/epeople-registry/eperson-form/validators/email-taken.validator.ts +++ b/src/app/access-control/epeople-registry/eperson-form/validators/email-taken.validator.ts @@ -11,13 +11,15 @@ export class ValidateEmailNotTaken { * This method will create the validator with the ePersonDataService requested from component * @param ePersonDataService the service with DI in the component that this validator is being utilized. */ - static createValidator(ePersonDataService: EPersonDataService) { - return (control: AbstractControl): Promise | Observable => { - return ePersonDataService.getEPersonByEmail(control.value) - .pipe( - getFirstSucceededRemoteData(), - map(res => { return !!res.payload ? { emailTaken: true } : null; }) - ); - }; - } + static createValidator(ePersonDataService: EPersonDataService) { + return (control: AbstractControl): Promise | Observable => { + return ePersonDataService.getEPersonByEmail(control.value) + .pipe( + getFirstSucceededRemoteData(), + map(res => { + return !!res.payload ? { emailTaken: true } : null; + }) + ); + }; + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 131e6c6b58..e2cb10691b 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,7 +7,11 @@ import { EffectsModule } from '@ngrx/effects'; import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; import { MetaReducer, Store, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; -import { DYNAMIC_MATCHER_PROVIDERS } from '@ng-dynamic-forms/core'; +import { + DYNAMIC_ERROR_MESSAGES_MATCHER, + DYNAMIC_MATCHER_PROVIDERS, + DynamicErrorMessagesMatcher +} from '@ng-dynamic-forms/core'; import { TranslateModule } from '@ngx-translate/core'; import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; @@ -52,6 +56,7 @@ import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { UUIDService } from './core/shared/uuid.service'; import { CookieService } from './core/services/cookie.service'; +import { AbstractControl } from '@angular/forms'; export function getBase() { return environment.ui.nameSpace; @@ -61,6 +66,14 @@ export function getMetaReducers(): MetaReducer[] { return environment.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers; } +/** + * Condition for displaying error messages on email form field + */ +export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = + (control: AbstractControl, model: any, hasFocus: boolean) => { + return (control.touched && !hasFocus) || (control.errors?.emailTaken && hasFocus); + }; + const IMPORTS = [ CommonModule, SharedModule, @@ -146,6 +159,10 @@ const PROVIDERS = [ multi: true, deps: [ CookieService, UUIDService ] }, + { + provide: DYNAMIC_ERROR_MESSAGES_MATCHER, + useValue: ValidateEmailErrorStateMatcher + }, ...DYNAMIC_MATCHER_PROVIDERS, ]; From bd02bcd0deaa48d93a3e19c0c69b2cab0b597cce Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 14 Oct 2021 16:02:20 +0200 Subject: [PATCH 5/5] [CST-4634] Remove impersonate button from eperson creation form --- .../eperson-form/eperson-form.component.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index aa5ecdff37..723939df77 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -299,7 +299,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy { ); this.canImpersonate$ = activeEPerson$.pipe( - switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined)) + switchMap((eperson) => { + if (hasValue(eperson)) { + return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self); + } else { + return observableOf(false); + } + }) ); this.canDelete$ = activeEPerson$.pipe( switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))