From ce4379bf70420b8ac9e93d4cb5408ce07b6f22e9 Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Thu, 1 Mar 2018 14:48:22 +0000 Subject: [PATCH] mgr/dashboard_v2: improve health page charts tooltips Extracted the charts into a new component and it is now using a new tooltip. Signed-off-by: Tiago Melo --- .../app/ceph/dashboard/dashboard.module.ts | 4 +- .../health-pie/health-pie.component.html | 15 ++ .../health-pie/health-pie.component.scss | 1 + .../health-pie/health-pie.component.spec.ts | 30 ++++ .../health-pie/health-pie.component.ts | 117 +++++++++++++ .../dashboard/health/health.component.html | 36 ++-- .../dashboard/health/health.component.spec.ts | 20 +-- .../ceph/dashboard/health/health.component.ts | 155 +++--------------- 8 files changed, 206 insertions(+), 172 deletions(-) create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.scss create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.spec.ts create mode 100644 src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/dashboard.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/dashboard.module.ts index cae29e974aa3e..cf4c025060fc5 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/dashboard.module.ts +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/dashboard.module.ts @@ -8,6 +8,7 @@ import { TabsModule } from 'ngx-bootstrap/tabs'; import { SharedModule } from '../../shared/shared.module'; import { DashboardService } from './dashboard.service'; import { DashboardComponent } from './dashboard/dashboard.component'; +import { HealthPieComponent } from './health-pie/health-pie.component'; import { HealthComponent } from './health/health.component'; import { LogColorPipe } from './log-color.pipe'; import { MdsSummaryPipe } from './mds-summary.pipe'; @@ -28,7 +29,8 @@ import { PgStatusPipe } from './pg-status.pipe'; MgrSummaryPipe, PgStatusPipe, MdsSummaryPipe, - PgStatusStylePipe + PgStatusStylePipe, + HealthPieComponent ], providers: [DashboardService] }) diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html new file mode 100644 index 0000000000000..7135f96f67bb5 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html @@ -0,0 +1,15 @@ +
+ +
+
+
+
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.scss b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.scss new file mode 100644 index 0000000000000..b3abf8681a29c --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.scss @@ -0,0 +1 @@ +@import '../../../../styles/chart-tooltip.scss'; diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.spec.ts new file mode 100644 index 0000000000000..dca539f041c2f --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.spec.ts @@ -0,0 +1,30 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ChartsModule } from 'ng2-charts/ng2-charts'; + +import { SharedModule } from '../../../shared/shared.module'; +import { HealthPieComponent } from './health-pie.component'; + +describe('HealthPieComponent', () => { + let component: HealthPieComponent; + let fixture: ComponentFixture; + + beforeEach( + async(() => { + TestBed.configureTestingModule({ + imports: [ChartsModule, SharedModule], + declarations: [HealthPieComponent] + }).compileComponents(); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthPieComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts new file mode 100644 index 0000000000000..196d871066ac9 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts @@ -0,0 +1,117 @@ +import { + Component, + ElementRef, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + ViewChild +} from '@angular/core'; + +import * as Chart from 'chart.js'; +import * as _ from 'lodash'; + +import { ChartTooltip } from '../../../shared/models/chart-tooltip'; +import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe'; + +@Component({ + selector: 'cd-health-pie', + templateUrl: './health-pie.component.html', + styleUrls: ['./health-pie.component.scss'] +}) +export class HealthPieComponent implements OnChanges, OnInit { + @ViewChild('chartCanvas') chartCanvasRef: ElementRef; + @ViewChild('chartTooltip') chartTooltipRef: ElementRef; + + @Input() data: any; + @Input() tooltipFn: any; + @Output() prepareFn = new EventEmitter(); + + chart: any = { + chartType: 'doughnut', + dataset: [ + { + label: null, + borderWidth: 0 + } + ], + options: { + responsive: true, + legend: { display: false }, + animation: { duration: 0 }, + + tooltips: { + enabled: false + } + }, + colors: [ + { + borderColor: 'transparent' + } + ] + }; + + constructor(private dimlessBinary: DimlessBinaryPipe) {} + + ngOnInit() { + // An extension to Chart.js to enable rendering some + // text in the middle of a doughnut + Chart.pluginService.register({ + beforeDraw: function(chart) { + if (!chart.options.center_text) { + return; + } + + const width = chart.chart.width, + height = chart.chart.height, + ctx = chart.chart.ctx; + + ctx.restore(); + const fontSize = (height / 114).toFixed(2); + ctx.font = fontSize + 'em sans-serif'; + ctx.textBaseline = 'middle'; + + const text = chart.options.center_text, + textX = Math.round((width - ctx.measureText(text).width) / 2), + textY = height / 2; + + ctx.fillText(text, textX, textY); + ctx.save(); + } + }); + + const getStyleTop = (tooltip, positionY) => { + return positionY + tooltip.caretY - tooltip.height - 10 + 'px'; + }; + + const getStyleLeft = (tooltip, positionX) => { + return positionX + tooltip.caretX + 'px'; + }; + + const getBody = (body) => { + const bodySplit = body[0].split(': '); + bodySplit[1] = this.dimlessBinary.transform(bodySplit[1]); + return bodySplit.join(': '); + }; + + const chartTooltip = new ChartTooltip( + this.chartCanvasRef, + this.chartTooltipRef, + getStyleLeft, + getStyleTop, + ); + chartTooltip.getBody = getBody; + + const self = this; + this.chart.options.tooltips.custom = (tooltip) => { + chartTooltip.customTooltips(tooltip); + }; + + this.prepareFn.emit([this.chart, this.data]); + } + + ngOnChanges() { + this.prepareFn.emit([this.chart, this.data]); + } +} diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.html index 98ebb918953d5..348324e4dc135 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.html +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.html @@ -27,7 +27,9 @@
Monitors + i18n="ceph monitors"> + Monitors + {{ contentData.mon_status | monSummary }}
@@ -41,7 +43,9 @@
OSDs + i18n="ceph OSDs"> + OSDs + {{ contentData.osd_map | osdSummary }}
@@ -94,31 +98,15 @@ {{ contentData.df.stats.total_objects | dimless }} -
- +
+
-
- +
+
diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.spec.ts index cac806a0f77c9..983b1452e89da 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.spec.ts +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.spec.ts @@ -1,36 +1,28 @@ import { HttpClientModule } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ChartsModule } from 'ng2-charts'; import { TabsModule } from 'ngx-bootstrap/tabs'; -import { AppModule } from '../../../app.module'; import { SharedModule } from '../../../shared/shared.module'; import { DashboardService } from '../dashboard.service'; -import { LogColorPipe } from '../log-color.pipe'; -import { MdsSummaryPipe } from '../mds-summary.pipe'; -import { MgrSummaryPipe } from '../mgr-summary.pipe'; -import { MonSummaryPipe } from '../mon-summary.pipe'; -import { OsdSummaryPipe } from '../osd-summary.pipe'; -import { PgStatusStylePipe } from '../pg-status-style.pipe'; -import { PgStatusPipe } from '../pg-status.pipe'; import { HealthComponent } from './health.component'; describe('HealthComponent', () => { let component: HealthComponent; let fixture: ComponentFixture; - const dashboardServiceStub = { + + const fakeService = { getHealth() { return {}; } }; + beforeEach( async(() => { TestBed.configureTestingModule({ - providers: [ - { provide: DashboardService, useValue: dashboardServiceStub } - ], - imports: [AppModule] + providers: [{ provide: DashboardService, useValue: fakeService }], + imports: [SharedModule], + declarations: [HealthComponent] }).compileComponents(); }) ); diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.ts index 0a065c10bd354..3cdddc970e3c4 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.ts +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.ts @@ -1,9 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import * as Chart from 'chart.js'; import * as _ from 'lodash'; -import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe'; import { DashboardService } from '../dashboard.service'; @Component({ @@ -13,48 +11,13 @@ import { DashboardService } from '../dashboard.service'; }) export class HealthComponent implements OnInit, OnDestroy { contentData: any; - interval: any; - poolUsage: any = { - chartType: 'doughnut' - }; - rawUsage: any = { - chartType: 'doughnut', - center_text: 0 - }; + interval: number; - constructor( - private dimlessBinary: DimlessBinaryPipe, - private dashboardService: DashboardService - ) {} + constructor(private dashboardService: DashboardService) {} ngOnInit() { - // An extension to Chart.js to enable rendering some - // text in the middle of a doughnut - Chart.pluginService.register({ - beforeDraw: function(chart) { - if (!chart.options.center_text) { - return; - } - const width = chart.chart.width, - height = chart.chart.height, - ctx = chart.chart.ctx; - - ctx.restore(); - const fontSize = (height / 114).toFixed(2); - ctx.font = fontSize + 'em sans-serif'; - ctx.textBaseline = 'middle'; - - const text = chart.options.center_text, - textX = Math.round((width - ctx.measureText(text).width) / 2), - textY = height / 2; - - ctx.fillText(text, textX, textY); - ctx.save(); - } - }); - this.getInfo(); - this.interval = setInterval(() => { + this.interval = window.setInterval(() => { this.getInfo(); }, 5000); } @@ -66,80 +29,38 @@ export class HealthComponent implements OnInit, OnDestroy { getInfo() { this.dashboardService.getHealth().subscribe((data: any) => { this.contentData = data; - this.draw_usage_charts(); }); } - draw_usage_charts() { + prepareRawUsage(chart, data) { let rawUsageChartColor; + const rawUsageText = - Math.round( - 100 * - (this.contentData.df.stats.total_used_bytes / - this.contentData.df.stats.total_bytes) - ) + '%'; - if ( - this.contentData.df.stats.total_used_bytes / - this.contentData.df.stats.total_bytes >= - this.contentData.osd_map.full_ratio - ) { + Math.round(100 * (data.df.stats.total_used_bytes / data.df.stats.total_bytes)) + '%'; + + if (data.df.stats.total_used_bytes / data.df.stats.total_bytes >= data.osd_map.full_ratio) { rawUsageChartColor = '#ff0000'; } else if ( - this.contentData.df.stats.total_used_bytes / - this.contentData.df.stats.total_bytes >= - this.contentData.osd_map.backfillfull_ratio + data.df.stats.total_used_bytes / data.df.stats.total_bytes >= + data.osd_map.backfillfull_ratio ) { rawUsageChartColor = '#ff6600'; } else if ( - this.contentData.df.stats.total_used_bytes / - this.contentData.df.stats.total_bytes >= - this.contentData.osd_map.nearfull_ratio + data.df.stats.total_used_bytes / data.df.stats.total_bytes >= + data.osd_map.nearfull_ratio ) { rawUsageChartColor = '#ffc200'; } else { rawUsageChartColor = '#00bb00'; } - this.rawUsage = { - chartType: 'doughnut', - dataset: [ - { - label: null, - borderWidth: 0, - data: [ - this.contentData.df.stats.total_used_bytes, - this.contentData.df.stats.total_avail_bytes - ] - } - ], - options: { - center_text: rawUsageText, - responsive: true, - legend: { display: false }, - animation: { duration: 0 }, - tooltips: { - callbacks: { - label: (tooltipItem, chart) => { - return ( - chart.labels[tooltipItem.index] + - ': ' + - this.dimlessBinary.transform( - chart.datasets[0].data[tooltipItem.index] - ) - ); - } - } - } - }, - colors: [ - { - backgroundColor: [rawUsageChartColor, '#424d52'], - borderColor: 'transparent' - } - ], - labels: ['Raw Used', 'Raw Available'] - }; + chart.dataset[0].data = [data.df.stats.total_used_bytes, data.df.stats.total_avail_bytes]; + chart.options.center_text = rawUsageText; + chart.colors = [{ backgroundColor: [rawUsageChartColor, '#424d52'] }]; + chart.labels = ['Raw Used', 'Raw Available']; + } + preparePoolUsage(chart, data) { const colors = [ '#3366CC', '#109618', @@ -166,45 +87,13 @@ export class HealthComponent implements OnInit, OnDestroy { const poolLabels = []; const poolData = []; - _.each(this.contentData.df.pools, function(pool, i) { + _.each(data.df.pools, (pool, i) => { poolLabels.push(pool['name']); poolData.push(pool['stats']['bytes_used']); }); - this.poolUsage = { - chartType: 'doughnut', - dataset: [ - { - label: null, - borderWidth: 0, - data: poolData - } - ], - options: { - responsive: true, - legend: { display: false }, - animation: { duration: 0 }, - tooltips: { - callbacks: { - label: (tooltipItem, chart) => { - return ( - chart.labels[tooltipItem.index] + - ': ' + - this.dimlessBinary.transform( - chart.datasets[0].data[tooltipItem.index] - ) - ); - } - } - } - }, - colors: [ - { - backgroundColor: colors, - borderColor: 'transparent' - } - ], - labels: poolLabels - }; + chart.dataset[0].data = poolData; + chart.colors = [{ backgroundColor: colors }]; + chart.labels = poolLabels; } } -- 2.39.5