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);
+}