When you create a new task, and it stays running, it will be added automatically
to the summary data.
This will allows for us to deal with it more quickly, by subscribing
to the summaryService, and removes the need to pass a runningTasks array
between services.
Added 3 new methods to SummaryService.
Signed-off-by: Tiago Melo <tmelo@suse.com>
import * as _ from 'lodash';
import { BsModalRef, BsModalService } from 'ngx-bootstrap';
+import { Subscription } from 'rxjs';
import { RbdService } from '../../../shared/api/rbd.service';
import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
permission: Permission;
images: any;
- executingTasks: ExecutingTask[] = [];
columns: CdTableColumn[];
retries: number;
viewCacheStatusList: any[];
selection = new CdTableSelection();
- summaryDataSubscription = null;
+ summaryDataSubscription: Subscription;
modalRef: BsModalRef;
private dimlessPipe: DimlessPipe,
private summaryService: SummaryService,
private modalService: BsModalService,
- private taskWrapper: TaskWrapperService) {
+ private taskWrapper: TaskWrapperService
+ ) {
this.permission = this.authStorageService.getPermissions().rbdImage;
}
}
];
- this.summaryService.get().subscribe((resp: any) => {
- if (!resp) {
+ this.summaryDataSubscription = this.summaryService.subscribe((data: any) => {
+ if (!data) {
+ this.table.reset(); // Disable loading indicator.
+ this.viewCacheStatusList = [{ status: ViewCacheStatus.ValueException }];
return;
}
- this.loadImages(resp.executing_tasks);
- this.summaryDataSubscription = this.summaryService.summaryData$.subscribe((data: any) => {
- this.loadImages(data.executing_tasks);
- });
- },
- () => {
- this.table.reset(); // Disable loading indicator.
- this.viewCacheStatusList = [{ status: ViewCacheStatus.ValueException }];
+ this.loadImages(data.executing_tasks);
});
}
}
loadImages(executingTasks) {
- if (executingTasks === null) {
- executingTasks = this.executingTasks;
- }
this.rbdService.list().subscribe(
(resp: any[]) => {
let images = [];
images = images.concat(pool.value);
});
const viewCacheStatusList = [];
- _.forEach(viewCacheStatusMap, (value, key) => {
+ _.forEach(viewCacheStatusMap, (value: any, key) => {
viewCacheStatusList.push({
status: parseInt(key, 10),
statusFor:
);
});
this.images = this.merge(images, executingTasks);
- this.executingTasks = executingTasks;
},
() => {
this.table.reset(); // Disable loading indicator.
pool_name: poolName,
image_name: imageName
}),
- tasks: this.executingTasks,
call: this.rbdService.delete(poolName, imageName)
}),
modalRef: this.modalRef
pool_name: poolName,
image_name: imageName
}),
- tasks: this.executingTasks,
call: this.rbdService.flatten(poolName, imageName)
})
.subscribe(undefined, undefined, () => {
this.modalRef.hide();
- this.loadImages(null);
});
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BsModalRef } from 'ngx-bootstrap';
-import 'rxjs/add/observable/of';
-import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs';
import { configureTestBed } from '../../../../testing/unit-test-helper';
import { SummaryService } from '../../../shared/services/summary.service';
import { SharedModule } from '../../../shared/shared.module';
import { AboutComponent } from './about.component';
-class SummaryServiceMock {
- summaryData$ = Observable.of({
+export class SummaryServiceMock {
+ summaryDataSource = new BehaviorSubject({
version:
'ceph version 14.0.0-855-gb8193bb4cd ' +
'(b8193bb4cda16ccc5b028c3e1df62bc72350a15d) nautilus (dev)'
});
+ summaryData$ = this.summaryDataSource.asObservable();
+
+ subscribe(call) {
+ return this.summaryData$.subscribe(call);
+ }
}
describe('AboutComponent', () => {
constructor(public modalRef: BsModalRef, private summaryService: SummaryService) {}
ngOnInit() {
- this.subs = this.summaryService.summaryData$.subscribe((summary: any) => {
+ this.subs = this.summaryService.subscribe((summary: any) => {
if (!summary) {
return;
}
) {}
ngOnInit() {
- const subs = this.summaryService.summaryData$.subscribe((summary: any) => {
+ const subs = this.summaryService.subscribe((summary: any) => {
if (!summary) {
return;
}
+
const releaseName = this.cephReleaseNamePipe.transform(summary.version);
this.docsUrl = `http://docs.ceph.com/docs/${releaseName}/mgr/dashboard/`;
- subs.unsubscribe();
+
+ setTimeout(() => {
+ subs.unsubscribe();
+ }, 0);
});
}
}
ngOnInit() {
- this.summaryService.summaryData$.subscribe((data: any) => {
+ this.summaryService.subscribe((data: any) => {
if (!data) {
return;
}
) {}
ngOnInit() {
- this.summaryService.summaryData$.subscribe((data: any) => {
+ this.summaryService.subscribe((data: any) => {
if (!data) {
return;
}
import { HttpClient } from '@angular/common/http';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
-import { of as observableOf } from 'rxjs';
+import { of as observableOf, Subscriber } from 'rxjs';
import { configureTestBed } from '../../../testing/unit-test-helper';
+import { ExecutingTask } from '../models/executing-task';
import { AuthStorageService } from './auth-storage.service';
import { SummaryService } from './summary.service';
let summaryService: SummaryService;
let authStorageService: AuthStorageService;
+ const summary = {
+ executing_tasks: [],
+ health_status: 'HEALTH_OK',
+ mgr_id: 'x',
+ rbd_mirroring: { errors: 0, warnings: 0 },
+ rbd_pools: [],
+ have_mon_connection: true,
+ finished_tasks: [],
+ filesystems: [{ id: 1, name: 'cephfs_a' }]
+ };
+
const httpClientSpy = {
- get: () =>
- observableOf({
- executing_tasks: [],
- health_status: 'HEALTH_OK',
- mgr_id: 'x',
- rbd_mirroring: { errors: 0, warnings: 0 },
- rbd_pools: [],
- have_mon_connection: true,
- finished_tasks: [],
- filesystems: [{ id: 1, name: 'cephfs_a' }]
- })
+ get: () => observableOf(summary)
};
configureTestBed({
authStorageService.set('foobar');
let result = false;
summaryService.refresh();
- summaryService.summaryData$.subscribe((res) => {
+ summaryService.subscribe(() => {
result = true;
});
tick(5000);
})
);
- it('should get summary', () => {
- expect(summaryService.get()).toEqual(jasmine.any(Object));
+ describe('Should test methods after first refresh', () => {
+ beforeEach(() => {
+ authStorageService.set('foobar');
+ summaryService.refresh();
+ });
+
+ it('should call getCurrentSummary', () => {
+ expect(summaryService.getCurrentSummary ()).toEqual(summary);
+ });
+
+ it('should call subscribe', () => {
+ let result;
+ const subscriber = summaryService.subscribe((data) => {
+ result = data;
+ });
+ expect(subscriber).toEqual(jasmine.any(Subscriber));
+ expect(result).toEqual(summary);
+ });
+
+ it('should call addRunningTask', () => {
+ summaryService.addRunningTask(
+ new ExecutingTask('rbd/delete', {
+ pool_name: 'somePool',
+ image_name: 'someImage'
+ })
+ );
+ const result = summaryService.getCurrentSummary ();
+ expect(result.executing_tasks.length).toBe(1);
+ expect(result.executing_tasks[0]).toEqual({
+ metadata: { image_name: 'someImage', pool_name: 'somePool' },
+ name: 'rbd/delete'
+ });
+ });
});
});
import { HttpClient } from '@angular/common/http';
import { Injectable, NgZone } from '@angular/core';
-import { BehaviorSubject } from 'rxjs';
+import * as _ from 'lodash';
+import { BehaviorSubject, Subscription } from 'rxjs';
+import { ExecutingTask } from '../models/executing-task';
import { AuthStorageService } from './auth-storage.service';
import { ServicesModule } from './services.module';
refresh() {
if (this.authStorageService.isLoggedIn()) {
- this.http.get('api/summary').subscribe(data => {
+ this.http.get('api/summary').subscribe((data) => {
this.summaryDataSource.next(data);
});
}
});
}
- get() {
- return this.http.get('api/summary');
+ /**
+ * Returns the current value of summaryData
+ *
+ * @returns {object}
+ * @memberof SummaryService
+ */
+ getCurrentSummary () {
+ return this.summaryDataSource.getValue();
+ }
+
+ /**
+ * Subscribes to the summaryData,
+ * which is updated once every 5 seconds or when a new task is created.
+ *
+ * @param {(summary: any) => void} call
+ * @returns {Subscription}
+ * @memberof SummaryService
+ */
+ subscribe(call: (summary: any) => void): Subscription {
+ return this.summaryData$.subscribe(call);
+ }
+
+ /**
+ * Inserts a newly created task to the local list of executing tasks.
+ * After that, it will automatically push that new information
+ * to all subscribers.
+ *
+ * @param {ExecutingTask} task
+ * @memberof SummaryService
+ */
+ addRunningTask(task: ExecutingTask) {
+ const current = this.summaryDataSource.getValue();
+ if (!current) {
+ return;
+ }
+
+ if (_.isArray(current.executing_tasks)) {
+ current.executing_tasks.push(task);
+ } else {
+ current.executing_tasks = [task];
+ }
+
+ this.summaryDataSource.next(current);
}
}
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import * as _ from 'lodash';
-import { Subject } from 'rxjs';
+import { BehaviorSubject } from 'rxjs';
import { configureTestBed } from '../../../testing/unit-test-helper';
import { SummaryService } from './summary.service';
import { TaskManagerService } from './task-manager.service';
+const summary = {
+ executing_tasks: [],
+ health_status: 'HEALTH_OK',
+ mgr_id: 'x',
+ rbd_mirroring: { errors: 0, warnings: 0 },
+ rbd_pools: [],
+ have_mon_connection: true,
+ finished_tasks: [{ name: 'foo', metadata: {} }],
+ filesystems: [{ id: 1, name: 'cephfs_a' }]
+};
+
+export class SummaryServiceMock {
+ summaryDataSource = new BehaviorSubject(summary);
+ summaryData$ = this.summaryDataSource.asObservable();
+
+ refresh() {
+ this.summaryDataSource.next(summary);
+ }
+ subscribe(call) {
+ return this.summaryData$.subscribe(call);
+ }
+}
+
describe('TaskManagerService', () => {
let taskManagerService: TaskManagerService;
+ let summaryService: any;
let called: boolean;
- const summaryDataSource = new Subject();
- const fakeService = {
- summaryData$: summaryDataSource.asObservable()
- };
-
- const summary = {
- executing_tasks: [],
- health_status: 'HEALTH_OK',
- mgr_id: 'x',
- rbd_mirroring: { errors: 0, warnings: 0 },
- rbd_pools: [],
- have_mon_connection: true,
- finished_tasks: [{ name: 'foo', metadata: {} }],
- filesystems: [{ id: 1, name: 'cephfs_a' }]
- };
-
configureTestBed(
{
- providers: [TaskManagerService, { provide: SummaryService, useValue: fakeService }]
+ providers: [TaskManagerService, { provide: SummaryService, useClass: SummaryServiceMock }]
},
true
);
beforeEach(() => {
taskManagerService = TestBed.get(TaskManagerService);
+ summaryService = TestBed.get(SummaryService);
called = false;
taskManagerService.subscribe('foo', {}, () => (called = true));
});
'should subscribe and be notified when task is finished',
fakeAsync(() => {
expect(taskManagerService.subscriptions.length).toBe(1);
- summaryDataSource.next(summary);
+ summaryService.refresh();
tick();
expect(called).toEqual(true);
expect(taskManagerService.subscriptions).toEqual([]);
executing_tasks: [{ name: 'foo', metadata: {} }],
finished_tasks: []
});
- summaryDataSource.next(summary);
+ summaryService.refresh();
tick();
expect(taskManagerService.subscriptions).toEqual(original_subscriptions);
})
subscriptions: Array<TaskSubscription> = [];
constructor(summaryService: SummaryService) {
- summaryService.summaryData$.subscribe((data: any) => {
+ summaryService.subscribe((data: any) => {
if (!data) {
return;
}
import { Observable } from 'rxjs/Observable';
import { configureTestBed } from '../../../testing/unit-test-helper';
-import { ExecutingTask } from '../models/executing-task';
import { FinishedTask } from '../models/finished-task';
import { SharedModule } from '../shared.module';
import { NotificationService } from './notification.service';
+import { SummaryService } from './summary.service';
import { TaskManagerService } from './task-manager.service';
import { TaskWrapperService } from './task-wrapper.service';
describe('wrapTaskAroundCall', () => {
let notify: NotificationService;
- let tasks: ExecutingTask[];
let passed: boolean;
+ let summaryService: SummaryService;
const fakeCall = (status?) =>
new Observable((observer) => {
const callWrapTaskAroundCall = (status, name) => {
return service.wrapTaskAroundCall({
task: new FinishedTask(name, { sth: 'else' }),
- call: fakeCall(status),
- tasks: tasks
+ call: fakeCall(status)
});
};
beforeEach(() => {
passed = false;
- tasks = [];
notify = TestBed.get(NotificationService);
+ summaryService = TestBed.get(SummaryService);
spyOn(notify, 'show');
spyOn(service, '_handleExecutingTasks').and.callThrough();
+ spyOn(summaryService, 'addRunningTask').and.callThrough();
});
it('should simulate a synchronous task', () => {
callWrapTaskAroundCall(200, 'sync').subscribe(null, null, () => (passed = true));
expect(service._handleExecutingTasks).not.toHaveBeenCalled();
expect(passed).toBeTruthy();
- expect(tasks.length).toBe(0);
+ expect(summaryService.addRunningTask).not.toHaveBeenCalled();
});
it('should simulate a asynchronous task', () => {
callWrapTaskAroundCall(202, 'async').subscribe(null, null, () => (passed = true));
expect(service._handleExecutingTasks).toHaveBeenCalled();
expect(passed).toBeTruthy();
- expect(tasks.length).toBe(1);
+ expect(summaryService.addRunningTask).toHaveBeenCalledTimes(1);
});
it('should call notifyTask if asynchronous task would have been finished', () => {
callWrapTaskAroundCall(null, 'async').subscribe(null, () => (passed = true), null);
expect(service._handleExecutingTasks).not.toHaveBeenCalled();
expect(passed).toBeTruthy();
- expect(tasks.length).toBe(0);
+ expect(summaryService.addRunningTask).not.toHaveBeenCalled();
});
});
});
import { FinishedTask } from '../models/finished-task';
import { NotificationService } from './notification.service';
import { ServicesModule } from './services.module';
+import { SummaryService } from './summary.service';
import { TaskManagerMessageService } from './task-manager-message.service';
import { TaskManagerService } from './task-manager.service';
export class TaskWrapperService {
constructor(
private notificationService: NotificationService,
+ private summaryService: SummaryService,
private taskManagerMessageService: TaskManagerMessageService,
private taskManagerService: TaskManagerService
) {}
- wrapTaskAroundCall({
- task,
- call,
- tasks
- }: {
- task: FinishedTask;
- call: Observable<any>;
- tasks?: ExecutingTask[];
- }) {
+ wrapTaskAroundCall({ task, call }: { task: FinishedTask; call: Observable<any> }) {
return new Observable((observer: Subscriber<any>) => {
call.subscribe(
(resp) => {
if (resp.status === 202) {
- this._handleExecutingTasks(task, tasks);
+ this._handleExecutingTasks(task);
} else {
+ this.summaryService.refresh();
task.success = true;
this.notificationService.notifyTask(task);
}
});
}
- _handleExecutingTasks(task: FinishedTask, tasks?: ExecutingTask[]) {
+ _handleExecutingTasks(task: FinishedTask) {
this.notificationService.show(
NotificationType.info,
this.taskManagerMessageService.getRunningMessage(task),
this.taskManagerMessageService.getDescription(task)
);
+
const executingTask = new ExecutingTask(task.name, task.metadata);
- if (tasks) {
- tasks.push(executingTask);
- }
+ this.summaryService.addRunningTask(executingTask);
+
this.taskManagerService.subscribe(
executingTask.name,
executingTask.metadata,