Skip to content

Commit

Permalink
mgr/dashboard: add telemetry activation notification
Browse files Browse the repository at this point in the history
The commit adds 2 different parts to show the
Telemetry activation notification in the dashboard:

1. The Telemetry activation notification component
   itself. It contains the definition of the
   notification panel.
2. The Telemetry notification service. The service
   is needed to be able to show/hide the
   notification from:
   * the component itself (e.g. when clicking the
     button button)
   * the Telemetry configuration component (when
     enabling/disabling Telemetry)
   * the navigation component (to set the css-
     classes accordingly)

Fixes: https://tracker.ceph.com/issues/45464
Signed-off-by: Tatjana Dehler <tdehler@suse.com>
  • Loading branch information
Tatjana Dehler committed Jun 19, 2020
1 parent 455dca6 commit f7e4579
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 2 deletions.
Expand Up @@ -14,6 +14,7 @@ import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
import { NotificationService } from '../../../shared/services/notification.service';
import { TelemetryNotificationService } from '../../../shared/services/telemetry-notification.service';
import { TextToDownloadService } from '../../../shared/services/text-to-download.service';

@Component({
Expand Down Expand Up @@ -50,7 +51,8 @@ export class TelemetryComponent extends CdForm implements OnInit {
private router: Router,
private telemetryService: TelemetryService,
private i18n: I18n,
private textToDownloadService: TextToDownloadService
private textToDownloadService: TextToDownloadService,
private telemetryNotificationService: TelemetryNotificationService
) {
super();
}
Expand Down Expand Up @@ -172,6 +174,7 @@ complete the next step and accept the license.`

disableModule(message: string = null, followUpFunc: Function = null) {
this.telemetryService.enable(false).subscribe(() => {
this.telemetryNotificationService.setVisibility(true);
if (message) {
this.notificationService.show(NotificationType.success, message);
}
Expand All @@ -197,6 +200,7 @@ complete the next step and accept the license.`

onSubmit() {
this.telemetryService.enable().subscribe(() => {
this.telemetryNotificationService.setVisibility(false);
this.notificationService.show(
NotificationType.success,
this.i18n('The Telemetry module has been configured and activated successfully.')
Expand Down
@@ -1,4 +1,5 @@
<cd-pwd-expiration-notification></cd-pwd-expiration-notification>
<cd-telemetry-notification></cd-telemetry-notification>
<cd-notifications-sidebar></cd-notifications-sidebar>

<div class="cd-navbar-top">
Expand Down
Expand Up @@ -57,7 +57,8 @@ describe('NavigationComponent', () => {
provide: AuthStorageService,
useValue: {
getPermissions: jest.fn(),
isPwdDisplayed$: { subscribe: jest.fn() }
isPwdDisplayed$: { subscribe: jest.fn() },
telemetryNotification$: { subscribe: jest.fn() }
}
},
{ provide: SummaryService, useValue: { subscribe: jest.fn() } },
Expand Down
Expand Up @@ -11,6 +11,7 @@ import {
} from '../../../shared/services/feature-toggles.service';
import { PrometheusAlertService } from '../../../shared/services/prometheus-alert.service';
import { SummaryService } from '../../../shared/services/summary.service';
import { TelemetryNotificationService } from '../../../shared/services/telemetry-notification.service';

@Component({
selector: 'cd-navigation',
Expand Down Expand Up @@ -41,6 +42,7 @@ export class NavigationComponent implements OnInit, OnDestroy {
private authStorageService: AuthStorageService,
private summaryService: SummaryService,
private featureToggles: FeatureTogglesService,
private telemetryNotificationService: TelemetryNotificationService,
public prometheusAlertService: PrometheusAlertService
) {
this.permissions = this.authStorageService.getPermissions();
Expand All @@ -63,6 +65,11 @@ export class NavigationComponent implements OnInit, OnDestroy {
this.showTopNotification('isPwdDisplayed', isDisplayed);
})
);
this.subs.add(
this.telemetryNotificationService.update.subscribe((visible: boolean) => {
this.showTopNotification('telemetryNotificationEnabled', visible);
})
);
}

ngOnDestroy(): void {
Expand Down
Expand Up @@ -38,6 +38,7 @@ import { SelectBadgesComponent } from './select-badges/select-badges.component';
import { SelectComponent } from './select/select.component';
import { SparklineComponent } from './sparkline/sparkline.component';
import { SubmitButtonComponent } from './submit-button/submit-button.component';
import { TelemetryNotificationComponent } from './telemetry-notification/telemetry-notification.component';
import { UsageBarComponent } from './usage-bar/usage-bar.component';
import { ViewCacheComponent } from './view-cache/view-cache.component';

Expand Down Expand Up @@ -82,6 +83,7 @@ import { ViewCacheComponent } from './view-cache/view-cache.component';
AlertPanelComponent,
FormModalComponent,
PwdExpirationNotificationComponent,
TelemetryNotificationComponent,
OrchestratorDocPanelComponent,
OrchestratorDocModalComponent
],
Expand All @@ -104,6 +106,7 @@ import { ViewCacheComponent } from './view-cache/view-cache.component';
ConfigOptionComponent,
AlertPanelComponent,
PwdExpirationNotificationComponent,
TelemetryNotificationComponent,
OrchestratorDocPanelComponent
]
})
Expand Down
@@ -0,0 +1,9 @@
<ngb-alert class="no-margin-bottom"
type="warning"
*ngIf="displayNotification"
(close)="close($event)">
<div i18n>The Telemetry module is not submitting telemetry data at the
moment. Click
<a routerLink="/telemetry"
class="alert-link">here</a> to activate it now.</div>
</ngb-alert>
@@ -0,0 +1,3 @@
::ng-deep .no-margin-bottom {
margin-bottom: 0;
}
@@ -0,0 +1,125 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap';
import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';

import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { UserFormModel } from '../../../core/auth/user-form/user-form.model';
import { MgrModuleService } from '../../api/mgr-module.service';
import { UserService } from '../../api/user.service';
import { PipesModule } from '../../pipes/pipes.module';
import { AuthStorageService } from '../../services/auth-storage.service';
import { NotificationService } from '../../services/notification.service';
import { TelemetryNotificationService } from '../../services/telemetry-notification.service';
import { TelemetryNotificationComponent } from './telemetry-notification.component';

describe('TelemetryActivationNotificationComponent', () => {
let component: TelemetryNotificationComponent;
let fixture: ComponentFixture<TelemetryNotificationComponent>;

let authStorageService: AuthStorageService;
let userService: UserService;
let mgrModuleService: MgrModuleService;
let notificationService: NotificationService;

let isNotificationHiddenSpy: jasmine.Spy;
let getUsernameSpy: jasmine.Spy;
let userServiceGetSpy: jasmine.Spy;
let getConfigSpy: jasmine.Spy;

const user: UserFormModel = {
username: 'username',
password: undefined,
name: 'User 1',
email: 'user1@email.com',
roles: ['read-only'],
enabled: true,
pwdExpirationDate: undefined,
pwdUpdateRequired: true
};
const admin: UserFormModel = {
username: 'admin',
password: undefined,
name: 'User 1',
email: 'user1@email.com',
roles: ['administrator'],
enabled: true,
pwdExpirationDate: undefined,
pwdUpdateRequired: true
};
const telemetryEnabledConfig = {
enabled: true
};
const telemetryDisabledConfig = {
enabled: false
};

configureTestBed({
declarations: [TelemetryNotificationComponent],
imports: [NgbAlertModule, HttpClientTestingModule, ToastrModule.forRoot(), PipesModule],
providers: [MgrModuleService, UserService, i18nProviders]
});

beforeEach(() => {
fixture = TestBed.createComponent(TelemetryNotificationComponent);
component = fixture.componentInstance;
authStorageService = TestBed.inject(AuthStorageService);
userService = TestBed.inject(UserService);
mgrModuleService = TestBed.inject(MgrModuleService);
notificationService = TestBed.inject(NotificationService);

isNotificationHiddenSpy = spyOn(component, 'isNotificationHidden').and.returnValue(false);
getUsernameSpy = spyOn(authStorageService, 'getUsername').and.returnValue('username');
userServiceGetSpy = spyOn(userService, 'get').and.returnValue(of(admin)); // Not the best name but it sounded better than `getSpy`
getConfigSpy = spyOn(mgrModuleService, 'getConfig').and.returnValue(
of(telemetryDisabledConfig)
);
});

it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});

it('should not show notification again if the user closed it before', () => {
isNotificationHiddenSpy.and.returnValue(true);
fixture.detectChanges();
expect(component.displayNotification).toBe(false);
});

it('should not show notification for an user without administrator role', () => {
userServiceGetSpy.and.returnValue(of(user));
fixture.detectChanges();
expect(component.displayNotification).toBe(false);
});

it('should not show notification if the module is enabled already', () => {
getUsernameSpy.and.returnValue('admin');
getConfigSpy.and.returnValue(of(telemetryEnabledConfig));
fixture.detectChanges();
expect(component.displayNotification).toBe(false);
});

it('should show the notification if all pre-conditions set accordingly', () => {
fixture.detectChanges();
expect(component.displayNotification).toBe(true);
});

it('should hide the notification if the user closes it', () => {
spyOn(notificationService, 'show');
fixture.detectChanges();
component.close();
expect(notificationService.show).toHaveBeenCalled();
expect(localStorage.getItem('telemetry_notification_hidden')).toBe('true');
});

it('should hide the notification if the user logs out', () => {
const telemetryNotificationService = TestBed.inject(TelemetryNotificationService);
spyOn(telemetryNotificationService, 'setVisibility');
fixture.detectChanges();
component.ngOnDestroy();
expect(telemetryNotificationService.setVisibility).toHaveBeenCalledWith(false);
});
});
@@ -0,0 +1,69 @@
import { Component, OnDestroy, OnInit } from '@angular/core';

import { I18n } from '@ngx-translate/i18n-polyfill';

import { UserFormModel } from '../../../core/auth/user-form/user-form.model';
import { MgrModuleService } from '../../api/mgr-module.service';
import { UserService } from '../../api/user.service';
import { NotificationType } from '../../enum/notification-type.enum';
import { AuthStorageService } from '../../services/auth-storage.service';
import { NotificationService } from '../../services/notification.service';
import { TelemetryNotificationService } from '../../services/telemetry-notification.service';

@Component({
selector: 'cd-telemetry-notification',
templateUrl: './telemetry-notification.component.html',
styleUrls: ['./telemetry-notification.component.scss']
})
export class TelemetryNotificationComponent implements OnInit, OnDestroy {
displayNotification = false;

constructor(
private mgrModuleService: MgrModuleService,
private authStorageService: AuthStorageService,
private userService: UserService,
private notificationService: NotificationService,
private telemetryNotificationService: TelemetryNotificationService,
private i18n: I18n
) {}

ngOnInit() {
this.telemetryNotificationService.update.subscribe((visible: boolean) => {
this.displayNotification = visible;
});

if (!this.isNotificationHidden()) {
const username = this.authStorageService.getUsername();
this.userService.get(username).subscribe((user: UserFormModel) => {
if (user.roles.includes('administrator')) {
this.mgrModuleService.getConfig('telemetry').subscribe((options) => {
if (!options['enabled']) {
this.telemetryNotificationService.setVisibility(true);
}
});
}
});
}
}

ngOnDestroy() {
this.telemetryNotificationService.setVisibility(false);
}

isNotificationHidden(): boolean {
return localStorage.getItem('telemetry_notification_hidden') === 'true';
}

close() {
this.telemetryNotificationService.setVisibility(false);
localStorage.setItem('telemetry_notification_hidden', 'true');
this.notificationService.show(
NotificationType.success,
this.i18n('Telemetry activation reminder muted'),
this.i18n(
'You can activate the module on the Telemetry configuration ' +
'page (<b>Dashboard Settings</b> -> <b>Telemetry configuration</b>) at any time.'
)
);
}
}
@@ -0,0 +1,33 @@
import { TestBed } from '@angular/core/testing';

import { configureTestBed } from '../../../testing/unit-test-helper';
import { TelemetryNotificationService } from './telemetry-notification.service';

describe('TelemetryNotificationService', () => {
let service: TelemetryNotificationService;

configureTestBed({
providers: [TelemetryNotificationService]
});

beforeEach(() => {
service = TestBed.inject(TelemetryNotificationService);
spyOn(service.update, 'emit');
});

it('should be created', () => {
expect(service).toBeTruthy();
});

it('should set notification visibility to true', () => {
service.setVisibility(true);
expect(service.visible).toBe(true);
expect(service.update.emit).toHaveBeenCalledWith(true);
});

it('should set notification visibility to false', () => {
service.setVisibility(false);
expect(service.visible).toBe(false);
expect(service.update.emit).toHaveBeenCalledWith(false);
});
});
@@ -0,0 +1,16 @@
import { EventEmitter, Injectable, Output } from '@angular/core';

@Injectable({
providedIn: 'root'
})
export class TelemetryNotificationService {
visible = false;

@Output()
update: EventEmitter<boolean> = new EventEmitter<boolean>();

setVisibility(visible: boolean) {
this.visible = visible;
this.update.emit(visible);
}
}

0 comments on commit f7e4579

Please sign in to comment.