The new notification panel only includes the notifications currently and not the executing tasks. We need to include it in the panel as well
Fixes: https://tracker.ceph.com/issues/74364
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
ToggleModule,
ButtonModule,
PlaceholderModule,
- TagModule
+ TagModule,
+ ProgressBarModule
} from 'carbon-components-angular';
import { AppRoutingModule } from '~/app/app-routing.module';
ToggleModule,
ButtonModule,
PlaceholderModule,
- TagModule
+ TagModule,
+ ProgressBarModule
],
declarations: [
AboutComponent,
-<ng-template #notificationItemTemplate
- let-notification="notification"
- let-last="last">
+@if (executingTasks.length > 0) {
+<div
+ class="notification-section-heading"
+ i18n>
+ Running tasks
+</div>
+
+@for (task of executingTasks; track task.begin_time) {
+<div class="notification-wrapper">
+ <div class="notification-item task-item">
+ <cd-icon type="infoCircle"></cd-icon>
+
+ <div class="notification-content">
+ <div class="notification-title">
+ {{ task.description }}
+ </div>
+
+ <cds-progress-bar
+ [max]="100"
+ [value]="task.progress || 0"
+ status="active"
+ type="inline"
+ size="small">
+ </cds-progress-bar>
+
+ <div class="task-row">
+ <span class="notification-timestamp">
+ {{ task.begin_time | relativeDate }}
+ </span>
+
+ <span class="task-progress">
+ {{ task.progress || 0 }} %
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <div class="notification-divider"></div>
+</div>
+}
+}
+
+<ng-template
+ #notificationItemTemplate
+ let-notification="notification"
+ let-last="last">
<div class="notification-wrapper">
<div class="notification-item">
- <cd-icon id="notification-icon"
- [type]="notificationIconMap[notification.type] || notificationIconMap['default']">
+ <cd-icon
+ id="notification-icon"
+ [type]="notificationIconMap[notification.type] || notificationIconMap['default']">
</cd-icon>
+
<div class="notification-content">
- <div class="notification-timestamp">{{ notification.timestamp | relativeDate }}</div>
- <div class="notification-title">{{ notification.title }}</div>
- <div class="notification-message"
- [innerHTML]="notification.message | sanitizeHtml"></div>
+ <div class="notification-timestamp">
+ {{ notification.timestamp | relativeDate }}
+ </div>
+ <div class="notification-title">
+ {{ notification.title }}
+ </div>
+ <div
+ class="notification-message"
+ [innerHTML]="notification.message | sanitizeHtml">
+ </div>
</div>
- <button cdsButton="ghost"
- size="sm"
- class="notification-close"
- (click)="removeNotification(notification, $event)">
+
+ <button
+ cdsButton="ghost"
+ size="sm"
+ class="notification-close"
+ (click)="removeNotification(notification, $event)">
<cd-icon type="destroy"></cd-icon>
</button>
</div>
- @if (!last) {
+
+ @if (!last) {
<div class="notification-divider"></div>
- }
+ }
</div>
</ng-template>
@if (todayNotifications.length > 0) {
- <div class="notification-section-heading"
- i18n>Today</div>
- @for (notification of todayNotifications; track notification.timestamp; let last = $last) {
- <ng-container *ngTemplateOutlet="notificationItemTemplate; context: { notification: notification, last: last }"></ng-container>
- }
+<div
+ class="notification-section-heading"
+ i18n>
+ Today
+</div>
+
+@for (notification of todayNotifications; track notification.timestamp; let last = $last) {
+<ng-container
+ *ngTemplateOutlet="notificationItemTemplate;
+ context: { notification: notification, last: last }">
+</ng-container>
+}
}
@if (previousNotifications.length > 0) {
- <div class="notification-section-heading"
- i18n>Previous</div>
- @for (notification of previousNotifications; track notification.timestamp; let last = $last) {
- <ng-container *ngTemplateOutlet="notificationItemTemplate; context: { notification: notification, last: last }"></ng-container>
- }
+<div
+ class="notification-section-heading"
+ i18n>
+ Previous
+</div>
+
+@for (notification of previousNotifications; track notification.timestamp; let last = $last) {
+<ng-container
+ *ngTemplateOutlet="notificationItemTemplate;
+ context: { notification: notification, last: last }">
+</ng-container>
+}
}
@if (todayNotifications.length === 0 && previousNotifications.length === 0) {
- <div class="notification-empty">
- <div i18n>No notifications</div>
- </div>
+<div class="notification-empty">
+ <div i18n>No notifications</div>
+</div>
}
+@use './src/styles/defaults' as *;
@use '@carbon/styles/scss/theme' as *;
@use '@carbon/styles/scss/spacing' as *;
@use '@carbon/type';
display: block;
}
+.task-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: var(--cds-spacing-02);
+}
+
+.task-progress {
+ @include type.type-style('label-01');
+
+ color: var(--cds-text-secondary);
+ text-align: right;
+}
+
.notification-timestamp {
@include type.type-style('label-01');
overflow-y: auto;
background-color: $layer-01;
}
+
+:host ::ng-deep .infoCircle-icon {
+ fill: $primary !important;
+}
import { NotificationType } from '../../../../shared/enum/notification-type.enum';
import { SharedModule } from '../../../../shared/shared.module';
import { configureTestBed } from '~/testing/unit-test-helper';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
describe('NotificationAreaComponent', () => {
let component: NotificationAreaComponent;
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
+ configureTestBed({
+ imports: [HttpClientTestingModule]
+ });
+
const createNotification = (
type: NotificationType,
title: string,
});
it('should unsubscribe from notification service on destroy', () => {
- const subSpy = spyOn(component['sub'], 'unsubscribe');
+ const subSpy = spyOn(component['subs'], 'unsubscribe');
component.ngOnDestroy();
expect(subSpy).toHaveBeenCalled();
});
-import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { NotificationService } from '../../../../shared/services/notification.service';
import { CdNotification } from '../../../../shared/models/cd-notification';
import { NotificationType } from '../../../../shared/enum/notification-type.enum';
+import { SummaryService } from '~/app/shared/services/summary.service';
+import { Mutex } from 'async-mutex';
+import _ from 'lodash';
+import { FinishedTask } from '~/app/shared/models/finished-task';
+import moment from 'moment';
+import { ExecutingTask } from '~/app/shared/models/executing-task';
+import { TaskMessageService } from '~/app/shared/services/task-message.service';
+import { Icons } from '~/app/shared/enum/icons.enum';
@Component({
selector: 'cd-notification-area',
export class NotificationAreaComponent implements OnInit, OnDestroy {
todayNotifications: CdNotification[] = [];
previousNotifications: CdNotification[] = [];
- private sub: Subscription;
+ private subs = new Subscription();
+ last_task = '';
+ mutex = new Mutex();
+ icons = Icons;
+ executingTasks: ExecutingTask[] = [];
readonly notificationIconMap = {
[NotificationType.success]: 'success',
default: 'infoCircle'
} as const;
- constructor(private notificationService: NotificationService) {}
+ constructor(
+ private notificationService: NotificationService,
+ private summaryService: SummaryService,
+ private cdRef: ChangeDetectorRef,
+ private taskMessageService: TaskMessageService
+ ) {}
ngOnInit(): void {
- this.sub = this.notificationService.data$.subscribe((notifications: CdNotification[]) => {
- const today: Date = new Date();
- this.todayNotifications = [];
- this.previousNotifications = [];
- notifications.forEach((n: CdNotification) => {
- const notifDate = new Date(n.timestamp);
- if (
- notifDate.getDate() === today.getDate() &&
- notifDate.getMonth() === today.getMonth() &&
- notifDate.getFullYear() === today.getFullYear()
- ) {
- this.todayNotifications.push(n);
- } else {
- this.previousNotifications.push(n);
- }
- });
- });
+ this.subs.add(
+ this.notificationService.data$.subscribe((notifications: CdNotification[]) => {
+ const today: Date = new Date();
+ this.todayNotifications = [];
+ this.previousNotifications = [];
+ notifications.forEach((n: CdNotification) => {
+ const notifDate = new Date(n.timestamp);
+ if (
+ notifDate.getDate() === today.getDate() &&
+ notifDate.getMonth() === today.getMonth() &&
+ notifDate.getFullYear() === today.getFullYear()
+ ) {
+ this.todayNotifications.push(n);
+ } else {
+ this.previousNotifications.push(n);
+ }
+ });
+ })
+ );
+
+ this.subs.add(
+ this.summaryService.subscribe((summary) => {
+ this._handleTasks(summary.executing_tasks);
+
+ this.mutex.acquire().then((release) => {
+ _.filter(
+ summary.finished_tasks,
+ (task: FinishedTask) => !this.last_task || moment(task.end_time).isAfter(this.last_task)
+ ).forEach((task) => {
+ const config = this.notificationService.finishedTaskToNotification(task, task.success);
+ const notification = new CdNotification(config);
+ notification.timestamp = task.end_time;
+ notification.duration = task.duration;
+
+ if (!this.last_task || moment(task.end_time).isAfter(this.last_task)) {
+ this.last_task = task.end_time;
+ window.localStorage.setItem('last_task', this.last_task);
+ }
+
+ this.notificationService.save(notification);
+ });
+
+ this.cdRef.detectChanges();
+
+ release();
+ });
+ })
+ );
}
- ngOnDestroy(): void {
- if (this.sub) {
- this.sub.unsubscribe();
+ _handleTasks(executingTasks: ExecutingTask[]) {
+ for (const executingTask of executingTasks) {
+ executingTask.description = this.taskMessageService.getRunningTitle(executingTask);
}
+ this.executingTasks = executingTasks;
+ }
+
+ ngOnDestroy(): void {
+ this.subs.unsubscribe();
}
removeNotification(notification: CdNotification, event: MouseEvent) {
-webkit-text-fill-color: inherit;
transition: background-color 5000s ease-in-out 0s;
}
+
+.cds--progress-bar__track {
+ background-color: colors.$gray-30;
+}
+
+.cds--progress-bar__bar {
+ background-color: var(--cds-primary);
+}