From 995f60b355f7bf58378f6b509bd3cf6c984e4bb3 Mon Sep 17 00:00:00 2001 From: Ivo Almeida Date: Mon, 29 Jan 2024 10:58:43 +0000 Subject: [PATCH] mgr/dashboard: added dir search to snap schdl form Fixes: https://tracker.ceph.com/issues/64246 Signed-off-by: Ivo Almeida --- ...ephfs-snapshotschedule-form.component.html | 25 ++++---- .../cephfs-snapshotschedule-form.component.ts | 28 +++++--- .../src/app/ceph/cephfs/cephfs.module.ts | 3 +- .../src/app/shared/api/cephfs.service.ts | 3 +- .../api/directory-store.service.spec.ts | 24 +++++++ .../app/shared/api/directory-store.service.ts | 64 +++++++++++++++++++ 6 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/directory-store.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/api/directory-store.service.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.html index 9e9cde86b32..e315e8ab767 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.html @@ -15,20 +15,19 @@ i18n>Directory
- +
+ +
- Loading directories - - +
+
This field is required. diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts index 5b6d900e752..41e55c72599 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts @@ -2,10 +2,10 @@ import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; import { NgbActiveModal, NgbDateStruct, NgbTimeStruct } from '@ng-bootstrap/ng-bootstrap'; import { uniq } from 'lodash'; -import { Observable, timer } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; +import { Observable, OperatorFunction, of, timer } from 'rxjs'; +import { catchError, debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators'; import { CephfsSnapshotScheduleService } from '~/app/shared/api/cephfs-snapshot-schedule.service'; -import { CephfsService } from '~/app/shared/api/cephfs.service'; +import { DirectoryStoreService } from '~/app/shared/api/directory-store.service'; import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; import { Icons } from '~/app/shared/enum/icons.enum'; import { RepeatFrequency } from '~/app/shared/enum/repeat-frequency.enum'; @@ -13,12 +13,12 @@ import { RetentionFrequency } from '~/app/shared/enum/retention-frequency.enum'; import { CdForm } from '~/app/shared/forms/cd-form'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; import { CdTableColumn } from '~/app/shared/models/cd-table-column'; -import { CephfsDir } from '~/app/shared/models/cephfs-directory-models'; import { FinishedTask } from '~/app/shared/models/finished-task'; import { RetentionPolicy, SnapshotScheduleFormValue } from '~/app/shared/models/snapshot-schedule'; import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; const VALIDATON_TIMER = 300; +const DEBOUNCE_TIMER = 300; @Component({ selector: 'cd-cephfs-snapshotschedule-form', @@ -42,15 +42,14 @@ export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnIni resource!: string; columns!: CdTableColumn[]; - directories$!: Observable; constructor( public activeModal: NgbActiveModal, private actionLabels: ActionLabelsI18n, - private cephfsService: CephfsService, private snapScheduleService: CephfsSnapshotScheduleService, private taskWrapper: TaskWrapperService, - private cd: ChangeDetectorRef + private cd: ChangeDetectorRef, + public directoryStore: DirectoryStoreService ) { super(); this.resource = $localize`Snapshot schedule`; @@ -70,7 +69,7 @@ export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnIni ngOnInit(): void { this.action = this.actionLabels.CREATE; - this.directories$ = this.cephfsService.lsDir(this.id, '/', 3); + this.directoryStore.loadDirectories(this.id, '/', 3); this.createForm(); this.loadingReady(); } @@ -79,6 +78,19 @@ export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnIni return this.snapScheduleForm.get('retentionPolicies') as FormArray; } + search: OperatorFunction = (input: Observable) => + input.pipe( + debounceTime(DEBOUNCE_TIMER), + distinctUntilChanged(), + switchMap((term) => + this.directoryStore.search(term, this.id).pipe( + catchError(() => { + return of([]); + }) + ) + ) + ); + createForm() { this.snapScheduleForm = new CdFormGroup( { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts index b13273dc4a2..687dd0b93ee 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts @@ -45,7 +45,8 @@ import { CephfsSnapshotscheduleFormComponent } from './cephfs-snapshotschedule-f NgbTooltipModule, DataTableModule, NgbDatepickerModule, - NgbTimepickerModule + NgbTimepickerModule, + NgbTypeaheadModule ], declarations: [ CephfsDetailComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts index a265ae7a265..d2dfbc0e2a7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts @@ -6,6 +6,7 @@ import { Observable } from 'rxjs'; import { cdEncode } from '../decorators/cd-encode'; import { CephfsDir, CephfsQuotas } from '../models/cephfs-directory-models'; +import { shareReplay } from 'rxjs/operators'; @cdEncode @Injectable({ @@ -26,7 +27,7 @@ export class CephfsService { if (path) { apiPath += `&path=${encodeURIComponent(path)}`; } - return this.http.get(apiPath); + return this.http.get(apiPath).pipe(shareReplay()); } getCephfs(id: number) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/directory-store.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/directory-store.service.spec.ts new file mode 100644 index 00000000000..78590c89f53 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/directory-store.service.spec.ts @@ -0,0 +1,24 @@ +import { TestBed } from '@angular/core/testing'; + +import { DirectoryStoreService } from './directory-store.service'; +import { configureTestBed } from '~/testing/unit-test-helper'; +import { CephfsService } from './cephfs.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('DirectoryStoreService', () => { + let service: DirectoryStoreService; + + configureTestBed({ + imports: [HttpClientTestingModule], + providers: [CephfsService] + }); + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DirectoryStoreService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/directory-store.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/directory-store.service.ts new file mode 100644 index 00000000000..cdc5337ac12 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/directory-store.service.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@angular/core'; +import { CephfsService } from './cephfs.service'; +import { BehaviorSubject, Observable, Subject, timer } from 'rxjs'; +import { CephfsDir } from '../models/cephfs-directory-models'; +import { filter, map, retry, share, switchMap, takeUntil, tap } from 'rxjs/operators'; + +type DirectoryStore = Record; + +const POLLING_INTERVAL = 600 * 1000; + +@Injectable({ + providedIn: 'root' +}) +export class DirectoryStoreService { + private _directoryStoreSubject = new BehaviorSubject({}); + + readonly directoryStore$: Observable = this._directoryStoreSubject.asObservable(); + + stopDirectoryPolling = new Subject(); + + isLoading = true; + + constructor(private cephFsService: CephfsService) {} + + loadDirectories(id: number, path = '/', depth = 3) { + this.directoryStore$ + .pipe( + filter((store: DirectoryStore) => !Boolean(store[id])), + switchMap(() => + timer(0, POLLING_INTERVAL).pipe( + switchMap(() => + this.cephFsService.lsDir(id, path, depth).pipe( + tap((response) => { + this.isLoading = false; + this._directoryStoreSubject.next({ [id]: response }); + }) + ) + ), + retry(), + share(), + takeUntil(this.stopDirectoryPolling) + ) + ) + ) + .subscribe(); + } + + search(term: string, id: number, limit = 5) { + return this.directoryStore$.pipe( + map((store: DirectoryStore) => { + const regEx = new RegExp(term, 'gi'); + const results = store[id] + .filter((x) => regEx.test(x.path)) + .map((x) => x.path) + .slice(0, limit); + return results; + }) + ); + } + + stopPollingDictories() { + this.stopDirectoryPolling.next(); + } +} -- 2.39.5