]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard_v2: improve health page charts tooltips 20757/head
authorTiago Melo <tmelo@suse.com>
Thu, 1 Mar 2018 14:48:22 +0000 (14:48 +0000)
committerTiago Melo <tmelo@suse.com>
Mon, 12 Mar 2018 14:50:11 +0000 (14:50 +0000)
Extracted the charts into a new component and it is now using a new tooltip.

Signed-off-by: Tiago Melo <tmelo@suse.com>
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/dashboard.module.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.html
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.spec.ts
src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/dashboard/health/health.component.ts

index cae29e974aa3e2424ecf616f5c78a26b5dd768ff..cf4c025060fc56963a4caf4a7e40af79badfe6dc 100644 (file)
@@ -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 (file)
index 0000000..7135f96
--- /dev/null
@@ -0,0 +1,15 @@
+<div class="chart-container">
+  <canvas baseChart
+          #chartCanvas
+          [datasets]="chart.dataset"
+          [chartType]="chart.chartType"
+          [options]="chart.options"
+          [labels]="chart.labels"
+          [colors]="chart.colors"
+          width="120"
+          height="120"></canvas>
+  <div class="chartjs-tooltip"
+       #chartTooltip>
+    <table></table>
+  </div>
+</div>
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 (file)
index 0000000..b3abf86
--- /dev/null
@@ -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 (file)
index 0000000..dca539f
--- /dev/null
@@ -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<HealthPieComponent>;
+
+  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 (file)
index 0000000..196d871
--- /dev/null
@@ -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]);
+  }
+}
index 98ebb918953d51a4e608ad0079809e39a3f9ce3d..348324e4dc1358e7cd110194e5eb29a5f315ae1e 100644 (file)
@@ -27,7 +27,9 @@
               </div>
               <div class="media-body">
                 <span class="media-heading"
-                      i18n="ceph monitors"><a routerLink="/monitor/">Monitors</a></span>
+                      i18n="ceph monitors">
+                  <a routerLink="/monitor/">Monitors</a>
+                </span>
                 <span class="media-text">{{ contentData.mon_status | monSummary }}</span>
               </div>
             </div>
@@ -41,7 +43,9 @@
               </div>
               <div class="media-body">
                 <span class="media-heading"
-                      i18n="ceph OSDs"><a routerLink="/osd/">OSDs</a></span>
+                      i18n="ceph OSDs">
+                  <a routerLink="/osd/">OSDs</a>
+                </span>
                 <span class="media-text">{{ contentData.osd_map | osdSummary }}</span>
               </div>
             </div>
                 <span style="font-size: 45px;">{{ contentData.df.stats.total_objects | dimless }}</span>
               </td>
               <td>
-                <div class="center-block pie"
-                     *ngIf="rawUsage.dataset">
-                  <canvas baseChart
-                          id="raw_usage_chart"
-                          [datasets]="rawUsage.dataset"
-                          [chartType]="rawUsage.chartType"
-                          [options]="rawUsage.options"
-                          [labels]="rawUsage.labels"
-                          [colors]="rawUsage.colors"
-                          width="120"
-                          height="120"></canvas>
+                <div class="center-block pie">
+                  <cd-health-pie [data]="contentData"
+                                 (prepareFn)="prepareRawUsage($event[0], $event[1])"></cd-health-pie>
                 </div>
               </td>
               <td>
-                <div class="center-block pie"
-                     *ngIf="poolUsage.dataset">
-                  <canvas baseChart
-                          id="pool_usage_chart"
-                          [datasets]="poolUsage.dataset"
-                          [chartType]="poolUsage.chartType"
-                          [options]="poolUsage.options"
-                          [labels]="poolUsage.labels"
-                          [colors]="poolUsage.colors"
-                          width="120"
-                          height="120"></canvas>
+                <div class="center-block pie">
+                  <cd-health-pie [data]="contentData"
+                                 (prepareFn)="preparePoolUsage($event[0], $event[1])"></cd-health-pie>
                 </div>
               </td>
             </tr>
index cac806a0f77c9d23aee52a64c3cd024783257ce5..983b1452e89da6e34c2917214479f1be41dbea69 100644 (file)
@@ -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<HealthComponent>;
-  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();
     })
   );
index 0a065c10bd3546afc24e9c5a8e6b1a377a4887a5..3cdddc970e3c402ec761cde0b61d0025c5a10497 100644 (file)
@@ -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;
   }
 }