<!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>[198265] 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/198265">198265</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-03-16 00:02:28 -0700 (Wed, 16 Mar 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Analysis task page should allow specifying commits that caused or fixed a regression or a progression
https://bugs.webkit.org/show_bug.cgi?id=155529

Reviewed by Chris Dumez.

Added the capability to associate revisions that caused or fixed a progression or a regression for which
an analysis task was created. Added task_commits that stores this relationship and added the backend
support to retrieve this table in /api/analysis-tasks and an privileged API to update this table at
/privileged-api/associate-commit.

Also extracted a new component, MutableListView, out of AnalysisTaskPage to render and manipulate a list
of mutable items, and used it to render the list of associated bugs and commits. The view takes a list of
kinds (e.g. repositories or bug trackers), and accepts a pair of a kind and arbitrary text as a new item
value.

* init-database.sql: Added task_commits table.

* public/api/analysis-tasks.php:
(main):
(fetch_associated_data_for_tasks): Renamed from fetch_and_push_bugs_to_tasks now that it also fetches
the list of commits associated with each analysis task by calling CommitLogFetcher::fetch_for_tasks.
Also fixe the bug that we were not taking
(format_task): No longer sets 'category' since the computation of category now depends on the list of
commits associated with this analysis task which aren't available until fetch_associated_data_for_tasks.
(determine_category): Added. Categorize any analysis tasks with &quot;fixes&quot; commits as &quot;closed&quot; and &quot;causes&quot;
commits as &quot;identified&quot;.

* public/include/commit-log-fetcher.php:
(CommitLogFetcher::__construct): Remove the unused instance variable.
(CommitLogFetcher::fetch_for_tasks): Added. Fetches all commits associated with a list of analysis tasks.
Assumes the caller (fetch_associated_data_for_tasks) had setup &quot;fixes&quot; and &quot;causes&quot; fields on each task.

* public/privileged-api/associate-commit.php: Added. Updates task_commits table to associate or disassociate
a commit with an analysis task. When the specified analysis task and the specified commit are already
associated, we simply update the table instead of adding a duplicating entry or error. For dissociation,
the front-end specifies the commit ID.
(main): Added.

* public/v3/index.html:
* public/v3/components/mutable-list-view.js: Added. Used by the list associated bugs and commits.
(MutableListView): Added.
(MutableListView.prototype.setList): Added.
(MutableListView.prototype.setKindList): Added.
(MutableListView.prototype.setAddCallback): Added. This callback is invoked when the user tries to add
a new item to the list.
(MutableListView.prototype.render): Added.
(MutableListView.prototype._submitted): Added.
(MutableListView.cssTemplate):
(MutableListView.htmlTemplate):
(MutableListItem): Added. RemovalLink could be a hyperlink or a callback and gets involved when the user
tries to delete this item.
(MutableListItem.prototype.content):

* public/v3/models/analysis-task.js:
(AnalysisTask): Added the support of the list of commits that fixed and caused changes.
(AnalysisTask.prototype.updateSingleton): Ditto.
(AnalysisTask.prototype.causes): Added.
(AnalysisTask.prototype.fixes): Added.
(AnalysisTask.prototype.associateCommit): Added. Use the API added at /privileged-api/associate-commit
to associate a new commit with this analysis task. Each commit has either caused or fixed the change.
(AnalysisTask.prototype.dissociateCommit): Added. Use the same API to disassociate each commit.
(AnalysisTask._constructAnalysisTasksFromRawData): Find all commits associated with each analysis task.
Because commit log objects use a fake ID fdue to /api/measurement-set not providing commit IDs, we must
use CommitLog.findByRemoteId to find each commit instead of usual CommitLog.findById.
(AnalysisTask._constructAnalysisTasksFromRawData.resolveCommits): Added.

* public/v3/models/build-request.js:
(BuildRequest.prototype.hasFinished): Renamed from hasCompleted since it was confusing for this._status
being &quot;completed&quot; wasn't a necessary condition for this function to return true.

* public/v3/models/commit-log.js:
(CommitLog): Added the static map for actual commit ID instead of a fake ID created in ensureSingleton.
(CommitLog.prototype.remoteId): Added. Returns the real commit ID.
(CommitLog.findByRemoteId): Added. Finds an CommitLog object using the real ID.

* public/v3/models/test-group.js:
(TestGroup.prototype.hasFinished): Renamed from hasCompleted to match the rename in BuildRequest.

* public/v3/pages/analysis-task-page.js:
(AnalysisTaskPage): Added lists for the commits that fixed and caused the change using MutableListView.
Also adopted MutableListView for the list of associated bugs.
(AnalysisTaskPage.prototype.render): Added the code to populate the newly added lists.
(AnalysisTaskPage.prototype._makeCommitListItem): Added.
(AnalysisTaskPage.prototype._associateBug): Now this is a callback from MutableListView.
(AnalysisTaskPage.prototype._associateCommit): Added.
(AnalysisTaskPage.prototype._dissociateCommit): Added.
(AnalysisTaskPage.htmlTemplate):
(AnalysisTaskPage.cssTemplate):

