<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[196440] trunk/Websites/perf.webkit.org</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/196440">196440</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-02-11 14:17:55 -0800 (Thu, 11 Feb 2016)</dd>
</dl>
<h3>Log Message</h3>
<pre>Perf dashboard should have UI to retry A/B testing
https://bugs.webkit.org/show_bug.cgi?id=154090
Reviewed by Chris Dumez.
Added a button to re-try an existing A/B testing group with a custom repetition count. The same button functions
as a way of confirming the progression/regression when there have been no A/B testing scheduled in the task.
Also fixed the bug that A/B testing groups that have been waiting for other test groups will be shown as "running".
* public/v3/components/results-table.js:
(ResultsTable.cssTemplate): Don't pad the list of extra repositories when it's empty.
* public/v3/components/test-group-results-table.js:
(TestGroupResultsTable.prototype.buildRowGroups): Use TestGroup.labelForRootSet instead of manually
computing the letter for each configuration set.
* public/v3/models/build-request.js:
(BuildRequest.prototype.hasStarted): Added.
* public/v3/models/data-model.js:
(DataModelObject.ensureSingleton): Added.
(DataModelObject.cachedFetch): Added noCache option. This is used when re-fetching the test groups after
creating one.
* public/v3/models/measurement-cluster.js:
(MeasurementCluster.prototype.startTime): Added.
* public/v3/models/measurement-set.js:
(MeasurementSet.prototype.hasFetchedRange): Added. Returns true only if there are no "holes" (cluster
yet to be fetched) between the specified time range. This was added to fix a bug in AnalysisTaskPage's
_didFetchMeasurement.
* public/v3/models/test-group.js:
(TestGroup): Added this._rootSetToLabel.
(TestGroup.prototype.addBuildRequest): Reset this._rootSetToLabel along with this._requestedRootSets.
(TestGroup.prototype.repetitionCount): Added. Returns the number of iterations executed per set. We assume that
every root set in the test group shares a single repetition count.
(TestGroup.prototype.requestedRootSets): Now populates this._rootSetToLabel for labelForRootSet.
(TestGroup.prototype.labelForRootSet): Added.
(TestGroup.prototype.hasStarted): Added.
(TestGroup.prototype.compareTestResults): Use 'running' and 'pending' to differentiate test groups that are waiting
for other groups to finish running from the ones that are actually running ('incomplete' before this patch).
(TestGroup.fetchByTask):
(TestGroup.createAndRefetchTestGroups): Added. Creates a new test group using the privileged-api/create-test-group
and fetches the list of test groups for the specified analysis task.
(TestGroup._createModelsFromFetchedTestGroups): Extracted from TestGroup.fetchByTask.
* public/v3/pages/analysis-task-page.js:
(AnalysisTaskPage): Initialize _renderedCurrentTestGroup to undefined so that we'd always can differentiate
the initial call to AnalysisTaskPage.render and subsequent calls in which it's identical to _currentTestGroup.
(AnalysisTaskPage.prototype._didFetchMeasurement): Fixed a bug that we don't exit early even when some
clusters in between startPoint and endPoint are still being fetched via newly added hasFetchedRange.
(AnalysisTaskPage.prototype.render): Update the default repetition count based on the current test group.
Also update the label of the button to "Confirm the change" if there is no A/B testing in this task.
(AnalysisTaskPage.prototype._retryCurrentTestGroup): Added. Re-triggers an existing A/B testing group or creates
the A/B testing for the entire range of the analysis task.
(AnalysisTaskPage.prototype._hasDuplicateTestGroupName): Added.
(AnalysisTaskPage.prototype._createRetryNameForTestGroup): Added.
(AnalysisTaskPage.htmlTemplate): Added form controls to re-trigger A/B testing.
(AnalysisTaskPage.cssTemplate): Updated the style.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsresultstablejs">trunk/Websites/perf.webkit.org/public/v3/components/results-table.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentstestgroupresultstablejs">trunk/Websites/perf.webkit.org/public/v3/components/test-group-results-table.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsbuildrequestjs">trunk/Websites/perf.webkit.org/public/v3/models/build-request.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsdatamodeljs">trunk/Websites/perf.webkit.org/public/v3/models/data-model.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsmeasurementclusterjs">trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsmeasurementsetjs">trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelstestgroupjs">trunk/Websites/perf.webkit.org/public/v3/models/test-group.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pagesanalysistaskpagejs">trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -1,5 +1,69 @@
</span><span class="cx"> 2016-02-10 Ryosuke Niwa <rniwa@webkit.org>
</span><span class="cx">
</span><ins>+ Perf dashboard should have UI to retry A/B testing
+ https://bugs.webkit.org/show_bug.cgi?id=154090
+
+ Reviewed by Chris Dumez.
+
+ Added a button to re-try an existing A/B testing group with a custom repetition count. The same button functions
+ as a way of confirming the progression/regression when there have been no A/B testing scheduled in the task.
+
+ Also fixed the bug that A/B testing groups that have been waiting for other test groups will be shown as "running".
+
+ * public/v3/components/results-table.js:
+ (ResultsTable.cssTemplate): Don't pad the list of extra repositories when it's empty.
+
+ * public/v3/components/test-group-results-table.js:
+ (TestGroupResultsTable.prototype.buildRowGroups): Use TestGroup.labelForRootSet instead of manually
+ computing the letter for each configuration set.
+
+ * public/v3/models/build-request.js:
+ (BuildRequest.prototype.hasStarted): Added.
+
+ * public/v3/models/data-model.js:
+ (DataModelObject.ensureSingleton): Added.
+ (DataModelObject.cachedFetch): Added noCache option. This is used when re-fetching the test groups after
+ creating one.
+
+ * public/v3/models/measurement-cluster.js:
+ (MeasurementCluster.prototype.startTime): Added.
+
+ * public/v3/models/measurement-set.js:
+ (MeasurementSet.prototype.hasFetchedRange): Added. Returns true only if there are no "holes" (cluster
+ yet to be fetched) between the specified time range. This was added to fix a bug in AnalysisTaskPage's
+ _didFetchMeasurement.
+
+ * public/v3/models/test-group.js:
+ (TestGroup): Added this._rootSetToLabel.
+ (TestGroup.prototype.addBuildRequest): Reset this._rootSetToLabel along with this._requestedRootSets.
+ (TestGroup.prototype.repetitionCount): Added. Returns the number of iterations executed per set. We assume that
+ every root set in the test group shares a single repetition count.
+ (TestGroup.prototype.requestedRootSets): Now populates this._rootSetToLabel for labelForRootSet.
+ (TestGroup.prototype.labelForRootSet): Added.
+ (TestGroup.prototype.hasStarted): Added.
+ (TestGroup.prototype.compareTestResults): Use 'running' and 'pending' to differentiate test groups that are waiting
+ for other groups to finish running from the ones that are actually running ('incomplete' before this patch).
+ (TestGroup.fetchByTask):
+ (TestGroup.createAndRefetchTestGroups): Added. Creates a new test group using the privileged-api/create-test-group
+ and fetches the list of test groups for the specified analysis task.
+ (TestGroup._createModelsFromFetchedTestGroups): Extracted from TestGroup.fetchByTask.
+
+ * public/v3/pages/analysis-task-page.js:
+ (AnalysisTaskPage): Initialize _renderedCurrentTestGroup to undefined so that we'd always can differentiate
+ the initial call to AnalysisTaskPage.render and subsequent calls in which it's identical to _currentTestGroup.
+ (AnalysisTaskPage.prototype._didFetchMeasurement): Fixed a bug that we don't exit early even when some
+ clusters in between startPoint and endPoint are still being fetched via newly added hasFetchedRange.
+ (AnalysisTaskPage.prototype.render): Update the default repetition count based on the current test group.
+ Also update the label of the button to "Confirm the change" if there is no A/B testing in this task.
+ (AnalysisTaskPage.prototype._retryCurrentTestGroup): Added. Re-triggers an existing A/B testing group or creates
+ the A/B testing for the entire range of the analysis task.
+ (AnalysisTaskPage.prototype._hasDuplicateTestGroupName): Added.
+ (AnalysisTaskPage.prototype._createRetryNameForTestGroup): Added.
+ (AnalysisTaskPage.htmlTemplate): Added form controls to re-trigger A/B testing.
+ (AnalysisTaskPage.cssTemplate): Updated the style.
+
+2016-02-10 Ryosuke Niwa <rniwa@webkit.org>
+
</ins><span class="cx"> Removed the duplicated definition of ChartPaneBase.
</span><span class="cx">
</span><span class="cx"> * public/v3/components/chart-pane-base.js:
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsresultstablejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/results-table.js (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/results-table.js        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/public/v3/components/results-table.js        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -226,6 +226,10 @@
</span><span class="cx"> font-size: 0.8rem;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ .results-table-extra-repositories:empty {
+ padding: 0;
+ }
+
</ins><span class="cx"> .results-table-extra-repositories li {
</span><span class="cx"> display: inline;
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentstestgroupresultstablejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/test-group-results-table.js (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/test-group-results-table.js        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/public/v3/components/test-group-results-table.js        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -30,7 +30,7 @@
</span><span class="cx"> return [];
</span><span class="cx">
</span><span class="cx"> var rootSets = this._testGroup.requestedRootSets();
</span><del>- var groups = rootSets.map(function (rootSet, setIndex) {
</del><ins>+ var groups = rootSets.map(function (rootSet) {
</ins><span class="cx"> var rows = [new ResultsTableRow('Mean', rootSet)];
</span><span class="cx"> var results = [];
</span><span class="cx">
</span><span class="lines">@@ -51,17 +51,17 @@
</span><span class="cx"> if (!isNaN(aggregatedResult.value))
</span><span class="cx"> rows[0].setResult(aggregatedResult);
</span><span class="cx">
</span><del>- return {heading: String.fromCharCode('A'.charCodeAt(0) + setIndex), rows:rows};
</del><ins>+ return {heading: testGroup.labelForRootSet(rootSet), rows:rows};
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> var comparisonRows = [];
</span><span class="cx"> for (var i = 0; i < rootSets.length; i++) {
</span><span class="cx"> for (var j = i + 1; j < rootSets.length; j++) {
</span><del>- var startConfig = String.fromCharCode('A'.charCodeAt(0) + i);
- var endConfig = String.fromCharCode('A'.charCodeAt(0) + j);
</del><ins>+ var startConfig = testGroup.labelForRootSet(rootSets[i]);
+ var endConfig = testGroup.labelForRootSet(rootSets[j]);
</ins><span class="cx">
</span><span class="cx"> var result = this._testGroup.compareTestResults(rootSets[i], rootSets[j]);
</span><del>- if (result.status == 'incomplete' || result.status == 'failed')
</del><ins>+ if (result.status == 'pending' || result.status == 'running' || result.status == 'failed')
</ins><span class="cx"> continue;
</span><span class="cx">
</span><span class="cx"> var row = new ResultsTableRow(`${startConfig} to ${endConfig}`, null);
</span><span class="lines">@@ -70,7 +70,7 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- groups.push({heading: '', rows: comparisonRows});
</del><ins>+ groups.unshift({heading: '', rows: comparisonRows});
</ins><span class="cx">
</span><span class="cx"> return groups;
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsbuildrequestjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/build-request.js (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/build-request.js        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/public/v3/models/build-request.js        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -21,6 +21,7 @@
</span><span class="cx"> rootSet() { return this._rootSet; }
</span><span class="cx">
</span><span class="cx"> hasCompleted() { return this._status == 'failed' || this._status == 'completed'; }
</span><ins>+ hasStarted() { return this._status != 'pending'; }
</ins><span class="cx"> statusLabel()
</span><span class="cx"> {
</span><span class="cx"> switch (this._status) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsdatamodeljs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/data-model.js (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/data-model.js        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/public/v3/models/data-model.js        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -7,6 +7,14 @@
</span><span class="cx"> }
</span><span class="cx"> id() { return this._id; }
</span><span class="cx">
</span><ins>+ static ensureSingleton(id, object)
+ {
+ var singleton = this.findById(id);
+ if (singleton)
+ return singleton;
+ return new (this)(id, object);
+ }
+
</ins><span class="cx"> static namedStaticMap(name)
</span><span class="cx"> {
</span><span class="cx"> var staticMap = this[DataModelObject.StaticMapSymbol];
</span><span class="lines">@@ -43,7 +51,7 @@
</span><span class="cx"> return list;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- static cachedFetch(path, params)
</del><ins>+ static cachedFetch(path, params, noCache)
</ins><span class="cx"> {
</span><span class="cx"> var query = [];
</span><span class="cx"> if (params) {
</span><span class="lines">@@ -53,6 +61,9 @@
</span><span class="cx"> if (query.length)
</span><span class="cx"> path += '?' + query.join('&');
</span><span class="cx">
</span><ins>+ if (noCache)
+ return getJSONWithStatus(path);
+
</ins><span class="cx"> var cacheMap = this.ensureNamedStaticMap(DataModelObject.CacheMapSymbol);
</span><span class="cx"> if (!cacheMap[path])
</span><span class="cx"> cacheMap[path] = getJSONWithStatus(path);
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsmeasurementclusterjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -7,6 +7,7 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> startTime() { return this._response['startTime']; }
</span><ins>+ endTime() { return this._response['endTime']; }
</ins><span class="cx">
</span><span class="cx"> addToSeries(series, configType, includeOutliers, idMap)
</span><span class="cx"> {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsmeasurementsetjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -191,6 +191,23 @@
</span><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ hasFetchedRange(startTime, endTime)
+ {
+ console.assert(startTime < endTime);
+ var hasHole = false;
+ var previousEndTime = null;
+ for (var cluster of this._sortedClusters) {
+ if (cluster.startTime() < startTime && startTime < cluster.endTime())
+ hasHole = false;
+ if (previousEndTime !== null && previousEndTime != cluster.startTime())
+ hasHole = true;
+ if (cluster.startTime() < endTime && endTime < cluster.endTime())
+ break;
+ previousEndTime = cluster.endTime();
+ }
+ return !hasHole;
+ }
+
</ins><span class="cx"> fetchedTimeSeries(configType, includeOutliers, extendToFuture)
</span><span class="cx"> {
</span><span class="cx"> Instrumentation.startMeasuringTime('MeasurementSet', 'fetchedTimeSeries');
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelstestgroupjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/test-group.js (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/test-group.js        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/public/v3/models/test-group.js        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -11,6 +11,7 @@
</span><span class="cx"> this._requestsAreInOrder = false;
</span><span class="cx"> this._repositories = null;
</span><span class="cx"> this._requestedRootSets = null;
</span><ins>+ this._rootSetToLabel = new Map;
</ins><span class="cx"> this._allRootSets = null;
</span><span class="cx"> console.assert(!object.platform || object.platform instanceof Platform);
</span><span class="cx"> this._platform = object.platform;
</span><span class="lines">@@ -23,8 +24,22 @@
</span><span class="cx"> this._buildRequests.push(request);
</span><span class="cx"> this._requestsAreInOrder = false;
</span><span class="cx"> this._requestedRootSets = null;
</span><ins>+ this._rootSetToLabel = null;
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+ repetitionCount()
+ {
+ if (!this._buildRequests.length)
+ return 0;
+ var rootSet = this._buildRequests[0].rootSet();
+ var count = 0;
+ for (var request of this._buildRequests) {
+ if (request.rootSet() == rootSet)
+ count++;
+ }
+ return count;
+ }
+
</ins><span class="cx"> requestedRootSets()
</span><span class="cx"> {
</span><span class="cx"> if (!this._requestedRootSets) {
</span><span class="lines">@@ -36,6 +51,12 @@
</span><span class="cx"> this._requestedRootSets.push(set);
</span><span class="cx"> }
</span><span class="cx"> this._requestedRootSets.sort(function (a, b) { return a.latestCommitTime() - b.latestCommitTime(); });
</span><ins>+ var setIndex = 0;
+ for (var set of this._requestedRootSets) {
+ this._rootSetToLabel.set(set, String.fromCharCode('A'.charCodeAt(0) + setIndex));
+ setIndex++;
+ }
+
</ins><span class="cx"> }
</span><span class="cx"> return this._requestedRootSets;
</span><span class="cx"> }
</span><span class="lines">@@ -46,6 +67,12 @@
</span><span class="cx"> return this._buildRequests.filter(function (request) { return request.rootSet() == rootSet; });
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ labelForRootSet(rootSet)
+ {
+ console.assert(this._requestedRootSets);
+ return this._rootSetToLabel.get(rootSet);
+ }
+
</ins><span class="cx"> _orderBuildRequests()
</span><span class="cx"> {
</span><span class="cx"> if (this._requestsAreInOrder)
</span><span class="lines">@@ -64,6 +91,11 @@
</span><span class="cx"> return this._buildRequests.every(function (request) { return request.hasCompleted(); });
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ hasStarted()
+ {
+ return this._buildRequests.some(function (request) { return request.hasStarted(); });
+ }
+
</ins><span class="cx"> compareTestResults(rootSetA, rootSetB)
</span><span class="cx"> {
</span><span class="cx"> var beforeValues = this._valuesForRootSet(rootSetA);
</span><span class="lines">@@ -85,9 +117,15 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (!this.hasCompleted()) {
</span><del>- result.status = 'incomplete';
- result.label = 'Running';
- result.fullLabel = 'Running';
</del><ins>+ if (this.hasStarted()) {
+ result.status = 'running';
+ result.label = 'Running';
+ result.fullLabel = 'Running';
+ } else {
+ result.status = 'pending';
+ result.label = 'Pending';
+ result.fullLabel = 'Pending';
+ }
</ins><span class="cx"> } else if (result.changeType) {
</span><span class="cx"> var significance = result.isStatisticallySignificant ? 'significant' : 'insignificant';
</span><span class="cx"> result.fullLabel = `${result.label} (statistically ${significance})`;
</span><span class="lines">@@ -107,32 +145,47 @@
</span><span class="cx"> return values;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ static createAndRefetchTestGroups(task, name, repetitionCount, rootSets)
+ {
+ var self = this;
+ return PrivilegedAPI.sendRequest('create-test-group', {
+ task: task.id(),
+ name: name,
+ repetitionCount: repetitionCount,
+ rootSets: rootSets,
+ }).then(function (data) {
+ return self.cachedFetch('../api/test-groups', {task: task.id()}, true).then(self._createModelsFromFetchedTestGroups.bind(self));
+ });
+ }
+
</ins><span class="cx"> static fetchByTask(taskId)
</span><span class="cx"> {
</span><del>- return this.cachedFetch('../api/test-groups', {task: taskId}).then(function (data) {
- var testGroups = data['testGroups'].map(function (row) {
- row.platform = Platform.findById(row.platform);
- return new TestGroup(row.id, row);
- });
</del><ins>+ return this.cachedFetch('../api/test-groups', {task: taskId}).then(this._createModelsFromFetchedTestGroups.bind(this));
+ }
</ins><span class="cx">
</span><del>- var rootIdMap = {};
- for (var root of data['roots'])
- rootIdMap[root.id] = root;
</del><ins>+ static _createModelsFromFetchedTestGroups(data)
+ {
+ var testGroups = data['testGroups'].map(function (row) {
+ row.platform = Platform.findById(row.platform);
+ return TestGroup.ensureSingleton(row.id, row);
+ });
</ins><span class="cx">
</span><del>- var rootSets = data['rootSets'].map(function (row) {
- row.roots = row.roots.map(function (rootId) { return rootIdMap[rootId]; });
- row.testGroup = RootSet.findById(row.testGroup);
- return new RootSet(row.id, row);
- });
</del><ins>+ var rootIdMap = {};
+ for (var root of data['roots'])
+ rootIdMap[root.id] = root;
</ins><span class="cx">
</span><del>- var buildRequests = data['buildRequests'].map(function (rawData) {
- rawData.testGroup = TestGroup.findById(rawData.testGroup);
- rawData.rootSet = RootSet.findById(rawData.rootSet);
- return new BuildRequest(rawData.id, rawData);
- });
</del><ins>+ var rootSets = data['rootSets'].map(function (row) {
+ row.roots = row.roots.map(function (rootId) { return rootIdMap[rootId]; });
+ row.testGroup = RootSet.findById(row.testGroup);
+ return RootSet.ensureSingleton(row.id, row);
+ });
</ins><span class="cx">
</span><del>- return testGroups;
</del><ins>+ var buildRequests = data['buildRequests'].map(function (rawData) {
+ rawData.testGroup = TestGroup.findById(rawData.testGroup);
+ rawData.rootSet = RootSet.findById(rawData.rootSet);
+ return BuildRequest.ensureSingleton(rawData.id, rawData);
</ins><span class="cx"> });
</span><ins>+
+ return testGroups;
</ins><span class="cx"> }
</span><del>-
</del><span class="cx"> }
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pagesanalysistaskpagejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js (196439 => 196440)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js        2016-02-11 22:15:45 UTC (rev 196439)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js        2016-02-11 22:17:55 UTC (rev 196440)
</span><span class="lines">@@ -13,7 +13,7 @@
</span><span class="cx"> this._task = null;
</span><span class="cx"> this._testGroups = null;
</span><span class="cx"> this._renderedTestGroups = null;
</span><del>- this._renderedCurrentTestGroup = null;
</del><ins>+ this._renderedCurrentTestGroup = undefined;
</ins><span class="cx"> this._analysisResults = null;
</span><span class="cx"> this._measurementSet = null;
</span><span class="cx"> this._startPoint = null;
</span><span class="lines">@@ -24,6 +24,8 @@
</span><span class="cx"> this._analysisResultsViewer = this.content().querySelector('analysis-results-viewer').component();
</span><span class="cx"> this._analysisResultsViewer.setTestGroupCallback(this._showTestGroup.bind(this));
</span><span class="cx"> this._testGroupResultsTable = this.content().querySelector('test-group-results-table').component();
</span><ins>+
+ this.content().querySelector('.test-group-retry-form').onsubmit = this._retryCurrentTestGroup.bind(this);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> title() { return this._task ? this._task.label() : 'Analysis Task'; }
</span><span class="lines">@@ -85,7 +87,7 @@
</span><span class="cx"> var series = this._measurementSet.fetchedTimeSeries('current', false, false);
</span><span class="cx"> var startPoint = series.findById(this._task.startMeasurementId());
</span><span class="cx"> var endPoint = series.findById(this._task.endMeasurementId());
</span><del>- if (!startPoint || !endPoint)
</del><ins>+ if (!startPoint || !endPoint || !this._measurementSet.hasFetchedRange(startPoint.time, endPoint.time))
</ins><span class="cx"> return;
</span><span class="cx">
</span><span class="cx"> this._analysisResultsViewer.setPoints(startPoint, endPoint);
</span><span class="lines">@@ -139,7 +141,7 @@
</span><span class="cx">
</span><span class="cx"> var v2URL = `/v2/#/analysis/task/${this._taskId}`;
</span><span class="cx"> this.content().querySelector('.error-message').innerHTML +=
</span><del>- `<p>This page is read only for now. To schedule a new A/B testing job, use <a href="${v2URL}">v2 page</a>.</p>`;
</del><ins>+ `<p>To schedule a custom A/B testing, use <a href="${v2URL}">v2 UI</a>.</p>`;
</ins><span class="cx">
</span><span class="cx"> this._chartPane.render();
</span><span class="cx">
</span><span class="lines">@@ -168,7 +170,8 @@
</span><span class="cx"> }));
</span><span class="cx"> this._renderedCurrentTestGroup = null;
</span><span class="cx"> }
</span><del>- if (this._renderedCurrentTestGroup != this._currentTestGroup) {
</del><ins>+
+ if (this._renderedCurrentTestGroup !== this._currentTestGroup) {
</ins><span class="cx"> if (this._renderedCurrentTestGroup) {
</span><span class="cx"> var element = this.content().querySelector('.test-group-list-' + this._renderedCurrentTestGroup.id());
</span><span class="cx"> if (element)
</span><span class="lines">@@ -179,9 +182,18 @@
</span><span class="cx"> if (element)
</span><span class="cx"> element.classList.add('selected');
</span><span class="cx"> }
</span><ins>+
+ this.content().querySelector('.test-group-retry-button').textContent = this._currentTestGroup ? 'Retry' : 'Confirm the change';
+
+ var repetitionCount = this._currentTestGroup ? this._currentTestGroup.repetitionCount() : 4;
+ var repetitionCountController = this.content().querySelector('.test-group-retry-repetition-count');
+ repetitionCountController.value = repetitionCount;
+
</ins><span class="cx"> this._renderedCurrentTestGroup = this._currentTestGroup;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ this.content().querySelector('.test-group-retry-button').disabled = !(this._currentTestGroup || this._startPoint);
+
</ins><span class="cx"> this._testGroupResultsTable.render();
</span><span class="cx">
</span><span class="cx"> Instrumentation.endMeasuringTime('AnalysisTaskPage', 'render');
</span><span class="lines">@@ -194,6 +206,86 @@
</span><span class="cx"> this.render();
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ _retryCurrentTestGroup(event)
+ {
+ event.preventDefault();
+ console.assert(this._currentTestGroup || this._startPoint);
+
+ var testGroupName;
+ var rootSetList;
+ var rootSetLabels;
+
+ if (this._currentTestGroup) {
+ var testGroup = this._currentTestGroup;
+ testGroupName = this._createRetryNameForTestGroup(testGroup.name());
+ rootSetList = testGroup.requestedRootSets();
+ rootSetLabels = rootSetList.map(function (rootSet) { return testGroup.labelForRootSet(rootSet); });
+ } else {
+ testGroupName = 'Confirming the change';
+ rootSetList = [this._startPoint.rootSet(), this._endPoint.rootSet()];
+ rootSetLabels = ['Point 0', `Point ${this._endPoint.seriesIndex - this._startPoint.seriesIndex}`];
+ }
+
+ var rootSetsByName = {};
+ for (var repository of rootSetList[0].repositories())
+ rootSetsByName[repository.name()] = [];
+
+ var setIndex = 0;
+ for (var rootSet of rootSetList) {
+ for (var repository of rootSet.repositories()) {
+ var list = rootSetsByName[repository.name()];
+ if (!list) {
+ alert(`Set ${rootSetLabels[setIndex]} specifies ${repository.label()} but set ${rootSetLabels[0]} does not.`);
+ return null;
+ }
+ list.push(rootSet.commitForRepository(repository).revision());
+ }
+ setIndex++;
+ for (var name in rootSetsByName) {
+ var list = rootSetsByName[name];
+ if (list.length < setIndex) {
+ alert(`Set ${rootSetLabels[0]} specifies ${repository.label()} but set ${rootSetLabels[setIndex]} does not.`);
+ return null;
+ }
+ }
+ }
+
+ var repetitionCount = this.content().querySelector('.test-group-retry-repetition-count').value;
+
+ TestGroup.createAndRefetchTestGroups(this._task, testGroupName, repetitionCount, rootSetsByName)
+ .then(this._didFetchTestGroups.bind(this), function (error) {
+ alert('Failed to create a new test group: ' + error);
+ });
+ }
+
+ _createRetryNameForTestGroup(name)
+ {
+ var nameWithNumberMatch = name.match(/(.+?)\s*\(\s*(\d+)\s*\)\s*$/);
+ var number = 1;
+ if (nameWithNumberMatch) {
+ name = nameWithNumberMatch[1];
+ number = parseInt(nameWithNumberMatch[2]);
+ }
+
+ var newName;
+ do {
+ number++;
+ newName = `${name} (${number})`;
+ } while (this._hasDuplicateTestGroupName(newName));
+
+ return newName;
+ }
+
+ _hasDuplicateTestGroupName(name)
+ {
+ console.assert(this._testGroups);
+ for (var group of this._testGroups) {
+ if (group.name() == name)
+ return true;
+ }
+ return false;
+ }
+
</ins><span class="cx"> static htmlTemplate()
</span><span class="cx"> {
</span><span class="cx"> return `
</span><span class="lines">@@ -208,7 +300,26 @@
</span><span class="cx"> </section>
</span><span class="cx"> <section class="test-group-view">
</span><span class="cx"> <ul class="test-group-list"></ul>
</span><del>- <div class="test-group-details"><test-group-results-table></test-group-results-table></div>
</del><ins>+ <div class="test-group-details">
+ <test-group-results-table></test-group-results-table>
+ <form class="test-group-retry-form">
+ <button class="test-group-retry-button" type="submit">Retry</button>
+ with
+ <select class="test-group-retry-repetition-count">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ <option>7</option>
+ <option>8</option>
+ <option>9</option>
+ <option>10</option>
+ </select>
+ iterations per set
+ </form>
+ </div>
</ins><span class="cx"> </section>
</span><span class="cx"> </div>
</span><span class="cx"> </div>
</span><span class="lines">@@ -268,18 +379,23 @@
</span><span class="cx"> .test-group-view {
</span><span class="cx"> display: table;
</span><span class="cx"> margin: 0 1rem;
</span><ins>+ margin-bottom: 2rem;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> .test-group-details {
</span><span class="cx"> display: table-cell;
</span><span class="cx"> margin-bottom: 1rem;
</span><ins>+ padding: 0;
+ margin: 0;
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+ .test-group-retry-form {
+ padding: 0;
+ margin: 0.5rem;
+ }
+
</ins><span class="cx"> .test-group-list {
</span><span class="cx"> display: table-cell;
</span><del>- }
-
- .test-group-list:not(:empty) {
</del><span class="cx"> margin: 0;
</span><span class="cx"> padding: 0.2rem 0;
</span><span class="cx"> list-style: none;
</span><span class="lines">@@ -287,6 +403,12 @@
</span><span class="cx"> white-space: nowrap;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ .test-group-list:empty {
+ margin: 0;
+ padding: 0;
+ border-right: none;
+ }
+
</ins><span class="cx"> .test-group-list li {
</span><span class="cx"> display: block;
</span><span class="cx"> }
</span></span></pre>
</div>
</div>
</body>
</html>