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;
+ });
}
});
}
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';
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();
});
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 }])
);
};
beforeEach(() => {
- summaryService['summaryDataSource'].next({ executingTasks: [] });
+ summaryService['summaryDataSource'].next(new Summary());
spyOn(rbdService, 'listTrash').and.callFake(() => {
of([{ pool_name: 'rbd', status: 1, value: images }]);
});
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
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(() => {
) {}
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 }) => {
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';
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;
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<any>[]) {
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';
let nfsService: NfsService;
let httpTesting: HttpTestingController;
- const refresh = (data: object) => {
+ const refresh = (data: Summary) => {
summaryService['summaryDataSource'].next(data);
};
});
it('should load exports on init', () => {
- refresh({});
+ refresh(new Summary());
httpTesting.expectOne('api/nfs-ganesha/export');
expect(nfsService.list).toHaveBeenCalled();
});
addExport('b');
addExport('c');
component.exports = exports;
- refresh({ executing_tasks: [], finished_tasks: [] });
+ refresh(new Summary());
spyOn(nfsService, 'list').and.callFake(() => of(exports));
fixture.detectChanges();
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', () => {
) {}
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 }) => {
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];
) {}
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);
});
}
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(
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;
})
);
}
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;
);
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);
) {}
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);
});
}
}
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;
--- /dev/null
+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;
+}
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';
let authStorageService: AuthStorageService;
let subs: Subscription;
- const summary: Record<string, any> = {
+ const summary: Summary = {
executing_tasks: [],
health_status: 'HEALTH_OK',
mgr_id: 'x',
get: () => observableOf(summary)
};
+ const nextSummary = (newData: any) => summaryService['summaryDataSource'].next(newData);
+
configureTestBed({
imports: [RouterTestingModule],
providers: [
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', () => {
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' },
});
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);
});
});
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({
export class SummaryService {
readonly REFRESH_INTERVAL = 5000;
// Observable sources
- private summaryDataSource = new BehaviorSubject(null);
+ private summaryDataSource = new BehaviorSubject<Summary>(null);
// Observable streams
summaryData$ = this.summaryDataSource.asObservable();
return this.retrieveSummaryObservable().subscribe(this.retrieveSummaryObserver());
}
- private retrieveSummaryObservable(): Observable<Object> {
- return this.http.get('api/summary');
+ private retrieveSummaryObservable(): Observable<Summary> {
+ return this.http.get<Summary>('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);
}
/**
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);
}
subscriptions: Array<TaskSubscription> = [];
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<TaskSubscription> = [];
for (const subscription of this.subscriptions) {
const finishedTask = <FinishedTask>this._getTask(subscription, finishedTasks);