<!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>[176422] 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/176422">176422</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2014-11-20 15:25:07 -0800 (Thu, 20 Nov 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>New perf dashboard should provide UI to create a new analysis task
https://bugs.webkit.org/show_bug.cgi?id=138910

Reviewed by Benjamin Poulain.

This patch reverts some parts of <a href="http://trac.webkit.org/projects/webkit/changeset/175006">r175006</a> and re-introduces bugs associated with analysis tasks.
I'll add UI to show and edit bug numbers associated with an analysis task in a follow up patch.

With this patch, we can create a new analysis task by selection a range of points and opening
&quot;analysis pane&quot; (renamed from &quot;bugs pane&quot;). Each analysis task created is represented by a yellow bar
in the chart hyperlinked to the analysis task.

* init-database.sql: Redefined the bugs to be associated with an analysis task instead of a test run.

* public/api/analysis-tasks.php: Added the support for querying analysis tasks for a specific metric
on a specific platform. Also retrieve and return all bugs associated with analysis tasks.
(main):
(fetch_and_push_bugs_to_tasks): Added. Fetches all bugs associated with an array of analysis tasks
and adds the associated bugs to each task in the array.
(format_task):

* public/api/runs.php: Reverted changes made in <a href="http://trac.webkit.org/projects/webkit/changeset/175006">r175006</a>.
(fetch_runs_for_config):
(format_run): 

* public/api/test-groups.php:
(fetch_test_groups_for_task): Use the newly added Database::select_rows.

* public/include/db.php:
(Database::select_first_or_last_row):
(Database::select_rows): Extracted from select_first_or_last_row.

* public/v2/analysis.js:
(App.AnalysisTask): Added &quot;bugs&quot; property.
(App.Bug): Added now that bugs are regular data store objects.

* public/v2/app.js:
(App.Pane._fetch): Calls this.fetchAnalyticRanges to fetch analysis tasks as well as test runs.
(App.Pane.fetchAnalyticRanges): Added. Fetches analysis tasks for the current metric on the current
platform that are associated with a specific range of runs.
(App.PaneController.actions.toggleBugsPane): Updated per showingBugsPane to showingAnalysisPane rename.
(App.PaneController.actions.associateBug): Deleted.
(App.PaneController.actions.createAnalysisTask): Replaced the pre-condition checks with assertions as
this action should never be triggered when the pre-condition is not met. Also re-fetch analysis tasks
once we've created one.
(App.PaneController.toggleSearchPane): Updated per showingBugsPane to showingAnalysisPane rename.
(App.PaneController._detailsChanged): Ditto. Removed selectedSinglePoint since it's no longer used.
(App.PaneController._showDetails): Call _updateCanAnalyze to update the status of &quot;Analyze&quot; button.
(App.PaneController._updateBugs): Deleted.
(App.PaneController._updateMarkedPoints): Deleted.
(App.PaneController._updateCanAnalyze): Added. Disables the button to create an analysis task when
the name is missing or when at most one point is selected.
        
(App.InteractiveChartComponent._constructGraphIfPossible): Update the locations of range rects.
(App.InteractiveChartComponent._relayoutDataAndAxes): Ditto.
(App.InteractiveChartComponent._mousePointInGraph): Don't return a point unless the mouse cursor is
on our svg element to avoid locking the current item when a bar shown for an analysis task is clicked.
(App.InteractiveChartComponent._rangesChanged): Added. Creates an array of objects representing
clickable bars for analysis tasks.
(App.InteractiveChartComponent._updateRangeBarRects): Computes the inline style used by each clickable
bar for analysis tasks to place them at the right location.
(App.InteractiveChartComponent.actions.openRange): Added. Forwards the action to the parent controller.

* public/v2/chart-pane.css:
(.chart .extent): Use the same color as the vertical indicator in the highlight behind the selection.
(.chart .rangeBar): Added.

* public/v2/data.js:
(TimeSeries.prototype.nextPoint): Added. Used by _rangesChanged.

