]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Task wrapper service
authorStephan Müller <smueller@suse.com>
Tue, 15 May 2018 14:45:25 +0000 (16:45 +0200)
committerStephan Müller <smueller@suse.com>
Thu, 14 Jun 2018 14:46:35 +0000 (16:46 +0200)
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>
src/pybind/mgr/dashboard/frontend/src/app/shared/models/task.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.ts [new file with mode: 0644]

index 60ad1ab91ebaeb18e8170bff98ddd2ae44081b7e..fab76717f73d1bc133d559ebc774f1af5950aeff 100644 (file)
@@ -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 (file)
index 0000000..78e511f
--- /dev/null
@@ -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 (file)
index 0000000..ae227c5
--- /dev/null
@@ -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);
+      }
+    );
+  }
+}