From: Tiago Melo Date: Tue, 12 May 2020 19:29:16 +0000 (+0000) Subject: mgr/dashboard: Improve Summary's subscribe methods X-Git-Tag: v15.2.5~169^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=1f7d690f2ef8bc81c487aca537c84697bd015907;p=ceph.git mgr/dashboard: Improve Summary's subscribe methods Now subscribe only emits if the value is not undefined, removing the need to do the validation after each subscribe. Add a new subscribeOnce method, which replaces getCurrentSummary. Like the previous one, this will only emit when there is a non undefined value, and will only emit once. After it emits, the subscription is closed automatically. Fixes: https://tracker.ceph.com/issues/36563 Signed-off-by: Tiago Melo (cherry picked from commit e41860412ebf7ebd1b1a03a9a0a822f1b56320b6) --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts index 026cf343240..40265dd6bc0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts @@ -146,10 +146,11 @@ export class IscsiTargetListComponent extends ListWithDetails implements OnInit, this.settings = settings; }); } else { - const summary = this.summaryservice.getCurrentSummary(); - const releaseName = this.cephReleaseNamePipe.transform(summary.version); - this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/#enabling-iscsi-management`; - this.status = result.message; + this.summaryservice.subscribeOnce((summary) => { + const releaseName = this.cephReleaseNamePipe.transform(summary.version); + this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/#enabling-iscsi-management`; + this.status = result.message; + }); } }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.spec.ts index 68dc6303f46..c51d5f70355 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.spec.ts @@ -16,6 +16,7 @@ import { import { RbdService } from '../../../shared/api/rbd.service'; import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { ExecutingTask } from '../../../shared/models/executing-task'; +import { Summary } from '../../../shared/models/summary.model'; import { SummaryService } from '../../../shared/services/summary.service'; import { TaskListService } from '../../../shared/services/task-list.service'; import { SharedModule } from '../../../shared/shared.module'; @@ -56,7 +57,7 @@ describe('RbdTrashListComponent', () => { it('should load trash images when summary is trigged', () => { spyOn(rbdService, 'listTrash').and.callThrough(); - summaryService['summaryDataSource'].next({ executingTasks: null }); + summaryService['summaryDataSource'].next(new Summary()); expect(rbdService.listTrash).toHaveBeenCalled(); }); @@ -90,7 +91,7 @@ describe('RbdTrashListComponent', () => { addImage('1'); addImage('2'); component.images = images; - summaryService['summaryDataSource'].next({ executingTasks: [] }); + summaryService['summaryDataSource'].next(new Summary()); spyOn(rbdService, 'listTrash').and.callFake(() => of([{ pool_name: 'rbd', status: 1, value: images }]) ); @@ -124,7 +125,7 @@ describe('RbdTrashListComponent', () => { }; beforeEach(() => { - summaryService['summaryDataSource'].next({ executingTasks: [] }); + summaryService['summaryDataSource'].next(new Summary()); spyOn(rbdService, 'listTrash').and.callFake(() => { of([{ pool_name: 'rbd', status: 1, value: images }]); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts index 5e96d126fdd..e08e235fe75 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts @@ -38,17 +38,9 @@ export class MonitoringListComponent implements OnInit { this.isPrometheusConfigured = true; }); - const subs = this.summaryService.subscribe((summary: any) => { - if (!summary) { - return; - } - + this.summaryService.subscribeOnce((summary) => { const releaseName = this.cephReleaseNamePipe.transform(summary.version); this.docsUrl = `https://docs.ceph.com/docs/${releaseName}/mgr/dashboard/#enabling-prometheus-alerting`; - - setTimeout(() => { - subs.unsubscribe(); - }, 0); }); // Activate tab according to given fragment diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.spec.ts index 2ac3ef6b281..97dfe0d03a8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.spec.ts @@ -18,7 +18,15 @@ describe('ServiceDetailsComponent', () => { configureTestBed({ imports: [HttpClientTestingModule, RouterTestingModule, TabsModule.forRoot(), SharedModule], declarations: [ServiceDetailsComponent, ServiceDaemonListComponent], - providers: [i18nProviders, { provide: SummaryService, useValue: { subscribe: jest.fn() } }] + providers: [ + i18nProviders, + { + provide: SummaryService, + useValue: { + subscribeOnce: jest.fn() + } + } + ] }); beforeEach(() => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-501/nfs-501.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-501/nfs-501.component.ts index 77b3b649332..985b3b3f531 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-501/nfs-501.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-501/nfs-501.component.ts @@ -24,19 +24,11 @@ export class Nfs501Component implements OnInit, OnDestroy { ) {} ngOnInit() { - const subs = this.summaryService.subscribe((summary: any) => { - if (!summary) { - return; - } - + this.summaryService.subscribeOnce((summary) => { const releaseName = this.cephReleaseNamePipe.transform(summary.version); this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/` + `#configuring-nfs-ganesha-in-the-dashboard`; - - setTimeout(() => { - subs.unsubscribe(); - }, 0); }); this.routeParamsSubscribe = this.route.params.subscribe((params: { message: string }) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts index 4b5ec0c9099..ec8ffe0f032 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts @@ -6,6 +6,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { TypeaheadModule } from 'ngx-bootstrap/typeahead'; import { ToastrModule } from 'ngx-toastr'; +import { of } from 'rxjs'; import { ActivatedRouteStub } from '../../../../testing/activated-route-stub'; import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; @@ -48,11 +49,11 @@ describe('NfsFormComponent', () => { beforeEach(() => { const summaryService = TestBed.get(SummaryService); spyOn(summaryService, 'refresh').and.callFake(() => true); - spyOn(summaryService, 'getCurrentSummary').and.callFake(() => { - return { + spyOn(summaryService, 'subscribeOnce').and.callFake(() => + of({ version: 'master' - }; - }); + }) + ); fixture = TestBed.createComponent(NfsFormComponent); component = fixture.componentInstance; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts index d152eaa96e6..50616fb2d94 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts @@ -123,9 +123,10 @@ export class NfsFormComponent implements OnInit { this.getData(promises); } - const summary = this.summaryservice.getCurrentSummary(); - const releaseName = this.cephReleaseNamePipe.transform(summary.version); - this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/radosgw/nfs/`; + this.summaryservice.subscribeOnce((summary) => { + const releaseName = this.cephReleaseNamePipe.transform(summary.version); + this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/radosgw/nfs/`; + }); } getData(promises: Observable[]) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.spec.ts index 36039aa4d97..8ad7e0d4e24 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.spec.ts @@ -16,6 +16,7 @@ import { import { NfsService } from '../../../shared/api/nfs.service'; import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component'; import { ExecutingTask } from '../../../shared/models/executing-task'; +import { Summary } from '../../../shared/models/summary.model'; import { SummaryService } from '../../../shared/services/summary.service'; import { TaskListService } from '../../../shared/services/task-list.service'; import { SharedModule } from '../../../shared/shared.module'; @@ -29,7 +30,7 @@ describe('NfsListComponent', () => { let nfsService: NfsService; let httpTesting: HttpTestingController; - const refresh = (data: object) => { + const refresh = (data: Summary) => { summaryService['summaryDataSource'].next(data); }; @@ -73,7 +74,7 @@ describe('NfsListComponent', () => { }); it('should load exports on init', () => { - refresh({}); + refresh(new Summary()); httpTesting.expectOne('api/nfs-ganesha/export'); expect(nfsService.list).toHaveBeenCalled(); }); @@ -130,7 +131,7 @@ describe('NfsListComponent', () => { addExport('b'); addExport('c'); component.exports = exports; - refresh({ executing_tasks: [], finished_tasks: [] }); + refresh(new Summary()); spyOn(nfsService, 'list').and.callFake(() => of(exports)); fixture.detectChanges(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts index 73d0925bc58..dbe4ec6f209 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts @@ -199,7 +199,10 @@ describe('PoolListComponent', () => { beforeEach(() => { summaryService = TestBed.get(SummaryService); - summaryService['summaryDataSource'].next({ executing_tasks: [], finished_tasks: [] }); + summaryService['summaryDataSource'].next({ + executing_tasks: [], + finished_tasks: [] + }); }); it('gets all pools without executing pools', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts index 67fb868acb4..02fbc18cdc6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts @@ -21,19 +21,11 @@ export class Rgw501Component implements OnInit, OnDestroy { ) {} ngOnInit() { - const subs = this.summaryService.subscribe((summary: any) => { - if (!summary) { - return; - } - + this.summaryService.subscribeOnce((summary) => { const releaseName = this.cephReleaseNamePipe.transform(summary.version); this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/` + `#enabling-the-object-gateway-management-frontend`; - - setTimeout(() => { - subs.unsubscribe(); - }, 0); }); this.routeParamsSubscribe = this.route.params.subscribe((params: { message: string }) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts index c22507eebed..231929933fe 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts @@ -40,10 +40,7 @@ export class AboutComponent implements OnInit, OnDestroy { this.projectConstants = AppConstants; this.hostAddr = window.location.hostname; this.modalVariables = this.setVariables(); - this.subs = this.summaryService.subscribe((summary: any) => { - if (!summary) { - return; - } + this.subs = this.summaryService.subscribe((summary) => { const version = summary.version.replace('ceph version ', '').split(' '); this.hostAddr = summary.mgr_host.replace(/(^\w+:|^)\/\//, '').replace(/\/$/, ''); this.versionNumber = version[0]; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts index cb6d0085dc9..e6821ddf960 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/dashboard-help/dashboard-help.component.ts @@ -28,17 +28,9 @@ export class DashboardHelpComponent implements OnInit { ) {} ngOnInit() { - const subs = this.summaryService.subscribe((summary: any) => { - if (!summary) { - return; - } - + this.summaryService.subscribeOnce((summary) => { const releaseName = this.cephReleaseNamePipe.transform(summary.version); this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/`; - - setTimeout(() => { - subs.unsubscribe(); - }, 0); }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts index e21c9eeb088..c184d0cda5f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts @@ -46,11 +46,8 @@ export class NavigationComponent implements OnInit, OnDestroy { ngOnInit() { this.subs.add( - this.summaryService.subscribe((data: any) => { - if (!data) { - return; - } - this.summaryData = data; + this.summaryService.subscribe((summary) => { + this.summaryData = summary; }) ); this.subs.add( diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts index aaedd546eff..b6cee7649d3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notifications/notifications.component.ts @@ -23,11 +23,8 @@ export class NotificationsComponent implements OnInit, OnDestroy { ngOnInit() { this.subs.add( - this.summaryService.subscribe((data: any) => { - if (!data) { - return; - } - this.hasRunningTasks = data.executing_tasks.length > 0; + this.summaryService.subscribe((summary) => { + this.hasRunningTasks = summary.executing_tasks.length > 0; }) ); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.ts index 4c4b5ecedfa..afb41361890 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/grafana/grafana.component.ts @@ -179,20 +179,13 @@ export class GrafanaComponent implements OnInit, OnChanges { three: 'grafana_three' }; - const subs = this.summaryService.subscribe((summary: any) => { - if (!summary) { - return; - } - + this.summaryService.subscribeOnce((summary) => { const releaseName = this.cephReleaseNamePipe.transform(summary.version); this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/` + `#enabling-the-embedding-of-grafana-dashboards`; - - setTimeout(() => { - subs.unsubscribe(); - }, 0); }); + this.settingsService.ifSettingConfigured('api/grafana/url', (url) => { this.grafanaExist = true; this.loading = false; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts index 0fdde25b6dc..5a0f9b603e9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts @@ -108,15 +108,12 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy { ); this.subs.add( - this.summaryService.subscribe((data: any) => { - if (!data) { - return; - } - this._handleTasks(data.executing_tasks); + this.summaryService.subscribe((summary) => { + this._handleTasks(summary.executing_tasks); this.mutex.acquire().then((release) => { _.filter( - data.finished_tasks, + summary.finished_tasks, (task: FinishedTask) => !this.last_task || moment(task.end_time).isAfter(this.last_task) ).forEach((task) => { const config = this.notificationService.finishedTaskToNotification(task, task.success); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.ts index 484c422314a..d4728bce702 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/orchestrator-doc-panel/orchestrator-doc-panel.component.ts @@ -17,17 +17,9 @@ export class OrchestratorDocPanelComponent implements OnInit { ) {} ngOnInit() { - const subs = this.summaryService.subscribe((summary: any) => { - if (!summary) { - return; - } - + this.summaryService.subscribeOnce((summary) => { const releaseName = this.cephReleaseNamePipe.transform(summary.version); this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/orchestrator/`; - - setTimeout(() => { - subs.unsubscribe(); - }, 0); }); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/finished-task.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/finished-task.ts index 9dc780963ad..9e7dd5f98ed 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/finished-task.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/finished-task.ts @@ -2,8 +2,8 @@ import { Task } from './task'; import { TaskException } from './task-exception'; export class FinishedTask extends Task { - begin_time: number; - end_time: number; + begin_time: string; + end_time: string; exception: TaskException; latency: number; progress: number; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/summary.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/summary.model.ts new file mode 100644 index 00000000000..f2854a0ebcf --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/summary.model.ts @@ -0,0 +1,15 @@ +import { ExecutingTask } from './executing-task'; +import { FinishedTask } from './finished-task'; + +export class Summary { + executing_tasks?: ExecutingTask[]; + filesystems?: any[]; + finished_tasks?: FinishedTask[]; + have_mon_connection?: boolean; + health_status?: string; + mgr_host?: string; + mgr_id?: string; + rbd_mirroring?: any; + rbd_pools?: any[]; + version?: string; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts index 100beefd2fb..14144921ef1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.spec.ts @@ -6,6 +6,7 @@ import { of as observableOf, Subscriber, Subscription } from 'rxjs'; import { configureTestBed } from '../../../testing/unit-test-helper'; import { ExecutingTask } from '../models/executing-task'; +import { Summary } from '../models/summary.model'; import { AuthStorageService } from './auth-storage.service'; import { SummaryService } from './summary.service'; @@ -14,7 +15,7 @@ describe('SummaryService', () => { let authStorageService: AuthStorageService; let subs: Subscription; - const summary: Record = { + const summary: Summary = { executing_tasks: [], health_status: 'HEALTH_OK', mgr_id: 'x', @@ -29,6 +30,8 @@ describe('SummaryService', () => { get: () => observableOf(summary) }; + const nextSummary = (newData: any) => summaryService['summaryDataSource'].next(newData); + configureTestBed({ imports: [RouterTestingModule], providers: [ @@ -66,23 +69,71 @@ describe('SummaryService', () => { subs.unsubscribe(); })); - describe('Should test methods after first refresh', () => { + describe('Should test subscribe without initial value', () => { + let result: Summary; + let i: number; + + const callback = (response: Summary) => { + i++; + result = response; + }; + beforeEach(() => { - authStorageService.set('foobar', undefined, undefined); - summaryService.refresh(); + i = 0; + result = undefined; + nextSummary(undefined); }); - it('should call getCurrentSummary', () => { - expect(summaryService.getCurrentSummary()).toEqual(summary); + it('should call subscribeOnce', () => { + const subscriber = summaryService.subscribeOnce(callback); + + expect(subscriber).toEqual(jasmine.any(Subscriber)); + expect(i).toBe(0); + expect(result).toEqual(undefined); + + nextSummary(undefined); + expect(i).toBe(0); + expect(result).toEqual(undefined); + expect(subscriber.closed).toBe(false); + + nextSummary(summary); + expect(result).toEqual(summary); + expect(i).toBe(1); + expect(subscriber.closed).toBe(true); + + nextSummary(summary); + expect(result).toEqual(summary); + expect(i).toBe(1); }); it('should call subscribe', () => { - let result; - const subscriber = summaryService.subscribe((data) => { - result = data; - }); + const subscriber = summaryService.subscribe(callback); + expect(subscriber).toEqual(jasmine.any(Subscriber)); + expect(i).toBe(0); + expect(result).toEqual(undefined); + + nextSummary(undefined); + expect(i).toBe(0); + expect(result).toEqual(undefined); + expect(subscriber.closed).toBe(false); + + nextSummary(summary); + expect(result).toEqual(summary); + expect(i).toBe(1); + expect(subscriber.closed).toBe(false); + + nextSummary(summary); expect(result).toEqual(summary); + expect(i).toBe(2); + expect(subscriber.closed).toBe(false); + }); + }); + + describe('Should test methods after first refresh', () => { + beforeEach(() => { + authStorageService.set('foobar', undefined, undefined); + summaryService.refresh(); }); it('should call addRunningTask', () => { @@ -92,7 +143,11 @@ describe('SummaryService', () => { image_name: 'someImage' }) ); - const result = summaryService.getCurrentSummary(); + let result: any; + summaryService.subscribeOnce((response) => { + result = response; + }); + expect(result.executing_tasks.length).toBe(1); expect(result.executing_tasks[0]).toEqual({ metadata: { image_name: 'someImage', pool_name: 'somePool' }, @@ -101,19 +156,23 @@ describe('SummaryService', () => { }); it('should call addRunningTask with duplicate task', () => { - let result = summaryService.getCurrentSummary(); + let result: any; + summaryService.subscribe((response) => { + result = response; + }); + const exec_task = new ExecutingTask('rbd/delete', { pool_name: 'somePool', image_name: 'someImage' }); result.executing_tasks = [exec_task]; - summaryService['summaryDataSource'].next(result); - result = summaryService.getCurrentSummary(); + nextSummary(result); + expect(result.executing_tasks.length).toBe(1); summaryService.addRunningTask(exec_task); - result = summaryService.getCurrentSummary(); + expect(result.executing_tasks.length).toBe(1); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts index 26038419214..a0783402773 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/summary.service.ts @@ -3,8 +3,10 @@ import { Injectable } from '@angular/core'; import * as _ from 'lodash'; import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { filter, first } from 'rxjs/operators'; import { ExecutingTask } from '../models/executing-task'; +import { Summary } from '../models/summary.model'; import { TimerService } from './timer.service'; @Injectable({ @@ -13,7 +15,7 @@ import { TimerService } from './timer.service'; export class SummaryService { readonly REFRESH_INTERVAL = 5000; // Observable sources - private summaryDataSource = new BehaviorSubject(null); + private summaryDataSource = new BehaviorSubject(null); // Observable streams summaryData$ = this.summaryDataSource.asObservable(); @@ -29,29 +31,35 @@ export class SummaryService { return this.retrieveSummaryObservable().subscribe(this.retrieveSummaryObserver()); } - private retrieveSummaryObservable(): Observable { - return this.http.get('api/summary'); + private retrieveSummaryObservable(): Observable { + return this.http.get('api/summary'); } - private retrieveSummaryObserver(): (data: any) => void { - return (data: Object) => { + private retrieveSummaryObserver(): (data: Summary) => void { + return (data: Summary) => { this.summaryDataSource.next(data); }; } /** - * Returns the current value of summaryData + * Subscribes to the summaryData and receive only the first, non undefined, value. */ - getCurrentSummary(): { [key: string]: any; executing_tasks: object[] } { - return this.summaryDataSource.getValue(); + subscribeOnce(next: (summary: Summary) => void, error?: (error: any) => void): Subscription { + return this.summaryData$ + .pipe( + filter((value) => !!value), + first() + ) + .subscribe(next, error); } /** * Subscribes to the summaryData, * which is updated periodically or when a new task is created. + * Will receive only non undefined values. */ - subscribe(next: (summary: any) => void, error?: (error: any) => void): Subscription { - return this.summaryData$.subscribe(next, error); + subscribe(next: (summary: Summary) => void, error?: (error: any) => void): Subscription { + return this.summaryData$.pipe(filter((value) => !!value)).subscribe(next, error); } /** diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-list.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-list.service.ts index 6d1f74b9fb1..9e5a7aa7327 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-list.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-list.service.ts @@ -56,12 +56,10 @@ export class TaskListService implements OnDestroy { this.itemFilter = itemFilter; this.builders = builders || {}; - this.summaryDataSubscription = this.summaryService.subscribe((tasks: any) => { - if (tasks) { - this.getUpdate().subscribe((resp: any) => { - this.updateData(resp, tasks.executing_tasks.filter(this.taskFilter)); - }, this.onFetchError); - } + this.summaryDataSubscription = this.summaryService.subscribe((summary) => { + this.getUpdate().subscribe((resp: any) => { + this.updateData(resp, summary['executing_tasks'].filter(this.taskFilter)); + }, this.onFetchError); }, this.onFetchError); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.ts index 2ef8da4136c..7e99e5aad90 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager.service.ts @@ -26,12 +26,9 @@ export class TaskManagerService { subscriptions: Array = []; init(summaryService: SummaryService) { - return summaryService.subscribe((data: any) => { - if (!data) { - return; - } - const executingTasks = data.executing_tasks; - const finishedTasks = data.finished_tasks; + return summaryService.subscribe((summary) => { + const executingTasks = summary.executing_tasks; + const finishedTasks = summary.finished_tasks; const newSubscriptions: Array = []; for (const subscription of this.subscriptions) { const finishedTask = this._getTask(subscription, finishedTasks);