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';
27 export class SummaryServiceMock {
28 summaryDataSource = new BehaviorSubject({
30 'ceph version 17.0.0-12222-gcd0cd7cb ' +
31 '(b8193bb4cda16ccc5b028c3e1df62bc72350a15d) quincy (dev)'
33 summaryData$ = this.summaryDataSource.asObservable();
35 subscribe(call: any) {
36 return this.summaryData$.subscribe(call);
40 describe('Dashbord Component', () => {
41 let component: DashboardV3Component;
42 let fixture: ComponentFixture<DashboardV3Component>;
43 let healthService: HealthService;
44 let orchestratorService: OrchestratorService;
45 let getHealthSpy: jasmine.Spy;
46 let getAlertsSpy: jasmine.Spy;
47 let fakeFeatureTogglesService: jasmine.Spy;
49 const healthPayload: Record<string, any> = {
50 health: { status: 'HEALTH_OK' },
51 mon_status: { monmap: { mons: [] }, quorum: [] },
52 osd_map: { osds: [] },
53 mgr_map: { standbys: [] },
56 fs_map: { filesystems: [], standbys: [] },
59 scrub_status: 'Inactive',
62 pg_info: { object_stats: { num_objects: 1 } }
65 const alertsPayload: AlertmanagerAlert[] = [
68 alertname: 'CephMgrPrometheusModuleInactive',
69 instance: 'ceph2:9283',
74 description: 'The mgr/prometheus module at ceph2:9283 is unreachable.',
75 summary: 'The mgr/prometheus module is not available'
77 startsAt: '2022-09-28T08:23:41.152Z',
78 endsAt: '2022-09-28T15:28:01.152Z',
79 generatorURL: 'http://prometheus:9090/testUrl',
86 fingerprint: 'fingerprint'
90 alertname: 'CephOSDDownHigh',
91 instance: 'ceph:9283',
96 description: '66.67% or 2 of 3 OSDs are down (>= 10%).',
97 summary: 'More than 10% of OSDs are down'
99 startsAt: '2022-09-28T14:17:22.665Z',
100 endsAt: '2022-09-28T15:28:32.665Z',
101 generatorURL: 'http://prometheus:9090/testUrl',
107 receivers: ['default'],
108 fingerprint: 'fingerprint'
112 alertname: 'CephHealthWarning',
113 instance: 'ceph:9283',
118 description: 'The cluster state has been HEALTH_WARN for more than 15 minutes.',
119 summary: 'Ceph is in the WARNING state'
121 startsAt: '2022-09-28T08:41:38.454Z',
122 endsAt: '2022-09-28T15:28:38.454Z',
123 generatorURL: 'http://prometheus:9090/testUrl',
130 fingerprint: 'fingerprint'
134 const configValueData: any = 'e90a0d58-658e-4148-8f61-e896c86f0696';
136 const orchName: any = 'Cephadm';
139 imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule],
140 declarations: [DashboardV3Component, DashboardPieComponent, PgSummaryPipe],
141 schemas: [NO_ERRORS_SCHEMA],
143 { provide: SummaryService, useClass: SummaryServiceMock },
144 PrometheusAlertService,
151 fakeFeatureTogglesService = spyOn(TestBed.inject(FeatureTogglesService), 'get').and.returnValue(
160 fixture = TestBed.createComponent(DashboardV3Component);
161 component = fixture.componentInstance;
162 healthService = TestBed.inject(HealthService);
163 orchestratorService = TestBed.inject(OrchestratorService);
164 getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth');
165 getHealthSpy.and.returnValue(of(healthPayload));
166 getAlertsSpy = spyOn(TestBed.inject(PrometheusService), 'getAlerts');
167 getAlertsSpy.and.returnValue(of(alertsPayload));
168 component.prometheusAlertService.alerts = alertsPayload;
169 component.isAlertmanagerConfigured = true;
170 let prometheusAlertService = TestBed.inject(PrometheusAlertService);
171 spyOn(prometheusAlertService, 'getAlerts').and.callFake(() => of([]));
172 prometheusAlertService.activeCriticalAlerts = 2;
173 prometheusAlertService.activeWarningAlerts = 1;
176 it('should create', () => {
177 expect(component).toBeTruthy();
180 it('should render all cards', () => {
181 fixture.detectChanges();
182 const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card');
183 expect(dashboardCards.length).toBe(5);
186 it('should get corresponding data into detailsCardData', () => {
187 spyOn(healthService, 'getClusterFsid').and.returnValue(of(configValueData));
188 spyOn(orchestratorService, 'getName').and.returnValue(of(orchName));
189 component.ngOnInit();
190 expect(component.detailsCardData.fsid).toBe('e90a0d58-658e-4148-8f61-e896c86f0696');
191 expect(component.detailsCardData.orchestrator).toBe('Cephadm');
192 expect(component.detailsCardData.cephVersion).toBe('17.0.0-12222-gcd0cd7cb quincy (dev)');
195 it('should check if the respective icon is shown for each status', () => {
196 const payload = _.cloneDeep(healthPayload);
199 payload.health['status'] = 'HEALTH_WARN';
200 payload.health['checks'] = [
201 { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } }
204 getHealthSpy.and.returnValue(of(payload));
205 fixture.detectChanges();
206 const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"] i'));
207 expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
210 payload.health['status'] = 'HEALTH_ERR';
211 payload.health['checks'] = [
212 { severity: 'HEALTH_ERR', type: 'ERR', summary: { message: 'fake error' } }
215 getHealthSpy.and.returnValue(of(payload));
216 fixture.detectChanges();
217 expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
220 payload.health['status'] = 'HEALTH_OK';
221 payload.health['checks'] = [
222 { severity: 'HEALTH_OK', type: 'OK', summary: { message: 'fake success' } }
225 getHealthSpy.and.returnValue(of(payload));
226 fixture.detectChanges();
227 expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
230 it('should show the actual alert count on each alerts pill', () => {
231 fixture.detectChanges();
233 const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts] span'));
235 const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts] span'));
237 expect(warningAlerts.nativeElement.textContent).toBe('1');
238 expect(dangerAlerts.nativeElement.textContent).toBe('2');
241 it('should show the critical alerts window and its content', () => {
242 const payload = _.cloneDeep(alertsPayload[0]);
243 component.toggleAlertsWindow(AlertClass[0]);
244 fixture.detectChanges();
246 const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));
248 expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
249 expect(component.alertType).not.toBe('warning');
252 it('should show the warning alerts window and its content', () => {
253 const payload = _.cloneDeep(alertsPayload[2]);
254 component.toggleAlertsWindow(AlertClass.warning);
255 fixture.detectChanges();
257 const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));
259 expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
260 expect(component.alertType).not.toBe('critical');
263 it('should only show the pills when the alerts are not empty', () => {
264 spyOn(TestBed.inject(PrometheusAlertService), 'alerts').and.returnValue(0);
265 fixture.detectChanges();
267 const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts]'));
269 const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts]'));
271 expect(warningAlerts).toBe(null);
272 expect(dangerAlerts).toBe(null);
275 it('should render "Status" card text that is not clickable', () => {
276 fixture.detectChanges();
278 const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
279 const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
280 expect(clickableContent).toBeNull();
283 it('should render "Status" card text that is clickable (popover)', () => {
284 const payload = _.cloneDeep(healthPayload);
285 payload.health['status'] = 'HEALTH_WARN';
286 payload.health['checks'] = [
287 { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } }
290 getHealthSpy.and.returnValue(of(payload));
291 fixture.detectChanges();
293 const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
294 const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
295 expect(clickableContent).not.toBeNull();
298 describe('features disabled', () => {
300 fakeFeatureTogglesService.and.returnValue(
309 fixture = TestBed.createComponent(DashboardV3Component);
310 component = fixture.componentInstance;
313 it('should not render items related to disabled features', () => {
314 fixture.detectChanges();
316 const iscsiCard = fixture.debugElement.query(By.css('li[id=iscsi-item]'));
317 const rgwCard = fixture.debugElement.query(By.css('li[id=rgw-item]'));
318 const mds = fixture.debugElement.query(By.css('li[id=mds-item]'));
320 expect(iscsiCard).toBeFalsy();
321 expect(rgwCard).toBeFalsy();
322 expect(mds).toBeFalsy();