Skip to content

Commit c053782

Browse files
committed
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 <[email protected]>
1 parent af71de4 commit c053782

File tree

5 files changed

+38
-24
lines changed

5 files changed

+38
-24
lines changed

src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PageHelper } from '../page-helper.po';
22

3+
const WAIT_TIMER = 500;
34
const pages = {
45
index: { url: '#/rgw/bucket', id: 'cd-rgw-bucket-list' },
56
create: { url: '#/rgw/bucket/create', id: 'cd-rgw-bucket-form' }
@@ -44,7 +45,7 @@ export class BucketsPageHelper extends PageHelper {
4445
}
4546

4647
// Click the create button and wait for bucket to be made
47-
cy.contains('button', 'Create Bucket').click();
48+
cy.contains('button', 'Create Bucket').wait(WAIT_TIMER).click();
4849

4950
this.getFirstTableCell(name).should('exist');
5051
}
@@ -119,7 +120,7 @@ export class BucketsPageHelper extends PageHelper {
119120

120121
cy.get('label[for=versioning]').click();
121122
cy.get('input[id=versioning]').should('not.be.checked');
122-
cy.contains('button', 'Edit Bucket').click();
123+
cy.contains('button', 'Edit Bucket').wait(WAIT_TIMER).click();
123124

124125
// Check versioning suspended:
125126
this.getExpandCollapseElement(name).click();
@@ -134,7 +135,7 @@ export class BucketsPageHelper extends PageHelper {
134135
// Gives an invalid name (too short), then waits for dashboard to determine validity
135136
cy.get('@nameInputField').type('rq');
136137

137-
cy.contains('button', 'Create Bucket').click(); // To trigger a validation
138+
cy.contains('button', 'Create Bucket').wait(WAIT_TIMER).click(); // To trigger a validation
138139

139140
// Waiting for website to decide if name is valid or not
140141
// Check that name input field was marked invalid in the css
@@ -166,7 +167,7 @@ export class BucketsPageHelper extends PageHelper {
166167

167168
// Clicks the Create Bucket button but the page doesn't move.
168169
// Done by testing for the breadcrumb
169-
cy.contains('button', 'Create Bucket').click(); // Clicks Create Bucket button
170+
cy.contains('button', 'Create Bucket').wait(WAIT_TIMER).click(); // Clicks Create Bucket button
170171
this.expectBreadcrumbText('Create');
171172
// content in fields seems to subsist through tests if not cleared, so it is cleared
172173
cy.get('@nameInputField').clear();

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,22 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC
267267

268268
submit() {
269269
// Exit immediately if the form isn't dirty.
270-
if (this.bucketForm.getValue('encryption_enabled') == null) {
271-
this.bucketForm.get('encryption_enabled').setValue(false);
272-
this.bucketForm.get('encryption_type').setValue(null);
273-
}
274270
if (this.bucketForm.pristine) {
275271
this.goToListView();
276272
return;
277273
}
274+
275+
// Ensure that no validation is pending
276+
if (this.bucketForm.pending) {
277+
this.bucketForm.setErrors({ cdSubmitButton: true });
278+
return;
279+
}
280+
281+
if (this.bucketForm.getValue('encryption_enabled') == null) {
282+
this.bucketForm.get('encryption_enabled').setValue(false);
283+
this.bucketForm.get('encryption_type').setValue(null);
284+
}
285+
278286
const values = this.bucketForm.value;
279287
const xmlStrTags = this.tagsToXML(this.tags);
280288
const bucketPolicy = this.getBucketPolicy();

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { RgwUserCapabilities } from '../models/rgw-user-capabilities';
1818
import { RgwUserCapability } from '../models/rgw-user-capability';
1919
import { RgwUserS3Key } from '../models/rgw-user-s3-key';
2020
import { RgwUserFormComponent } from './rgw-user-form.component';
21+
import { DUE_TIMER } from '~/app/shared/forms/cd-validators';
2122

2223
describe('RgwUserFormComponent', () => {
2324
let component: RgwUserFormComponent;
@@ -162,14 +163,14 @@ describe('RgwUserFormComponent', () => {
162163
it('should validate that username is valid', fakeAsync(() => {
163164
spyOn(rgwUserService, 'get').and.returnValue(throwError('foo'));
164165
formHelper.setValue('user_id', 'ab', true);
165-
tick();
166+
tick(DUE_TIMER);
166167
formHelper.expectValid('user_id');
167168
}));
168169

169170
it('should validate that username is invalid', fakeAsync(() => {
170171
spyOn(rgwUserService, 'get').and.returnValue(observableOf({}));
171172
formHelper.setValue('user_id', 'abc', true);
172-
tick();
173+
tick(DUE_TIMER);
173174
formHelper.expectError('user_id', 'notUnique');
174175
}));
175176
});

src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { of as observableOf } from 'rxjs';
66

77
import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
88
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
9-
import { CdValidators } from '~/app/shared/forms/cd-validators';
9+
import { CdValidators, DUE_TIMER } from '~/app/shared/forms/cd-validators';
1010
import { FormHelper } from '~/testing/unit-test-helper';
1111

1212
let mockBucketExists = observableOf(true);
@@ -771,7 +771,7 @@ describe('CdValidators', () => {
771771
describe('bucket', () => {
772772
const testValidator = (name: string, valid: boolean, expectedError?: string) => {
773773
formHelper.setValue('x', name, true);
774-
tick();
774+
tick(DUE_TIMER);
775775
if (valid) {
776776
formHelper.expectValid('x');
777777
} else {

src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export function isEmptyInputValue(value: any): boolean {
2020

2121
export type existsServiceFn = (value: any, ...args: any[]) => Observable<boolean>;
2222

23+
export const DUE_TIMER = 500;
24+
2325
export class CdValidators {
2426
/**
2527
* Validator that performs email validation. In contrast to the Angular
@@ -347,9 +349,12 @@ export class CdValidators {
347349
* boolean 'true' if the given value exists, otherwise 'false'.
348350
* @param serviceFnThis {any} The object to be used as the 'this' object
349351
* when calling the serviceFn function. Defaults to null.
350-
* @param {number|Date} dueTime The delay time to wait before the
351-
* serviceFn call is executed. This is useful to prevent calls on
352-
* every keystroke. Defaults to 500.
352+
* @param usernameFn {Function} Specifically used in rgw user form to
353+
* validate the tenant$username format
354+
* @param uidField {boolean} Specifically used in rgw user form to
355+
* validate the tenant$username format
356+
* @param extraArgs {...any} Any extra arguments that need to be passed
357+
* to the serviceFn function.
353358
* @return {AsyncValidatorFn} Returns an asynchronous validator function
354359
* that returns an error map with the `notUnique` property if the
355360
* validation check succeeds, otherwise `null`.
@@ -377,7 +382,7 @@ export class CdValidators {
377382
}
378383
}
379384

380-
return observableTimer().pipe(
385+
return observableTimer(DUE_TIMER).pipe(
381386
switchMapTo(serviceFn.call(serviceFnThis, uName, ...extraArgs)),
382387
map((resp: boolean) => {
383388
if (!resp) {
@@ -480,7 +485,7 @@ export class CdValidators {
480485
if (_.isFunction(usernameFn)) {
481486
username = usernameFn();
482487
}
483-
return observableTimer(500).pipe(
488+
return observableTimer(DUE_TIMER).pipe(
484489
switchMapTo(_.invoke(userServiceThis, 'validatePassword', control.value, username)),
485490
map((resp: { valid: boolean; credits: number; valuation: string }) => {
486491
if (_.isFunction(callback)) {
@@ -601,13 +606,12 @@ export class CdValidators {
601606
if (control.pristine || !control.value) {
602607
return observableOf({ required: true });
603608
}
604-
return rgwBucketService
605-
.exists(control.value)
606-
.pipe(
607-
map((existenceResult: boolean) =>
608-
existenceResult === requiredExistenceResult ? null : { bucketNameNotAllowed: true }
609-
)
610-
);
609+
return observableTimer(DUE_TIMER).pipe(
610+
switchMapTo(rgwBucketService.exists(control.value)),
611+
map((existenceResult: boolean) =>
612+
existenceResult === requiredExistenceResult ? null : { bucketNameNotAllowed: true }
613+
)
614+
);
611615
};
612616
}
613617

0 commit comments

Comments
 (0)