+2016-02-16 Ryosuke Niwa <rniwa@webkit.org>
+
+ v3 UI has the capability to schedule an A/B testing in a specific range
+ https://bugs.webkit.org/show_bug.cgi?id=154329
+
+ Reviewed by Chris Dumez.
+
+ Extended AnalysisTaskChartPane and ResultsTable so that users can select a range of points in either
+ the overview chart pane and the results viewer table. Extracted TestGroupForm out of the analysis task
+ page and used right below those two components in the analysis task page.
+
+ * public/v3/components/results-table.js:
+ (ResultsTable):
+ (ResultsTable.prototype.setRangeSelectorLabels): Added.
+ (ResultsTable.prototype.setRangeSelectorCallback): Added.
+ (ResultsTable.prototype.selectedRange): Added.
+ (ResultsTable.prototype._rangeSelectorClicked): Added.
+ (ResultsTable.prototype.render): Generate radio boxes to select a range.
+
+ * public/v3/components/test-group-form.js:
+ (TestGroupForm):
+ (TestGroupForm.prototype.setStartCallback): Added.
+ (TestGroupForm.prototype.setNeedsName): Added.
+ (TestGroupForm.prototype.setDisabled): Added.
+ (TestGroupForm.prototype.setLabel): Added.
+ (TestGroupForm.prototype.setRepetitionCount): Added.
+ (TestGroupForm.prototype._submitted): Added.
+ (TestGroupForm.htmlTemplate): Extracted from AnalysisTaskPage.htmlTemplate.
+
+ * public/v3/index.html:
+
+ * public/v3/pages/analysis-task-page.js:
+ (AnalysisTaskChartPane.prototype._mainSelectionDidChange): Added. Delegates the work to AnalysisTaskPage.
+ (AnalysisTaskChartPane.prototype.selectedPoints): Added.
+ (AnalysisTaskPage):
+ (AnalysisTaskPage.prototype.title):
+ (AnalysisTaskPage.prototype.render):
+ (AnalysisTaskPage.prototype._renderTestGroupDetails): Use TestGroupForm's methods instead of mutating DOM.
+ (AnalysisTaskPage.prototype._retryCurrentTestGroup):
+ (AnalysisTaskPage.prototype._chartSelectionDidChange): Added.
+ (AnalysisTaskPage.prototype._createNewTestGroupFromChart): Added.
+ (AnalysisTaskPage.prototype._selectedRowInAnalysisResultsViewer): Added.
+ (AnalysisTaskPage.prototype._createNewTestGroupFromViewer): Added.
+ (AnalysisTaskPage.prototype._createRetryNameForTestGroup):
+ (AnalysisTaskPage.prototype._createTestGroupAfterVerifyingRootSetList): Extracted from _retryCurrentTestGroup
+ so that we can call it in _createNewTestGroupFromChart and _createNewTestGroupFromViewer.
+ (AnalysisTaskPage.htmlTemplate):
+
2016-02-15 Ryosuke Niwa <rniwa@webkit.org>
Extract the code specific to v2 UI out of shared statistics.js
super(name);
this._repositoryList = [];
this._valueFormatter = null;
+ this._rangeSelectorLabels = null;
+ this._rangeSelectorCallback = null;
+ this._selectedRange = {};
}
setValueFormatter(valueFormatter) { this._valueFormatter = valueFormatter; }
+ setRangeSelectorLabels(labels) { this._rangeSelectorLabels = labels; }
+ setRangeSelectorCallback(callback) { this._rangeSelectorCallback = callback; }
+ selectedRange() { return this._selectedRange; }
+
+ _rangeSelectorClicked(label, row)
+ {
+ this._selectedRange[label] = row;
+ if (this._rangeSelectorCallback)
+ this._rangeSelectorCallback();
+ }
render()
{
var barGraphGroup = new BarGraphGroup(this._valueFormatter);
var element = ComponentBase.createElement;
var self = this;
+ var hasGroupHeading = false;
var tableBodies = rowGroups.map(function (group) {
var groupHeading = group.heading;
var revisionSupressionCount = {};
+ hasGroupHeading = !!groupHeading;
return element('tbody', group.rows.map(function (row, rowIndex) {
var cells = [];
+ if (self._rangeSelectorLabels) {
+ self._selectedRange = {};
+ for (var label of self._rangeSelectorLabels)
+ cells.push(element('td', element('input',
+ {type: 'radio', name: label, onchange: self._rangeSelectorClicked.bind(self, label, row)})));
+ }
+
if (groupHeading !== undefined && !rowIndex)
cells.push(element('th', {rowspan: group.rows.length}, groupHeading));
cells.push(element('td', row.heading()));
this.renderReplace(this.content().querySelector('table'), [
element('thead', [
+ this._rangeSelectorLabels ? this._rangeSelectorLabels.map(function (label) { return element('th', label) }) : [],
this.heading(),
element('th', 'Result'),
repositoryList.map(function (repository) { return element('th', repository.label()); }),
setPage(page) { this._page = page; }
router() { return this._page.router(); }
+
+ _mainSelectionDidChange(selection, didEndDrag)
+ {
+ super._mainSelectionDidChange(selection);
+ if (didEndDrag)
+ this._page._chartSelectionDidChange();
+ }
+
+ selectedPoints()
+ {
+ var selection = this._mainChart ? this._mainChart.currentSelection() : null;
+ if (!selection)
+ return null;
+
+ return this._mainChart.sampledDataBetween('current', selection[0], selection[1]);
+ }
}
ComponentBase.defineElement('analysis-task-chart-pane', AnalysisTaskChartPane);
this._chartPane.setPage(this);
this._analysisResultsViewer = this.content().querySelector('analysis-results-viewer').component();
this._analysisResultsViewer.setTestGroupCallback(this._showTestGroup.bind(this));
+ this._analysisResultsViewer.setRangeSelectorLabels(['A', 'B']);
+ this._analysisResultsViewer.setRangeSelectorCallback(this._selectedRowInAnalysisResultsViewer.bind(this));
this._testGroupResultsTable = this.content().querySelector('test-group-results-table').component();
this._taskNameLabel = this.content().querySelector('.analysis-task-name editable-text').component();
this._taskNameLabel.setStartedEditingCallback(this._didStartEditingTaskName.bind(this));
this._bugTrackerControl = this.content().querySelector('.bug-tracker-control');
this._bugNumberControl = this.content().querySelector('.bug-number-control');
- this.content().querySelector('.test-group-retry-form').onsubmit = this._retryCurrentTestGroup.bind(this);
+ this._newTestGroupFormForChart = this.content().querySelector('.overview-chart test-group-form').component();
+ this._newTestGroupFormForChart.setStartCallback(this._createNewTestGroupFromChart.bind(this));
+
+ this._newTestGroupFormForViewer = this.content().querySelector('.analysis-results-view test-group-form').component();
+ this._newTestGroupFormForViewer.setStartCallback(this._createNewTestGroupFromViewer.bind(this));
+ this._selectedRowInAnalysisResultsViewer();
+
+ this._retryForm = this.content().querySelector('.test-group-retry-form').firstChild.component();
+ this._retryForm.setStartCallback(this._retryCurrentTestGroup.bind(this));
+ this._retryForm.setNeedsName(false);
}
title() { return this._task ? this._task.label() : 'Analysis Task'; }
}));
}
+ this._newTestGroupFormForChart.render();
+
this._analysisResultsViewer.setCurrentTestGroup(this._currentTestGroup);
this._analysisResultsViewer.render();
this._chartPane.setMainSelection([startTime, endTime]);
}
- 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;
+ this._retryForm.setLabel('Retry');
+ if (this._currentTestGroup)
+ this._retryForm.setRepetitionCount(this._currentTestGroup.repetitionCount());
+ this._retryForm.element().style.display = this._currentTestGroup ? null : 'none';
this._renderedCurrentTestGroup = this._currentTestGroup;
}
-
- this.content().querySelector('.test-group-retry-button').disabled = !(this._currentTestGroup || this._startPoint);
+ this._retryForm.render();
}
_showTestGroup(testGroup)
});
}
- _retryCurrentTestGroup(event)
+ _retryCurrentTestGroup(unusedName, repetitionCount)
{
- 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}`];
- }
+ console.assert(this._currentTestGroup);
+ var testGroup = this._currentTestGroup;
+ var newName = this._createRetryNameForTestGroup(testGroup.name());
+ var rootSetList = testGroup.requestedRootSets();
+ var rootSetLabels = rootSetList.map(function (rootSet) { return testGroup.labelForRootSet(rootSet); });
+ return this._createTestGroupAfterVerifyingRootSetList(newName, repetitionCount, rootSetList, rootSetLabels);
+ }
+
+ _chartSelectionDidChange()
+ {
+ var points = this._chartPane.selectedPoints();
+ this._newTestGroupFormForChart.setDisabled(!points || points.length < 2);
+ }
+
+ _createNewTestGroupFromChart(name, repetitionCount)
+ {
+ var points = this._chartPane.selectedPoints();
+ console.assert(points && points.length >= 2);
+ return this._createTestGroupAfterVerifyingRootSetList(name, repetitionCount,
+ [points[0].rootSet(), points[points.length - 1].rootSet()], ['A', 'B']);
+ }
+
+ _selectedRowInAnalysisResultsViewer()
+ {
+ var selectedRange = this._analysisResultsViewer.selectedRange();
+ this._newTestGroupFormForViewer.setDisabled(!selectedRange['A'] || !selectedRange['B']);
+ }
+
+ _createNewTestGroupFromViewer(name, repetitionCount)
+ {
+ var selectedRange = this._analysisResultsViewer.selectedRange();
+ console.assert(selectedRange && selectedRange['A'] && selectedRange['B']);
+ return this._createTestGroupAfterVerifyingRootSetList(name, repetitionCount,
+ [selectedRange['A'].rootSet(), selectedRange['B'].rootSet()], ['A', 'B']);
+ }
+
+ _createTestGroupAfterVerifyingRootSetList(testGroupName, repetitionCount, rootSetList, rootSetLabels)
+ {
+ if (this._hasDuplicateTestGroupName(testGroupName))
+ alert(`There is already a test group named "${testGroupName}"`);
var rootSetsByName = {};
for (var repository of rootSetList[0].repositories())
}
}
- 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);
</div>
<section class="overview-chart">
<analysis-task-chart-pane></analysis-task-chart-pane>
+ <div class="new-test-group-form"><test-group-form></test-group-form></div>
</section>
<section class="analysis-results-view">
<analysis-results-viewer></analysis-results-viewer>
+ <div class="new-test-group-form"><test-group-form></test-group-form></div>
</section>
<section class="test-group-view">
<ul class="test-group-list"></ul>
<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 class="test-group-retry-form"><test-group-form></test-group-form></div>
</div>
</section>
</div>
}
.analysis-results-view {
- margin: 1rem;
+ margin: 2.5rem 1rem;
}
.test-configuration h3 {
margin: 0;
}
+ .new-test-group-form,
.test-group-retry-form {
padding: 0;
margin: 0.5rem;