From e52ebd776b3529f27c8e236cc439ae05dd3c29b4 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 27 Jul 2017 18:50:19 +0200 Subject: [PATCH] chore: add coverage chart to dashboard (#5981) * chore: add coverage chart to dashboard * Switch to md-spinner --- tools/dashboard/.angular-cli.json | 2 +- .../app/coverage-chart/coverage-chart.html | 17 ++++ .../app/coverage-chart/coverage-chart.scss | 20 ++++ .../src/app/coverage-chart/coverage-chart.ts | 93 +++++++++++++++++++ tools/dashboard/src/app/dashboard-app.html | 11 ++- .../{dashboard-app.css => dashboard-app.scss} | 5 +- tools/dashboard/src/app/dashboard-app.ts | 8 +- tools/dashboard/src/app/dashboard-module.ts | 4 +- tools/dashboard/src/app/data-definitions.ts | 20 ++++ .../{payload-chart => }/ngx-definitions.ts | 0 .../src/app/payload-chart/payload-chart.html | 3 +- .../src/app/payload-chart/payload-chart.ts | 2 +- .../dashboard/src/{styles.css => styles.scss} | 0 tools/dashboard/src/theme.scss | 6 ++ 14 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 tools/dashboard/src/app/coverage-chart/coverage-chart.html create mode 100644 tools/dashboard/src/app/coverage-chart/coverage-chart.scss create mode 100644 tools/dashboard/src/app/coverage-chart/coverage-chart.ts rename tools/dashboard/src/app/{dashboard-app.css => dashboard-app.scss} (50%) rename tools/dashboard/src/app/{payload-chart => }/ngx-definitions.ts (100%) rename tools/dashboard/src/{styles.css => styles.scss} (100%) diff --git a/tools/dashboard/.angular-cli.json b/tools/dashboard/.angular-cli.json index 50c2d2e47c3..48476a57b52 100644 --- a/tools/dashboard/.angular-cli.json +++ b/tools/dashboard/.angular-cli.json @@ -17,7 +17,7 @@ "tsconfig": "tsconfig.json", "prefix": "app", "styles": [ - "styles.css", + "styles.scss", "theme.scss" ], "scripts": [], diff --git a/tools/dashboard/src/app/coverage-chart/coverage-chart.html b/tools/dashboard/src/app/coverage-chart/coverage-chart.html new file mode 100644 index 00000000000..d566cbd5aea --- /dev/null +++ b/tools/dashboard/src/app/coverage-chart/coverage-chart.html @@ -0,0 +1,17 @@ + + + + diff --git a/tools/dashboard/src/app/coverage-chart/coverage-chart.scss b/tools/dashboard/src/app/coverage-chart/coverage-chart.scss new file mode 100644 index 00000000000..7f0563ac5cd --- /dev/null +++ b/tools/dashboard/src/app/coverage-chart/coverage-chart.scss @@ -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; +} diff --git a/tools/dashboard/src/app/coverage-chart/coverage-chart.ts b/tools/dashboard/src/app/coverage-chart/coverage-chart.ts new file mode 100644 index 00000000000..df47bfa1082 --- /dev/null +++ b/tools/dashboard/src/app/coverage-chart/coverage-chart.ts @@ -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(); + + 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 }; + }); + } +} + diff --git a/tools/dashboard/src/app/dashboard-app.html b/tools/dashboard/src/app/dashboard-app.html index 727af508f05..59e513cdeb8 100644 --- a/tools/dashboard/src/app/dashboard-app.html +++ b/tools/dashboard/src/app/dashboard-app.html @@ -3,10 +3,19 @@
- + + Payload Size Chart + + + Coverage Reports + + + + +
diff --git a/tools/dashboard/src/app/dashboard-app.css b/tools/dashboard/src/app/dashboard-app.scss similarity index 50% rename from tools/dashboard/src/app/dashboard-app.css rename to tools/dashboard/src/app/dashboard-app.scss index 97e74af3105..e2f0cef1069 100644 --- a/tools/dashboard/src/app/dashboard-app.css +++ b/tools/dashboard/src/app/dashboard-app.scss @@ -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; } diff --git a/tools/dashboard/src/app/dashboard-app.ts b/tools/dashboard/src/app/dashboard-app.ts index 904cfd21c09..74da7a9d9ae 100644 --- a/tools/dashboard/src/app/dashboard-app.ts +++ b/tools/dashboard/src/app/dashboard-app.ts @@ -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; + /** Observable that emits all coverage reports from Firebase. */ + coverage: FirebaseListObservable; + constructor(database: AngularFireDatabase) { this.payloads = database.list(`payloads`); + this.coverage = database.list(`coverage-reports`); } } diff --git a/tools/dashboard/src/app/dashboard-module.ts b/tools/dashboard/src/app/dashboard-module.ts index 84d5f0e8c1e..8d01d9af088 100644 --- a/tools/dashboard/src/app/dashboard-module.ts +++ b/tools/dashboard/src/app/dashboard-module.ts @@ -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, diff --git a/tools/dashboard/src/app/data-definitions.ts b/tools/dashboard/src/app/data-definitions.ts index 4c14fdbf71b..bde49cc6c7e 100644 --- a/tools/dashboard/src/app/data-definitions.ts +++ b/tools/dashboard/src/app/data-definitions.ts @@ -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; +} diff --git a/tools/dashboard/src/app/payload-chart/ngx-definitions.ts b/tools/dashboard/src/app/ngx-definitions.ts similarity index 100% rename from tools/dashboard/src/app/payload-chart/ngx-definitions.ts rename to tools/dashboard/src/app/ngx-definitions.ts diff --git a/tools/dashboard/src/app/payload-chart/payload-chart.html b/tools/dashboard/src/app/payload-chart/payload-chart.html index 067e2492f6b..a7b907d9bce 100644 --- a/tools/dashboard/src/app/payload-chart/payload-chart.html +++ b/tools/dashboard/src/app/payload-chart/payload-chart.html @@ -14,5 +14,4 @@ [autoScale]="true"> - - + diff --git a/tools/dashboard/src/app/payload-chart/payload-chart.ts b/tools/dashboard/src/app/payload-chart/payload-chart.ts index bb662efbcc1..33703ee2231 100644 --- a/tools/dashboard/src/app/payload-chart/payload-chart.ts +++ b/tools/dashboard/src/app/payload-chart/payload-chart.ts @@ -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', diff --git a/tools/dashboard/src/styles.css b/tools/dashboard/src/styles.scss similarity index 100% rename from tools/dashboard/src/styles.css rename to tools/dashboard/src/styles.scss diff --git a/tools/dashboard/src/theme.scss b/tools/dashboard/src/theme.scss index 3d31a884153..1c58ec812cc 100644 --- a/tools/dashboard/src/theme.scss +++ b/tools/dashboard/src/theme.scss @@ -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); +}