From: Tatjana Dehler Date: Wed, 13 May 2020 11:19:47 +0000 (+0200) Subject: mgr/dashboard: add telemetry activation notification X-Git-Tag: wip-pdonnell-testing-20200918.022351~865^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=f7e457952ba5780b4b7fc3a1e4290c02d4e5d70d;p=ceph-ci.git mgr/dashboard: add telemetry activation notification 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 --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts index b3282e1b5d5..40c00795e36 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts @@ -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({ @@ -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(); } @@ -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); } @@ -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.') diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html index 092770d044b..2a42f711fb5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html @@ -1,4 +1,5 @@ +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts index 0f7764e05d9..25fb4d6f789 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts @@ -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() } }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts index ce041477540..5d45104cb82 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts @@ -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', @@ -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(); @@ -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 { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index 84ecc66fc7c..55a2779a040 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -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'; @@ -82,6 +83,7 @@ import { ViewCacheComponent } from './view-cache/view-cache.component'; AlertPanelComponent, FormModalComponent, PwdExpirationNotificationComponent, + TelemetryNotificationComponent, OrchestratorDocPanelComponent, OrchestratorDocModalComponent ], @@ -104,6 +106,7 @@ import { ViewCacheComponent } from './view-cache/view-cache.component'; ConfigOptionComponent, AlertPanelComponent, PwdExpirationNotificationComponent, + TelemetryNotificationComponent, OrchestratorDocPanelComponent ] }) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html new file mode 100644 index 00000000000..7a801ecf87e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html @@ -0,0 +1,9 @@ + +
The Telemetry module is not submitting telemetry data at the + moment. Click + here to activate it now.
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.scss new file mode 100644 index 00000000000..6862c2f5574 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.scss @@ -0,0 +1,3 @@ +::ng-deep .no-margin-bottom { + margin-bottom: 0; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts new file mode 100644 index 00000000000..7f2b4725193 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts @@ -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; + + 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); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.ts new file mode 100644 index 00000000000..974f625e1c6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.ts @@ -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 (Dashboard Settings -> Telemetry configuration) at any time.' + ) + ); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/telemetry-notification.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/telemetry-notification.service.spec.ts new file mode 100644 index 00000000000..41d8060ea15 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/telemetry-notification.service.spec.ts @@ -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); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/telemetry-notification.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/telemetry-notification.service.ts new file mode 100644 index 00000000000..fcb2e026462 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/telemetry-notification.service.ts @@ -0,0 +1,16 @@ +import { EventEmitter, Injectable, Output } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class TelemetryNotificationService { + visible = false; + + @Output() + update: EventEmitter = new EventEmitter(); + + setVisibility(visible: boolean) { + this.visible = visible; + this.update.emit(visible); + } +}