From: Stephan Müller Date: Fri, 22 Jun 2018 11:35:35 +0000 (+0200) Subject: mgr/dashboard: Move validators into forms directory X-Git-Tag: v14.0.1~954^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=2b5f8b1e70317ef81470d3a9ab78c94f4069b0d4;p=ceph.git mgr/dashboard: Move validators into forms directory Now that 'forms' directory exists, the validators are moved to it. Angular also does this with it's validators ('@angular/forms'). Signed-off-by: Stephan Müller --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts index bfed5882a32e..fb79e8bfe796 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts @@ -9,8 +9,8 @@ import { forkJoin as observableForkJoin, Observable } from 'rxjs'; import { RgwUserService } from '../../../shared/api/rgw-user.service'; import { CdFormBuilder } from '../../../shared/forms/cd-form-builder'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; +import { CdValidators, isEmptyInputValue } from '../../../shared/forms/cd-validators'; import { FormatterService } from '../../../shared/services/formatter.service'; -import { CdValidators, isEmptyInputValue } from '../../../shared/validators/cd-validators'; import { RgwUserCapability } from '../models/rgw-user-capability'; import { RgwUserS3Key } from '../models/rgw-user-s3-key'; import { RgwUserSubuser } from '../models/rgw-user-subuser'; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.ts index 7514eada1e5a..3741581973f0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.ts @@ -6,7 +6,7 @@ import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; import { CdFormBuilder } from '../../../shared/forms/cd-form-builder'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; -import { CdValidators } from '../../../shared/validators/cd-validators'; +import { CdValidators } from '../../../shared/forms/cd-validators'; import { RgwUserS3Key } from '../models/rgw-user-s3-key'; @Component({ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.ts index 0da588a02427..8f5b427bd78f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-subuser-modal/rgw-user-subuser-modal.component.ts @@ -6,7 +6,7 @@ import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; import { CdFormBuilder } from '../../../shared/forms/cd-form-builder'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; -import { CdValidators, isEmptyInputValue } from '../../../shared/validators/cd-validators'; +import { CdValidators, isEmptyInputValue } from '../../../shared/forms/cd-validators'; import { RgwUserSubuser } from '../models/rgw-user-subuser'; @Component({ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts new file mode 100644 index 000000000000..fb682970c807 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts @@ -0,0 +1,171 @@ +import { FormControl, FormGroup } from '@angular/forms'; + +import { CdValidators } from './cd-validators'; + +describe('CdValidators', () => { + describe('email', () => { + it('should not error on an empty email address', () => { + const control = new FormControl(''); + expect(CdValidators.email(control)).toBeNull(); + }); + + it('should not error on valid email address', () => { + const control = new FormControl('dashboard@ceph.com'); + expect(CdValidators.email(control)).toBeNull(); + }); + + it('should error on invalid email address', () => { + const control = new FormControl('xyz'); + expect(CdValidators.email(control)).toEqual({ email: true }); + }); + }); + + describe('requiredIf', () => { + let form: FormGroup; + + beforeEach(() => { + form = new FormGroup({ + x: new FormControl(true), + y: new FormControl('abc'), + z: new FormControl('') + }); + }); + + it('should not error because all conditions are fulfilled', () => { + form.get('z').setValue('zyx'); + const validatorFn = CdValidators.requiredIf({ + x: true, + y: 'abc' + }); + expect(validatorFn(form.controls['z'])).toBeNull(); + }); + + it('should not error because of unmet prerequisites', () => { + // Define prereqs that do not match the current values of the form fields. + const validatorFn = CdValidators.requiredIf({ + x: false, + y: 'xyz' + }); + // The validator must succeed because the prereqs do not match, so the + // validation of the 'z' control will be skipped. + expect(validatorFn(form.controls['z'])).toBeNull(); + }); + + it('should error because of an empty value', () => { + // Define prereqs that force the validator to validate the value of + // the 'z' control. + const validatorFn = CdValidators.requiredIf({ + x: true, + y: 'abc' + }); + // The validator must fail because the value of control 'z' is empty. + expect(validatorFn(form.controls['z'])).toEqual({ required: true }); + }); + + it('should not error because of unsuccessful condition', () => { + form.get('z').setValue('zyx'); + // Define prereqs that force the validator to validate the value of + // the 'z' control. + const validatorFn = CdValidators.requiredIf( + { + x: true, + z: 'zyx' + }, + () => false + ); + expect(validatorFn(form.controls['z'])).toBeNull(); + }); + + it('should error because of successful condition', () => { + const conditionFn = (value) => { + return value === 'abc'; + }; + // Define prereqs that force the validator to validate the value of + // the 'y' control. + const validatorFn = CdValidators.requiredIf( + { + x: true, + z: '' + }, + conditionFn + ); + expect(validatorFn(form.controls['y'])).toEqual({ required: true }); + }); + }); + + describe('custom validation', () => { + let form: FormGroup; + + beforeEach(() => { + form = new FormGroup({ + x: new FormControl(3, CdValidators.custom('odd', (x) => x % 2 === 1)), + y: new FormControl( + 5, + CdValidators.custom('not-dividable-by-x', (y) => { + const x = (form && form.get('x').value) || 1; + return y % x !== 0; + }) + ) + }); + }); + + it('should test error and valid condition for odd x', () => { + const x = form.get('x'); + x.updateValueAndValidity(); + expect(x.hasError('odd')).toBeTruthy(); + x.setValue(4); + expect(x.valid).toBeTruthy(); + }); + + it('should test error and valid condition for y if its dividable by x', () => { + const y = form.get('y'); + y.updateValueAndValidity(); + expect(y.hasError('not-dividable-by-x')).toBeTruthy(); + y.setValue(6); + y.updateValueAndValidity(); + expect(y.valid).toBeTruthy(); + }); + }); + + describe('validate if condition', () => { + let form: FormGroup; + + beforeEach(() => { + form = new FormGroup({ + x: new FormControl(3), + y: new FormControl(5) + }); + CdValidators.validateIf(form.get('x'), () => ((form && form.get('y').value) || 0) > 10, [ + CdValidators.custom('min', (x) => x < 7), + CdValidators.custom('max', (x) => x > 12) + ]); + }); + + it('should test min error', () => { + const x = form.get('x'); + const y = form.get('y'); + expect(x.valid).toBeTruthy(); + y.setValue(11); + x.updateValueAndValidity(); + expect(x.hasError('min')).toBeTruthy(); + }); + + it('should test max error', () => { + const x = form.get('x'); + const y = form.get('y'); + expect(x.valid).toBeTruthy(); + y.setValue(11); + x.setValue(13); + expect(x.hasError('max')).toBeTruthy(); + }); + + it('should test valid number with validation', () => { + const x = form.get('x'); + const y = form.get('y'); + expect(x.valid).toBeTruthy(); + y.setValue(11); + x.setValue(12); + expect(x.valid).toBeTruthy(); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts new file mode 100644 index 000000000000..875e18379e94 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts @@ -0,0 +1,99 @@ +import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; + +import * as _ from 'lodash'; + +export function isEmptyInputValue(value: any): boolean { + return value == null || value.length === 0; +} + +export class CdValidators { + /** + * Validator that performs email validation. In contrast to the Angular + * email validator an empty email will not be handled as invalid. + */ + static email(control: AbstractControl): ValidationErrors | null { + // Exit immediately if value is empty. + if (isEmptyInputValue(control.value)) { + return null; + } + return Validators.email(control); + } + + /** + * Validator that requires controls to fulfill the specified condition if + * the specified prerequisites matches. If the prerequisites are fulfilled, + * then the given function is executed and if it succeeds, the 'required' + * validation error will be returned, otherwise null. + * @param {Object} prerequisites An object containing the prerequisites. + * ### Example + * ```typescript + * { + * 'generate_key': true, + * 'username': 'Max Mustermann' + * } + * ``` + * Only if all prerequisites are fulfilled, then the validation of the + * control will be triggered. + * @param {Function | undefined} condition The function to be executed when all + * prerequisites are fulfilled. If not set, then the {@link isEmptyInputValue} + * function will be used by default. The control's value is used as function + * argument. The function must return true to set the validation error. + * @return {ValidatorFn} Returns the validator function. + */ + static requiredIf(prerequisites: Object, condition?: Function | undefined): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + // Check if all prerequisites matches. + if ( + !Object.keys(prerequisites).every((key) => { + return control.parent && control.parent.get(key).value === prerequisites[key]; + }) + ) { + return null; + } + const success = _.isFunction(condition) + ? condition.call(condition, control.value) + : isEmptyInputValue(control.value); + return success ? { required: true } : null; + }; + } + + /** + * Custom validation by passing a name for the error and a function as error condition. + * + * @param {string} error + * @param {Function} condition - a truthy return value will trigger the error + * @returns {ValidatorFn} + */ + static custom(error: string, condition: Function): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } => { + const value = condition.call(this, control.value); + if (value) { + return { [error]: value }; + } + return null; + }; + } + + /** + * Validate form control if condition is true with validators. + * + * @param {AbstractControl} formControl + * @param {Function} condition + * @param {ValidatorFn[]} validators + */ + static validateIf( + formControl: AbstractControl, + condition: Function, + validators: ValidatorFn[] + ) { + formControl.setValidators((control: AbstractControl): { + [key: string]: any; + } => { + const value = condition.call(this); + if (value) { + return Validators.compose(validators)(control); + } + return null; + }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/validators/cd-validators.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/validators/cd-validators.spec.ts deleted file mode 100644 index fb682970c807..000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/validators/cd-validators.spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { FormControl, FormGroup } from '@angular/forms'; - -import { CdValidators } from './cd-validators'; - -describe('CdValidators', () => { - describe('email', () => { - it('should not error on an empty email address', () => { - const control = new FormControl(''); - expect(CdValidators.email(control)).toBeNull(); - }); - - it('should not error on valid email address', () => { - const control = new FormControl('dashboard@ceph.com'); - expect(CdValidators.email(control)).toBeNull(); - }); - - it('should error on invalid email address', () => { - const control = new FormControl('xyz'); - expect(CdValidators.email(control)).toEqual({ email: true }); - }); - }); - - describe('requiredIf', () => { - let form: FormGroup; - - beforeEach(() => { - form = new FormGroup({ - x: new FormControl(true), - y: new FormControl('abc'), - z: new FormControl('') - }); - }); - - it('should not error because all conditions are fulfilled', () => { - form.get('z').setValue('zyx'); - const validatorFn = CdValidators.requiredIf({ - x: true, - y: 'abc' - }); - expect(validatorFn(form.controls['z'])).toBeNull(); - }); - - it('should not error because of unmet prerequisites', () => { - // Define prereqs that do not match the current values of the form fields. - const validatorFn = CdValidators.requiredIf({ - x: false, - y: 'xyz' - }); - // The validator must succeed because the prereqs do not match, so the - // validation of the 'z' control will be skipped. - expect(validatorFn(form.controls['z'])).toBeNull(); - }); - - it('should error because of an empty value', () => { - // Define prereqs that force the validator to validate the value of - // the 'z' control. - const validatorFn = CdValidators.requiredIf({ - x: true, - y: 'abc' - }); - // The validator must fail because the value of control 'z' is empty. - expect(validatorFn(form.controls['z'])).toEqual({ required: true }); - }); - - it('should not error because of unsuccessful condition', () => { - form.get('z').setValue('zyx'); - // Define prereqs that force the validator to validate the value of - // the 'z' control. - const validatorFn = CdValidators.requiredIf( - { - x: true, - z: 'zyx' - }, - () => false - ); - expect(validatorFn(form.controls['z'])).toBeNull(); - }); - - it('should error because of successful condition', () => { - const conditionFn = (value) => { - return value === 'abc'; - }; - // Define prereqs that force the validator to validate the value of - // the 'y' control. - const validatorFn = CdValidators.requiredIf( - { - x: true, - z: '' - }, - conditionFn - ); - expect(validatorFn(form.controls['y'])).toEqual({ required: true }); - }); - }); - - describe('custom validation', () => { - let form: FormGroup; - - beforeEach(() => { - form = new FormGroup({ - x: new FormControl(3, CdValidators.custom('odd', (x) => x % 2 === 1)), - y: new FormControl( - 5, - CdValidators.custom('not-dividable-by-x', (y) => { - const x = (form && form.get('x').value) || 1; - return y % x !== 0; - }) - ) - }); - }); - - it('should test error and valid condition for odd x', () => { - const x = form.get('x'); - x.updateValueAndValidity(); - expect(x.hasError('odd')).toBeTruthy(); - x.setValue(4); - expect(x.valid).toBeTruthy(); - }); - - it('should test error and valid condition for y if its dividable by x', () => { - const y = form.get('y'); - y.updateValueAndValidity(); - expect(y.hasError('not-dividable-by-x')).toBeTruthy(); - y.setValue(6); - y.updateValueAndValidity(); - expect(y.valid).toBeTruthy(); - }); - }); - - describe('validate if condition', () => { - let form: FormGroup; - - beforeEach(() => { - form = new FormGroup({ - x: new FormControl(3), - y: new FormControl(5) - }); - CdValidators.validateIf(form.get('x'), () => ((form && form.get('y').value) || 0) > 10, [ - CdValidators.custom('min', (x) => x < 7), - CdValidators.custom('max', (x) => x > 12) - ]); - }); - - it('should test min error', () => { - const x = form.get('x'); - const y = form.get('y'); - expect(x.valid).toBeTruthy(); - y.setValue(11); - x.updateValueAndValidity(); - expect(x.hasError('min')).toBeTruthy(); - }); - - it('should test max error', () => { - const x = form.get('x'); - const y = form.get('y'); - expect(x.valid).toBeTruthy(); - y.setValue(11); - x.setValue(13); - expect(x.hasError('max')).toBeTruthy(); - }); - - it('should test valid number with validation', () => { - const x = form.get('x'); - const y = form.get('y'); - expect(x.valid).toBeTruthy(); - y.setValue(11); - x.setValue(12); - expect(x.valid).toBeTruthy(); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/validators/cd-validators.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/validators/cd-validators.ts deleted file mode 100644 index 875e18379e94..000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/validators/cd-validators.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; - -import * as _ from 'lodash'; - -export function isEmptyInputValue(value: any): boolean { - return value == null || value.length === 0; -} - -export class CdValidators { - /** - * Validator that performs email validation. In contrast to the Angular - * email validator an empty email will not be handled as invalid. - */ - static email(control: AbstractControl): ValidationErrors | null { - // Exit immediately if value is empty. - if (isEmptyInputValue(control.value)) { - return null; - } - return Validators.email(control); - } - - /** - * Validator that requires controls to fulfill the specified condition if - * the specified prerequisites matches. If the prerequisites are fulfilled, - * then the given function is executed and if it succeeds, the 'required' - * validation error will be returned, otherwise null. - * @param {Object} prerequisites An object containing the prerequisites. - * ### Example - * ```typescript - * { - * 'generate_key': true, - * 'username': 'Max Mustermann' - * } - * ``` - * Only if all prerequisites are fulfilled, then the validation of the - * control will be triggered. - * @param {Function | undefined} condition The function to be executed when all - * prerequisites are fulfilled. If not set, then the {@link isEmptyInputValue} - * function will be used by default. The control's value is used as function - * argument. The function must return true to set the validation error. - * @return {ValidatorFn} Returns the validator function. - */ - static requiredIf(prerequisites: Object, condition?: Function | undefined): ValidatorFn { - return (control: AbstractControl): ValidationErrors | null => { - // Check if all prerequisites matches. - if ( - !Object.keys(prerequisites).every((key) => { - return control.parent && control.parent.get(key).value === prerequisites[key]; - }) - ) { - return null; - } - const success = _.isFunction(condition) - ? condition.call(condition, control.value) - : isEmptyInputValue(control.value); - return success ? { required: true } : null; - }; - } - - /** - * Custom validation by passing a name for the error and a function as error condition. - * - * @param {string} error - * @param {Function} condition - a truthy return value will trigger the error - * @returns {ValidatorFn} - */ - static custom(error: string, condition: Function): ValidatorFn { - return (control: AbstractControl): { [key: string]: any } => { - const value = condition.call(this, control.value); - if (value) { - return { [error]: value }; - } - return null; - }; - } - - /** - * Validate form control if condition is true with validators. - * - * @param {AbstractControl} formControl - * @param {Function} condition - * @param {ValidatorFn[]} validators - */ - static validateIf( - formControl: AbstractControl, - condition: Function, - validators: ValidatorFn[] - ) { - formControl.setValidators((control: AbstractControl): { - [key: string]: any; - } => { - const value = condition.call(this); - if (value) { - return Validators.compose(validators)(control); - } - return null; - }); - } -}