From: Nizamudeen A Date: Fri, 24 May 2024 15:16:17 +0000 (+0530) Subject: mgr/dashboard: add dueTime to rgw bucket validator X-Git-Tag: v18.2.5~551^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=795d5b2395bc0128ed7a5f381c061128c8c9da4b;p=ceph.git mgr/dashboard: add dueTime to rgw bucket validator the unique async validator which checks if the typed bucket is existing or not in the bucket creation form sends a request to the backend on each keystroke. Each keystroke will raise an exception if the bucket is not found. Fixes: https://tracker.ceph.com/issues/66221 Signed-off-by: Nizamudeen A (cherry picked from commit c053782cc5d7f3f4493094d37b164e0531f46caf) --- diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts index 91f852024ffbd..32f89c263a439 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts @@ -1,5 +1,6 @@ import { PageHelper } from '../page-helper.po'; +const WAIT_TIMER = 500; const pages = { index: { url: '#/rgw/bucket', id: 'cd-rgw-bucket-list' }, create: { url: '#/rgw/bucket/create', id: 'cd-rgw-bucket-form' } @@ -44,7 +45,7 @@ export class BucketsPageHelper extends PageHelper { } // Click the create button and wait for bucket to be made - cy.contains('button', 'Create Bucket').click(); + cy.contains('button', 'Create Bucket').wait(WAIT_TIMER).click(); this.getFirstTableCell(name).should('exist'); } @@ -119,7 +120,7 @@ export class BucketsPageHelper extends PageHelper { cy.get('label[for=versioning]').click(); cy.get('input[id=versioning]').should('not.be.checked'); - cy.contains('button', 'Edit Bucket').click(); + cy.contains('button', 'Edit Bucket').wait(WAIT_TIMER).click(); // Check versioning suspended: this.getExpandCollapseElement(name).click(); @@ -134,7 +135,7 @@ export class BucketsPageHelper extends PageHelper { // Gives an invalid name (too short), then waits for dashboard to determine validity cy.get('@nameInputField').type('rq'); - cy.contains('button', 'Create Bucket').click(); // To trigger a validation + cy.contains('button', 'Create Bucket').wait(WAIT_TIMER).click(); // To trigger a validation // Waiting for website to decide if name is valid or not // Check that name input field was marked invalid in the css @@ -166,7 +167,7 @@ export class BucketsPageHelper extends PageHelper { // Clicks the Create Bucket button but the page doesn't move. // Done by testing for the breadcrumb - cy.contains('button', 'Create Bucket').click(); // Clicks Create Bucket button + cy.contains('button', 'Create Bucket').wait(WAIT_TIMER).click(); // Clicks Create Bucket button this.expectBreadcrumbText('Create'); // content in fields seems to subsist through tests if not cleared, so it is cleared cy.get('@nameInputField').clear(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts index 836ab3d301b3f..ddc3799d15fdd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts @@ -267,14 +267,22 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC submit() { // Exit immediately if the form isn't dirty. - if (this.bucketForm.getValue('encryption_enabled') == null) { - this.bucketForm.get('encryption_enabled').setValue(false); - this.bucketForm.get('encryption_type').setValue(null); - } if (this.bucketForm.pristine) { this.goToListView(); return; } + + // Ensure that no validation is pending + if (this.bucketForm.pending) { + this.bucketForm.setErrors({ cdSubmitButton: true }); + return; + } + + if (this.bucketForm.getValue('encryption_enabled') == null) { + this.bucketForm.get('encryption_enabled').setValue(false); + this.bucketForm.get('encryption_type').setValue(null); + } + const values = this.bucketForm.value; const xmlStrTags = this.tagsToXML(this.tags); const bucketPolicy = this.getBucketPolicy(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts index 64afa205e6926..a00d08ad75e1f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts @@ -18,6 +18,7 @@ import { RgwUserCapabilities } from '../models/rgw-user-capabilities'; import { RgwUserCapability } from '../models/rgw-user-capability'; import { RgwUserS3Key } from '../models/rgw-user-s3-key'; import { RgwUserFormComponent } from './rgw-user-form.component'; +import { DUE_TIMER } from '~/app/shared/forms/cd-validators'; describe('RgwUserFormComponent', () => { let component: RgwUserFormComponent; @@ -162,14 +163,14 @@ describe('RgwUserFormComponent', () => { it('should validate that username is valid', fakeAsync(() => { spyOn(rgwUserService, 'get').and.returnValue(throwError('foo')); formHelper.setValue('user_id', 'ab', true); - tick(); + tick(DUE_TIMER); formHelper.expectValid('user_id'); })); it('should validate that username is invalid', fakeAsync(() => { spyOn(rgwUserService, 'get').and.returnValue(observableOf({})); formHelper.setValue('user_id', 'abc', true); - tick(); + tick(DUE_TIMER); formHelper.expectError('user_id', 'notUnique'); })); }); 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 index 5cf90fdea5cb3..2924c9c641408 100644 --- 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 @@ -6,7 +6,7 @@ import { of as observableOf } from 'rxjs'; import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; -import { CdValidators } from '~/app/shared/forms/cd-validators'; +import { CdValidators, DUE_TIMER } from '~/app/shared/forms/cd-validators'; import { FormHelper } from '~/testing/unit-test-helper'; let mockBucketExists = observableOf(true); @@ -771,7 +771,7 @@ describe('CdValidators', () => { describe('bucket', () => { const testValidator = (name: string, valid: boolean, expectedError?: string) => { formHelper.setValue('x', name, true); - tick(); + tick(DUE_TIMER); if (valid) { formHelper.expectValid('x'); } else { 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 index 78171f650f5f2..e2bd674184286 100644 --- 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 @@ -20,6 +20,8 @@ export function isEmptyInputValue(value: any): boolean { export type existsServiceFn = (value: any, ...args: any[]) => Observable; +export const DUE_TIMER = 500; + export class CdValidators { /** * Validator that performs email validation. In contrast to the Angular @@ -347,9 +349,12 @@ export class CdValidators { * boolean 'true' if the given value exists, otherwise 'false'. * @param serviceFnThis {any} The object to be used as the 'this' object * when calling the serviceFn function. Defaults to null. - * @param {number|Date} dueTime The delay time to wait before the - * serviceFn call is executed. This is useful to prevent calls on - * every keystroke. Defaults to 500. + * @param usernameFn {Function} Specifically used in rgw user form to + * validate the tenant$username format + * @param uidField {boolean} Specifically used in rgw user form to + * validate the tenant$username format + * @param extraArgs {...any} Any extra arguments that need to be passed + * to the serviceFn function. * @return {AsyncValidatorFn} Returns an asynchronous validator function * that returns an error map with the `notUnique` property if the * validation check succeeds, otherwise `null`. @@ -377,7 +382,7 @@ export class CdValidators { } } - return observableTimer().pipe( + return observableTimer(DUE_TIMER).pipe( switchMapTo(serviceFn.call(serviceFnThis, uName, ...extraArgs)), map((resp: boolean) => { if (!resp) { @@ -480,7 +485,7 @@ export class CdValidators { if (_.isFunction(usernameFn)) { username = usernameFn(); } - return observableTimer(500).pipe( + return observableTimer(DUE_TIMER).pipe( switchMapTo(_.invoke(userServiceThis, 'validatePassword', control.value, username)), map((resp: { valid: boolean; credits: number; valuation: string }) => { if (_.isFunction(callback)) { @@ -601,13 +606,12 @@ export class CdValidators { if (control.pristine || !control.value) { return observableOf({ required: true }); } - return rgwBucketService - .exists(control.value) - .pipe( - map((existenceResult: boolean) => - existenceResult === requiredExistenceResult ? null : { bucketNameNotAllowed: true } - ) - ); + return observableTimer(DUE_TIMER).pipe( + switchMapTo(rgwBucketService.exists(control.value)), + map((existenceResult: boolean) => + existenceResult === requiredExistenceResult ? null : { bucketNameNotAllowed: true } + ) + ); }; }