1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { NO_ERRORS_SCHEMA } from '@angular/core';
3 import { ComponentFixture, TestBed } from '@angular/core/testing';
4 import { By } from '@angular/platform-browser';
5 import { RouterTestingModule } from '@angular/router/testing';
7 import _ from 'lodash';
8 import { ToastrModule } from 'ngx-toastr';
9 import { BehaviorSubject, of } from 'rxjs';
11 import { HealthService } from '~/app/shared/api/health.service';
12 import { PrometheusService } from '~/app/shared/api/prometheus.service';
13 import { CssHelper } from '~/app/shared/classes/css-helper';
14 import { AlertmanagerAlert } from '~/app/shared/models/prometheus-alerts';
15 import { FeatureTogglesService } from '~/app/shared/services/feature-toggles.service';
16 import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
17 import { SummaryService } from '~/app/shared/services/summary.service';
18 import { SharedModule } from '~/app/shared/shared.module';
19 import { configureTestBed } from '~/testing/unit-test-helper';
20 import { PgCategoryService } from '../../shared/pg-category.service';
21 import { DashboardPieComponent } from '../dashboard-pie/dashboard-pie.component';
22 import { PgSummaryPipe } from '../pg-summary.pipe';
23 import { DashboardV3Component } from './dashboard-v3.component';
24 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
25 import { AlertClass } from '~/app/shared/enum/health-icon.enum';
26 import { HealthSnapshotMap } from '~/app/shared/models/health.interface';
28 export class SummaryServiceMock {
29 summaryDataSource = new BehaviorSubject({
31 'ceph version 17.0.0-12222-gcd0cd7cb ' +
32 '(b8193bb4cda16ccc5b028c3e1df62bc72350a15d) quincy (dev)'
34 summaryData$ = this.summaryDataSource.asObservable();
36 subscribe(call: any) {
37 return this.summaryData$.subscribe(call);
41 describe('Dashbord Component', () => {
42 let component: DashboardV3Component;
43 let fixture: ComponentFixture<DashboardV3Component>;
44 let orchestratorService: OrchestratorService;
45 let getHealthStatusSpy: jasmine.Spy;
46 let getAlertsSpy: jasmine.Spy;
47 let fakeFeatureTogglesService: jasmine.Spy;
49 const healthStatusPayload: HealthSnapshotMap = {
50 fsid: '7d0cc9da-ca8d-4539-a953-ab062139c26a',
52 status: 'HEALTH_WARN',
55 severity: 'HEALTH_WARN',
57 message: 'Dashboard debug mode is enabled',
76 state_name: 'active+clean',
81 bytes_used: 3236978688,
82 bytes_total: 325343772672,
101 const alertsPayload: AlertmanagerAlert[] = [
104 alertname: 'CephMgrPrometheusModuleInactive',
105 instance: 'ceph2:9283',
110 description: 'The mgr/prometheus module at ceph2:9283 is unreachable.',
111 summary: 'The mgr/prometheus module is not available'
113 startsAt: '2022-09-28T08:23:41.152Z',
114 endsAt: '2022-09-28T15:28:01.152Z',
115 generatorURL: 'http://prometheus:9090/testUrl',
121 receivers: ['ceph2'],
122 fingerprint: 'fingerprint'
126 alertname: 'CephOSDDownHigh',
127 instance: 'ceph:9283',
132 description: '66.67% or 2 of 3 OSDs are down (>= 10%).',
133 summary: 'More than 10% of OSDs are down'
135 startsAt: '2022-09-28T14:17:22.665Z',
136 endsAt: '2022-09-28T15:28:32.665Z',
137 generatorURL: 'http://prometheus:9090/testUrl',
143 receivers: ['default'],
144 fingerprint: 'fingerprint'
148 alertname: 'CephHealthWarning',
149 instance: 'ceph:9283',
154 description: 'The cluster state has been HEALTH_WARN for more than 15 minutes.',
155 summary: 'Ceph is in the WARNING state'
157 startsAt: '2022-09-28T08:41:38.454Z',
158 endsAt: '2022-09-28T15:28:38.454Z',
159 generatorURL: 'http://prometheus:9090/testUrl',
166 fingerprint: 'fingerprint'
170 const orchName: any = 'Cephadm';
173 imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule],
174 declarations: [DashboardV3Component, DashboardPieComponent, PgSummaryPipe],
175 schemas: [NO_ERRORS_SCHEMA],
177 { provide: SummaryService, useClass: SummaryServiceMock },
178 PrometheusAlertService,
185 fakeFeatureTogglesService = spyOn(TestBed.inject(FeatureTogglesService), 'get').and.returnValue(
194 fixture = TestBed.createComponent(DashboardV3Component);
195 component = fixture.componentInstance;
196 orchestratorService = TestBed.inject(OrchestratorService);
197 getHealthStatusSpy = spyOn(TestBed.inject(HealthService), 'getHealthSnapshot');
198 getHealthStatusSpy.and.returnValue(of(healthStatusPayload));
199 getAlertsSpy = spyOn(TestBed.inject(PrometheusService), 'getAlerts');
200 getAlertsSpy.and.returnValue(of(alertsPayload));
201 component.prometheusAlertService.alerts = alertsPayload;
202 component.isAlertmanagerConfigured = true;
203 let prometheusAlertService = TestBed.inject(PrometheusAlertService);
204 spyOn(prometheusAlertService, 'getAlerts').and.callFake(() => of([]));
205 prometheusAlertService.activeCriticalAlerts = 2;
206 prometheusAlertService.activeWarningAlerts = 1;
209 it('should create', () => {
210 expect(component).toBeTruthy();
213 it('should render all cards', () => {
214 fixture.detectChanges();
215 const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card');
216 expect(dashboardCards.length).toBe(5);
219 it('should get corresponding data into detailsCardData', () => {
220 spyOn(orchestratorService, 'getName').and.returnValue(of(orchName));
221 component.ngOnInit();
222 expect(component.detailsCardData.fsid).toBe(healthStatusPayload['fsid']);
223 expect(component.detailsCardData.orchestrator).toBe('Cephadm');
224 expect(component.detailsCardData.cephVersion).toBe('17.0.0-12222-gcd0cd7cb quincy (dev)');
227 it('should check if the respective icon is shown for each status', () => {
228 const payload = _.cloneDeep(healthStatusPayload);
231 payload.health['status'] = 'HEALTH_WARN';
232 payload.health['checks'] = {
234 severity: 'HEALTH_WARN',
235 summary: { message: 'fake warning', count: 1 },
240 getHealthStatusSpy.and.returnValue(of(payload));
241 fixture.detectChanges();
242 const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"] i'));
243 expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
246 payload.health['status'] = 'HEALTH_ERR';
247 payload.health['checks'] = {
249 severity: 'HEALTH_ERR',
250 summary: { message: 'fake error', count: 1 },
255 getHealthStatusSpy.and.returnValue(of(payload));
256 fixture.detectChanges();
257 expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
260 payload.health['status'] = 'HEALTH_OK';
261 payload.health['checks'] = {};
263 getHealthStatusSpy.and.returnValue(of(payload));
264 fixture.detectChanges();
265 expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
268 it('should show the actual alert count on each alerts pill', () => {
269 fixture.detectChanges();
271 const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts] span'));
273 const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts] span'));
275 expect(warningAlerts.nativeElement.textContent).toBe('1');
276 expect(dangerAlerts.nativeElement.textContent).toBe('2');
279 it('should show the critical alerts window and its content', () => {
280 const payload = _.cloneDeep(alertsPayload[0]);
281 component.toggleAlertsWindow(AlertClass[0]);
282 fixture.detectChanges();
284 const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));
286 expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
287 expect(component.alertType).not.toBe('warning');
290 it('should show the warning alerts window and its content', () => {
291 const payload = _.cloneDeep(alertsPayload[2]);
292 component.toggleAlertsWindow(AlertClass.warning);
293 fixture.detectChanges();
295 const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));
297 expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
298 expect(component.alertType).not.toBe('critical');
301 it('should only show the pills when the alerts are not empty', () => {
302 spyOn(TestBed.inject(PrometheusAlertService), 'alerts').and.returnValue(0);
303 fixture.detectChanges();
305 const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts]'));
307 const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts]'));
309 expect(warningAlerts).toBe(null);
310 expect(dangerAlerts).toBe(null);
313 it('should render "Status" card text that is not clickable', () => {
314 const payload = _.cloneDeep(healthStatusPayload);
315 payload.health['status'] = 'HEALTH_OK';
316 payload.health['checks'] = null;
318 getHealthStatusSpy.and.returnValue(of(payload));
319 fixture.detectChanges();
320 const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
321 const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
322 expect(clickableContent).toBeNull();
325 it('should render "Status" card text that is clickable (popover)', () => {
326 const payload = _.cloneDeep(healthStatusPayload);
327 payload.health['status'] = 'HEALTH_WARN';
328 payload.health['checks'] = {
330 severity: 'HEALTH_WARN',
331 summary: { message: 'fake warning', count: 1 },
336 getHealthStatusSpy.and.returnValue(of(payload));
337 fixture.detectChanges();
339 const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
340 const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
341 expect(clickableContent).not.toBeNull();
344 describe('features disabled', () => {
346 fakeFeatureTogglesService.and.returnValue(
355 fixture = TestBed.createComponent(DashboardV3Component);
356 component = fixture.componentInstance;
359 it('should not render items related to disabled features', () => {
360 fixture.detectChanges();
362 const iscsiCard = fixture.debugElement.query(By.css('li[id=iscsi-item]'));
363 const rgwCard = fixture.debugElement.query(By.css('li[id=rgw-item]'));
364 const mds = fixture.debugElement.query(By.css('li[id=mds-item]'));
366 expect(iscsiCard).toBeFalsy();
367 expect(rgwCard).toBeFalsy();
368 expect(mds).toBeFalsy();