Merge pull request #25325 from Devp00l/wip-issue-37469

mgr/dashboard: Notification queue

Reviewed-by: Alfonso Martínez <almartin@redhat.com>
Reviewed-by: Ernesto Puerta <epuertat@redhat.com>
Reviewed-by: Tiago Melo <tmelo@suse.com>
This commit is contained in:
Lenz Grimmer 2018-12-12 17:59:29 +01:00 committed by GitHub
commit 339ff6abf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 17 deletions

View File

@ -1,16 +1,23 @@
import { ToastOptions } from 'ng2-toastr';
import { NotificationType } from '../enum/notification-type.enum';
export class CdNotificationConfig {
constructor(
public type: NotificationType,
public title: string,
public message?: string, // Use this for error notifications only
public options?: any | ToastOptions
) {}
}
export class CdNotification {
message: string;
timestamp: string;
title: string;
type: NotificationType;
constructor(type: NotificationType = NotificationType.info, title?: string, message?: string) {
this.type = type;
this.title = title;
this.message = message;
constructor(
public type: NotificationType = NotificationType.info,
public title?: string,
public message?: string
) {
/* string representation of the Date object so it can be directly compared
with the timestamps parsed from localStorage */
this.timestamp = new Date().toJSON();

View File

@ -5,6 +5,7 @@ import { ToastsManager } from 'ng2-toastr';
import { configureTestBed, i18nProviders } from '../../../testing/unit-test-helper';
import { NotificationType } from '../enum/notification-type.enum';
import { CdNotificationConfig } from '../models/cd-notification';
import { FinishedTask } from '../models/finished-task';
import { NotificationService } from './notification.service';
import { TaskMessageService } from './task-message.service';
@ -57,7 +58,7 @@ describe('NotificationService', () => {
}));
it('should create a success notification and save it', fakeAsync(() => {
notificationService.show(NotificationType.success, 'Simple test');
notificationService.show(new CdNotificationConfig(NotificationType.success, 'Simple test'));
tick(100);
expect(notificationService['dataSource'].getValue().length).toBe(1);
expect(notificationService['dataSource'].getValue()[0].type).toBe(NotificationType.success);
@ -71,7 +72,7 @@ describe('NotificationService', () => {
}));
it('should create an info notification and save it', fakeAsync(() => {
notificationService.show(NotificationType.info, 'Simple test');
notificationService.show(new CdNotificationConfig(NotificationType.info, 'Simple test'));
tick(100);
expect(notificationService['dataSource'].getValue().length).toBe(1);
const notification = notificationService['dataSource'].getValue()[0];
@ -122,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);
}));
});
});

View File

@ -5,7 +5,7 @@ import { ToastsManager } from 'ng2-toastr';
import { BehaviorSubject } from 'rxjs';
import { NotificationType } from '../enum/notification-type.enum';
import { CdNotification } from '../models/cd-notification';
import { CdNotification, CdNotificationConfig } from '../models/cd-notification';
import { FinishedTask } from '../models/finished-task';
import { ServicesModule } from './services.module';
import { TaskMessageService } from './task-message.service';
@ -16,10 +16,12 @@ import { TaskMessageService } from './task-message.service';
export class NotificationService {
// Observable sources
private dataSource = new BehaviorSubject<CdNotification[]>([]);
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
@ -70,11 +87,23 @@ export class NotificationService {
* @param {string} [message] The message to be displayed. Note, use this field
* for error notifications only.
* @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, title: string, message?: string, options?: any) {
return setTimeout(() => {
show(type: NotificationType, title: string, message?: string, options?: any): number;
show(config: CdNotificationConfig): number;
show(
arg: NotificationType | CdNotificationConfig,
title?: string,
message?: string,
options?: any
): number {
let type;
if (_.isObject(arg)) {
({ message, type, title, options } = <CdNotificationConfig>arg);
} else {
type = arg;
}
return window.setTimeout(() => {
this.save(type, title, message);
if (!message) {
message = '';
@ -94,15 +123,20 @@ export class NotificationService {
}
notifyTask(finishedTask: FinishedTask, success: boolean = true) {
let notification: CdNotificationConfig;
if (finishedTask.success && success) {
this.show(NotificationType.success, this.taskMessageService.getSuccessTitle(finishedTask));
notification = new CdNotificationConfig(
NotificationType.success,
this.taskMessageService.getSuccessTitle(finishedTask)
);
} else {
this.show(
notification = new CdNotificationConfig(
NotificationType.error,
this.taskMessageService.getErrorTitle(finishedTask),
this.taskMessageService.getErrorMessage(finishedTask)
);
}
this.show(notification);
}
/**
@ -110,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);
}
}