From ac867c7f832bada36fe1ca16d6eca4b00cd19fec Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stephan=20M=C3=BCller?= Date: Fri, 27 Jul 2018 11:34:51 +0200 Subject: [PATCH] mgr/dashboard: Cleaner notifications MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 --- src/pybind/mgr/dashboard/HACKING.rst | 108 ++++--- .../task-manager.component.spec.ts | 13 +- .../task-manager/task-manager.component.ts | 6 +- .../src/app/shared/models/cd-notification.ts | 4 +- .../services/notification.service.spec.ts | 32 ++- .../shared/services/notification.service.ts | 21 +- .../task-manager-message.service.spec.ts | 159 ++++++++--- .../services/task-manager-message.service.ts | 269 ++++++++---------- .../shared/services/task-wrapper.service.ts | 4 +- 9 files changed, 348 insertions(+), 268 deletions(-) diff --git a/src/pybind/mgr/dashboard/HACKING.rst b/src/pybind/mgr/dashboard/HACKING.rst index 5faeee2ec487e..d7e3cb0f221ea 100644 --- a/src/pybind/mgr/dashboard/HACKING.rst +++ b/src/pybind/mgr/dashboard/HACKING.rst @@ -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 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.spec.ts index 8b8f4a25cc869..447c4243c0549 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.spec.ts @@ -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'.` ); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.ts index a78c8c0a755cc..4e17c704a1f49 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/task-manager/task-manager.component.ts @@ -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; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-notification.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-notification.ts index f69da46117f0f..ba6a73a000ed7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-notification.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-notification.ts @@ -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 */ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts index 28471e6f0b429..763a086b71ef3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts @@ -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'.`); }) ); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts index 13a9f4350a4c9..f736a1e41905b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts @@ -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) ); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts index 32af77108b6d8..aeb36234077a5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.spec.ts @@ -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); + }); }); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.ts index 9f32645a24c07..f8210fb9eb825 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-manager-message.service.ts @@ -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); } } 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 index 7409608fefd23..9e91cab8e5418 100644 --- 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 @@ -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); -- 2.39.5