From: Stephan Müller Date: Tue, 15 May 2018 14:45:25 +0000 (+0200) Subject: mgr/dashboard: Task wrapper service X-Git-Tag: v14.0.1~1110^2~3 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=3201ba6f7d58aaf61ad04e1dd1bfd57123e12887;p=ceph.git 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 --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/task.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/task.ts index 60ad1ab91eb..fab76717f73 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/task.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/task.ts @@ -1,4 +1,8 @@ export class Task { + constructor(name?, metadata?) { + this.name = name; + this.metadata = metadata; + } name: string; metadata: object; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.spec.ts new file mode 100644 index 00000000000..78e511f6263 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.spec.ts @@ -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); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.ts new file mode 100644 index 00000000000..ae227c5c83b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.ts @@ -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; + tasks?: ExecutingTask[]; + }) { + return new Observable((observer: Subscriber) => { + 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); + } + ); + } +}