* public/v2/index.html: Renamed &quot;bugs pane&quot; to &quot;analysis pane&quot; and removed the UI to associate bugs.
This ability will be reinstated in a follow up patch. Also added a container div and spans for analysis
task bars in the interactive chart component.</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="#trunkWebsitesperfwebkitorgpublicapirunsphp">trunk/Websites/perf.webkit.org/public/api/runs.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicapitestgroupsphp">trunk/Websites/perf.webkit.org/public/api/test-groups.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicincludedbphp">trunk/Websites/perf.webkit.org/public/include/db.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2analysisjs">trunk/Websites/perf.webkit.org/public/v2/analysis.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2appjs">trunk/Websites/perf.webkit.org/public/v2/app.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2chartpanecss">trunk/Websites/perf.webkit.org/public/v2/chart-pane.css</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2datajs">trunk/Websites/perf.webkit.org/public/v2/data.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv2indexhtml">trunk/Websites/perf.webkit.org/public/v2/index.html</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 (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -1,3 +1,79 @@
</span><ins>+2014-11-20  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
+
+        New perf dashboard should provide UI to create a new analysis task
+        https://bugs.webkit.org/show_bug.cgi?id=138910
+
+        Reviewed by Benjamin Poulain.
+
+        This patch reverts some parts of r175006 and re-introduces bugs associated with analysis tasks.
+        I'll add UI to show and edit bug numbers associated with an analysis task in a follow up patch.
+
+        With this patch, we can create a new analysis task by selection a range of points and opening
+        &quot;analysis pane&quot; (renamed from &quot;bugs pane&quot;). Each analysis task created is represented by a yellow bar
+        in the chart hyperlinked to the analysis task.
+
+        * init-database.sql: Redefined the bugs to be associated with an analysis task instead of a test run.
+
+        * public/api/analysis-tasks.php: Added the support for querying analysis tasks for a specific metric
+        on a specific platform. Also retrieve and return all bugs associated with analysis tasks.
+        (main):
+        (fetch_and_push_bugs_to_tasks): Added. Fetches all bugs associated with an array of analysis tasks
+        and adds the associated bugs to each task in the array.
+        (format_task):
+
+        * public/api/runs.php: Reverted changes made in r175006.
+        (fetch_runs_for_config):
+        (format_run): 
+
+        * public/api/test-groups.php:
+        (fetch_test_groups_for_task): Use the newly added Database::select_rows.
+
+        * public/include/db.php:
+        (Database::select_first_or_last_row):
+        (Database::select_rows): Extracted from select_first_or_last_row.
+
+        * public/v2/analysis.js:
+        (App.AnalysisTask): Added &quot;bugs&quot; property.
+        (App.Bug): Added now that bugs are regular data store objects.
+
+        * public/v2/app.js:
+        (App.Pane._fetch): Calls this.fetchAnalyticRanges to fetch analysis tasks as well as test runs.
+        (App.Pane.fetchAnalyticRanges): Added. Fetches analysis tasks for the current metric on the current
+        platform that are associated with a specific range of runs.
+        (App.PaneController.actions.toggleBugsPane): Updated per showingBugsPane to showingAnalysisPane rename.
+        (App.PaneController.actions.associateBug): Deleted.
+        (App.PaneController.actions.createAnalysisTask): Replaced the pre-condition checks with assertions as
+        this action should never be triggered when the pre-condition is not met. Also re-fetch analysis tasks
+        once we've created one.
+        (App.PaneController.toggleSearchPane): Updated per showingBugsPane to showingAnalysisPane rename.
+        (App.PaneController._detailsChanged): Ditto. Removed selectedSinglePoint since it's no longer used.
+        (App.PaneController._showDetails): Call _updateCanAnalyze to update the status of &quot;Analyze&quot; button.
+        (App.PaneController._updateBugs): Deleted.
+        (App.PaneController._updateMarkedPoints): Deleted.
+        (App.PaneController._updateCanAnalyze): Added. Disables the button to create an analysis task when
+        the name is missing or when at most one point is selected.
+        
+        (App.InteractiveChartComponent._constructGraphIfPossible): Update the locations of range rects.
+        (App.InteractiveChartComponent._relayoutDataAndAxes): Ditto.
+        (App.InteractiveChartComponent._mousePointInGraph): Don't return a point unless the mouse cursor is
+        on our svg element to avoid locking the current item when a bar shown for an analysis task is clicked.
+        (App.InteractiveChartComponent._rangesChanged): Added. Creates an array of objects representing
+        clickable bars for analysis tasks.
+        (App.InteractiveChartComponent._updateRangeBarRects): Computes the inline style used by each clickable
+        bar for analysis tasks to place them at the right location.
+        (App.InteractiveChartComponent.actions.openRange): Added. Forwards the action to the parent controller.
+
+        * public/v2/chart-pane.css:
+        (.chart .extent): Use the same color as the vertical indicator in the highlight behind the selection.
+        (.chart .rangeBar): Added.
+
+        * public/v2/data.js:
+        (TimeSeries.prototype.nextPoint): Added. Used by _rangesChanged.
+
+        * public/v2/index.html: Renamed &quot;bugs pane&quot; to &quot;analysis pane&quot; and removed the UI to associate bugs.
+        This ability will be reinstated in a follow up patch. Also added a container div and spans for analysis
+        task bars in the interactive chart component.
+
</ins><span class="cx"> 2014-11-19  Ryosuke Niwa  &lt;rniwa@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         Fix typos in r176203.
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorginitdatabasesql"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/init-database.sql (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/init-database.sql        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/init-database.sql        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -130,15 +130,6 @@
</span><span class="cx">     report_failure varchar(64),
</span><span class="cx">     report_failure_details text);
</span><span class="cx"> 
</span><del>-CREATE TABLE bugs (
-    bug_id serial PRIMARY KEY,
-    bug_run integer REFERENCES test_runs NOT NULL,
-    bug_tracker integer REFERENCES bug_trackers NOT NULL,
-    bug_number integer NOT NULL,
-    CONSTRAINT bug_tracker_and_run_must_be_unique UNIQUE(bug_tracker, bug_run));
-CREATE INDEX bugs_tracker_number_index ON bugs(bug_tracker, bug_number);
-CREATE INDEX bugs_run_index ON bugs(bug_run);
-
</del><span class="cx"> CREATE TABLE analysis_tasks (
</span><span class="cx">     task_id serial PRIMARY KEY,
</span><span class="cx">     task_name varchar(256) NOT NULL,
</span><span class="lines">@@ -148,8 +139,17 @@
</span><span class="cx">     task_metric integer REFERENCES test_metrics NOT NULL,
</span><span class="cx">     task_start_run integer REFERENCES test_runs,
</span><span class="cx">     task_end_run integer REFERENCES test_runs,
</span><del>-    CONSTRAINT analysis_task_should_be_unique_for_range UNIQUE(task_start_run, task_end_run));
</del><ins>+    CONSTRAINT analysis_task_should_be_unique_for_range UNIQUE(task_start_run, task_end_run)
+    CONSTRAINT analysis_task_should_not_be_associated_with_single_run
+        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)));
</ins><span class="cx"> 
</span><ins>+CREATE TABLE bugs (
+    bug_id serial PRIMARY KEY,
+    bug_task integer REFERENCES analysis_tasks NOT NULL,
+    bug_tracker integer REFERENCES bug_trackers NOT NULL,
+    bug_number integer NOT NULL,
+    CONSTRAINT bug_task_and_tracker_must_be_unique UNIQUE(bug_task, bug_tracker));
+
</ins><span class="cx"> CREATE TABLE analysis_test_groups (
</span><span class="cx">     testgroup_id serial PRIMARY KEY,
</span><span class="cx">     testgroup_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 (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -17,15 +17,49 @@
</span><span class="cx">             exit_with_error('TaskNotFound', array('id' =&gt; $task_id));
</span><span class="cx">         $tasks = array($task);
</span><span class="cx">     } else {
</span><del>-        // FIXME: Limit the number of tasks we fetch.
-        $tasks = array_reverse($db-&gt;fetch_table('analysis_tasks', 'task_created_at'));
</del><ins>+        $metric_id = array_get($_GET, 'metric');
+        $platform_id = array_get($_GET, 'platform');
+        if (!!$metric_id != !!$platform_id)
+            exit_with_error('InvalidArguments', array('metricId' =&gt; $metric_id, 'platformId' =&gt; $platform_id));
+
+        if ($metric_id)
+            $tasks = $db-&gt;select_rows('analysis_tasks', 'task', array('platform' =&gt; $platform_id, 'metric' =&gt; $metric_id));
+        else {
+            // FIXME: Limit the number of tasks we fetch.
+            $tasks = array_reverse($db-&gt;fetch_table('analysis_tasks', 'task_created_at'));
+        }
+
</ins><span class="cx">         if (!is_array($tasks))
</span><span class="cx">             exit_with_error('FailedToFetchTasks');
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    exit_with_success(array('analysisTasks' =&gt; array_map(&quot;format_task&quot;, $tasks)));
</del><ins>+    $tasks = array_map(&quot;format_task&quot;, $tasks);
+    $bugs = fetch_and_push_bugs_to_tasks($db, $tasks);
+
+    exit_with_success(array('analysisTasks' =&gt; $tasks, 'bugs' =&gt; $bugs));
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+function fetch_and_push_bugs_to_tasks($db, &amp;$tasks) {
+    $task_ids = array();
+    $task_by_id = array();
+    foreach ($tasks as &amp;$task) {
+        array_push($task_ids, $task['id']);
+        $task_by_id[$task['id']] = &amp;$task;
+    }
+
+    $bugs = $db-&gt;query_and_fetch_all('SELECT bug_id AS &quot;id&quot;, bug_task AS &quot;task&quot;, bug_tracker AS &quot;bugTracker&quot;, bug_number AS &quot;number&quot;
+        FROM bugs WHERE bug_task = ANY ($1)', array('{' . implode(', ', $task_ids) . '}'));
+    if (!is_array($bugs))
+        exit_with_error('FailedToFetchBugs');
+
+    foreach ($bugs as $bug) {
+        $associated_task = &amp;$task_by_id[$bug['task']];
+        array_push($associated_task['bugs'], $bug['id']);
+    }
+
+    return $bugs;
+}
+
</ins><span class="cx"> date_default_timezone_set('UTC');
</span><span class="cx"> function format_task($task_row) {
</span><span class="cx">     return array(
</span><span class="lines">@@ -37,6 +71,7 @@
</span><span class="cx">         'metric' =&gt; $task_row['task_metric'],
</span><span class="cx">         'startRun' =&gt; $task_row['task_start_run'],
</span><span class="cx">         'endRun' =&gt; $task_row['task_end_run'],
</span><ins>+        'bugs' =&gt; array(),
</ins><span class="cx">     );
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicapirunsphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/api/runs.php (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/api/runs.php        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/api/runs.php        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -31,13 +31,11 @@
</span><span class="cx"> function fetch_runs_for_config($db, $config) {
</span><span class="cx">     $raw_runs = $db-&gt;query_and_fetch_all('
</span><span class="cx">         SELECT test_runs.*, builds.*, array_agg((commit_repository, commit_revision, commit_time)) AS revisions
</span><del>-            FROM builds LEFT OUTER JOIN build_commits ON commit_build = build_id
-                LEFT OUTER JOIN commits ON build_commit = commit_id,
-                (SELECT test_runs.*, array_agg((bug_tracker, bug_number)) AS bugs
-                    FROM test_runs LEFT OUTER JOIN bugs ON bug_run = run_id WHERE run_config = $1 GROUP BY run_id) as test_runs
-                WHERE run_build = build_id
-                GROUP BY run_id, run_config, run_build, run_mean_cache, run_iteration_count_cache,
-                    run_sum_cache, run_square_sum_cache, bugs, build_id', array($config['config_id']));
</del><ins>+            FROM builds
+                LEFT OUTER JOIN build_commits ON commit_build = build_id
+                LEFT OUTER JOIN commits ON build_commit = commit_id, test_runs
+            WHERE run_build = build_id AND run_config = $1
+            GROUP BY build_id, run_id', array($config['config_id']));
</ins><span class="cx"> 
</span><span class="cx">     $formatted_runs = array();
</span><span class="cx">     if (!$raw_runs)
</span><span class="lines">@@ -66,19 +64,6 @@
</span><span class="cx">     return $revisions;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-function parse_bugs_array($postgres_array) {
-    // e.g. {&quot;(1 /* Bugzilla */, 12345)&quot;,&quot;(2 /* Radar */, 67890)&quot;}
-    $outer_array = json_decode('[' . trim($postgres_array, '{}') . ']');
-    $bugs = array();
-    foreach ($outer_array as $item) {
-        $raw_data = explode(',', trim($item, '()'));
-        if (!$raw_data[0])
-            continue;
-        $bugs[trim($raw_data[0], '&quot;')] = trim($raw_data[1], '&quot;');
-    }
-    return $bugs;
-}
-
</del><span class="cx"> function format_run($run) {
</span><span class="cx">     return array(
</span><span class="cx">         'id' =&gt; intval($run['run_id']),
</span><span class="lines">@@ -87,7 +72,6 @@
</span><span class="cx">         'sum' =&gt; floatval($run['run_sum_cache']),
</span><span class="cx">         'squareSum' =&gt; floatval($run['run_square_sum_cache']),
</span><span class="cx">         'revisions' =&gt; parse_revisions_array($run['revisions']),
</span><del>-        'bugs' =&gt; parse_bugs_array($run['bugs']),
</del><span class="cx">         'buildTime' =&gt; strtotime($run['build_time']) * 1000,
</span><span class="cx">         'buildNumber' =&gt; intval($run['build_number']),
</span><span class="cx">         'builder' =&gt; $run['build_builder']);
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicapitestgroupsphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/api/test-groups.php (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/api/test-groups.php        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/api/test-groups.php        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -43,8 +43,7 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function fetch_test_groups_for_task($db, $task_id) {
</span><del>-    return $db-&gt;query_and_fetch_all('SELECT * FROM analysis_test_groups WHERE testgroup_task = $1
-        ORDER BY testgroup_created_at', array($task_id));
</del><ins>+    return $db-&gt;select_rows('analysis_test_groups', 'testgroup', array('task' =&gt; $task_id));
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> function fetch_build_requests_for_task($db, $task_id) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicincludedbphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/include/db.php (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/include/db.php        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/include/db.php        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -158,6 +158,13 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     private function select_first_or_last_row($table, $prefix, $params, $order_by, $descending_order) {
</span><ins>+        $rows = $this-&gt;select_rows($table, $prefix, $params, $order_by, $descending_order, 0, 1);
+        return $rows ? $rows[0] : NULL;
+    }
+
+    function select_rows($table, $prefix, $params,
+        $order_by = NULL, $descending_order = FALSE, $offset = NULL, $limit = NULL) {
+
</ins><span class="cx">         $placeholders = array();
</span><span class="cx">         $values = array();
</span><span class="cx">         $column_names = $this-&gt;prefixed_column_names($this-&gt;prepare_params($params, $placeholders, $values), $prefix);
</span><span class="lines">@@ -169,9 +176,12 @@
</span><span class="cx">             if ($descending_order)
</span><span class="cx">                 $query .= ' DESC';
</span><span class="cx">         }
</span><del>-        $rows = $this-&gt;query_and_fetch_all($query . ' LIMIT 1', $values);
</del><ins>+        if ($offset !== NULL)
+            $query .= ' OFFSET ' . intval($offset);
+        if ($limit !== NULL)
+            $query .= ' LIMIT ' . intval($limit);
</ins><span class="cx"> 
</span><del>-        return $rows ? $rows[0] : NULL;
</del><ins>+        return $this-&gt;query_and_fetch_all($query, $values);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     function query_and_get_affected_rows($query, $params = array()) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2analysisjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/analysis.js (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/analysis.js        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/v2/analysis.js        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -5,11 +5,19 @@
</span><span class="cx">     metric: DS.belongsTo('metric'),
</span><span class="cx">     startRun: DS.attr('number'),
</span><span class="cx">     endRun: DS.attr('number'),
</span><ins>+    bugs: DS.hasMany('bugs'),
</ins><span class="cx">     testGroups: function () {
</span><span class="cx">         return this.store.find('testGroup', {task: this.get('id')});
</span><span class="cx">     }.property(),
</span><span class="cx"> });
</span><span class="cx"> 
</span><ins>+App.Bug = App.NameLabelModel.extend({
+    task: DS.belongsTo('AnalysisTask'),
+    bugTracker: DS.belongsTo('BugTracker'),
+    createdAt: DS.attr('date'),
+    number: DS.attr('number'),
+});
+
</ins><span class="cx"> // FIXME: Use DS.RESTAdapter instead.
</span><span class="cx"> App.AnalysisTask.create = function (name, startMeasurement, endMeasurement)
</span><span class="cx"> {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2appjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/app.js (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/app.js        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/v2/app.js        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -358,8 +358,21 @@
</span><span class="cx">                 else
</span><span class="cx">                     self.set('failure', 'An internal error');
</span><span class="cx">             });
</span><ins>+
+            this.fetchAnalyticRanges();
</ins><span class="cx">         }
</span><span class="cx">     }.observes('platformId', 'metricId').on('init'),
</span><ins>+    fetchAnalyticRanges: function ()
+    {
+        var platformId = this.get('platformId');
+        var metricId = this.get('metricId');
+        var self = this;
+        this.get('store')
+            .find('analysisTask', {platform: platformId, metric: metricId})
+            .then(function (tasks) {
+                self.set('analyticRanges', tasks.filter(function (task) { return task.get('startRun') &amp;&amp; task.get('endRun'); }));
+            });
+    },
</ins><span class="cx">     _isValidId: function (id)
</span><span class="cx">     {
</span><span class="cx">         if (typeof(id) == &quot;number&quot;)
</span><span class="lines">@@ -657,34 +670,23 @@
</span><span class="cx">         },
</span><span class="cx">         toggleBugsPane: function ()
</span><span class="cx">         {
</span><del>-            if (this.toggleProperty('showingBugsPane'))
</del><ins>+            if (this.toggleProperty('showingAnalysisPane'))
</ins><span class="cx">                 this.set('showingSearchPane', false);
</span><span class="cx">         },
</span><del>-        associateBug: function (bugTracker, bugNumber)
-        {
-            var point = this.get('selectedSinglePoint');
-            if (!point)
-                return;
-            var self = this;
-            point.measurement.associateBug(bugTracker.get('id'), bugNumber).then(function () {
-                self._updateBugs();
-                self._updateMarkedPoints();
-            }, function (error) {
-                alert(error);
-            });
-        },
</del><span class="cx">         createAnalysisTask: function ()
</span><span class="cx">         {
</span><span class="cx">             var name = this.get('newAnalysisTaskName');
</span><span class="cx">             var points = this._selectedPoints;
</span><del>-            if (!name || !points || points.length &lt; 2)
-                return;
</del><ins>+            Ember.assert('The analysis name should not be empty', name);
+            Ember.assert('There should be at least two points in the range', points &amp;&amp; points.length &gt;= 2);
</ins><span class="cx"> 
</span><span class="cx">             var newWindow = window.open();
</span><ins>+            var self = this;
</ins><span class="cx">             App.AnalysisTask.create(name, points[0].measurement, points[points.length - 1].measurement).then(function (data) {
</span><span class="cx">                 // FIXME: Update the UI to show the new analysis task.
</span><span class="cx">                 var url = App.Router.router.generate('analysisTask', data['taskId']);
</span><span class="cx">                 newWindow.location.href = '#' + url;
</span><ins>+                self.get('model').fetchAnalyticRanges();
</ins><span class="cx">             }, function (error) {
</span><span class="cx">                 newWindow.close();
</span><span class="cx">                 if (error === 'DuplicateAnalysisTask') {
</span><span class="lines">@@ -701,7 +703,7 @@
</span><span class="cx">             if (!model.get('commitSearchRepository'))
</span><span class="cx">                 model.set('commitSearchRepository', App.Manifest.repositoriesWithReportedCommits[0]);
</span><span class="cx">             if (this.toggleProperty('showingSearchPane'))
</span><del>-                this.set('showingBugsPane', false);
</del><ins>+                this.set('showingAnalysisPane', false);
</ins><span class="cx">         },
</span><span class="cx">         searchCommit: function () {
</span><span class="cx">             var model = this.get('model');
</span><span class="lines">@@ -733,8 +735,7 @@
</span><span class="cx">     },
</span><span class="cx">     _detailsChanged: function ()
</span><span class="cx">     {
</span><del>-        this.set('showingBugsPane', false);
-        this.set('selectedSinglePoint', !this._hasRange &amp;&amp; this._selectedPoints ? this._selectedPoints[0] : null);
</del><ins>+        this.set('showingAnalysisPane', false);
</ins><span class="cx">     }.observes('details'),
</span><span class="cx">     _overviewSelectionChanged: function ()
</span><span class="cx">     {
</span><span class="lines">@@ -812,58 +813,13 @@
</span><span class="cx">             buildTime: currentMeasurement.formattedBuildTime(),
</span><span class="cx">             revisions: revisions,
</span><span class="cx">         }));
</span><del>-        this._updateBugs();
</del><ins>+        this._updateCanAnalyze();
</ins><span class="cx">     },
</span><del>-    _updateBugs: function ()
</del><ins>+    _updateCanAnalyze: function ()
</ins><span class="cx">     {
</span><del>-        if (!this._selectedPoints)
-            return;
-
-        var bugTrackers = App.Manifest.get('bugTrackers');
-        var trackerToBugNumbers = {};
-        bugTrackers.forEach(function (tracker) { trackerToBugNumbers[tracker.get('id')] = new Array(); });
-
-        var points = this._hasRange ? this._selectedPoints : [this._selectedPoints[1]];
-        points.map(function (point) {
-            var bugs = point.measurement.bugs();
-            bugTrackers.forEach(function (tracker) {
-                var bugNumber = bugs[tracker.get('id')];
-                if (bugNumber)
-                    trackerToBugNumbers[tracker.get('id')].push(bugNumber);
-            });
-        });
-
-        this.set('details.bugTrackers', App.Manifest.get('bugTrackers').map(function (tracker) {
-            var bugNumbers = trackerToBugNumbers[tracker.get('id')];
-            return Ember.ObjectProxy.create({
-                content: tracker,
-                bugs: bugNumbers.map(function (bugNumber) {
-                    return {
-                        bugNumber: bugNumber,
-                        bugUrl: bugNumber &amp;&amp; tracker.get('bugUrl') ? tracker.get('bugUrl').replace(/\$number/g, bugNumber) : null
-                    };
-                }),
-                editedBugNumber: this._hasRange ? null : bugNumbers[0],
-            }); // FIXME: Create urls for new bugs.
-        }));
-    },
-    _updateMarkedPoints: function ()
-    {
-        var chartData = this.get('chartData');
-        if (!chartData || !chartData.current) {
-            this.set('markedPoints', {});
-            return;
-        }
-
-        var series = chartData.current.timeSeriesByCommitTime().series();
-        var markedPoints = {};
-        for (var i = 0; i &lt; series.length; i++) {
-            var measurement = series[i].measurement;
-            if (measurement.hasBugs())
-                markedPoints[measurement.id()] = true;
-        }
-        this.set('markedPoints', markedPoints);
-    }.observes('chartData'),
</del><ins>+        var points = this._selectedPoints;
+        this.set('cannotAnalyze', !this.get('newAnalysisTaskName') || !this._hasRange || !points || points.length &lt; 2);
+    }.observes('newAnalysisTaskName'),
</ins><span class="cx"> });
</span><span class="cx"> 
</span><span class="cx"> App.InteractiveChartComponent = Ember.Component.extend({
</span><span class="lines">@@ -1047,6 +1003,8 @@
</span><span class="cx">         setTimeout(this._selectedItemChanged.bind(this), 0);
</span><span class="cx"> 
</span><span class="cx">         this._needsConstruction = false;
</span><ins>+
+        this._rangesChanged();
</ins><span class="cx">     },
</span><span class="cx">     _updateDomain: function ()
</span><span class="cx">     {
</span><span class="lines">@@ -1142,6 +1100,7 @@
</span><span class="cx">         });
</span><span class="cx">         this._updateMarkedDots();
</span><span class="cx">         this._updateHighlightPositions();
</span><ins>+        this._updateRangeBarRects();
</ins><span class="cx"> 
</span><span class="cx">         if (this._brush) {
</span><span class="cx">             if (selection)
</span><span class="lines">@@ -1163,7 +1122,7 @@
</span><span class="cx">             this._yAxisUnitContainer.remove();
</span><span class="cx">         this._yAxisUnitContainer = this._yAxisLabels.append(&quot;text&quot;)
</span><span class="cx">             .attr(&quot;x&quot;, 0.5 * this._rem)
</span><del>-            .attr(&quot;y&quot;, this._rem)
</del><ins>+            .attr(&quot;y&quot;, 0.2 * this._rem)
</ins><span class="cx">             .attr(&quot;dy&quot;, 0.8 * this._rem)
</span><span class="cx">             .style(&quot;text-anchor&quot;, &quot;start&quot;)
</span><span class="cx">             .style(&quot;z-index&quot;, &quot;100&quot;)
</span><span class="lines">@@ -1350,7 +1309,7 @@
</span><span class="cx">     _mousePointInGraph: function (event)
</span><span class="cx">     {
</span><span class="cx">         var offset = $(this.get('element')).offset();
</span><del>-        if (!offset)
</del><ins>+        if (!offset || !$(event.target).closest('svg').length)
</ins><span class="cx">             return null;
</span><span class="cx"> 
</span><span class="cx">         var point = {
</span><span class="lines">@@ -1483,6 +1442,99 @@
</span><span class="cx">         this._updateHighlightPositions();
</span><span class="cx"> 
</span><span class="cx">     }.observes('highlightedItems'),
</span><ins>+    _rangesChanged: function ()
+    {
+        if (!this._currentTimeSeries)
+            return;
+
+        function midPoint(firstPoint, secondPoint) {
+            if (firstPoint &amp;&amp; secondPoint)
+                return (+firstPoint.time + +secondPoint.time) / 2;
+            if (firstPoint)
+                return firstPoint.time;
+            return secondPoint.time;
+        }
+        var currentTimeSeries = this._currentTimeSeries;
+        var linkRoute = this.get('rangeRoute');
+        this.set('rangeBars', (this.get('ranges') || []).map(function (range) {
+            var start = currentTimeSeries.findPointByMeasurementId(range.get('startRun'));
+            var end = currentTimeSeries.findPointByMeasurementId(range.get('endRun'));
+            return Ember.Object.create({
+                startTime: midPoint(currentTimeSeries.previousPoint(start), start),
+                endTime: midPoint(end, currentTimeSeries.nextPoint(end)),
+                range: range,
+                left: null,
+                right: null,
+                rowIndex: null,
+                top: null,
+                bottom: null,
+                linkRoute: linkRoute,
+                linkId: range.get('id'),
+            });
+        }));
+
+        this._updateRangeBarRects();
+    }.observes('ranges'),
+    _updateRangeBarRects: function () {
+        var rangeBars = this.get('rangeBars');
+        if (!rangeBars || !rangeBars.length)
+            return;
+
+        var xScale = this._x;
+        var yScale = this._y;
+
+        // Expand the width of each range as needed and sort ranges by the left-edge of ranges.
+        var minWidth = 3;
+        var sortedBars = rangeBars.map(function (bar) {
+            var left = xScale(bar.get('startTime'));
+            var right = xScale(bar.get('endTime'));
+            if (right - left &lt; minWidth) {
+                left -= minWidth / 2;
+                right += minWidth / 2;
+            }
+            bar.set('left', left);
+            bar.set('right', right);
+            return bar;
+        }).sort(function (first, second) { return first.get('left') - second.get('left'); });
+
+        // At this point, left edges of all ranges prior to a range R1 is on the left of R1.
+        // Place R1 into a row in which right edges of all ranges prior to R1 is on the left of R1 to avoid overlapping ranges.
+        var rows = [];
+        sortedBars.forEach(function (bar) {
+            var rowIndex = 0;
+            for (; rowIndex &lt; rows.length; rowIndex++) {
+                var currentRow = rows[rowIndex];
+                if (currentRow[currentRow.length - 1].get('right') &lt; bar.get('left')) {
+                    currentRow.push(bar);
+                    break;
+                }
+            }
+            if (rowIndex &gt;= rows.length)
+                rows.push([bar]);
+            bar.set('rowIndex', rowIndex);
+        });
+        var rowHeight = 0.6 * this._rem;
+        var firstRowTop = this._contentHeight - rows.length * rowHeight;
+        var barHeight = 0.5 * this._rem;
+
+        $(this.get('element')).find('.rangeBarsContainerInlineStyle').css({
+            left: this._margin.left + 'px',
+            top: this._margin.top + firstRowTop + 'px',
+            width: this._contentWidth + 'px',
+            height: rows.length * barHeight + 'px',
+            overflow: 'hidden',
+            position: 'absolute',
+        });
+
+        var margin = this._margin;
+        sortedBars.forEach(function (bar) {
+            var top = bar.get('rowIndex') * rowHeight;
+            var height = barHeight;
+            var left = bar.get('left');
+            var width = bar.get('right') - left;
+            bar.set('inlineStyle', 'left: ' + left + 'px; top: ' + top + 'px; width: ' + width + 'px; height: ' + height + 'px;');
+        });
+    },
</ins><span class="cx">     _updateCurrentItemIndicators: function ()
</span><span class="cx">     {
</span><span class="cx">         if (!this._currentItemLine)
</span><span class="lines">@@ -1548,6 +1600,10 @@
</span><span class="cx">             this.sendAction('zoom', this._currentSelection());
</span><span class="cx">             this.set('selection', null);
</span><span class="cx">         },
</span><ins>+        openRange: function (range)
+        {
+            this.sendAction('openRange', range);
+        },
</ins><span class="cx">     },
</span><span class="cx"> });
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2chartpanecss"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/chart-pane.css (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/chart-pane.css        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/v2/chart-pane.css        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -315,7 +315,7 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .chart .extent {
</span><del>-    stroke: #9c6;
</del><ins>+    stroke: #f93;
</ins><span class="cx">     stroke-width: 1px;
</span><span class="cx">     fill: #9c6;
</span><span class="cx">     fill-opacity: .125;
</span><span class="lines">@@ -326,3 +326,9 @@
</span><span class="cx">     fill: #333;
</span><span class="cx">     stroke: none;
</span><span class="cx"> }
</span><ins>+
+.chart .rangeBar {
+    display: block;
+    background-color: #fc6;
+    position: absolute;
+}
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2datajs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/data.js (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/data.js        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/v2/data.js        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -423,3 +423,10 @@
</span><span class="cx">         return null;
</span><span class="cx">     return this._series[point.seriesIndex - 1];
</span><span class="cx"> }
</span><ins>+
+TimeSeries.prototype.nextPoint = function (point)
+{
+    if (!point.seriesIndex)
+        return null;
+    return this._series[point.seriesIndex + 1];
+}
</ins></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv2indexhtml"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v2/index.html (176421 => 176422)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v2/index.html        2014-11-20 23:04:51 UTC (rev 176421)
+++ trunk/Websites/perf.webkit.org/public/v2/index.html        2014-11-20 23:25:07 UTC (rev 176422)
</span><span class="lines">@@ -132,8 +132,8 @@
</span><span class="cx">                     &lt;h1 {{action &quot;toggleDetails&quot;}}&gt;{{metric.fullName}} - {{ platform.name}}&lt;/h1&gt;
</span><span class="cx">                     &lt;a href=&quot;#&quot; title=&quot;Close&quot; class=&quot;close-button&quot; {{action &quot;close&quot;}}&gt;{{partial &quot;close-button&quot;}}&lt;/a&gt;
</span><span class="cx">                     {{#if App.Manifest.bugTrackers}}
</span><del>-                        &lt;a href=&quot;#&quot; title=&quot;Bugs and Analysis&quot; class=&quot;bugs-button&quot; {{action &quot;toggleBugsPane&quot;}}&gt;
-                            {{partial &quot;bugs-button&quot;}}
</del><ins>+                        &lt;a href=&quot;#&quot; title=&quot;Analysis&quot; class=&quot;bugs-button&quot; {{action &quot;toggleBugsPane&quot;}}&gt;
+                            {{partial &quot;analysis-button&quot;}}
</ins><span class="cx">                         &lt;/a&gt;
</span><span class="cx">                     {{/if}}
</span><span class="cx">                     {{#if App.Manifest.repositoriesWithReportedCommits}}
</span><span class="lines">@@ -146,6 +146,7 @@
</span><span class="cx">                     {{#if chartData}}
</span><span class="cx">                         {{interactive-chart
</span><span class="cx">                             chartData=chartData
</span><ins>+                            ranges=analyticRanges
</ins><span class="cx">                             domain=mainPlotDomain
</span><span class="cx">                             interactive=true
</span><span class="cx">                             chartPointRadius=2
</span><span class="lines">@@ -153,6 +154,7 @@
</span><span class="cx">                             currentTime=sharedTime
</span><span class="cx">                             selectedItem=selectedItem
</span><span class="cx">                             highlightedItems=highlightedItems
</span><ins>+                            rangeRoute=&quot;analysisTask&quot;
</ins><span class="cx">                             selection=timeRange
</span><span class="cx">                             sharedSelection=sharedSelection
</span><span class="cx">                             selectionChanged=&quot;rangeChanged&quot;
</span><span class="lines">@@ -194,25 +196,13 @@
</span><span class="cx">                     {{input action=&quot;searchCommit&quot; placeholder=&quot;Name or email&quot; value=commitSearchKeyword}}
</span><span class="cx">                 &lt;/form&gt;
</span><span class="cx"> 
</span><del>-                &lt;div {{bind-attr class=&quot;:bugs-pane showingBugsPane::hidden&quot;}}&gt;
</del><ins>+                &lt;div {{bind-attr class=&quot;:bugs-pane showingAnalysisPane::hidden&quot;}}&gt;
</ins><span class="cx">                     &lt;table&gt;
</span><span class="cx">                         &lt;tbody&gt;
</span><del>-                            {{#if selectedSinglePoint}}
-                                {{#each details.bugTrackers}}
-                                    &lt;tr&gt;
-                                        &lt;th&gt;{{label}}&lt;/th&gt;
-                                        &lt;td&gt;
-                                            &lt;form {{action &quot;associateBug&quot; this editedBugNumber on=&quot;submit&quot;}}&gt;
-                                                {{input type=text value=editedBugNumber}}
-                                            &lt;/form&gt;
-                                        &lt;/td&gt;
-                                    &lt;/tr&gt;
-                                {{/each}}
-                            {{/if}}
</del><span class="cx">                             &lt;tr&gt;
</span><span class="cx">                                 &lt;th&gt;
</span><span class="cx">                                     &lt;label&gt;Name: {{input type=text value=newAnalysisTaskName}}&lt;/label&gt;
</span><del>-                                    &lt;button {{action &quot;createAnalysisTask&quot;}}&gt;Analyze&lt;/button&gt;
</del><ins>+                                    &lt;button {{action &quot;createAnalysisTask&quot;}} {{bind-attr disabled=cannotAnalyze}}&gt;Analyze&lt;/button&gt;
</ins><span class="cx">                                 &lt;/th&gt;
</span><span class="cx">                             &lt;/tr&gt;
</span><span class="cx">                         &lt;/tbody&gt;
</span><span class="lines">@@ -237,6 +227,13 @@
</span><span class="cx">                 &lt;/a&gt;
</span><span class="cx">             &lt;/div&gt;
</span><span class="cx">         {{/if}}
</span><ins>+        &lt;div class=&quot;rangeBarsContainerInlineStyle&quot;&gt;
+            {{#each rangeBars}}
+                {{#link-to linkRoute linkId}}
+                    &lt;span class=&quot;rangeBar&quot; {{bind-attr style=inlineStyle}}&gt;&lt;/span&gt;
+                {{/link-to}}
+            {{/each}}
+        &lt;/div&gt;
</ins><span class="cx">     &lt;/script&gt;
</span><span class="cx"> 
</span><span class="cx">     &lt;script type=&quot;text/x-handlebars&quot; data-template-name=&quot;chart-details&quot;&gt;
</span><span class="lines">@@ -323,8 +320,8 @@
</span><span class="cx">         &lt;/svg&gt;
</span><span class="cx">     &lt;/script&gt;
</span><span class="cx"> 
</span><del>-    &lt;script type=&quot;text/x-handlebars&quot; data-template-name=&quot;bugs-button&quot;&gt;
-        &lt;svg class=&quot;bugs-button icon-button&quot; viewBox=&quot;0 0 100 100&quot;&gt;
</del><ins>+    &lt;script type=&quot;text/x-handlebars&quot; data-template-name=&quot;analysis-button&quot;&gt;
+        &lt;svg class=&quot;analysis-button icon-button&quot; viewBox=&quot;0 0 100 100&quot;&gt;
</ins><span class="cx">             &lt;g stroke=&quot;black&quot; stroke-width=&quot;15&quot;&gt;
</span><span class="cx">                 &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;40&quot; fill=&quot;transparent&quot;/&gt;
</span><span class="cx">                 &lt;line x1=&quot;50&quot; y1=&quot;25&quot; x2=&quot;50&quot; y2=&quot;55&quot;/&gt;
</span></span></pre>
</div>
</div>

</body>
</html>