2 class TestGroupResultsViewer extends ComponentBase {
5 super('test-group-results-table');
6 this._analysisResults = null;
7 this._testGroup = null;
8 this._startPoint = null;
10 this._currentMetric = null;
11 this._expandedTests = new Set;
12 this._barGraphCellMap = new Map;
13 this._renderResultsTableLazily = new LazilyEvaluatedFunction(this._renderResultsTable.bind(this));
14 this._renderCurrentMetricsLazily = new LazilyEvaluatedFunction(this._renderCurrentMetrics.bind(this));
17 setTestGroup(currentTestGroup)
19 this._testGroup = currentTestGroup;
20 this.enqueueToRender();
23 setAnalysisResults(analysisResults, metric)
25 this._analysisResults = analysisResults;
26 this._currentMetric = metric;
28 const path = metric.test().path();
29 for (let i = path.length - 2; i >= 0; i--)
30 this._expandedTests.add(path[i]);
33 this.enqueueToRender();
38 if (!this._testGroup || !this._analysisResults)
41 this._renderResultsTableLazily.evaluate(this._testGroup, this._expandedTests,
42 ...this._analysisResults.topLevelTestsForTestGroup(this._testGroup));
45 _renderResultsTable(testGroup, expandedTests, ...tests)
48 for (const test of expandedTests)
49 maxDepth = Math.max(maxDepth, test.path().length);
51 const element = ComponentBase.createElement;
52 this.renderReplace(this.content('results'), [
55 element('th', {colspan: maxDepth + 1}, 'Test'),
56 element('th', {class: 'metric-direction'}, ''),
57 element('th', {colspan: 2}, 'Results'),
58 element('th', 'Averages'),
59 element('th', 'Comparison'),
62 tests.map((test) => this._buildRowsForTest(testGroup, expandedTests, test, [], maxDepth, 0))]);
65 _buildRowsForTest(testGroup, expandedTests, test, sharedPath, maxDepth, depth)
67 if (!this._analysisResults.containsTest(test))
70 const element = ComponentBase.createElement;
71 const rows = element('tbody', test.metrics().map((metric) => this._buildRowForMetric(testGroup, metric, sharedPath, maxDepth, depth)));
73 if (expandedTests.has(test)) {
74 return [rows, test.childTests().map((childTest) => {
75 return this._buildRowsForTest(testGroup, expandedTests, childTest, test.path(), maxDepth, depth + 1);
79 if (test.childTests().length) {
80 const link = ComponentBase.createLink;
81 return [rows, element('tr', {class: 'breakdown'}, [
82 element('td', {colspan: maxDepth + 1}, link('(Breakdown)', () => {
83 this._expandedTests = new Set([...expandedTests, test]);
84 this.enqueueToRender();
86 element('td', {colspan: 3}),
93 _buildRowForMetric(testGroup, metric, sharedPath, maxDepth, depth)
95 const commitSets = testGroup.requestedCommitSets();
96 const valueMap = this._buildValueMap(testGroup, this._analysisResults.viewForMetric(metric));
98 const formatter = metric.makeFormatter(4);
99 const deltaFormatter = metric.makeFormatter(2, false);
100 const formatValue = (value, interval) => {
101 const delta = interval ? (interval[1] - interval[0]) / 2 : null;
102 return value == null || isNaN(value) ? '-' : `${formatter(value)} \u00b1 ${deltaFormatter(delta)}`;
105 const barGroup = new BarGraphGroup();
107 const createConfigurationRow = (commitSet, previousCommitSet, barColor, meanIndicatorColor) => {
108 const entry = valueMap.get(commitSet);
109 const previousEntry = valueMap.get(previousCommitSet);
111 const comparison = entry && previousEntry ? testGroup.compareTestResults(metric, previousEntry.filteredValues, entry.filteredValues) : null;
112 const valueLabels = entry.measurements.map((measurement) => measurement ? formatValue(measurement.value, measurement.interval) : '-');
114 const barCell = element('td', {class: 'plot-bar'},
115 element('div', barGroup.addBar(entry.allValues, valueLabels, entry.mean, entry.interval, barColor, meanIndicatorColor)));
116 barCell.expandedHeight = +valueLabels.length + 'rem';
117 barCells.push(barCell);
119 const significance = comparison && comparison.isStatisticallySignificant ? 'significant' : 'negligible';
120 const changeType = comparison ? comparison.changeType : null;
122 element('th', testGroup.labelForCommitSet(commitSet)),
124 element('td', formatValue(entry.mean, entry.interval)),
125 element('td', {class: `comparison ${changeType} ${significance}`}, comparison ? comparison.fullLabel : ''),
129 this._barGraphCellMap.set(metric, {barGroup, barCells});
131 const rowspan = commitSets.length;
132 const element = ComponentBase.createElement;
133 const link = ComponentBase.createLink;
134 const metricName = metric.test().metrics().length == 1 ? metric.test().relativeName(sharedPath) : metric.relativeName(sharedPath);
135 const onclick = this.createEventHandler((event) => {
136 if (this._currentMetric == metric) {
137 if (event.target.localName == 'bar-graph')
139 this._currentMetric = null;
141 this._currentMetric = metric;
142 this.enqueueToRender();
145 element('tr', {onclick}, [
146 this._buildEmptyCells(depth, rowspan),
147 element('th', {colspan: maxDepth - depth + 1, rowspan}, link(metricName, onclick)),
148 element('td', {class: 'metric-direction', rowspan}, metric.isSmallerBetter() ? '\u21A4' : '\u21A6'),
149 createConfigurationRow(commitSets[0], null, '#ddd', '#333')
151 commitSets.slice(1).map((commitSet, setIndex) => {
152 return element('tr', {onclick},
153 createConfigurationRow(commitSet, commitSets[setIndex], '#aaa', '#000'));
158 _buildValueMap(testGroup, resultsView)
160 const commitSets = testGroup.requestedCommitSets();
162 for (const commitSet of commitSets) {
163 const requests = testGroup.requestsForCommitSet(commitSet);
164 const measurements = requests.map((request) => resultsView.resultForRequest(request));
165 const filteredValues = measurements.filter((result) => !!result).map((measurement) => measurement.value);
166 const allValues = measurements.map((result) => result != null ? result.value : NaN);
167 const interval = Statistics.confidenceInterval(filteredValues);
168 map.set(commitSet, {requests, measurements, filteredValues, allValues, mean: Statistics.mean(filteredValues), interval});
173 _buildEmptyCells(count, rowspan)
175 const element = ComponentBase.createElement;
176 const emptyCells = [];
177 for (let i = 0; i < count; i++)
178 emptyCells.push(element('td', {rowspan}, ''));
182 _renderCurrentMetrics(currentMetric)
184 for (const entry of this._barGraphCellMap.values()) {
185 for (const cell of entry.barCells) {
186 cell.style.height = null;
187 cell.parentNode.className = null;
189 entry.barGroup.setShowLabels(false);
192 const entry = this._barGraphCellMap.get(currentMetric);
194 for (const cell of entry.barCells) {
195 cell.style.height = cell.expandedHeight;
196 cell.parentNode.className = 'selected';
198 entry.barGroup.setShowLabels(true);
202 static htmlTemplate()
204 return `<table id="results"></table>`;
211 border-collapse: collapse;
221 td:not(.metric-direction),
222 th:not(.metric-direction) {
223 padding: 0.1rem 0.5rem;
225 td:not(.metric-direction) {
228 td.metric-direction {
236 font-weight: inherit;
239 font-weight: inherit;
245 background: rgba(204, 153, 51, 0.05);
249 tr:first-child > th {
250 border-top: solid 1px #eee;
262 text-decoration: inherit;
289 .significant.better {
298 display: inline-block;
299 text-decoration: none;
301 margin-bottom: 0.2rem;
307 ComponentBase.defineElement('test-group-results-viewer', TestGroupResultsViewer);