Diff
Modified: trunk/Websites/perf.webkit.org/ChangeLog (196520 => 196521)
--- trunk/Websites/perf.webkit.org/ChangeLog 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/ChangeLog 2016-02-12 23:33:18 UTC (rev 196521)
@@ -1,3 +1,100 @@
+2016-02-12 Ryosuke Niwa <rn...@webkit.org>
+
+ Perf dashboard should allow renaming analysis tasks and test groups
+ https://bugs.webkit.org/show_bug.cgi?id=154200
+
+ Reviewed by Chris Dumez.
+
+ Allow editing names of analysis tasks and A/B testing groups in the v3 UI.
+
+ Added the support for updating the name to the privileged API at /privileged-api/update-analysis-task
+ and added a new prevailed API to update A/B testing groups at /privileged-api/update-test-group.
+
+ * public/privileged-api/update-analysis-task.php: Added the support for renaming the analysis task.
+ (main):
+
+ * public/privileged-api/update-test-group.php: Added. Supports updating the test group's name.
+ (main):
+
+ * public/v3/components/editable-text.js: Added.
+ (EditableText): Added. A new editable text label control. It looks like a text node with "(Edit)" link
+ at the end which allow users to go into the "editing mode", which reveals an input element.
+ The user can exit the editing mode by either moving the focus away from the control or clicking on
+ "(Save)" at the end. It calls _updateCallback in the latter case.
+ (EditableText.prototype.editedText): Returns the current value of the input element user.
+ (EditableText.prototype.setText): Sets the label. This does not live-update the input element until
+ the user exists the current editing mode and re-enters it.
+ (EditableText.prototype.setStartedEditingCallback): Sets a callback which gets called when the user
+ requested to enter the editing mode. Since EditableText relies on AnalysisTaskPage to render, this
+ callback only exits to call EditableText.render() in AnalysisTask._didStartEditingTaskName.
+ (EditableText.prototype.setUpdateCallback): Sets a callback which gets called when the user exits
+ the editing mode by activating the "(Save)" link. This callback MUST return a promise upon resolution
+ of which the control gets out of the editing mode. While the promise is in flight, the input element
+ becomes readonly.
+ (EditableText.prototype.render): Updates various states of the elements. When _updatingPromise is not
+ falsy, we make the input element readonly and show '(...)' on the link. Don't show the action link
+ if the label is empty (e.g. analysis task or test group is still being fetched).
+ (EditableText.prototype._didClick): Called when the user clicked on the action link. Enter the editing
+ mode or save the edited label via _updateCallback.
+ (EditableText.prototype._didBlur): Exit the editing mode without saving if the input element is not
+ focused, there is no inflight promise returned by _updateCallback, and the action link "(Save)" does
+ not have the focus.
+ (EditableText.prototype._didUpdate): Called when exiting the editing mode.
+ (EditableText.htmlTemplate):
+ (EditableText.cssTemplate):
+
+ * public/v3/index.html: Include newly added editable-text.js.
+
+ * public/v3/models/analysis-task.js:
+ (AnalysisTask.prototype.updateSingleton): Added.
+ (AnalysisTask.prototype.updateName): Added. Uses PrivilegedAPI to update the name and re-fetches
+ the analysis task from the sever.
+ (AnalysisTask._constructAnalysisTasksFromRawData): Use ensureSingleton instead of manually calling
+ findById since we need to update the name of the singleton object we found (via updateSingleton).
+
+ * public/v3/models/bug.js:
+ (Bug.ensureSingleton): Moved the code to compute the synthetic id from AnalysisTask's
+ _constructAnalysisTasksFromRawData.
+ (Bug.prototype.updateSingleton): Added. Just assert that nothing changes.
+
+ * public/v3/models/build-request.js:
+ (BuildRequest.prototype.updateSingleton): Added. Assert that the intrinsic values of a build request
+ doesn't change and update status text, status url, and build id as they could change.
+
+ * public/v3/models/commit-log.js:
+ (CommitLog): Made the constructor argument conform to the convention of id, object pair so that we can
+ use DataModelObject.ensureSingleton.
+ (CommitLog.ensureSingleton):
+ (CommitLog.prototype.updateSingleton): Extracted from CommitLog.ensureSingleton.
+
+ * public/v3/models/data-model.js:
+ (DataModelObject.ensureSingleton): Call newly added updateSingleton.
+ (DataModelObject.prototype.updateSingleton):
+ (LabeledObject): Removed the name map since it's never used (findByName is never called anywhere).
+ (LabeledObject.prototype.updateSingleton): Added. Updates _name.
+ (LabeledObject.findByName): Deleted.
+
+ * public/v3/models/test-group.js:
+ (TestGroup.prototype.updateName): Added. Uses PrivilegedAPI to update the name and re-fetches
+ the test group from the sever.
+ (TestGroup._createModelsFromFetchedTestGroups): Removed bogus code. A root set doesn't have a test
+ group associated with it since multiple test groups can share a single root set (this property doesn't
+ even exist).
+
+ * public/v3/pages/analysis-task-page.js:
+ (AnalysisTaskPage): Removed useless _taskId and added this._testGroupLabelMap and this._taskNameLabel.
+ (AnalysisTaskPage.prototype.updateFromSerializedState): Cleanup.
+ (AnalysisTaskPage.prototype._didFetchTask): Assert that this function is called exactly once.
+ (AnalysisTaskPage.prototype.render): Use this._task.id() to show the v2 link. Use EditableText to show
+ the names of the analysis task and the associated test groups. Hide the overview chart and the list of
+ test groups (along with the retry/confirm button) when the analysis task failed to fetch. We always
+ update the names of the analysis task and the associated test groups since they could be updated by
+ the server.
+ (AnalysisTaskPage.prototype._didStartEditingTaskName): Added.
+ (AnalysisTaskPage.prototype._updateTaskName): Added.
+ (AnalysisTaskPage.prototype._updateTestGroupName): Added.
+ (AnalysisTaskPage.htmlTemplate): Updated the style.
+
2016-02-11 Ryosuke Niwa <rn...@webkit.org>
Land the change that was supposed to be the part of r196463.
Modified: trunk/Websites/perf.webkit.org/public/privileged-api/update-analysis-task.php (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/privileged-api/update-analysis-task.php 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/privileged-api/update-analysis-task.php 2016-02-12 23:33:18 UTC (rev 196521)
@@ -11,6 +11,9 @@
$values = array();
+ if (array_key_exists('name', $data))
+ $values['name'] = $data['name'];
+
if (array_key_exists('result', $data)) {
require_match_one_of_values('Result', $data['result'], array(null, 'progression', 'regression', 'unchanged', 'inconclusive'));
$values['result'] = $data['result'];
Added: trunk/Websites/perf.webkit.org/public/privileged-api/update-test-group.php (0 => 196521)
--- trunk/Websites/perf.webkit.org/public/privileged-api/update-test-group.php (rev 0)
+++ trunk/Websites/perf.webkit.org/public/privileged-api/update-test-group.php 2016-02-12 23:33:18 UTC (rev 196521)
@@ -0,0 +1,35 @@
+<?php
+
+require_once('../include/json-header.php');
+
+function main() {
+ $data = ""
+
+ $test_group_id = array_get($data, 'group');
+ if (!$test_group_id)
+ exit_with_error('TestGroupNotSpecified');
+
+ $values = array();
+
+ if (array_key_exists('name', $data))
+ $values['name'] = $data['name'];
+
+ if (!$values)
+ exit_with_error('NothingToUpdate');
+
+ $db = connect();
+ $db->begin_transaction();
+
+ if (!$db->update_row('analysis_test_groups', 'testgroup', array('id' => $test_group_id), $values)) {
+ $db->rollback_transaction();
+ exit_with_error('FailedToUpdateTestGroup', array('id' => $test_group_id, 'values' => $values));
+ }
+
+ $db->commit_transaction();
+
+ exit_with_success();
+}
+
+main();
+
+?>
Added: trunk/Websites/perf.webkit.org/public/v3/components/editable-text.js (0 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/components/editable-text.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/editable-text.js 2016-02-12 23:33:18 UTC (rev 196521)
@@ -0,0 +1,123 @@
+
+class EditableText extends ComponentBase {
+
+ constructor(text)
+ {
+ super('editable-text');
+ this._text = text;
+ this._inEditingMode = false;
+ this._startedEditingCallback = null;
+ this._updateCallback = null;
+ this._updatingPromise = null;
+ this._actionLink = this.content().querySelector('.editable-text-action a');
+ this._actionLink._onclick_ = this._didClick.bind(this);
+ this._actionLink._onmousedown_ = this._didClick.bind(this);
+ this._textField = this.content().querySelector('.editable-text-field');
+ this._textField._onblur_ = this._didBlur.bind(this);
+ this._label = this.content().querySelector('.editable-text-label');
+ }
+
+ editedText() { return this._textField.value; }
+ setText(text) { this._text = text; }
+
+ setStartedEditingCallback(callback) { this._startedEditingCallback = callback; }
+ setUpdateCallback(callback) { this._updateCallback = callback; }
+
+ render()
+ {
+ this._label.textContent = this._text;
+ this._actionLink.textContent = this._inEditingMode ? (this._updatingPromise ? '...' : 'Save') : 'Edit';
+ this._actionLink.parentNode.style.display = this._text ? null : 'none';
+
+ if (this._inEditingMode) {
+ this._textField.readOnly = !!this._updatingPromise;
+ this._textField.style.display = null;
+ this._label.style.display = 'none';
+ if (!this._updatingPromise)
+ this._textField.focus();
+ } else {
+ this._textField.style.display = 'none';
+ this._label.style.display = null;
+ }
+
+ super.render();
+ }
+
+ _didClick(event)
+ {
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (!this._updateCallback || this._updatingPromise)
+ return;
+
+ if (this._inEditingMode)
+ this._updatingPromise = this._updateCallback().then(this._didUpdate.bind(this));
+ else {
+ this._inEditingMode = true;
+ this._textField.value = this._text;
+ this._textField.style.width = (this._text.length / 1.5) + 'rem';
+ if (this._startedEditingCallback)
+ this._startedEditingCallback();
+ }
+ }
+
+ _didBlur(event)
+ {
+ var self = this;
+ if (self._inEditingMode && !self._updatingPromise && !self.hasFocus())
+ self._didUpdate();
+ }
+
+ _didUpdate()
+ {
+ this._inEditingMode = false;
+ this._updatingPromise = null;
+ this.render();
+ }
+
+ static htmlTemplate()
+ {
+ return `
+ <span class="editable-text-container">
+ <input type="text" class="editable-text-field">
+ <span class="editable-text-label"></span>
+ <span class="editable-text-action">(<a href=""
+ </span>`;
+ }
+
+ static cssTemplate()
+ {
+ return `
+ .editable-text-container {
+ position: relative;
+ padding-right: 2.5rem;
+ }
+ .editable-text-field {
+ background: transparent;
+ margin: 0;
+ padding: 0;
+ color: inherit;
+ font-weight: inherit;
+ font-size: inherit;
+ width: 8rem;
+ border: none;
+ }
+ .editable-text-action {
+ position: absolute;
+ padding-left: 0.2rem;
+ color: #999;
+ font-size: 0.8rem;
+ top: 50%;
+ margin-top: -0.4rem;
+ vertical-align: middle;
+ }
+ .editable-text-action a {
+ color: inherit;
+ text-decoration: none;
+ }
+ `;
+ }
+}
+
+ComponentBase.defineElement('editable-text', EditableText);
Modified: trunk/Websites/perf.webkit.org/public/v3/index.html (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/index.html 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/v3/index.html 2016-02-12 23:33:18 UTC (rev 196521)
@@ -66,6 +66,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
Modified: trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js 2016-02-12 23:33:18 UTC (rev 196521)
@@ -29,6 +29,27 @@
return this.all().filter(function (task) { return task._platform.id() == platformId && task._metric.id() == metricId; });
}
+ updateSingleton(object)
+ {
+ super.updateSingleton(object);
+
+ console.assert(this._author == object.author);
+ console.assert(+this._createdAt == +object.createdAt);
+ console.assert(this._platform == object.platform);
+ console.assert(this._metric == object.metric);
+ console.assert(this._startMeasurementId == object.startRun);
+ console.assert(this._startTime == object.startRunTime);
+ console.assert(this._endMeasurementId == object.endRun);
+ console.assert(this._endTime == object.endRunTime);
+
+ this._category = object.category;
+ this._changeType = object.result;
+ this._needed = object.needed;
+ this._bugs = object.bugs || [];
+ this._buildRequestCount = object.buildRequestCount;
+ this._finishedBuildRequestCount = object.finishedBuildRequestCount;
+ }
+
hasResults() { return this._finishedBuildRequestCount; }
hasPendingRequests() { return this._finishedBuildRequestCount < this._buildRequestCount; }
requestLabel() { return `${this._finishedBuildRequestCount} of ${this._buildRequestCount}`; }
@@ -46,6 +67,19 @@
category() { return this._category; }
changeType() { return this._changeType; }
+ updateName(newName)
+ {
+ var self = this;
+ var id = this.id();
+ return PrivilegedAPI.sendRequest('update-analysis-task', {
+ task: id,
+ name: newName,
+ }).then(function (data) {
+ return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
+ .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
+ });
+ }
+
static categories()
{
return [
@@ -94,12 +128,11 @@
// FIXME: The backend shouldn't create a separate bug row per task for the same bug number.
var taskToBug = {};
for (var rawData of data.bugs) {
- var id = rawData.bugTracker + '-' + rawData.number;
rawData.bugTracker = BugTracker.findById(rawData.bugTracker);
if (!rawData.bugTracker)
continue;
- var bug = Bug.findById(id) || new Bug(id, rawData);
+ var bug = Bug.ensureSingleton(rawData);
if (!taskToBug[rawData.task])
taskToBug[rawData.task] = [];
taskToBug[rawData.task].push(bug);
@@ -113,8 +146,7 @@
continue;
rawData.bugs = taskToBug[rawData.id];
- var task = AnalysisTask.findById(rawData.id) || new AnalysisTask(rawData.id, rawData);
- results.push(task);
+ results.push(AnalysisTask.ensureSingleton(rawData.id, rawData));
}
Instrumentation.endMeasuringTime('AnalysisTask', 'construction');
Modified: trunk/Websites/perf.webkit.org/public/v3/models/bug.js (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/models/bug.js 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/v3/models/bug.js 2016-02-12 23:33:18 UTC (rev 196521)
@@ -9,6 +9,20 @@
this._bugNumber = object.number;
}
+ static ensureSingleton(object)
+ {
+ console.assert(object.bugTracker instanceof BugTracker);
+ var id = object.bugTracker.id() + '-' + object.number;
+ return super.ensureSingleton(id, object);
+ }
+
+ updateSingleton(object)
+ {
+ super.updateSingleton(object);
+ console.assert(this._bugTracker == object.bugTracker);
+ console.assert(this._bugNumber == object.number);
+ }
+
bugTracker() { return this._bugTracker; }
bugNumber() { return this._bugNumber; }
url() { return this._bugTracker.bugUrl(this._bugNumber); }
Modified: trunk/Websites/perf.webkit.org/public/v3/models/build-request.js (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/models/build-request.js 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/v3/models/build-request.js 2016-02-12 23:33:18 UTC (rev 196521)
@@ -16,6 +16,16 @@
this._result = null;
}
+ updateSingleton(object)
+ {
+ console.assert(this._testGroup == object.testGroup);
+ console.assert(this._order == object.order);
+ console.assert(this._rootSet == object.rootSet);
+ this._status = object.status;
+ this._statusUrl = object.url;
+ this._buildId = object.build;
+ }
+
testGroup() { return this._testGroup; }
order() { return this._order; }
rootSet() { return this._rootSet; }
Modified: trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js 2016-02-12 23:33:18 UTC (rev 196521)
@@ -1,26 +1,32 @@
class CommitLog extends DataModelObject {
- constructor(id, repository, rawData)
+ constructor(id, rawData)
{
super(id);
- this._repository = repository;
+ this._repository = rawData.repository;
this._rawData = rawData;
}
static ensureSingleton(repository, rawData)
{
var id = repository.id() + '-' + rawData['revision'];
- var singleton = this.findById(id);
- if (singleton) {
- if (rawData.authorName)
- singleton._rawData.authorName = rawData.authorName;
- if (rawData.message)
- singleton._rawData.message = rawData.message;
- return singleton;
- }
- return new CommitLog(id, repository, rawData);
+ rawData.repository = repository;
+ return super.ensureSingleton(id, rawData);
}
+ updateSingleton(rawData)
+ {
+ super.updateSingleton(rawData);
+
+ console.assert(+this._rawData['time'] == +rawData['time']);
+ console.assert(this._rawData['revision'] == rawData['revision']);
+
+ if (rawData.authorName)
+ this._rawData.authorName = rawData.authorName;
+ if (rawData.message)
+ this._rawData.message = rawData.message;
+ }
+
repository() { return this._repository; }
time() { return new Date(this._rawData['time']); }
author() { return this._rawData['authorName']; }
Modified: trunk/Websites/perf.webkit.org/public/v3/models/data-model.js (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/models/data-model.js 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/v3/models/data-model.js 2016-02-12 23:33:18 UTC (rev 196521)
@@ -10,11 +10,15 @@
static ensureSingleton(id, object)
{
var singleton = this.findById(id);
- if (singleton)
+ if (singleton) {
+ singleton.updateSingleton(object)
return singleton;
+ }
return new (this)(id, object);
}
+ updateSingleton(object) { }
+
static namedStaticMap(name)
{
var staticMap = this[DataModelObject.StaticMapSymbol];
@@ -80,14 +84,9 @@
{
super(id);
this._name = object.name;
- this.ensureNamedStaticMap('name')[this._name] = this;
}
- static findByName(name)
- {
- var nameMap = this.namedStaticMap('id');
- return nameMap ? nameMap[name] : null;
- }
+ updateSingleton(object) { this._name = object.name; }
static sortByName(list)
{
Modified: trunk/Websites/perf.webkit.org/public/v3/models/test-group.js (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/models/test-group.js 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/v3/models/test-group.js 2016-02-12 23:33:18 UTC (rev 196521)
@@ -152,6 +152,19 @@
return values;
}
+ updateName(newName)
+ {
+ var self = this;
+ var id = this.id();
+ return PrivilegedAPI.sendRequest('update-test-group', {
+ group: id,
+ name: newName,
+ }).then(function (data) {
+ return TestGroup.cachedFetch(`../api/test-groups/${id}`, {}, true)
+ .then(TestGroup._createModelsFromFetchedTestGroups.bind(TestGroup));
+ });
+ }
+
static createAndRefetchTestGroups(task, name, repetitionCount, rootSets)
{
var self = this;
@@ -183,7 +196,6 @@
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);
});
Modified: trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js (196520 => 196521)
--- trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js 2016-02-12 23:25:02 UTC (rev 196520)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js 2016-02-12 23:33:18 UTC (rev 196521)
@@ -16,10 +16,10 @@
constructor()
{
super('Analysis Task');
- this._taskId = null;
this._task = null;
this._testGroups = null;
this._renderedTestGroups = null;
+ this._testGroupLabelMap = new Map;
this._renderedCurrentTestGroup = undefined;
this._analysisResults = null;
this._measurementSet = null;
@@ -32,6 +32,9 @@
this._analysisResultsViewer = this.content().querySelector('analysis-results-viewer').component();
this._analysisResultsViewer.setTestGroupCallback(this._showTestGroup.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._taskNameLabel.setUpdateCallback(this._updateTaskName.bind(this));
this.content().querySelector('.test-group-retry-form')._onsubmit_ = this._retryCurrentTestGroup.bind(this);
}
@@ -43,20 +46,18 @@
{
var self = this;
if (state.remainingRoute) {
- this._taskId = parseInt(state.remainingRoute);
- AnalysisTask.fetchById(this._taskId).then(this._didFetchTask.bind(this), function (error) {
+ var taskId = parseInt(state.remainingRoute);
+ AnalysisTask.fetchById(taskId).then(this._didFetchTask.bind(this), function (error) {
self._errorMessage = `Failed to fetch the analysis task ${state.remainingRoute}: ${error}`;
self.render();
});
- TestGroup.fetchByTask(this._taskId).then(this._didFetchTestGroups.bind(this));
- AnalysisResults.fetch(this._taskId).then(this._didFetchAnalysisResults.bind(this));
+ TestGroup.fetchByTask(taskId).then(this._didFetchTestGroups.bind(this));
+ AnalysisResults.fetch(taskId).then(this._didFetchAnalysisResults.bind(this));
} else if (state.buildRequest) {
var buildRequestId = parseInt(state.buildRequest);
- AnalysisTask.fetchByBuildRequestId(buildRequestId).then(this._didFetchTask.bind(this)).then(function () {
- if (self._task) {
- TestGroup.fetchByTask(self._task.id()).then(self._didFetchTestGroups.bind(self));
- AnalysisResults.fetch(self._task.id()).then(this._didFetchAnalysisResults.bind(this));
- }
+ AnalysisTask.fetchByBuildRequestId(buildRequestId).then(this._didFetchTask.bind(this)).then(function (task) {
+ TestGroup.fetchByTask(task.id()).then(self._didFetchTestGroups.bind(self));
+ AnalysisResults.fetch(task.id()).then(self._didFetchAnalysisResults.bind(self));
}, function (error) {
self._errorMessage = `Failed to fetch the analysis task for the build request ${buildRequestId}: ${error}`;
self.render();
@@ -66,6 +67,8 @@
_didFetchTask(task)
{
+ console.assert(!this._task);
+
this._task = task;
var platform = task.platform();
var metric = task.metric();
@@ -85,6 +88,8 @@
this._chartPane.setMainDomain(domain[0], domain[1]);
this.render();
+
+ return task;
}
_didFetchMeasurement()
@@ -146,20 +151,25 @@
this.content().querySelector('.error-message').textContent = this._errorMessage || '';
- var v2URL = `/v2/#/analysis/task/${this._taskId}`;
- this.content().querySelector('.error-message').innerHTML +=
- `<p>To schedule a custom A/B testing, use <a href="" UI</a>.</p>`;
+ if (this._task) {
+ var v2URL = `/v2/#/analysis/task/${this._task.id()}`;
+ this.content().querySelector('.error-message').innerHTML =
+ `<p>To schedule a custom A/B testing, use <a href="" UI</a>.</p>`;
+ }
- this._chartPane.render();
+ this._chartPane.render();
if (this._task) {
- this.renderReplace(this.content().querySelector('.analysis-task-name'), this._task.name());
+ this._taskNameLabel.setText(this._task.name());
var platform = this._task.platform();
var metric = this._task.metric();
var anchor = this.content().querySelector('.platform-metric-names a');
this.renderReplace(anchor, metric.fullName() + ' on ' + platform.label());
anchor.href = "" ChartsPage.createStateForAnalysisTask(this._task));
}
+ this.content().querySelector('.overview-chart').style.display = this._task ? null : 'none';
+ this.content().querySelector('.test-group-view').style.display = this._task ? null : 'none';
+ this._taskNameLabel.render();
this._analysisResultsViewer.setCurrentTestGroup(this._currentTestGroup);
this._analysisResultsViewer.render();
@@ -168,16 +178,34 @@
var link = ComponentBase.createLink;
if (this._testGroups != this._renderedTestGroups) {
this._renderedTestGroups = this._testGroups;
+ this._testGroupLabelMap.clear();
+
var self = this;
+ var updateTestGroupName = this._updateTestGroupName.bind(this);
+ var showTestGroup = this._showTestGroup.bind(this);
+
this.renderReplace(this.content().querySelector('.test-group-list'),
this._testGroups.map(function (group) {
- return element('li', {class: 'test-group-list-' + group.id()}, link(group.label(), function () {
- self._showTestGroup(group);
- }));
+ var text = new EditableText(group.label());
+ text.setStartedEditingCallback(function () { return text.render(); });
+ text.setUpdateCallback(function () { return updateTestGroupName(group); });
+
+ self._testGroupLabelMap.set(group, text);
+ return element('li', {class: 'test-group-list-' + group.id()},
+ link(text, group.label(), function () { showTestGroup(group); }));
}).reverse());
+
this._renderedCurrentTestGroup = null;
}
+ if (this._testGroups) {
+ for (var testGroup of this._testGroups) {
+ var label = this._testGroupLabelMap.get(testGroup);
+ label.setText(testGroup.label());
+ label.render();
+ }
+ }
+
if (this._renderedCurrentTestGroup !== this._currentTestGroup) {
if (this._renderedCurrentTestGroup) {
var element = this.content().querySelector('.test-group-list-' + this._renderedCurrentTestGroup.id());
@@ -222,6 +250,39 @@
this.render();
}
+ _didStartEditingTaskName()
+ {
+ this._taskNameLabel.render();
+ }
+
+ _updateTaskName()
+ {
+ console.assert(this._task);
+ this._taskNameLabel.render();
+
+ var self = this;
+ return self._task.updateName(self._taskNameLabel.editedText()).then(function () {
+ self.render();
+ }, function (error) {
+ self.render();
+ alert('Failed to update the name: ' + error);
+ });
+ }
+
+ _updateTestGroupName(testGroup)
+ {
+ var label = this._testGroupLabelMap.get(testGroup);
+ label.render();
+
+ var self = this;
+ return testGroup.updateName(label.editedText()).then(function () {
+ self.render();
+ }, function (error) {
+ self.render();
+ alert('Failed to update the name: ' + error);
+ });
+ }
+
_retryCurrentTestGroup(event)
{
event.preventDefault();
@@ -307,7 +368,7 @@
return `
<div class="analysis-tasl-page-container">
<div class="analysis-tasl-page">
- <h2 class="analysis-task-name"></h2>
+ <h2 class="analysis-task-name"><editable-text></editable-text></h2>
<h3 class="platform-metric-names"><a href=""
<p class="error-message"></p>
<div class="overview-chart"><analysis-task-chart-pane></analysis-task-chart-pane></div>
@@ -425,11 +486,11 @@
border-right: none;
}
- .test-group-list li {
+ .test-group-list > li {
display: block;
}
- .test-group-list a {
+ .test-group-list > li > a {
display: block;
color: inherit;
text-decoration: none;
@@ -438,11 +499,11 @@
padding: 0.2rem;
}
- .test-group-list li.selected a {
+ .test-group-list > li.selected > a {
background: rgba(204, 153, 51, 0.1);
}
- .test-group-list li:not(.selected) a:hover {
+ .test-group-list > li:not(.selected) > a:hover {
background: #eee;
}