chore: add coverage chart to dashboard (#5981)

* chore: add coverage chart to dashboard

* Switch to md-spinner
This commit is contained in:
Paul Gschwendtner
2017-07-27 18:50:19 +02:00
committed by Andrew Seguin
parent 2e3d616941
commit e52ebd776b
14 changed files with 182 additions and 9 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
"tsconfig": "tsconfig.json",
"prefix": "app",
"styles": [
"styles.css",
"styles.scss",
"theme.scss"
],
"scripts": [],
@@ -0,0 +1,17 @@
<ngx-charts-line-chart
*ngIf="chartData.length"
[scheme]="colorScheme"
[results]="chartData"
[gradient]="false"
[xAxis]="true"
[yAxis]="true"
[legend]="true"
[showXAxisLabel]="true"
[showYAxisLabel]="true"
[showGridLines]="true"
[xAxisLabel]="xAxisLabel"
[yAxisLabel]="yAxisLabel"
[autoScale]="true">
</ngx-charts-line-chart>
<md-spinner *ngIf="!chartData.length" class="coverage-chart-loading"></md-spinner>
@@ -0,0 +1,20 @@
$coverage-chart-loading-size: 50px;
:host {
display: block;
position: relative;
height: 400px;
width: 100%;
max-width: 700px;
}
.coverage-chart-loading {
position: absolute;
top: calc(50% - #{$coverage-chart-loading-size});
left: calc(50% - #{$coverage-chart-loading-size});
height: $coverage-chart-loading-size;
width: $coverage-chart-loading-size;
}
@@ -0,0 +1,93 @@
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {CoverageEntries, CoverageResult} from '../data-definitions';
import {NgxChartResult} from '../ngx-definitions';
@Component({
selector: 'coverage-chart',
templateUrl: './coverage-chart.html',
styleUrls: ['./coverage-chart.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CoverageChart {
/** Color scheme for the coverage graph. */
colorScheme = {
domain: ['#5AA454', '#A10A28', '#C7B42C', '#AAAAAA']
};
/** X-Axis label of the Chart. */
xAxisLabel = 'Date';
/** Y-Axis label of the Chart. */
yAxisLabel = 'Coverage Percentage (%)';
/** Chart data that is taken by NGX-Charts. */
chartData: NgxChartResult[];
/** Coverage data that will be rendered in the chart. */
@Input()
set data(value: CoverageResult[]) {
this.chartData = this.createChartResults(value);
}
/** Remove duplicate coverage results for similar days. */
private filterDuplicateDays(data: CoverageResult[]) {
const filteredData = new Map<string, CoverageResult>();
data.forEach(result => {
// Parse the timestamp from the coverage result as a date.
const date = new Date(result.timestamp);
// Ignore hours, minutes, seconds and milliseconds from the date to allow comparisons
// only of the day.
date.setHours(0, 0, 0, 0);
// Store the ISO string of the date in a Map to overwrite the previous coverage result for
// the same day.
filteredData.set(date.toISOString(), result);
});
return Array.from(filteredData.values());
}
/** Creates a list of ngx-chart results of the coverage results. */
private createChartResults(data: CoverageResult[]) {
if (!data) {
return [];
}
// In the early stages of the coverage reports that will be uploaded to Firebase, the timestamp
// was not added to the reports. All old results need to be filtered out for the chart.
data = data.filter(result => result.timestamp);
// Data snapshot from Firebase is not ordered by timestamp. Before rendering the graph
// manually sort the results by their timestamp.
data = data.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1);
// It can happen that there will be multiple coverage reports for the same day. This happens
// if multiple PRs are merged at the same day. For the charts we only want to
// have the last coverage report of a day (for performance and value correctness)
data = this.filterDuplicateDays(data);
return [
{ name: 'Lines', series: this.createCoverageChartItems(data, 'lines') },
{ name: 'Branches', series: this.createCoverageChartItems(data, 'branches') },
{ name: 'Functions', series: this.createCoverageChartItems(data, 'functions') },
{ name: 'Statements', series: this.createCoverageChartItems(data, 'statements') }
];
}
/** Creates a list of items for NGX-Charts that represent a coverage entry type. */
private createCoverageChartItems(data: CoverageResult[], entryType: CoverageEntries) {
return data.map(result => {
const entryResult = result[entryType];
// Convert the timestamp of the coverage result into a date because NGX-Charts can group
// dates in the x-axis.
const date = new Date(result.timestamp);
return { name: date, value: entryResult.pct };
});
}
}
+10 -1
View File
@@ -3,10 +3,19 @@
</md-toolbar>
<div class="dashboard-content">
<md-card class="payload-size-card">
<md-card class="dashboard-panel-card">
<md-card-title>Payload Size Chart</md-card-title>
<md-card-content>
<payload-chart [data]="payloads | async"></payload-chart>
</md-card-content>
</md-card>
<md-card class="dashboard-panel-card">
<md-card-title>Coverage Reports</md-card-title>
<md-card-content>
<coverage-chart [data]="coverage | async"></coverage-chart>
</md-card-content>
</md-card>
</div>
@@ -1,8 +1,11 @@
.dashboard-content {
display: flex;
flex-flow: wrap;
padding: 16px;
}
.payload-size-card {
.dashboard-panel-card {
width: 100%;
max-width: 700px;
margin: 12px;
}
+6 -2
View File
@@ -1,19 +1,23 @@
import {Component} from '@angular/core';
import {AngularFireDatabase, FirebaseListObservable} from 'angularfire2/database';
import {PayloadResult} from './data-definitions';
import {PayloadResult, CoverageResult} from './data-definitions';
@Component({
selector: 'dashboard-app',
templateUrl: './dashboard-app.html',
styleUrls: ['./dashboard-app.css']
styleUrls: ['./dashboard-app.scss']
})
export class DashboardApp {
/** Observable that emits all payload results from Firebase. */
payloads: FirebaseListObservable<PayloadResult[]>;
/** Observable that emits all coverage reports from Firebase. */
coverage: FirebaseListObservable<CoverageResult[]>;
constructor(database: AngularFireDatabase) {
this.payloads = database.list(`payloads`);
this.coverage = database.list(`coverage-reports`);
}
}
+3 -1
View File
@@ -8,6 +8,7 @@ import {MdCardModule, MdProgressSpinnerModule, MdToolbarModule} from '@angular/m
import {NgxChartsModule} from '@swimlane/ngx-charts';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {PayloadChart} from './payload-chart/payload-chart';
import {CoverageChart} from './coverage-chart/coverage-chart';
@NgModule({
exports: [
@@ -21,7 +22,8 @@ export class DashboardMaterialModule {}
@NgModule({
declarations: [
DashboardApp,
PayloadChart
PayloadChart,
CoverageChart
],
imports: [
BrowserModule,
@@ -12,3 +12,23 @@ export interface PayloadResult {
cdk_fesm_2015: number;
cdk_fesm_2014: number;
}
/** Type that specifies the available coverage entries. */
export type CoverageEntries = 'branches' | 'functions' | 'lines' | 'statements';
/** Interface that describes the coverage reports in Firebase. */
export interface CoverageResult {
branches: CoverageDataEntry;
functions: CoverageDataEntry;
lines: CoverageDataEntry;
statements: CoverageDataEntry;
timestamp: number;
}
/** Interface that describes data entries for different coverage types. */
export interface CoverageDataEntry {
covered: number;
pct: number;
skipped: number;
total: number;
}
@@ -14,5 +14,4 @@
[autoScale]="true">
</ngx-charts-line-chart>
<md-progress-spinner *ngIf="!chartData.length" class="payload-chart-loading" mode="indeterminate">
</md-progress-spinner>
<md-spinner *ngIf="!chartData.length" class="payload-chart-loading"></md-spinner>
@@ -1,6 +1,6 @@
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {PayloadResult} from '../data-definitions';
import {NgxChartItem, NgxChartResult} from './ngx-definitions';
import {NgxChartItem, NgxChartResult} from '../ngx-definitions';
@Component({
selector: 'payload-chart',
+6
View File
@@ -10,3 +10,9 @@ $dashboard-light-theme: mat-light-theme($dashboard-primary, $dashboard-accent);
@include mat-toolbar-theme($dashboard-light-theme);
@include mat-progress-spinner-theme($dashboard-light-theme);
@include mat-card-theme($dashboard-light-theme);
body {
// As a custom background for the dashboard we want to use hue 200 of the grey palette.
// Setting the body style manually is easier than overwriting the whole $background variables.
background-color: map_get($mat-grey, 200);
}