Title: [196521] trunk/Websites/perf.webkit.org
Revision
196521
Author
rn...@webkit.org
Date
2016-02-12 15:33:18 -0800 (Fri, 12 Feb 2016)

Log Message

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.

Modified Paths

Added Paths

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;
             }
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to