mgr/dashboard: Task wrapper service

Has a method to wrap an API call into a task.

Fixes: https://tracker.ceph.com/issues/24134

Signed-off-by: Stephan Müller <smueller@suse.com>
This commit is contained in:
Stephan Müller 2018-05-15 16:45:25 +02:00
parent d785010a35
commit 3201ba6f7d
3 changed files with 170 additions and 0 deletions

View File

@ -1,4 +1,8 @@
export class Task {
constructor(name?, metadata?) {
this.name = name;
this.metadata = metadata;
}
name: string;
metadata: object;

View File

@ -0,0 +1,92 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import { ToastModule } from 'ng2-toastr';
import { Observable } from 'rxjs/Observable';
import { ExecutingTask } from '../models/executing-task';
import { FinishedTask } from '../models/finished-task';
import { SharedModule } from '../shared.module';
import { configureTestBed } from '../unit-test-helper';
import { NotificationService } from './notification.service';
import { TaskManagerService } from './task-manager.service';
import { TaskWrapperService } from './task-wrapper.service';
describe('TaskWrapperService', () => {
let service: TaskWrapperService;
configureTestBed({
imports: [HttpClientTestingModule, ToastModule.forRoot(), SharedModule],
providers: [TaskWrapperService]
});
beforeEach(inject([TaskWrapperService], (wrapper: TaskWrapperService) => {
service = wrapper;
}));
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('wrapTaskAroundCall', () => {
let notify: NotificationService;
let tasks: ExecutingTask[];
let passed: boolean;
const fakeCall = (status?) =>
new Observable((observer) => {
if (!status) {
observer.error({ error: 'failed' });
}
observer.next({ status: status });
observer.complete();
});
const callWrapTaskAroundCall = (status, name) => {
return service.wrapTaskAroundCall({
task: new FinishedTask(name, { sth: 'else' }),
call: fakeCall(status),
tasks: tasks
});
};
beforeEach(() => {
passed = false;
tasks = [];
notify = TestBed.get(NotificationService);
spyOn(notify, 'show');
spyOn(service, '_handleExecutingTasks').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);
});
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);
});
it('should call notifyTask if asynchronous task would have been finished', () => {
const taskManager = TestBed.get(TaskManagerService);
spyOn(taskManager, 'subscribe').and.callFake((name, metadata, onTaskFinished) => {
onTaskFinished();
});
spyOn(notify, 'notifyTask').and.stub();
callWrapTaskAroundCall(202, 'async').subscribe(null, null, () => (passed = true));
expect(notify.notifyTask).toHaveBeenCalled();
});
it('should simulate a task failure', () => {
callWrapTaskAroundCall(null, 'async').subscribe(null, () => (passed = true), null);
expect(service._handleExecutingTasks).not.toHaveBeenCalled();
expect(passed).toBeTruthy();
expect(tasks.length).toBe(0);
});
});
});

View File

@ -0,0 +1,74 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/Subscriber';
import { NotificationType } from '../enum/notification-type.enum';
import { ExecutingTask } from '../models/executing-task';
import { FinishedTask } from '../models/finished-task';
import { NotificationService } from './notification.service';
import { ServicesModule } from './services.module';
import { TaskManagerMessageService } from './task-manager-message.service';
import { TaskManagerService } from './task-manager.service';
@Injectable({
providedIn: ServicesModule
})
export class TaskWrapperService {
constructor(
private notificationService: NotificationService,
private taskManagerMessageService: TaskManagerMessageService,
private taskManagerService: TaskManagerService
) {}
wrapTaskAroundCall({
task,
call,
tasks
}: {
task: FinishedTask;
call: Observable<any>;
tasks?: ExecutingTask[];
}) {
return new Observable((observer: Subscriber<any>) => {
call.subscribe(
(resp) => {
if (resp.status === 202) {
this._handleExecutingTasks(task, tasks);
} else {
task.success = true;
this.notificationService.notifyTask(task);
}
},
(resp) => {
task.success = false;
task.exception = resp.error;
this.notificationService.notifyTask(task);
observer.error();
},
() => {
observer.complete();
}
);
});
}
_handleExecutingTasks(task: FinishedTask, tasks?: ExecutingTask[]) {
this.notificationService.show(
NotificationType.info,
task.name + ' in progress...',
this.taskManagerMessageService.getDescription(task)
);
const executingTask = new ExecutingTask(task.name, task.metadata);
if (tasks) {
tasks.push(executingTask);
}
this.taskManagerService.subscribe(
executingTask.name,
executingTask.metadata,
(asyncTask: FinishedTask) => {
this.notificationService.notifyTask(asyncTask);
}
);
}
}