]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add dueTime to rgw bucket validator 58247/head
authorNizamudeen A <nia@redhat.com>
Fri, 24 May 2024 15:16:17 +0000 (20:46 +0530)
committerNizamudeen A <nia@redhat.com>
Tue, 25 Jun 2024 06:22:48 +0000 (11:52 +0530)
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 <nia@redhat.com>
(cherry picked from commit c053782cc5d7f3f4493094d37b164e0531f46caf)

src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts

index 91f852024ffbda6ff79663b1f2bfa64c0df4609d..32f89c263a439b8520fd591fe7fba23e7473b2f7 100644 (file)
@@ -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();
index 836ab3d301b3f23a17961916549c4e00e7dd3594..ddc3799d15fdd6a15f4e6e48d200aa3828bb2645 100644 (file)
@@ -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();
index 64afa205e6926c98e2727cee0256d73ee6683b37..a00d08ad75e1f9697e93e62740281351840dc865 100644 (file)
@@ -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');
     }));
   });
index 5cf90fdea5cb3b68af08cda544bc2391c686e749..2924c9c64140803114eb414f51a44a0603127f22 100644 (file)
@@ -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 {
index 78171f650f5f29ddd277f8fbe4a2baae9cf74305..e2bd67418428657a8600b6e77cfe6e984360246f 100644 (file)
@@ -20,6 +20,8 @@ export function isEmptyInputValue(value: any): boolean {
 
 export type existsServiceFn = (value: any, ...args: any[]) => Observable<boolean>;
 
+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 }
+        )
+      );
     };
   }