mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
chore: add coverage chart to dashboard (#5981)
* chore: add coverage chart to dashboard * Switch to md-spinner
This commit is contained in:
committed by
Andrew Seguin
parent
2e3d616941
commit
e52ebd776b
@@ -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 };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+4
-1
@@ -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;
|
||||
}
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user