From bc908a215ac3aa02a0dca185cd50f822b34ba0b5 Mon Sep 17 00:00:00 2001 From: Volker Theile Date: Thu, 2 Aug 2018 13:17:24 +0200 Subject: [PATCH] mgr/dashboard: Every keystroke for the username in the RGW user form triggers an API call Fixes: https://tracker.ceph.com/issues/25161 Signed-off-by: Volker Theile --- .../rgw-user-form.component.spec.ts | 74 ++++++++++--------- .../rgw-user-form/rgw-user-form.component.ts | 42 +++++++---- 2 files changed, 68 insertions(+), 48 deletions(-) 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 a2609aa0a45d6..e47a33571925c 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 @@ -1,10 +1,10 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { BsModalService } from 'ngx-bootstrap/modal'; -import { of as observableOf } from 'rxjs'; +import { Observable, of as observableOf } from 'rxjs'; import { configureTestBed } from '../../../../testing/unit-test-helper'; import { RgwUserService } from '../../../shared/api/rgw-user.service'; @@ -123,41 +123,49 @@ describe('RgwUserFormComponent', () => { it('should validate user id (1/3)', () => { const validatorFn = component.userIdValidator(); const ctrl = new FormControl(''); - const validatorPromise = validatorFn(ctrl); - expect(validatorPromise instanceof Promise).toBeTruthy(); - if (validatorPromise instanceof Promise) { - validatorPromise.then((resp) => { + const validator$ = validatorFn(ctrl); + expect(validator$ instanceof Observable).toBeTruthy(); + if (validator$ instanceof Observable) { + validator$.subscribe((resp) => { expect(resp).toBe(null); }); } }); - it('should validate user id (2/3)', () => { - const validatorFn = component.userIdValidator(); - const ctrl = new FormControl('ab'); - ctrl.markAsDirty(); - const validatorPromise = validatorFn(ctrl); - expect(validatorPromise instanceof Promise).toBeTruthy(); - if (validatorPromise instanceof Promise) { - validatorPromise.then((resp) => { - expect(resp).toBe(null); - }); - } - }); - - it('should validate user id (3/3)', () => { - queryResult = ['abc']; - const validatorFn = component.userIdValidator(); - const ctrl = new FormControl('abc'); - ctrl.markAsDirty(); - const validatorPromise = validatorFn(ctrl); - expect(validatorPromise instanceof Promise).toBeTruthy(); - if (validatorPromise instanceof Promise) { - validatorPromise.then((resp) => { - expect(resp instanceof Object).toBeTruthy(); - expect(resp.userIdExists).toBeTruthy(); - }); - } - }); + it( + 'should validate user id (2/3)', + fakeAsync(() => { + const validatorFn = component.userIdValidator(0); + const ctrl = new FormControl('ab'); + ctrl.markAsDirty(); + const validator$ = validatorFn(ctrl); + expect(validator$ instanceof Observable).toBeTruthy(); + if (validator$ instanceof Observable) { + validator$.subscribe((resp) => { + expect(resp).toBe(null); + }); + tick(); + } + }) + ); + + it( + 'should validate user id (3/3)', + fakeAsync(() => { + queryResult = ['abc']; + const validatorFn = component.userIdValidator(0); + const ctrl = new FormControl('abc'); + ctrl.markAsDirty(); + const validator$ = validatorFn(ctrl); + expect(validator$ instanceof Observable).toBeTruthy(); + if (validator$ instanceof Observable) { + validator$.subscribe((resp) => { + expect(resp instanceof Object).toBeTruthy(); + expect(resp.userIdExists).toBeTruthy(); + }); + tick(); + } + }) + ); }); }); 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 fb79e8bfe7964..f681542fee6ae 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 @@ -4,7 +4,13 @@ import { ActivatedRoute, Router } from '@angular/router'; import * as _ from 'lodash'; import { BsModalService } from 'ngx-bootstrap'; -import { forkJoin as observableForkJoin, Observable } from 'rxjs'; +import { + forkJoin as observableForkJoin, + Observable, + of as observableOf, + timer as observableTimer +} from 'rxjs'; +import { map, switchMapTo, take } from 'rxjs/operators'; import { RgwUserService } from '../../../shared/api/rgw-user.service'; import { CdFormBuilder } from '../../../shared/forms/cd-form-builder'; @@ -274,25 +280,31 @@ export class RgwUserFormComponent implements OnInit { /** * Validate the username. + * @param {number|Date} dueTime The delay time to wait before the + * API call is executed. This is useful to prevent API calls on + * every keystroke. Defaults to 500. */ - userIdValidator(): AsyncValidatorFn { + userIdValidator(dueTime = 500): AsyncValidatorFn { const rgwUserService = this.rgwUserService; - return (control: AbstractControl): Promise => { - return new Promise((resolve) => { - // Exit immediately if user has not interacted with the control yet - // or the control value is empty. - if (control.pristine || control.value === '') { - resolve(null); - return; - } - rgwUserService.exists(control.value).subscribe((resp: boolean) => { + return (control: AbstractControl): Observable => { + // Exit immediately if user has not interacted with the control yet + // or the control value is empty. + if (control.pristine || control.value === '') { + return observableOf(null); + } + // Forgot previous requests if a new one arrives within the specified + // delay time. + return observableTimer(dueTime).pipe( + switchMapTo(rgwUserService.exists(control.value)), + map((resp: boolean) => { if (!resp) { - resolve(null); + return null; } else { - resolve({ userIdExists: true }); + return { userIdExists: true }; } - }); - }); + }), + take(1) + ); }; } -- 2.39.5