* public/v3/remote.js:
(getJSON): Spit out the entire responseText when JSON failed to parse to make debugging easier.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorginitdatabasesql">trunk/Websites/perf.webkit.org/init-database.sql</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicapianalysistasksphp">trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicincludecommitlogfetcherphp">trunk/Websites/perf.webkit.org/public/include/commit-log-fetcher.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3indexhtml">trunk/Websites/perf.webkit.org/public/v3/index.html</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsanalysistaskjs">trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsbuildrequestjs">trunk/Websites/perf.webkit.org/public/v3/models/build-request.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelscommitlogjs">trunk/Websites/perf.webkit.org/public/v3/models/commit-log.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>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3remotejs">trunk/Websites/perf.webkit.org/public/v3/remote.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgpublicprivilegedapiassociatecommitphp">trunk/Websites/perf.webkit.org/public/privileged-api/associate-commit.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsmutablelistviewjs">trunk/Websites/perf.webkit.org/public/v3/components/mutable-list-view.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 (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -1,3 +1,97 @@
</span><ins>+2016-03-16  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        Analysis task page should allow specifying commits that caused or fixed a regression or a progression
+        https://bugs.webkit.org/show_bug.cgi?id=155529
+
+        Reviewed by Chris Dumez.
+
+        Added the capability to associate revisions that caused or fixed a progression or a regression for which
+        an analysis task was created. Added task_commits that stores this relationship and added the backend
+        support to retrieve this table in /api/analysis-tasks and an privileged API to update this table at
+        /privileged-api/associate-commit.
+
+        Also extracted a new component, MutableListView, out of AnalysisTaskPage to render and manipulate a list
+        of mutable items, and used it to render the list of associated bugs and commits. The view takes a list of
+        kinds (e.g. repositories or bug trackers), and accepts a pair of a kind and arbitrary text as a new item
+        value.
+
+        * init-database.sql: Added task_commits table.
+
+        * public/api/analysis-tasks.php:
+        (main):
+        (fetch_associated_data_for_tasks): Renamed from fetch_and_push_bugs_to_tasks now that it also fetches
+        the list of commits associated with each analysis task by calling CommitLogFetcher::fetch_for_tasks.
+        Also fixe the bug that we were not taking 
+        (format_task): No longer sets 'category' since the computation of category now depends on the list of
+        commits associated with this analysis task which aren't available until fetch_associated_data_for_tasks.
+        (determine_category): Added. Categorize any analysis tasks with &quot;fixes&quot; commits as &quot;closed&quot; and &quot;causes&quot;
+        commits as &quot;identified&quot;.
+
+        * public/include/commit-log-fetcher.php:
+        (CommitLogFetcher::__construct): Remove the unused instance variable.
+        (CommitLogFetcher::fetch_for_tasks): Added. Fetches all commits associated with a list of analysis tasks.
+        Assumes the caller (fetch_associated_data_for_tasks) had setup &quot;fixes&quot; and &quot;causes&quot; fields on each task.
+
+        * public/privileged-api/associate-commit.php: Added. Updates task_commits table to associate or disassociate
+        a commit with an analysis task. When the specified analysis task and the specified commit are already
+        associated, we simply update the table instead of adding a duplicating entry or error. For dissociation,
+        the front-end specifies the commit ID.
+        (main): Added.
+
+        * public/v3/index.html:
+        * public/v3/components/mutable-list-view.js: Added. Used by the list associated bugs and commits.
+        (MutableListView): Added.
+        (MutableListView.prototype.setList): Added.
+        (MutableListView.prototype.setKindList): Added.
+        (MutableListView.prototype.setAddCallback): Added. This callback is invoked when the user tries to add
+        a new item to the list.
+        (MutableListView.prototype.render): Added.
+        (MutableListView.prototype._submitted): Added.
+        (MutableListView.cssTemplate):
+        (MutableListView.htmlTemplate):
+        (MutableListItem): Added. RemovalLink could be a hyperlink or a callback and gets involved when the user
+        tries to delete this item.
+        (MutableListItem.prototype.content):
+
+        * public/v3/models/analysis-task.js:
+        (AnalysisTask): Added the support of the list of commits that fixed and caused changes.
+        (AnalysisTask.prototype.updateSingleton): Ditto.
+        (AnalysisTask.prototype.causes): Added.
+        (AnalysisTask.prototype.fixes): Added.
+        (AnalysisTask.prototype.associateCommit): Added. Use the API added at /privileged-api/associate-commit
+        to associate a new commit with this analysis task. Each commit has either caused or fixed the change.
+        (AnalysisTask.prototype.dissociateCommit): Added. Use the same API to disassociate each commit.
+        (AnalysisTask._constructAnalysisTasksFromRawData): Find all commits associated with each analysis task.
+        Because commit log objects use a fake ID fdue to /api/measurement-set not providing commit IDs, we must
+        use CommitLog.findByRemoteId to find each commit instead of usual CommitLog.findById.
+        (AnalysisTask._constructAnalysisTasksFromRawData.resolveCommits): Added.
+
+        * public/v3/models/build-request.js:
+        (BuildRequest.prototype.hasFinished): Renamed from hasCompleted since it was confusing for this._status
+        being &quot;completed&quot; wasn't a necessary condition for this function to return true.
+
+        * public/v3/models/commit-log.js:
+        (CommitLog): Added the static map for actual commit ID instead of a fake ID created in ensureSingleton.
+        (CommitLog.prototype.remoteId): Added. Returns the real commit ID.
+        (CommitLog.findByRemoteId): Added. Finds an CommitLog object using the real ID.
+
+        * public/v3/models/test-group.js:
+        (TestGroup.prototype.hasFinished): Renamed from hasCompleted to match the rename in BuildRequest.
+
+        * public/v3/pages/analysis-task-page.js:
+        (AnalysisTaskPage): Added lists for the commits that fixed and caused the change using MutableListView.
+        Also adopted MutableListView for the list of associated bugs.
+        (AnalysisTaskPage.prototype.render): Added the code to populate the newly added lists.
+        (AnalysisTaskPage.prototype._makeCommitListItem): Added.
+        (AnalysisTaskPage.prototype._associateBug): Now this is a callback from MutableListView.
+        (AnalysisTaskPage.prototype._associateCommit): Added.
+        (AnalysisTaskPage.prototype._dissociateCommit): Added.
+        (AnalysisTaskPage.htmlTemplate):
+        (AnalysisTaskPage.cssTemplate):
+
+        * public/v3/remote.js:
+        (getJSON): Spit out the entire responseText when JSON failed to parse to make debugging easier.
+
</ins><span class="cx"> 2016-03-15  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         Extract the code to format commit logs into its own PHP file
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorginitdatabasesql"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/init-database.sql (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/init-database.sql        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/init-database.sql        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -16,6 +16,7 @@
</span><span class="cx"> DROP TABLE reports CASCADE;
</span><span class="cx"> DROP TABLE tracker_repositories CASCADE;
</span><span class="cx"> DROP TABLE bug_trackers CASCADE;
</span><ins>+DROP TABLE task_commits CASCADE;
</ins><span class="cx"> DROP TABLE analysis_tasks CASCADE;
</span><span class="cx"> DROP TABLE analysis_strategies CASCADE;
</span><span class="cx"> DROP TYPE analysis_task_result_type CASCADE;
</span><span class="lines">@@ -202,6 +203,12 @@
</span><span class="cx">     CONSTRAINT analysis_task_should_not_be_associated_with_single_run
</span><span class="cx">         CHECK ((task_start_run IS NULL AND task_end_run IS NULL) OR (task_start_run IS NOT NULL AND task_end_run IS NOT NULL)));
</span><span class="cx"> 
</span><ins>+CREATE TABLE task_commits (
+    taskcommit_task integer NOT NULL REFERENCES analysis_tasks ON DELETE CASCADE,
+    taskcommit_commit integer NOT NULL REFERENCES commits ON DELETE CASCADE,
+    taskcommit_is_fix boolean NOT NULL
+    CONSTRAINT task_commit_must_be_unique UNIQUE(taskcommit_task, taskcommit_commit));
+
</ins><span class="cx"> CREATE TABLE bugs (
</span><span class="cx">     bug_id serial PRIMARY KEY,
</span><span class="cx">     bug_task integer REFERENCES analysis_tasks NOT NULL,
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicapianalysistasksphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -1,6 +1,7 @@
</span><span class="cx"> &lt;?php
</span><span class="cx"> 
</span><span class="cx"> require('../include/json-header.php');
</span><ins>+require('../include/commit-log-fetcher.php');
</ins><span class="cx"> 
</span><span class="cx"> function main($path) {
</span><span class="cx">     $db = new Database;
</span><span class="lines">@@ -42,12 +43,10 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     $tasks = array_map(&quot;format_task&quot;, $tasks);
</span><del>-    $bugs = fetch_and_push_bugs_to_tasks($db, $tasks);
-
-    exit_with_success(array('analysisTasks' =&gt; $tasks, 'bugs' =&gt; $bugs));
</del><ins>+    exit_with_success(fetch_associated_data_for_tasks($db, $tasks));
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-function fetch_and_push_bugs_to_tasks($db, &amp;$tasks) {
</del><ins>+function fetch_associated_data_for_tasks($db, &amp;$tasks) {
</ins><span class="cx">     $task_ids = array();
</span><span class="cx">     $task_by_id = array();
</span><span class="cx">     foreach ($tasks as &amp;$task) {
</span><span class="lines">@@ -65,10 +64,15 @@
</span><span class="cx">         array_push($associated_task['bugs'], $bug['id']);
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    $commit_log_fetcher = new CommitLogFetcher($db);
+    $commits = $commit_log_fetcher-&gt;fetch_for_tasks($task_ids, $task_by_id);
+    if (!is_array($commits))
+        exit_with_error('FailedToFetchCommits');
+
</ins><span class="cx">     $task_build_counts = $db-&gt;query_and_fetch_all('SELECT
</span><span class="cx">         testgroup_task AS &quot;task&quot;,
</span><span class="cx">         count(testgroup_id) as &quot;total&quot;,
</span><del>-        sum(case when request_status = \'failed\' or request_status = \'completed\' then 1 else 0 end) as &quot;finished&quot;
</del><ins>+        sum(case when request_status = \'failed\' or request_status = \'completed\' or request_status = \'canceled\' then 1 else 0 end) as &quot;finished&quot;
</ins><span class="cx">         FROM analysis_test_groups, build_requests
</span><span class="cx">         WHERE request_group = testgroup_id AND testgroup_task = ANY($1) GROUP BY testgroup_task',
</span><span class="cx">         array('{' . implode(', ', $task_ids) . '}'));
</span><span class="lines">@@ -79,19 +83,13 @@
</span><span class="cx">         $task = &amp;$task_by_id[$build_count['task']];
</span><span class="cx">         $task['buildRequestCount'] = $build_count['total'];
</span><span class="cx">         $task['finishedBuildRequestCount'] = $build_count['finished'];
</span><ins>+        $task['category'] = determine_category($task);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    return $bugs;
</del><ins>+    return array('analysisTasks' =&gt; $tasks, 'bugs' =&gt; $bugs, 'commits' =&gt; $commits);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function format_task($task_row) {
</span><del>-    $category = 'unconfirmed';
-    $result = $task_row['task_result'];
-    if ($result == 'unchanged' || $result == 'inconclusive')
-        $category = 'closed';
-    else if ($result)
-        $category = 'bisecting';
-
</del><span class="cx">     return array(
</span><span class="cx">         'id' =&gt; $task_row['task_id'],
</span><span class="cx">         'name' =&gt; $task_row['task_name'],
</span><span class="lines">@@ -105,13 +103,29 @@
</span><span class="cx">         'startRunTime' =&gt; Database::to_js_time($task_row['task_start_run_time']),
</span><span class="cx">         'endRun' =&gt; $task_row['task_end_run'],
</span><span class="cx">         'endRunTime' =&gt; Database::to_js_time($task_row['task_end_run_time']),
</span><del>-        'category' =&gt; $category,
-        'result' =&gt; $result,
</del><ins>+        'category' =&gt; null,
+        'result' =&gt; $task_row['task_result'],
</ins><span class="cx">         'needed' =&gt; $task_row['task_needed'] ? Database::is_true($task_row['task_needed']) : null,
</span><span class="cx">         'bugs' =&gt; array(),
</span><ins>+        'causes' =&gt; array(),
+        'fixes' =&gt; array(),
</ins><span class="cx">     );
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+function determine_category($task) {
+    $category = 'unconfirmed';
+
+    $result = $task['result'];
+    if ($result == 'unchanged' || $result == 'inconclusive' || $task['fixes'])
+        $category = 'closed';
+    else if ($task['causes'])
+        $category = 'identified';
+    else if ($result)
+        $category = 'bisecting';
+
+    return $category;
+}
+
</ins><span class="cx"> main(array_key_exists('PATH_INFO', $_SERVER) ? explode('/', trim($_SERVER['PATH_INFO'], '/')) : array());
</span><span class="cx"> 
</span><span class="cx"> ?&gt;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicincludecommitlogfetcherphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/include/commit-log-fetcher.php (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/include/commit-log-fetcher.php        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/include/commit-log-fetcher.php        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -4,9 +4,27 @@
</span><span class="cx"> 
</span><span class="cx">     function __construct($db) {
</span><span class="cx">         $this-&gt;db = $db;
</span><del>-        $this-&gt;commits = array();
</del><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    function fetch_for_tasks($task_id_list, $task_by_id)
+    {
+        $commit_rows = $this-&gt;db-&gt;query_and_fetch_all('SELECT task_commits.*, commits.*, committers.*
+            FROM task_commits, commits LEFT OUTER JOIN committers ON commit_committer = committer_id
+            WHERE taskcommit_commit = commit_id AND taskcommit_task = ANY ($1)', array('{' . implode(', ', $task_id_list) . '}'));
+        if (!is_array($commit_rows))
+            return NULL;
+
+        $commits = array();
+        foreach ($commit_rows as &amp;$commit_row) {
+            $associated_task = &amp;$task_by_id[$commit_row['taskcommit_task']];
+            $commit = $this-&gt;format_commit($commit_row, $commit_row);
+            $commit['repository'] = $commit_row['commit_repository'];
+            array_push($commits, $commit);
+            array_push($associated_task[Database::is_true($commit_row['taskcommit_is_fix']) ? 'fixes' : 'causes'], $commit_row['commit_id']);
+        }
+        return $commits;
+    }
+
</ins><span class="cx">     function repository_id_from_name($name)
</span><span class="cx">     {
</span><span class="cx">         $repository_row = $this-&gt;db-&gt;select_first_row('repositories', 'repository', array('name' =&gt; $name));
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicprivilegedapiassociatecommitphp"></a>
<div class="addfile"><h4>Added: trunk/Websites/perf.webkit.org/public/privileged-api/associate-commit.php (0 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/privileged-api/associate-commit.php                                (rev 0)
+++ trunk/Websites/perf.webkit.org/public/privileged-api/associate-commit.php        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -0,0 +1,55 @@
</span><ins>+&lt;?php
+
+require_once('../include/json-header.php');
+
+function main() {
+    $data = ensure_privileged_api_data_and_token();
+
+    $analysis_task_id = array_get($data, 'task');
+    $repository_id = array_get($data, 'repository');
+    $revision = array_get($data, 'revision');
+    $kind = array_get($data, 'kind');
+    $commit_id_to_diassociate = array_get($data, 'commit');
+
+    $db = connect();
+    $db-&gt;begin_transaction();
+
+    require_format('AnalysisTask', $analysis_task_id, '/^\d+$/');
+    if ($commit_id_to_diassociate) {
+        require_format('Commit', $commit_id_to_diassociate, '/^\d*$/');
+
+        $count = $db-&gt;query_and_get_affected_rows(&quot;DELETE FROM task_commits WHERE taskcommit_task = $1 AND taskcommit_commit = $2&quot;,
+            array($analysis_task_id, $commit_id_to_diassociate));
+        if ($count != 1) {
+            $db-&gt;rollback_transaction();
+            exit_with_error('UnexpectedNumberOfAffectedRows', array('affectedRows' =&gt; $count));
+        }
+    } else {
+        require_format('Repository', $repository_id, '/^\d+$/');
+        require_format('Kind', $kind, '/^(cause|fix)$/');
+
+        $commit_info = array('repository' =&gt; $repository_id, 'revision' =&gt; $revision);
+        $commit_row = $db-&gt;select_first_row('commits', 'commit', $commit_info);
+        if (!$commit_row) {
+            $db-&gt;rollback_transaction();
+            exit_with_error('CommitNotFound', $commit_info);
+        }
+        $commit_id = $commit_row['commit_id'];
+
+        $association = array('task' =&gt; $analysis_task_id, 'commit' =&gt; $commit_id, 'is_fix' =&gt; Database::to_database_boolean($kind == 'fix'));
+        $commit_id = $db-&gt;update_or_insert_row('task_commits', 'taskcommit',
+            array('task' =&gt; $analysis_task_id, 'commit' =&gt; $commit_id), $association, 'commit');
+        if (!$commit_id) {
+            $db-&gt;rollback_transaction();
+            exit_with_error('FailedToAssociateCommit', $association);
+        }
+    }
+
+    $db-&gt;commit_transaction();
+
+    exit_with_success();
+}
+
+main();
+
+?&gt;
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsmutablelistviewjs"></a>
<div class="addfile"><h4>Added: trunk/Websites/perf.webkit.org/public/v3/components/mutable-list-view.js (0 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/mutable-list-view.js                                (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/mutable-list-view.js        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -0,0 +1,104 @@
</span><ins>+
+
+class MutableListView extends ComponentBase {
+
+    constructor()
+    {
+        super('mutable-list-view');
+        this._list = [];
+        this._kindList = [];
+        this._addCallback = null;
+        this._kindMap = new Map;
+        this.content().querySelector('form').onsubmit = this._submitted.bind(this);
+    }
+
+    setList(list) { this._list = list; }
+    setKindList(list) { this._kindList = list; }
+    setAddCallback(callback) { this._addCallback = callback; }
+
+    render()
+    {
+        this.renderReplace(this.content().querySelector('.mutable-list'),
+            this._list.map(function (item) {
+                console.assert(item instanceof MutableListItem);
+                return item.content();
+            }));
+
+        var element = ComponentBase.createElement;
+        var kindMap = this._kindMap;
+        kindMap.clear();
+        this.renderReplace(this.content().querySelector('.kind'),
+            this._kindList.map(function (kind) {
+                kindMap.set(kind.id(), kind);
+                return element('option', {value: kind.id()}, kind.label());
+            }));
+    }
+
+    _submitted(event)
+    {
+        event.preventDefault();
+        if (this._addCallback)
+            this._addCallback(this._kindMap.get(this.content().querySelector('.kind').value), this.content().querySelector('.value').value);
+    }
+
+    static cssTemplate()
+    {
+        return `
+            .mutable-list,
+            .mutable-list li {
+                list-style: none;
+                padding: 0;
+                margin: 0;
+            }
+            
+            .mutable-list:not(:empty) {
+                margin-bottom: 1rem;
+            }
+
+            .mutable-list {
+                margin-bottom: 1rem;
+            }
+
+            .new-list-item-form {
+                white-space: nowrap;
+            }
+        `;
+    }
+
+    static htmlTemplate()
+    {
+        return `
+            &lt;ul class=&quot;mutable-list&quot;&gt;&lt;/ul&gt;
+            &lt;form class=&quot;new-list-item-form&quot;&gt;
+                &lt;select class=&quot;kind&quot;&gt;&lt;/select&gt;
+                &lt;input class=&quot;value&quot;&gt;
+                &lt;button type=&quot;submit&quot;&gt;Add&lt;/button&gt;
+            &lt;/form&gt;`;
+    }
+
+}
+
+class MutableListItem {
+    constructor(kind, value, valueTitle, valueLink, removalTitle, removalLink)
+    {
+        this._kind = kind;
+        this._value = value;
+        this._valueTitle = valueTitle;
+        this._valueLink = valueLink;
+        this._removalTitle = removalTitle;
+        this._removalLink = removalLink;
+    }
+
+    content()
+    {
+        var link = ComponentBase.createLink;
+        return ComponentBase.createElement('li', [
+            this._kind.label(),
+            ' ',
+            link(this._value, this._valueTitle, this._valueLink),
+            ' ',
+            link(new CloseButton, this._removalTitle, this._removalLink)]);
+    }
+}
+
+ComponentBase.defineElement('mutable-list-view', MutableListView);
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3indexhtml"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/index.html (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/index.html        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/v3/index.html        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -79,6 +79,7 @@
</span><span class="cx">         &lt;script src=&quot;components/customizable-test-group-form.js&quot;&gt;&lt;/script&gt;
</span><span class="cx">         &lt;script src=&quot;components/chart-styles.js&quot;&gt;&lt;/script&gt;
</span><span class="cx">         &lt;script src=&quot;components/chart-pane-base.js&quot;&gt;&lt;/script&gt;
</span><ins>+        &lt;script src=&quot;components/mutable-list-view.js&quot;&gt;&lt;/script&gt;
</ins><span class="cx">         &lt;script src=&quot;pages/page.js&quot;&gt;&lt;/script&gt;
</span><span class="cx">         &lt;script src=&quot;pages/page-router.js&quot;&gt;&lt;/script&gt;
</span><span class="cx">         &lt;script src=&quot;pages/heading.js&quot;&gt;&lt;/script&gt;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsanalysistaskjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -20,6 +20,8 @@
</span><span class="cx">         this._changeType = object.result; // Can't change due to v2 compatibility.
</span><span class="cx">         this._needed = object.needed;
</span><span class="cx">         this._bugs = object.bugs || [];
</span><ins>+        this._causes = object.causes || [];
+        this._fixes = object.fixes || [];
</ins><span class="cx">         this._buildRequestCount = object.buildRequestCount;
</span><span class="cx">         this._finishedBuildRequestCount = object.finishedBuildRequestCount;
</span><span class="cx">     }
</span><span class="lines">@@ -46,6 +48,8 @@
</span><span class="cx">         this._changeType = object.result; // Can't change due to v2 compatibility.
</span><span class="cx">         this._needed = object.needed;
</span><span class="cx">         this._bugs = object.bugs || [];
</span><ins>+        this._causes = object.causes || [];
+        this._fixes = object.fixes || [];
</ins><span class="cx">         this._buildRequestCount = object.buildRequestCount;
</span><span class="cx">         this._finishedBuildRequestCount = object.finishedBuildRequestCount;
</span><span class="cx">     }
</span><span class="lines">@@ -62,6 +66,8 @@
</span><span class="cx">     author() { return this._author || ''; }
</span><span class="cx">     createdAt() { return this._createdAt; }
</span><span class="cx">     bugs() { return this._bugs; }
</span><ins>+    causes() { return this._causes; }
+    fixes() { return this._fixes; }
</ins><span class="cx">     platform() { return this._platform; }
</span><span class="cx">     metric() { return this._metric; }
</span><span class="cx">     category() { return this._category; }
</span><span class="lines">@@ -110,6 +116,35 @@
</span><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    associateCommit(kind, repository, revision)
+    {
+        console.assert(kind == 'cause' || kind == 'fix');
+        console.assert(repository instanceof Repository);
+        var id = this.id();
+        return PrivilegedAPI.sendRequest('associate-commit', {
+            task: id,
+            repository: repository.id(),
+            revision: revision,
+            kind: kind,
+        }).then(function (data) {
+            return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
+                .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
+        });
+    }
+
+    dissociateCommit(commit)
+    {
+        console.assert(commit instanceof CommitLog);
+        var id = this.id();
+        return PrivilegedAPI.sendRequest('associate-commit', {
+            task: id,
+            commit: commit.remoteId(),
+        }).then(function (data) {
+            return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
+                .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
+        });
+    }
+
</ins><span class="cx">     static categories()
</span><span class="cx">     {
</span><span class="cx">         return [
</span><span class="lines">@@ -193,6 +228,17 @@
</span><span class="cx">             taskToBug[rawData.task].push(bug);
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        for (var rawData of data.commits) {
+            rawData.repository = Repository.findById(rawData.repository);
+            if (!rawData.repository)
+                continue;
+            CommitLog.ensureSingleton(rawData.repository, rawData);
+        }
+
+        function resolveCommits(commits) {
+            return commits.map(function (id) { return CommitLog.findByRemoteId(id); }).filter(function (commit) { return !!commit; });
+        }
+
</ins><span class="cx">         var results = [];
</span><span class="cx">         for (var rawData of data.analysisTasks) {
</span><span class="cx">             rawData.platform = Platform.findById(rawData.platform);
</span><span class="lines">@@ -201,6 +247,8 @@
</span><span class="cx">                 continue;
</span><span class="cx"> 
</span><span class="cx">             rawData.bugs = taskToBug[rawData.id];
</span><ins>+            rawData.causes = resolveCommits(rawData.causes);
+            rawData.fixes = resolveCommits(rawData.fixes);
</ins><span class="cx">             results.push(AnalysisTask.ensureSingleton(rawData.id, rawData));
</span><span class="cx">         }
</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 (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/build-request.js        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/v3/models/build-request.js        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -30,7 +30,7 @@
</span><span class="cx">     order() { return this._order; }
</span><span class="cx">     rootSet() { return this._rootSet; }
</span><span class="cx"> 
</span><del>-    hasCompleted() { return this._status == 'failed' || this._status == 'completed' || this._status == 'canceled'; }
</del><ins>+    hasFinished() { return this._status == 'failed' || this._status == 'completed' || this._status == 'canceled'; }
</ins><span class="cx">     hasStarted() { return this._status != 'pending'; }
</span><span class="cx">     hasPending() { return this._status == 'pending'; }
</span><span class="cx">     statusLabel()
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelscommitlogjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -5,8 +5,19 @@
</span><span class="cx">         super(id);
</span><span class="cx">         this._repository = rawData.repository;
</span><span class="cx">         this._rawData = rawData;
</span><ins>+        this._remoteId = rawData.id;
+        if (this._remoteId)
+            this.ensureNamedStaticMap('remoteId')[this._remoteId] = this;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    // FIXME: All this non-sense should go away once measurement-set start returning real commit id.
+    remoteId() { return this._remoteId; }
+    static findByRemoteId(id)
+    {
+        var remoteIdMap = super.namedStaticMap('remoteId');
+        return remoteIdMap ? remoteIdMap[id] : null;
+    }
+
</ins><span class="cx">     static ensureSingleton(repository, rawData)
</span><span class="cx">     {
</span><span class="cx">         var id = repository.id() + '-' + rawData['revision'];
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelstestgroupjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/test-group.js (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/test-group.js        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/v3/models/test-group.js        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -99,9 +99,9 @@
</span><span class="cx">         this._allRootSets = null;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    hasCompleted()
</del><ins>+    hasFinished()
</ins><span class="cx">     {
</span><del>-        return this._buildRequests.every(function (request) { return request.hasCompleted(); });
</del><ins>+        return this._buildRequests.every(function (request) { return request.hasFinished(); });
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     hasStarted()
</span><span class="lines">@@ -126,7 +126,7 @@
</span><span class="cx"> 
</span><span class="cx">         var result = {changeType: null, status: 'failed', label: 'Failed', fullLabel: 'Failed', isStatisticallySignificant: false};
</span><span class="cx"> 
</span><del>-        var hasCompleted = this.hasCompleted();
</del><ins>+        var hasCompleted = this.hasFinished();
</ins><span class="cx">         if (!hasCompleted) {
</span><span class="cx">             if (this.hasStarted()) {
</span><span class="cx">                 result.status = 'running';
</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 (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/analysis-task-page.js        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -61,10 +61,16 @@
</span><span class="cx">         this.content().querySelector('.change-type-form').onsubmit = this._updateChangeType.bind(this);
</span><span class="cx">         this._taskStatusControl = this.content().querySelector('.change-type-form select');
</span><span class="cx"> 
</span><del>-        this.content().querySelector('.associate-bug-form').onsubmit = this._associateBug.bind(this);
-        this._bugTrackerControl = this.content().querySelector('.bug-tracker-control');
-        this._bugNumberControl = this.content().querySelector('.bug-number-control');
</del><ins>+        this._bugList = this.content().querySelector('.associated-bugs mutable-list-view').component();
+        this._bugList.setKindList(BugTracker.all());
+        this._bugList.setAddCallback(this._associateBug.bind(this));
</ins><span class="cx"> 
</span><ins>+        this._causeList = this.content().querySelector('.cause-list mutable-list-view').component();
+        this._causeList.setAddCallback(this._associateCommit.bind(this, 'cause'));
+
+        this._fixList = this.content().querySelector('.fix-list mutable-list-view').component();
+        this._fixList.setAddCallback(this._associateCommit.bind(this, 'fix'));
+
</ins><span class="cx">         this._newTestGroupFormForChart = this.content().querySelector('.overview-chart customizable-test-group-form').component();
</span><span class="cx">         this._newTestGroupFormForChart.setStartCallback(this._createNewTestGroupFromChart.bind(this));
</span><span class="cx"> 
</span><span class="lines">@@ -229,25 +235,33 @@
</span><span class="cx">             this.renderReplace(anchor, metric.fullName() + ' on ' + platform.label());
</span><span class="cx">             anchor.href = this.router().url('charts', ChartsPage.createStateForAnalysisTask(this._task));
</span><span class="cx"> 
</span><del>-            var bugs = [];
-            for (var bug of this._task.bugs()) {
-                bugs.push(element('li', [
-                    bug.bugTracker().label() + ' ',
-                    link(bug.label(), bug.title(), bug.url()),
-                    ' ',
-                    link(new CloseButton, 'Disassociate this bug', this._disassociateBug.bind(this, bug))]));
-            }
-            this.renderReplace(this.content().querySelector('.associated-bugs'), bugs);
</del><ins>+            var self = this;
+            this._bugList.setList(this._task.bugs().map(function (bug) {
+                return new MutableListItem(bug.bugTracker(), bug.label(), bug.title(), bug.url(),
+                    'Disassociate this bug', self._disassociateBug.bind(self, bug));
+            }));
</ins><span class="cx"> 
</span><ins>+            this._causeList.setList(this._task.causes().map(this._makeCommitListItem.bind(this)));
+            this._fixList.setList(this._task.fixes().map(this._makeCommitListItem.bind(this)));
+
</ins><span class="cx">             this._taskStatusControl.value = this._task.changeType() || 'unconfirmed';
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        var element = ComponentBase.createElement;
-        this.renderReplace(this._bugTrackerControl,
-            BugTracker.all().map(function (tracker) {
-                return element('option', {value: tracker.id()}, tracker.label());
-            }));
</del><ins>+        var repositoryList;
+        if (this._startPoint) {
+            var rootSet = this._startPoint.rootSet();
+            repositoryList = Repository.sortByNamePreferringOnesWithURL(rootSet.repositories());
+        } else
+            repositoryList = Repository.sortByNamePreferringOnesWithURL(Repository.all());
</ins><span class="cx"> 
</span><ins>+        this._bugList.render();
+
+        this._causeList.setKindList(repositoryList);
+        this._causeList.render();
+
+        this._fixList.setKindList(repositoryList);
+        this._fixList.render();
+
</ins><span class="cx">         this.content().querySelector('.analysis-task-status').style.display = this._task ? null : 'none';
</span><span class="cx">         this.content().querySelector('.overview-chart').style.display = this._task ? null : 'none';
</span><span class="cx">         this.content().querySelector('.test-group-view').style.display = this._task ? null : 'none';
</span><span class="lines">@@ -290,6 +304,12 @@
</span><span class="cx">         Instrumentation.endMeasuringTime('AnalysisTaskPage', 'render');
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _makeCommitListItem(commit)
+    {
+        return new MutableListItem(commit.repository(), commit.label(), commit.title(), commit.url(),
+            'Disassociate this commit', this._dissociateCommit.bind(this, commit));
+    }
+
</ins><span class="cx">     _renderTestGroupList()
</span><span class="cx">     {
</span><span class="cx">         var element = ComponentBase.createElement;
</span><span class="lines">@@ -446,15 +466,11 @@
</span><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    _associateBug(event)
</del><ins>+    _associateBug(tracker, bugNumber)
</ins><span class="cx">     {
</span><del>-        event.preventDefault();
-        console.assert(this._task);
</del><ins>+        console.assert(tracker instanceof BugTracker);
+        bugNumber = parseInt(bugNumber);
</ins><span class="cx"> 
</span><del>-        var tracker = BugTracker.findById(this._bugTrackerControl.value);
-        console.assert(tracker);
-        var bugNumber = parseInt(this._bugNumberControl.value);
-
</del><span class="cx">         var render = this.render.bind(this);
</span><span class="cx">         return this._task.associateBug(tracker, bugNumber).then(render, function (error) {
</span><span class="cx">             render();
</span><span class="lines">@@ -471,6 +487,24 @@
</span><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _associateCommit(kind, repository, revision)
+    {
+        var render = this.render.bind(this);
+        return this._task.associateCommit(kind, repository, revision).then(render, function (error) {
+            render();
+            alert('Failed to associate the commit: ' + error);
+        });
+    }
+
+    _dissociateCommit(commit)
+    {
+        var render = this.render.bind(this);
+        return this._task.dissociateCommit(commit).then(render, function (error) {
+            render();
+            alert('Failed to disassociate the commit: ' + error);
+        });
+    }
+
</ins><span class="cx">     _retryCurrentTestGroup(repetitionCount)
</span><span class="cx">     {
</span><span class="cx">         console.assert(this._currentTestGroup);
</span><span class="lines">@@ -595,15 +629,16 @@
</span><span class="cx">                             &lt;button type=&quot;submit&quot;&gt;Save&lt;/button&gt;
</span><span class="cx">                         &lt;/form&gt;
</span><span class="cx">                     &lt;/section&gt;
</span><del>-                    &lt;section&gt;
</del><ins>+                    &lt;section class=&quot;associated-bugs&quot;&gt;
</ins><span class="cx">                         &lt;h3&gt;Associated Bugs&lt;/h3&gt;
</span><del>-                        &lt;ul class=&quot;associated-bugs&quot;&gt;&lt;/ul&gt;
-                        &lt;form class=&quot;associate-bug-form&quot;&gt;
-                            &lt;select class=&quot;bug-tracker-control&quot;&gt;&lt;/select&gt;
-                            &lt;input type=&quot;number&quot; class=&quot;bug-number-control&quot;&gt;
-                            &lt;button type=&quot;submit&quot;&gt;Add&lt;/button&gt;
-                        &lt;/form&gt;
</del><ins>+                        &lt;mutable-list-view&gt;&lt;/mutable-list-view&gt;
</ins><span class="cx">                     &lt;/section&gt;
</span><ins>+                    &lt;section class=&quot;cause-fix&quot;&gt;
+                        &lt;h3&gt;Caused by&lt;/h3&gt;
+                        &lt;span class=&quot;cause-list&quot;&gt;&lt;mutable-list-view&gt;&lt;/mutable-list-view&gt;&lt;/span&gt;
+                        &lt;h3&gt;Fixed by&lt;/h3&gt;
+                        &lt;span class=&quot;fix-list&quot;&gt;&lt;mutable-list-view&gt;&lt;/mutable-list-view&gt;&lt;/span&gt;
+                    &lt;/section&gt;
</ins><span class="cx">                     &lt;section class=&quot;related-tasks&quot;&gt;
</span><span class="cx">                         &lt;h3&gt;Related Tasks&lt;/h3&gt;
</span><span class="cx">                         &lt;ul class=&quot;related-tasks-list&quot;&gt;&lt;/ul&gt;
</span><span class="lines">@@ -680,18 +715,20 @@
</span><span class="cx"> 
</span><span class="cx">             .analysis-task-status &gt; section {
</span><span class="cx">                 flex-grow: 1;
</span><ins>+                flex-shrink: 0;
</ins><span class="cx">                 border-left: solid 1px #eee;
</span><span class="cx">                 padding-left: 1rem;
</span><ins>+                padding-right: 1rem;
</ins><span class="cx">             }
</span><span class="cx"> 
</span><ins>+            .analysis-task-status &gt; section.related-tasks {
+                flex-shrink: 1;
+            }
+
</ins><span class="cx">             .analysis-task-status &gt; section:first-child {
</span><span class="cx">                 border-left: none;
</span><span class="cx">             }
</span><span class="cx"> 
</span><del>-            .associated-bugs:not(:empty) {
-                margin-bottom: 1rem;
-            }
-
</del><span class="cx">             .analysis-task-status h3 {
</span><span class="cx">                 font-size: 1rem;
</span><span class="cx">                 font-weight: inherit;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3remotejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/remote.js (198264 => 198265)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/remote.js        2016-03-16 06:48:53 UTC (rev 198264)
+++ trunk/Websites/perf.webkit.org/public/v3/remote.js        2016-03-16 07:02:28 UTC (rev 198265)
</span><span class="lines">@@ -19,6 +19,7 @@
</span><span class="cx">                 var parsed = JSON.parse(xhr.responseText);
</span><span class="cx">                 resolve(parsed);
</span><span class="cx">             } catch (error) {
</span><ins>+                console.error(xhr.responseText);
</ins><span class="cx">                 reject(xhr.status + ', ' + error);
</span><span class="cx">             }
</span><span class="cx">         };
</span></span></pre>
</div>
</div>

</body>
</html>