]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Cleaner notifications
authorStephan Müller <smueller@suse.com>
Fri, 27 Jul 2018 09:34:51 +0000 (11:34 +0200)
committerStephan Müller <smueller@suse.com>
Thu, 16 Aug 2018 14:26:22 +0000 (16:26 +0200)
Now background tasks and recent notifications won't differ in their
wording anymore and all notifications have the same style.

Fixes: https://tracker.ceph.com/issues/24460
Signed-off-by: Stephan Müller <smueller@suse.com>
src/pybind/mgr/dashboard/HACKING.rst
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-notification.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-wrapper.service.ts

index 5faeee2ec487e60220112445fd6bd65a116c5999..d7e3cb0f221ea09e14a00008e531965af4c84265 100644 (file)
@@ -1026,68 +1026,86 @@ updates its progress:
 How to deal with asynchronous tasks in the front-end?
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-All executing and most recently finished asynchronous tasks are displayed on the
-"Backgroud-Tasks" menu.
-
-The front-end developer should provide a description, success message and error
-messages for each task on ``TaskManagerMessageService.messages``.
-This messages can make use of the task metadata to provide more personalized messages.
-
-When submitting an asynchronous task, the developer should provide a callback
-that will be automatically triggered after the execution of that task.
-This can be done by using the ``TaskManagerService.subscribe``.
-
-Most of the times, all we want to do after a task completes the execution, is
-displaying a notification message based on the execution result. The
-``NotificationService.notifyTask`` will use the messages from
-``TaskManagerMessageService`` to display a success / error message based on the
-execution result of a task.
+All executing and most recently finished asynchronous tasks are displayed on
+"Background-Tasks" and if finished on "Recent-Notifications" in the menu bar.
+For each task a operation name for three states (running, success and failure),
+a function that tells who is involved and error descriptions, if any, have to
+be provided. This can be  achieved by appending
+``TaskManagerMessageService.messages``.  This has to be done to achieve
+consistency among all tasks and states.
+
+Operation Object
+  Ensures consistency among all tasks. It consists of three verbs for each
+  different state f.e.
+  ``{running: 'Creating', failure: 'create', success: 'Created'}``.
+
+#. Put running operations in present participle f.e. ``'Updating'``.
+#. Failed messages always start with ``'Failed to '`` and should be continued
+   with the operation in present tense f.e. ``'update'``.
+#. Put successful operations in past tense f.e. ``'Updated'``.
+
+Involves Function
+  Ensures consistency among all messages of a task, it resembles who's
+  involved by the operation. It's a function that returns a string which
+  takes the metadata from the task to return f.e.
+  ``"RBD 'somePool/someImage'"``.
+
+Both combined create the following messages:
+
+* Failure => ``"Failed to create RBD 'somePool/someImage'"``
+* Running => ``"Creating RBD 'somePool/someImage'"``
+* Success => ``"Created RBD 'somePool/someImage'"``
+
+For automatic task handling use ``TaskWrapperService.wrapTaskAroundCall``.
+
+If for some reason ``wrapTaskAroundCall`` is not working for you,
+you have to subscribe to your asynchronous task manually through
+``TaskManagerService.subscribe``, and provide it with a callback,
+in case of a success to notify the user. A notification can
+be triggered with ``NotificationService.notifyTask``. It will use
+``TaskManagerMessageService.messages`` to display a message based on the state
+of a task.
+
+Notifications of API errors are handled by ``ApiInterceptorService``.
 
 Usage example:
 
 .. code-block:: javascript
 
   export class TaskManagerMessageService {
-
+    // ...
     messages = {
-      // Messages for 'rbd/create' task
+      // Messages for task 'rbd/create'
       'rbd/create': new TaskManagerMessage(
-        // Description
-        (metadata) => `Create RBD '${metadata.pool_name}/${metadata.image_name}'`,
-        // Success message
-        (metadata) => `RBD '${metadata.pool_name}/${metadata.image_name}'
-                       have been created successfully`,
-        // Error messages
-        (metadata) => {
-          return {
-            '17': `Name '${metadata.pool_name}/${metadata.image_name}' is already
-                   in use.`
-          };
-        }
+        // Message prefixes
+        ['create', 'Creating', 'Created'],
+        // Message suffix
+        (metadata) => `RBD '${metadata.pool_name}/${metadata.image_name}'`,
+        (metadata) => ({
+          // Error code and description
+          '17': `Name is already used by RBD '${metadata.pool_name}/${
+                 metadata.image_name}'.`
+        })
       ),
       // ...
     };
-
     // ...
   }
 
   export class RBDFormComponent {
     // ...
-
-    submit() {
-      // ...
-      this.rbdService.create(request).then((resp) => {
-        // Subscribe the submitted task
-        this.taskManagerService.subscribe('rbd/create',
-          {'pool_name': request.pool_name, 'rbd_name': request.name},
-          // Callback that will be invoked after task is finished
-          (finishedTask: FinishedTask) => {
-            // Will display a notification message (success or error)
-            this.notificationService.notifyTask(finishedTask, finishedTask.ret_value.success);
-          });
-        // ...
-      })
+    createAction() {
+      const request = this.createRequest();
+      // Subscribes to 'call' with submitted 'task' and handles notifications
+      return this.taskWrapper.wrapTaskAroundCall({
+        task: new FinishedTask('rbd/create', {
+          pool_name: request.pool_name,
+          image_name: request.name
+        }),
+        call: this.rbdService.create(request)
+      });
     }
+    // ...
   }
 
 Error Handling in Python
index 8b8f4a25cc869ef0d516a93b7af244685f48f79e..447c4243c05497b369e7f28788c186f7f19108fe 100644 (file)
@@ -56,14 +56,19 @@ describe('TaskManagerComponent', () => {
     expect(component.executingTasks[0].description).toBe(`Deleting RBD 'somePool/someImage'`);
   });
 
-  it('should get finished message for task', () => {
+  it('should get finished message for successful task', () => {
     component._handleTasks([], tasks.finished);
     expect(component.finishedTasks.length).toBe(2);
-    expect(component.finishedTasks[0].description).toBe(`Copy RBD 'somePool/someImage'`);
+    expect(component.finishedTasks[0].description).toBe(`Copied RBD 'somePool/someImage'`);
     expect(component.finishedTasks[0].errorMessage).toBe(undefined);
-    expect(component.finishedTasks[1].description).toBe(`Clone RBD 'somePool/someImage'`);
+  });
+
+  it('should get failed message for finished task', () => {
+    component._handleTasks([], tasks.finished);
+    expect(component.finishedTasks.length).toBe(2);
+    expect(component.finishedTasks[1].description).toBe(`Failed to clone RBD 'somePool/someImage'`);
     expect(component.finishedTasks[1].errorMessage).toBe(
-      `Name 'somePool/someImage' is already in use.`
+      `Name is already used by RBD 'somePool/someImage'.`
     );
   });
 
index a78c8c0a755cc80d8af1bbca107a26515ac6c1b2..4e17c704a1f493e094a244c8530baf754c411e88 100644 (file)
@@ -33,12 +33,14 @@ export class TaskManagerComponent implements OnInit {
 
   _handleTasks(executingTasks: ExecutingTask[], finishedTasks: FinishedTask[]) {
     for (const excutingTask of executingTasks) {
-      excutingTask.description = this.taskMessageManager.getRunningMessage(excutingTask);
+      excutingTask.description = this.taskMessageManager.getRunningTitle(excutingTask);
     }
     for (const finishedTask of finishedTasks) {
-      finishedTask.description = this.taskMessageManager.getDescription(finishedTask);
       if (finishedTask.success === false) {
+        finishedTask.description = this.taskMessageManager.getErrorTitle(finishedTask);
         finishedTask.errorMessage = this.taskMessageManager.getErrorMessage(finishedTask);
+      } else {
+        finishedTask.description = this.taskMessageManager.getSuccessTitle(finishedTask);
       }
     }
     this.executingTasks = executingTasks;
index f69da46117f0f89cd3c0fb95900911b096f1bec0..ba6a73a000ed7012b4cd041187833bbebc3ff596 100644 (file)
@@ -6,10 +6,10 @@ export class CdNotification {
   title: string;
   type: NotificationType;
 
-  constructor(type: NotificationType = NotificationType.info, message?: string, title?: string) {
+  constructor(type: NotificationType = NotificationType.info, title?: string, message?: string) {
     this.type = type;
-    this.message = message;
     this.title = title;
+    this.message = message;
 
     /* string representation of the Date object so it can be directly compared
     with the timestamps parsed from localStorage */
index 28471e6f0b429b439aee8d176d22cec69d1a2ba3..763a086b71ef3da2886a1101259d4636a91f49b7 100644 (file)
@@ -87,7 +87,10 @@ describe('NotificationService', () => {
       notificationService.show(NotificationType.info, 'Simple test');
       tick(100);
       expect(notificationService['dataSource'].getValue().length).toBe(1);
-      expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.info);
+      const notification = notificationService['dataSource'].getValue()[0];
+      expect(notification.type).toBe(NotificationType.info);
+      expect(notification.title).toBe('Simple test');
+      expect(notification.message).toBe(undefined);
     })
   );
 
@@ -111,24 +114,35 @@ describe('NotificationService', () => {
       notificationService.notifyTask(task, true);
       tick(100);
       expect(notificationService['dataSource'].getValue().length).toBe(1);
-      expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.success);
+      const notification = notificationService['dataSource'].getValue()[0];
+      expect(notification.type).toBe(NotificationType.success);
+      expect(notification.title).toBe('Executed unknown task');
+      expect(notification.message).toBe(undefined);
     })
   );
 
   it(
     'should show a error task notification',
     fakeAsync(() => {
-      const task = _.assign(new FinishedTask(), {
-        success: false,
-        metadata: 'failed',
-        exception: {
-          code: 404
+      const task = _.assign(
+        new FinishedTask('rbd/create', {
+          pool_name: 'somePool',
+          image_name: 'someImage'
+        }),
+        {
+          success: false,
+          exception: {
+            code: 17
+          }
         }
-      });
+      );
       notificationService.notifyTask(task);
       tick(100);
       expect(notificationService['dataSource'].getValue().length).toBe(1);
-      expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.error);
+      const notification = notificationService['dataSource'].getValue()[0];
+      expect(notification.type).toBe(NotificationType.error);
+      expect(notification.title).toBe(`Failed to create RBD 'somePool/someImage'`);
+      expect(notification.message).toBe(`Name is already used by RBD 'somePool/someImage'.`);
     })
   );
 });
index 13a9f4350a4c97ac2266287b4e3329063a5011f3..f736a1e41905b4d5e7086e234dfd506279d2e7b7 100644 (file)
@@ -53,8 +53,8 @@ export class NotificationService {
    * Method used for saving a shown notification (check show() method).
    * @param {Notification} notification
    */
-  save(type: NotificationType, message: string, title?: string) {
-    const notification = new CdNotification(type, message, title);
+  save(type: NotificationType, title: string, message?: string) {
+    const notification = new CdNotification(type, title, message);
 
     const recent = this.dataSource.getValue();
     recent.push(notification);
@@ -69,15 +69,18 @@ export class NotificationService {
   /**
    * Method for showing a notification.
    * @param {NotificationType} type toastr type
-   * @param {string} message
-   * @param {string} [title]
+   * @param {string} title
+   * @param {string} [message]
    * @param {*} [options] toastr compatible options, used when creating a toastr
    * @memberof NotificationService
    * @returns The timeout ID that is set to be able to cancel the notification.
    */
-  show(type: NotificationType, message: string, title?: string, options?: any) {
+  show(type: NotificationType, title: string, message?: string, options?: any) {
     return setTimeout(() => {
-      this.save(type, message, title);
+      this.save(type, title, message);
+      if (!message) {
+        message = '';
+      }
       switch (type) {
         case NotificationType.error:
           this.toastr.error(message, title, options);
@@ -96,13 +99,13 @@ export class NotificationService {
     if (finishedTask.success && success) {
       this.show(
         NotificationType.success,
-        this.taskManagerMessageService.getSuccessMessage(finishedTask)
+        this.taskManagerMessageService.getSuccessTitle(finishedTask)
       );
     } else {
       this.show(
         NotificationType.error,
-        this.taskManagerMessageService.getErrorMessage(finishedTask),
-        this.taskManagerMessageService.getDescription(finishedTask)
+        this.taskManagerMessageService.getErrorTitle(finishedTask),
+        this.taskManagerMessageService.getErrorMessage(finishedTask)
       );
     }
   }
index 32af77108b6d8b4566cbd44763da590738b0593a..aeb36234077a58e10142b5235107099f64438274 100644 (file)
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
 
 import { FinishedTask } from '../models/finished-task';
 import { TaskException } from '../models/task-exception';
-import { TaskManagerMessageService } from './task-manager-message.service';
+import { TaskManagerMessageService, TaskMessageOperation } from './task-manager-message.service';
 
 describe('TaskManagerMessageService', () => {
   let service: TaskManagerMessageService;
@@ -17,55 +17,134 @@ describe('TaskManagerMessageService', () => {
     expect(service).toBeTruthy();
   });
 
-  it('should getDescription', () => {
-    finishedTask.name = 'foo';
-    finishedTask.exception = _.assign(new TaskException(), {
-      code: 1
-    });
-    finishedTask.metadata = {};
-
-    const message = service.getDescription(finishedTask);
-    expect(message).toBe('Unknown Task');
+  it('should get default description', () => {
+    expect(service.getErrorTitle(finishedTask)).toBe('Failed to execute unknown task');
   });
 
   it('should get default running message', () => {
-    finishedTask.metadata = {};
-    let message = service.getRunningMessage(finishedTask);
-    expect(message).toBe('Executing unknown task');
-    finishedTask.metadata = { component: 'rbd' };
-    message = service.getRunningMessage(finishedTask);
-    expect(message).toBe('Executing RBD');
-  });
-
-  it('should get custom running message', () => {
-    finishedTask.name = 'rbd/create';
-    finishedTask.metadata = {
-      pool_name: 'somePool',
-      image_name: 'someImage'
-    };
-    const message = service.getRunningMessage(finishedTask);
-    expect(message).toBe(`Creating RBD 'somePool/someImage'`);
+    expect(service.getRunningTitle(finishedTask)).toBe('Executing unknown task');
   });
 
-  it('should getErrorMessage', () => {
-    finishedTask.exception = _.assign(new TaskException(), {
-      code: 1
-    });
-    const message = service.getErrorMessage(finishedTask);
-    expect(message).toBe(undefined);
+  it('should get default running message with a set component', () => {
+    finishedTask.metadata = { component: 'rbd' };
+    expect(service.getRunningTitle(finishedTask)).toBe('Executing RBD');
   });
 
   it('should getSuccessMessage', () => {
-    const message = service.getSuccessMessage(finishedTask);
-    expect(message).toBe('Task executed successfully');
+    expect(service.getSuccessTitle(finishedTask)).toBe('Executed unknown task');
   });
 
-  it('should test if all messages methods are defined', () => {
-    _.forIn(service.messages, (value, key) => {
-      expect(value.descr({})).toBeTruthy();
-      expect(value.success({})).toBeTruthy();
-      expect(value.error({})).toBeTruthy();
-      expect(value.running({})).toBeTruthy();
+  describe('defined tasks messages', () => {
+    const testMessages = (operation: TaskMessageOperation, involves: string) => {
+      expect(service.getRunningTitle(finishedTask)).toBe(operation.running + ' ' + involves);
+      expect(service.getErrorTitle(finishedTask)).toBe(
+        'Failed to ' + operation.failure + ' ' + involves
+      );
+      expect(service.getSuccessTitle(finishedTask)).toBe(operation.success + ' ' + involves);
+    };
+
+    const testCreate = (involves: string) => {
+      testMessages(new TaskMessageOperation('Creating', 'create', 'Created'), involves);
+    };
+
+    const testUpdate = (involves: string) => {
+      testMessages(new TaskMessageOperation('Updating', 'update', 'Updated'), involves);
+    };
+
+    const testDelete = (involves: string) => {
+      testMessages(new TaskMessageOperation('Deleting', 'delete', 'Deleted'), involves);
+    };
+
+    const testErrorCode = (code: number, msg: string) => {
+      finishedTask.exception = _.assign(new TaskException(), {
+        code: code
+      });
+      expect(service.getErrorMessage(finishedTask)).toBe(msg);
+    };
+
+    describe('rbd tasks', () => {
+      let defaultMsg: string;
+      let childMsg: string;
+      let destinationMsg: string;
+      let snapMsg: string;
+
+      beforeEach(() => {
+        const metadata = {
+          pool_name: 'somePool',
+          image_name: 'someImage',
+          snapshot_name: 'someSnapShot',
+          dest_pool_name: 'someDestinationPool',
+          dest_image_name: 'someDestinationImage',
+          child_pool_name: 'someChildPool',
+          child_image_name: 'someChildImage'
+        };
+        defaultMsg = `RBD '${metadata.pool_name}/${metadata.image_name}'`;
+        childMsg = `RBD '${metadata.child_pool_name}/${metadata.child_image_name}'`;
+        destinationMsg = `RBD '${metadata.dest_pool_name}/${metadata.dest_image_name}'`;
+        snapMsg = `RBD snapshot '${metadata.pool_name}/${metadata.image_name}@${
+          metadata.snapshot_name
+        }'`;
+        finishedTask.metadata = metadata;
+      });
+
+      it('tests rbd/create messages', () => {
+        finishedTask.name = 'rbd/create';
+        testCreate(defaultMsg);
+        testErrorCode(17, `Name is already used by ${defaultMsg}.`);
+      });
+
+      it('tests rbd/edit messages', () => {
+        finishedTask.name = 'rbd/edit';
+        testUpdate(defaultMsg);
+        testErrorCode(17, `Name is already used by ${defaultMsg}.`);
+      });
+
+      it('tests rbd/delete messages', () => {
+        finishedTask.name = 'rbd/delete';
+        testDelete(defaultMsg);
+        testErrorCode(39, `${defaultMsg} contains snapshots.`);
+      });
+
+      it('tests rbd/clone messages', () => {
+        finishedTask.name = 'rbd/clone';
+        testMessages(new TaskMessageOperation('Cloning', 'clone', 'Cloned'), childMsg);
+        testErrorCode(17, `Name is already used by ${childMsg}.`);
+        testErrorCode(22, `Snapshot of ${childMsg} must be protected.`);
+      });
+
+      it('tests rbd/copy messages', () => {
+        finishedTask.name = 'rbd/copy';
+        testMessages(new TaskMessageOperation('Copying', 'copy', 'Copied'), destinationMsg);
+        testErrorCode(17, `Name is already used by ${destinationMsg}.`);
+      });
+
+      it('tests rbd/flatten messages', () => {
+        finishedTask.name = 'rbd/flatten';
+        testMessages(new TaskMessageOperation('Flattening', 'flatten', 'Flattened'), defaultMsg);
+      });
+
+      it('tests rbd/snap/create messages', () => {
+        finishedTask.name = 'rbd/snap/create';
+        testCreate(snapMsg);
+        testErrorCode(17, `Name is already used by ${snapMsg}.`);
+      });
+
+      it('tests rbd/snap/edit messages', () => {
+        finishedTask.name = 'rbd/snap/edit';
+        testUpdate(snapMsg);
+        testErrorCode(16, `Cannot unprotect ${snapMsg} because it contains child images.`);
+      });
+
+      it('tests rbd/snap/delete messages', () => {
+        finishedTask.name = 'rbd/snap/delete';
+        testDelete(snapMsg);
+        testErrorCode(16, `Cannot delete ${snapMsg} because it's protected.`);
+      });
+
+      it('tests rbd/snap/rollback messages', () => {
+        finishedTask.name = 'rbd/snap/rollback';
+        testMessages(new TaskMessageOperation('Rolling back', 'rollback', 'Rolled back'), snapMsg);
+      });
     });
   });
 });
index 9f32645a24c07b663d6f6bb86352531a391e2ac4..f8210fb9eb82524830bd5032a1d85cb99da0a6da 100644 (file)
@@ -5,22 +5,43 @@ import { FinishedTask } from '../models/finished-task';
 import { Task } from '../models/task';
 import { ServicesModule } from './services.module';
 
+export class TaskMessageOperation {
+  running: string;
+  failure: string;
+  success: string;
+
+  constructor(running: string, failure: string, success: string) {
+    this.running = running;
+    this.failure = failure;
+    this.success = success;
+  }
+}
+
 class TaskManagerMessage {
-  descr: (metadata) => string;
-  running: (metadata) => string;
-  success: (metadata) => string;
-  error: (metadata) => object;
+  operation: TaskMessageOperation;
+  involves: (object) => string;
+  errors: (metadata) => object;
+
+  failure(metadata): string {
+    return `Failed to ${this.operation.failure} ${this.involves(metadata)}`;
+  }
+
+  running(metadata): string {
+    return `${this.operation.running} ${this.involves(metadata)}`;
+  }
+
+  success(metadata): string {
+    return `${this.operation.success} ${this.involves(metadata)}`;
+  }
 
   constructor(
-    descr: (metadata) => string,
-    running: (metadata) => string,
-    success: (metadata) => string,
-    error: (metadata) => object
+    operation: TaskMessageOperation,
+    involves: (metadata) => string,
+    errors?: (metadata) => object
   ) {
-    this.descr = descr;
-    this.running = running;
-    this.success = success;
-    this.error = error;
+    this.operation = operation;
+    this.involves = involves;
+    this.errors = errors || (() => ({}));
   }
 }
 
@@ -28,181 +49,119 @@ class TaskManagerMessage {
   providedIn: ServicesModule
 })
 export class TaskManagerMessageService {
+  defaultMessage = new TaskManagerMessage(
+    new TaskMessageOperation('Executing', 'execute', 'Executed'),
+    (metadata) => {
+      return (metadata && (Components[metadata.component] || metadata.component)) || 'unknown task';
+    },
+    () => {
+      return {};
+    }
+  );
+
+  commonOperations = {
+    create: new TaskMessageOperation('Creating', 'create', 'Created'),
+    update: new TaskMessageOperation('Updating', 'update', 'Updated'),
+    delete: new TaskMessageOperation('Deleting', 'delete', 'Deleted')
+  };
+
+  rbd = {
+    default: (metadata) => `RBD '${metadata.pool_name}/${metadata.image_name}'`,
+    child: (metadata) => `RBD '${metadata.child_pool_name}/${metadata.child_image_name}'`,
+    destination: (metadata) => `RBD '${metadata.dest_pool_name}/${metadata.dest_image_name}'`,
+    snapshot: (metadata) =>
+      `RBD snapshot '${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`
+  };
+
   messages = {
     'rbd/create': new TaskManagerMessage(
-      (metadata) => `Create RBD '${metadata.pool_name}/${metadata.image_name}'`,
-      (metadata) => `Creating RBD '${metadata.pool_name}/${metadata.image_name}'`,
-      (metadata) =>
-        `RBD '${metadata.pool_name}/${metadata.image_name}' has been created successfully`,
-      (metadata) => {
-        return {
-          '17': `Name '${metadata.pool_name}/${metadata.image_name}' is already in use.`
-        };
-      }
+      this.commonOperations.create,
+      this.rbd.default,
+      (metadata) => ({
+        '17': `Name is already used by ${this.rbd.default(metadata)}.`
+      })
     ),
     'rbd/edit': new TaskManagerMessage(
-      (metadata) => `Update RBD '${metadata.pool_name}/${metadata.image_name}'`,
-      (metadata) => `Updating RBD '${metadata.pool_name}/${metadata.image_name}'`,
-      (metadata) =>
-        `RBD '${metadata.pool_name}/${metadata.image_name}' has been updated successfully`,
-      (metadata) => {
-        return {
-          '17': `Name '${metadata.pool_name}/${metadata.name}' is already in use.`
-        };
-      }
+      this.commonOperations.update,
+      this.rbd.default,
+      (metadata) => ({
+        '17': `Name is already used by ${this.rbd.default(metadata)}.`
+      })
     ),
     'rbd/delete': new TaskManagerMessage(
-      (metadata) => `Delete RBD '${metadata.pool_name}/${metadata.image_name}'`,
-      (metadata) => `Deleting RBD '${metadata.pool_name}/${metadata.image_name}'`,
-      (metadata) =>
-        `RBD '${metadata.pool_name}/${metadata.image_name}' has been deleted successfully`,
-      (metadata) => {
-        return {
-          '39': `RBD image contains snapshots.`
-        };
-      }
+      this.commonOperations.delete,
+      this.rbd.default,
+      (metadata) => ({
+        '39': `${this.rbd.default(metadata)} contains snapshots.`
+      })
     ),
     'rbd/clone': new TaskManagerMessage(
-      (metadata) => `Clone RBD '${metadata.child_pool_name}/${metadata.child_image_name}'`,
-      (metadata) => `Cloning RBD '${metadata.child_pool_name}/${metadata.child_image_name}'`,
-      (metadata) =>
-        `RBD '${metadata.child_pool_name}/${
-          metadata.child_image_name
-        }' has been cloned successfully`,
-      (metadata) => {
-        return {
-          '17': `Name '${metadata.child_pool_name}/${
-            metadata.child_image_name
-          }' is already in use.`,
-          '22': `Snapshot must be protected.`
-        };
-      }
+      new TaskMessageOperation('Cloning', 'clone', 'Cloned'),
+      this.rbd.child,
+      (metadata) => ({
+        '17': `Name is already used by ${this.rbd.child(metadata)}.`,
+        '22': `Snapshot of ${this.rbd.child(metadata)} must be protected.`
+      })
     ),
     'rbd/copy': new TaskManagerMessage(
-      (metadata) => `Copy RBD '${metadata.dest_pool_name}/${metadata.dest_image_name}'`,
-      (metadata) => `Copying RBD '${metadata.dest_pool_name}/${metadata.dest_image_name}'`,
-      (metadata) =>
-        `RBD '${metadata.dest_pool_name}/${metadata.dest_image_name}' has been copied successfully`,
-      (metadata) => {
-        return {
-          '17': `Name '${metadata.dest_pool_name}/${metadata.dest_image_name}' is already in use.`
-        };
-      }
+      new TaskMessageOperation('Copying', 'copy', 'Copied'),
+      this.rbd.destination,
+      (metadata) => ({
+        '17': `Name is already used by ${this.rbd.destination(metadata)}.`
+      })
     ),
     'rbd/flatten': new TaskManagerMessage(
-      (metadata) => `Flatten RBD '${metadata.pool_name}/${metadata.image_name}'`,
-      (metadata) => `Flattening RBD '${metadata.pool_name}/${metadata.image_name}'`,
-      (metadata) =>
-        `RBD '${metadata.pool_name}/${metadata.image_name}' has been flattened successfully`,
-      () => {
-        return {};
-      }
+      new TaskMessageOperation('Flattening', 'flatten', 'Flattened'),
+      this.rbd.default
     ),
     'rbd/snap/create': new TaskManagerMessage(
-      (metadata) =>
-        `Create snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`,
-      (metadata) =>
-        `Creating snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`,
-      (metadata) =>
-        `Snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}' ` +
-        `has been created successfully`,
-      (metadata) => {
-        return {
-          '17': `Name '${metadata.snapshot_name}' is already in use.`
-        };
-      }
+      this.commonOperations.create,
+      this.rbd.snapshot,
+      (metadata) => ({
+        '17': `Name is already used by ${this.rbd.snapshot(metadata)}.`
+      })
     ),
     'rbd/snap/edit': new TaskManagerMessage(
-      (metadata) =>
-        `Update snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`,
-      (metadata) =>
-        `Updating snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`,
-      (metadata) =>
-        `Snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}' ` +
-        `has been updated successfully`,
-      () => {
-        return {
-          '16': `Cannot unprotect snapshot because it contains child images.`
-        };
-      }
+      this.commonOperations.update,
+      this.rbd.snapshot,
+      (metadata) => ({
+        '16': `Cannot unprotect ${this.rbd.snapshot(metadata)} because it contains child images.`
+      })
     ),
     'rbd/snap/delete': new TaskManagerMessage(
-      (metadata) =>
-        `Delete snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`,
-      (metadata) =>
-        `Deleting snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`,
-      (metadata) =>
-        `Snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}' ` +
-        `has been deleted successfully`,
-      () => {
-        return {
-          '16': `Snapshot is protected.`
-        };
-      }
+      this.commonOperations.delete,
+      this.rbd.snapshot,
+      (metadata) => ({
+        '16': `Cannot delete ${this.rbd.snapshot(metadata)} because it's protected.`
+      })
     ),
     'rbd/snap/rollback': new TaskManagerMessage(
-      (metadata) =>
-        `Rollback snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`,
-      (metadata) =>
-        `Rolling back snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}'`,
-      (metadata) =>
-        `Snapshot ` +
-        `'${metadata.pool_name}/${metadata.image_name}@${metadata.snapshot_name}' ` +
-        `has been rolled back successfully`,
-      () => {
-        return {};
-      }
+      new TaskMessageOperation('Rolling back', 'rollback', 'Rolled back'),
+      this.rbd.snapshot
     )
   };
 
-  defaultMessage = new TaskManagerMessage(
-    (metadata) => {
-      return Components[metadata.component] || metadata.component || 'Unknown Task';
-    },
-    (metadata) => {
-      return (
-        'Executing ' + (Components[metadata.component] || metadata.component || 'unknown task')
-      );
-    },
-    (metadata) => 'Task executed successfully',
-    () => {
-      return {};
-    }
-  );
-
   constructor() {}
 
-  getSuccessMessage(finishedTask: FinishedTask) {
-    const taskManagerMessage = this.messages[finishedTask.name] || this.defaultMessage;
-    return taskManagerMessage.success(finishedTask.metadata);
+  _getTaskTitle(task: Task) {
+    return this.messages[task.name] || this.defaultMessage;
+  }
+
+  getSuccessTitle(task: FinishedTask) {
+    return this._getTaskTitle(task).success(task.metadata);
   }
 
-  getErrorMessage(finishedTask: FinishedTask) {
-    const taskManagerMessage = this.messages[finishedTask.name] || this.defaultMessage;
+  getErrorMessage(task: FinishedTask) {
     return (
-      taskManagerMessage.error(finishedTask.metadata)[finishedTask.exception.code] ||
-      finishedTask.exception.detail
+      this._getTaskTitle(task).errors(task.metadata)[task.exception.code] || task.exception.detail
     );
   }
 
-  getDescription(task: Task) {
-    const taskManagerMessage = this.messages[task.name] || this.defaultMessage;
-    return taskManagerMessage.descr(task.metadata);
+  getErrorTitle(task: Task) {
+    return this._getTaskTitle(task).failure(task.metadata);
   }
 
-  getRunningMessage(task: Task) {
-    const taskManagerMessage = this.messages[task.name] || this.defaultMessage;
-    return taskManagerMessage.running(task.metadata);
+  getRunningTitle(task: Task) {
+    return this._getTaskTitle(task).running(task.metadata);
   }
 }
index 7409608fefd23e60f6cac008aac6268dceb0ce65..9e91cab8e54188a94fa7024fb24737ab113ec67e 100644 (file)
@@ -50,8 +50,8 @@ export class TaskWrapperService {
   _handleExecutingTasks(task: FinishedTask) {
     this.notificationService.show(
       NotificationType.info,
-      this.taskManagerMessageService.getRunningMessage(task),
-      this.taskManagerMessageService.getDescription(task)
+      this.taskManagerMessageService.getRunningTitle(task),
+      this.taskManagerMessageService.getErrorTitle(task)
     );
 
     const executingTask = new ExecutingTask(task.name, task.metadata);