From 0d3f312f57a92f9bfe1d04b916e0f5127600d52e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stephan=20M=C3=BCller?= Date: Mon, 26 Nov 2018 15:59:26 +0100 Subject: [PATCH] mgr/dashboard: Notification queue MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit It's now possible to send an array of notification configs to the notification service. These array is cached for, by default 500ms, before this array is unified and than converted to full notifications that are seen by the user. Useful if different service can come to the same conclusions regarding the notification. This is the case for the prometheus notification service and the prometheus alert service. Fixes: https://tracker.ceph.com/issues/37469 Signed-off-by: Stephan Müller --- .../services/notification.service.spec.ts | 40 +++++++++++++++++++ .../shared/services/notification.service.ts | 21 +++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) 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 a229c982ebed..b0b88df27368 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 @@ -123,4 +123,44 @@ describe('NotificationService', () => { expect(notification.title).toBe(`Failed to create RBD 'somePool/someImage'`); expect(notification.message).toBe(`Name is already used by RBD 'somePool/someImage'.`); })); + + describe('notification queue', () => { + const n1 = new CdNotificationConfig(NotificationType.success, 'Some success'); + const n2 = new CdNotificationConfig(NotificationType.info, 'Some info'); + + beforeEach(() => { + spyOn(notificationService, 'show').and.stub(); + }); + + it('filters out duplicated notifications on single call', fakeAsync(() => { + notificationService.queueNotifications([n1, n1, n2, n2]); + tick(500); + expect(notificationService.show).toHaveBeenCalledTimes(2); + })); + + it('filters out duplicated notifications presented in different calls', fakeAsync(() => { + notificationService.queueNotifications([n1, n2]); + notificationService.queueNotifications([n1, n2]); + tick(500); + expect(notificationService.show).toHaveBeenCalledTimes(2); + })); + + it('will reset the timeout on every call', fakeAsync(() => { + notificationService.queueNotifications([n1, n2]); + tick(400); + notificationService.queueNotifications([n1, n2]); + tick(100); + expect(notificationService.show).toHaveBeenCalledTimes(0); + tick(400); + expect(notificationService.show).toHaveBeenCalledTimes(2); + })); + + it('wont filter out duplicated notifications if timeout was reached before', fakeAsync(() => { + notificationService.queueNotifications([n1, n2]); + tick(500); + notificationService.queueNotifications([n1, n2]); + tick(500); + expect(notificationService.show).toHaveBeenCalledTimes(4); + })); + }); }); 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 8874e2728264..de1175b8e54d 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 @@ -16,10 +16,12 @@ import { TaskMessageService } from './task-message.service'; export class NotificationService { // Observable sources private dataSource = new BehaviorSubject([]); + private queuedNotifications: CdNotificationConfig[] = []; // Observable streams data$ = this.dataSource.asObservable(); + private queueTimeoutId: number; KEY = 'cdNotifications'; constructor(public toastr: ToastsManager, private taskMessageService: TaskMessageService) { @@ -63,6 +65,21 @@ export class NotificationService { localStorage.setItem(this.KEY, JSON.stringify(recent)); } + queueNotifications(notifications: CdNotificationConfig[]) { + this.queuedNotifications = this.queuedNotifications.concat(notifications); + this.cancel(this.queueTimeoutId); + this.queueTimeoutId = window.setTimeout(() => { + this.sendQueuedNotifications(); + }, 500); + } + + private sendQueuedNotifications() { + _.uniqWith(this.queuedNotifications, _.isEqual).forEach((notification) => { + this.show(notification); + }); + this.queuedNotifications = []; + } + /** * Method for showing a notification. * @param {NotificationType} type toastr type @@ -86,7 +103,7 @@ export class NotificationService { } else { type = arg; } - return setTimeout(() => { + return window.setTimeout(() => { this.save(type, title, message); if (!message) { message = ''; @@ -127,6 +144,6 @@ export class NotificationService { * @param {number} timeoutId A number representing the ID of the timeout to be canceled. */ cancel(timeoutId) { - clearTimeout(timeoutId); + window.clearTimeout(timeoutId); } } -- 2.47.3