Dashboard cleanup: Move all dashboard ui related code into ui.js.
authorjparent@chromium.org <jparent@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Mar 2013 03:48:16 +0000 (03:48 +0000)
committerjparent@chromium.org <jparent@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Mar 2013 03:48:16 +0000 (03:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=111621

Reviewed by Ojan Vafai.

Creates ui.js, a package for the common UI code the various
dashboards use. HTML generation is now in the ui.html namespace
and popup handling is in ui.popup.
Also moves the event listening for popups to ui.popup.show and
removes it on ui.popup.hid rather than having listener always
around.

* TestResultServer/static-dashboards/aggregate_results.html:
* TestResultServer/static-dashboards/dashboard_base.js:
* TestResultServer/static-dashboards/flakiness_dashboard.html:
* TestResultServer/static-dashboards/flakiness_dashboard.js:
(showPopupForBuild):
(htmlForNavBar):
(generatePageForIndividualTests.if):
(generatePageForIndividualTests):
* TestResultServer/static-dashboards/flakiness_dashboard_embedded_unittests.js:
* TestResultServer/static-dashboards/flakiness_dashboard_unittests.js:
* TestResultServer/static-dashboards/run-embedded-unittests.html:
* TestResultServer/static-dashboards/run-unittests.html:
* TestResultServer/static-dashboards/timeline_explorer.html:
* TestResultServer/static-dashboards/treemap.html:
* TestResultServer/static-dashboards/ui.js: Added.
(.):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@145028 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
Tools/ChangeLog
Tools/TestResultServer/static-dashboards/aggregate_results.html
Tools/TestResultServer/static-dashboards/dashboard_base.js
Tools/TestResultServer/static-dashboards/flakiness_dashboard.html
Tools/TestResultServer/static-dashboards/flakiness_dashboard.js
Tools/TestResultServer/static-dashboards/flakiness_dashboard_embedded_unittests.js
Tools/TestResultServer/static-dashboards/flakiness_dashboard_unittests.js
Tools/TestResultServer/static-dashboards/run-embedded-unittests.html
Tools/TestResultServer/static-dashboards/run-unittests.html
Tools/TestResultServer/static-dashboards/timeline_explorer.html
Tools/TestResultServer/static-dashboards/treemap.html
Tools/TestResultServer/static-dashboards/ui.js [new file with mode: 0644]

index bc4595e..d6dd38b 100644 (file)
@@ -1,3 +1,34 @@
+2013-03-06  Julie Parent  <jparent@chromium.org>
+
+        Dashboard cleanup: Move all dashboard ui related code into ui.js.
+        https://bugs.webkit.org/show_bug.cgi?id=111621
+
+        Reviewed by Ojan Vafai.
+        
+        Creates ui.js, a package for the common UI code the various
+        dashboards use. HTML generation is now in the ui.html namespace
+        and popup handling is in ui.popup.
+        Also moves the event listening for popups to ui.popup.show and
+        removes it on ui.popup.hid rather than having listener always
+        around.
+
+        * TestResultServer/static-dashboards/aggregate_results.html:
+        * TestResultServer/static-dashboards/dashboard_base.js:
+        * TestResultServer/static-dashboards/flakiness_dashboard.html:
+        * TestResultServer/static-dashboards/flakiness_dashboard.js:
+        (showPopupForBuild):
+        (htmlForNavBar):
+        (generatePageForIndividualTests.if):
+        (generatePageForIndividualTests):
+        * TestResultServer/static-dashboards/flakiness_dashboard_embedded_unittests.js:
+        * TestResultServer/static-dashboards/flakiness_dashboard_unittests.js:
+        * TestResultServer/static-dashboards/run-embedded-unittests.html:
+        * TestResultServer/static-dashboards/run-unittests.html:
+        * TestResultServer/static-dashboards/timeline_explorer.html:
+        * TestResultServer/static-dashboards/treemap.html:
+        * TestResultServer/static-dashboards/ui.js: Added.
+        (.):
+
 2013-03-06  Krzysztof Czech  <k.czech@samsung.com>
 
         [EFL] Missing implementation of AccessibilityControllerEfl and AccessibilityUIElementEfl files
index a735112..96171a9 100644 (file)
@@ -54,6 +54,7 @@ img {
 <script src="loader.js"></script>
 <script src="string.js"></script>
 <script src="dashboard_base.js"></script>
+<script src="ui.js"></script>
 <script>
 // @fileoverview Creates a dashboard for tracking number of passes/failures per run.
 //
@@ -68,7 +69,7 @@ img {
 //////////////////////////////////////////////////////////////////////////////
 function generatePage()
 {
-    var html = htmlForTestTypeSwitcher(true) + '<br>';
+    var html = ui.html.testTypeSwitcher(true) + '<br>';
     for (var builder in currentBuilders())
         html += htmlForBuilder(builder);
     document.body.innerHTML = html;
index 4fc13f3..5572e7d 100644 (file)
@@ -548,38 +548,6 @@ function joinParameters(stateObject)
     return state.join('&');
 }
 
-function hidePopup()
-{
-    var popup = $('popup');
-    if (popup)
-        popup.parentNode.removeChild(popup);
-}
-
-function showPopup(target, html)
-{
-    var popup = $('popup');
-    if (!popup) {
-        popup = document.createElement('div');
-        popup.id = 'popup';
-        document.body.appendChild(popup);
-    }
-
-    // Set html first so that we can get accurate size metrics on the popup.
-    popup.innerHTML = html;
-
-    var targetRect = target.getBoundingClientRect();
-
-    var x = Math.min(targetRect.left - 10, document.documentElement.clientWidth - popup.offsetWidth);
-    x = Math.max(0, x);
-    popup.style.left = x + document.body.scrollLeft + 'px';
-
-    var y = targetRect.top + targetRect.height;
-    if (y + popup.offsetHeight > document.documentElement.clientHeight)
-        y = targetRect.top - popup.offsetHeight;
-    y = Math.max(0, y);
-    popup.style.top = y + document.body.scrollTop + 'px';
-}
-
 // Create a new function with some of its arguements
 // pre-filled.
 // Taken from goog.partial in the Closure library.
@@ -599,7 +567,7 @@ function partial(fn, var_args)
     };
 };
 
-// Returns the appropriate expectatiosn map for the current testType.
+// Returns the appropriate expectations map for the current testType.
 function expectationsMap()
 {
     return isLayoutTestResults() ? LAYOUT_TEST_EXPECTATIONS_MAP_ : GTEST_EXPECTATIONS_MAP_;
@@ -615,126 +583,6 @@ function queryParameterValue(parameter)
     return g_currentState[parameter] || g_crossDashboardState[parameter];
 }
 
-function checkboxHTML(queryParameter, label, isChecked, opt_extraJavaScript)
-{
-    var js = opt_extraJavaScript || '';
-    return '<label style="padding-left: 2em">' +
-        '<input type="checkbox" onchange="toggleQueryParameter(\'' + queryParameter + '\');' + js + '" ' +
-            (isChecked ? 'checked' : '') + '>' + label +
-        '</label> ';
-}
-
-function selectHTML(label, queryParameter, options)
-{
-    var html = '<label style="padding-left: 2em">' + label + ': ' +
-        '<select onchange="setQueryParameter(\'' + queryParameter + '\', this[this.selectedIndex].value)">';
-
-    for (var i = 0; i < options.length; i++) {
-        var value = options[i];
-        html += '<option value="' + value + '" ' +
-            (queryParameterValue(queryParameter) == value ? 'selected' : '') +
-            '>' + value + '</option>'
-    }
-    html += '</select></label> ';
-    return html;
-}
-
-// Returns the HTML for the select element to switch to different testTypes.
-function htmlForTestTypeSwitcher(opt_noBuilderMenu, opt_extraHtml, opt_includeNoneBuilder)
-{
-    var html = '<div style="border-bottom:1px dashed">';
-    html += '' +
-        htmlForDashboardLink('Stats', 'aggregate_results.html') +
-        htmlForDashboardLink('Timeline', 'timeline_explorer.html') +
-        htmlForDashboardLink('Results', 'flakiness_dashboard.html') +
-        htmlForDashboardLink('Treemap', 'treemap.html');
-
-    html += selectHTML('Test type', 'testType', TEST_TYPES);
-
-    if (!opt_noBuilderMenu) {
-        var buildersForMenu = Object.keys(currentBuilders());
-        if (opt_includeNoneBuilder)
-            buildersForMenu.unshift('--------------');
-        html += selectHTML('Builder', 'builder', buildersForMenu);
-    }
-
-    html += selectHTML('Group', 'group',
-        Object.keys(currentBuilderGroupCategory()));
-
-    if (!isTreeMap())
-        html += checkboxHTML('showAllRuns', 'Show all runs', g_crossDashboardState.showAllRuns);
-
-    if (opt_extraHtml)
-        html += opt_extraHtml;
-    return html + '</div>';
-}
-
-function loadDashboard(fileName)
-{
-    var pathName = window.location.pathname;
-    pathName = pathName.substring(0, pathName.lastIndexOf('/') + 1);
-    window.location = pathName + fileName + window.location.hash;
-}
-
-function htmlForTopLink(html, onClick, isSelected)
-{
-    var cssText = isSelected ? 'font-weight: bold;' : 'color:blue;text-decoration:underline;cursor:pointer;';
-    cssText += 'margin: 0 5px;';
-    return '<span style="' + cssText + '" onclick="' + onClick + '">' + html + '</span>';
-}
-
-function htmlForDashboardLink(html, fileName)
-{
-    var pathName = window.location.pathname;
-    var currentFileName = pathName.substring(pathName.lastIndexOf('/') + 1);
-    var isSelected = currentFileName == fileName;
-    var onClick = 'loadDashboard(\'' + fileName + '\')';
-    return htmlForTopLink(html, onClick, isSelected);
-}
-
-function revisionLink(results, index, key, singleUrlTemplate, rangeUrlTemplate)
-{
-    var currentRevision = parseInt(results[key][index], 10);
-    var previousRevision = parseInt(results[key][index + 1], 10);
-
-    function singleUrl()
-    {
-        return singleUrlTemplate.replace('<rev>', currentRevision);
-    }
-
-    function rangeUrl()
-    {
-        return rangeUrlTemplate.replace('<rev1>', currentRevision).replace('<rev2>', previousRevision + 1);
-    }
-
-    if (currentRevision == previousRevision)
-        return 'At <a href="' + singleUrl() + '">r' + currentRevision    + '</a>';
-    else if (currentRevision - previousRevision == 1)
-        return '<a href="' + singleUrl() + '">r' + currentRevision    + '</a>';
-    else
-        return '<a href="' + rangeUrl() + '">r' + (previousRevision + 1) + ' to r' + currentRevision + '</a>';
-}
-
-function chromiumRevisionLink(results, index)
-{
-    return revisionLink(
-        results,
-        index,
-        CHROME_REVISIONS_KEY,
-        'http://src.chromium.org/viewvc/chrome?view=rev&revision=<rev>',
-        'http://build.chromium.org/f/chromium/perf/dashboard/ui/changelog.html?url=/trunk/src&range=<rev2>:<rev1>&mode=html');
-}
-
-function webKitRevisionLink(results, index)
-{
-    return revisionLink(
-        results,
-        index,
-        WEBKIT_REVISIONS_KEY,
-        'http://trac.webkit.org/changeset/<rev>',
-        'http://trac.webkit.org/log/trunk/?rev=<rev1>&stop_rev=<rev2>&limit=100&verbose=on');
-}
-
 // "Decompresses" the RLE-encoding of test results so that we can query it
 // by build index and test name.
 //
@@ -831,13 +679,6 @@ function decompressResults(builderResults)
     };
 }
 
-document.addEventListener('mousedown', function(e) {
-    // Clear the open popup, unless the click was inside the popup.
-    var popup = $('popup');
-    if (popup && e.target != popup && !(popup.compareDocumentPosition(e.target) & 16))
-        hidePopup();
-}, false);
-
 window.addEventListener('load', function() {
     g_resourceLoader = new loader.Loader();
     g_resourceLoader.load();
index 9658af1..0ffe26f 100644 (file)
@@ -34,4 +34,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 <script src="loader.js"></script>
 <script src="string.js"></script>
 <script src="dashboard_base.js"></script>
+<script src="ui.js"></script>
 <script src="flakiness_dashboard.js"></script>
index ab4899b..d28ab79 100644 (file)
@@ -1153,7 +1153,7 @@ function linkHTMLToOpenWindow(url, text)
     return '<a href="' + url + '" target="_blank">' + text + '</a>';
 }
 
-// FIXME: replaced with chromiumRevisionLink/webKitRevisionLink
+// FIXME: replaced with ui.chromiumRevisionLink/ui.webKitRevisionLink
 function createBlameListHTML(revisions, index, urlBase, separator, repo)
 {
     var thisRevision = revisions[index];
@@ -1250,7 +1250,7 @@ function showPopupForBuild(e, builder, index, opt_testName)
         html += '<li>' + linkHTMLToOpenWindow(buildBasePath + pathToFailureLog(opt_testName), 'Failure log') + '</li>';
 
     html += '</ul>';
-    showPopup(e.target, html);
+    ui.popup.show(e.target, html);
 }
 
 function htmlForTestResults(test)
@@ -2395,7 +2395,7 @@ function htmlForIndividualTests(tests)
 function htmlForNavBar()
 {
     var extraHTML = '';
-    var html = htmlForTestTypeSwitcher(false, extraHTML, isCrossBuilderView());
+    var html = ui.html.testTypeSwitcher(false, extraHTML, isCrossBuilderView());
     html += '<div class=forms><form id=result-form ' +
         'onsubmit="setQueryParameter(\'result\', result.value);' +
         'return false;">Show all tests with result: ' +
@@ -2586,7 +2586,7 @@ function postHeightChangedMessage()
 }
 
 if (window != parent)
-    window.addEventListener('blur', hidePopup);
+    window.addEventListener('blur', ui.popup.hide);
 
 document.addEventListener('keydown', function(e) {
     if (e.keyIdentifier == 'U+003F' || e.keyIdentifier == 'U+00BF') {
@@ -2596,6 +2596,6 @@ document.addEventListener('keydown', function(e) {
     } else if (e.keyIdentifier == 'U+001B') {
         // escape key
         hideLegend();
-        hidePopup();
+        ui.popup.hide();
     }
 }, false);
index 5599b3e..039467a 100644 (file)
@@ -27,7 +27,7 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 test('hidePopupOnBlur', 2, function() {
-    showPopup(document.body, 'dummy content');
+    ui.popup.show(document.body, 'dummy content');
     ok(document.querySelector('#popup'));
 
     // Cause the window to be blurred.
index dbdce35..f3c58ff 100644 (file)
@@ -365,7 +365,7 @@ test('headerForTestTableHtml', 1, function() {
 test('htmlForTestTypeSwitcherGroup', 6, function() {
     var container = document.createElement('div');
     g_crossDashboardState.testType = 'ui_tests';
-    container.innerHTML = htmlForTestTypeSwitcher(true);
+    container.innerHTML = ui.html.testTypeSwitcher(true);
     var selects = container.querySelectorAll('select');
     equal(selects.length, 2);
     var group = selects[1];
@@ -373,7 +373,7 @@ test('htmlForTestTypeSwitcherGroup', 6, function() {
     equal(group.children.length, 3);
 
     g_crossDashboardState.testType = 'layout-tests';
-    container.innerHTML = htmlForTestTypeSwitcher(true);
+    container.innerHTML = ui.html.testTypeSwitcher(true);
     var selects = container.querySelectorAll('select');
     equal(selects.length, 2);
     var group = selects[1];
@@ -647,9 +647,9 @@ test('sortTests', 4, function() {
 });
 
 test('popup', 2, function() {
-    showPopup(document.body, 'dummy content');
+    ui.popup.show(document.body, 'dummy content');
     ok(document.querySelector('#popup'));
-    hidePopup();
+    ui.popup.hide();
     ok(!document.querySelector('#popup'));
 });
 
index 143235f..b191c0b 100644 (file)
@@ -49,6 +49,7 @@ window.parent = null;
 
 <script src="string.js"></script>
 <script src="dashboard_base.js"></script>
+<script src="ui.js"></script>
 <script src="loader.js"></script>
 <script src="loader_unittests.js"></script>
 <script>
index 1fdb998..0fca706 100644 (file)
@@ -43,6 +43,7 @@ THE POSSIBILITY OF SUCH DAMAGE.
 <script src="builders_unittests.js"></script>
 <script src="string.js"></script>
 <script src="dashboard_base.js"></script>
+<script src="ui.js"></script>
 <script src="loader.js"></script>
 <script src="loader_unittests.js"></script>
 <script>
index 3a5944a..f7b8f99 100644 (file)
@@ -100,6 +100,7 @@ body {
 <script src="loader.js"></script>
 <script src="string.js"></script>
 <script src="dashboard_base.js"></script>
+<script src="ui.js"></script>
 <script>
 var FAILING_TESTS_DATASET_NAME = 'Failing tests';
 
@@ -136,8 +137,8 @@ function generatePage()
 
     initCurrentBuilderTestResults();
 
-    $('test-type-switcher').innerHTML = htmlForTestTypeSwitcher( false,
-        checkboxHTML('ignoreFlakyTests', 'Ignore flaky tests', g_currentState.ignoreFlakyTests, 'g_currentBuildIndex = -1')
+    $('test-type-switcher').innerHTML = ui.html.testTypeSwitcher( false,
+        ui.html.checkbox('ignoreFlakyTests', 'Ignore flaky tests', g_currentState.ignoreFlakyTests, 'g_currentBuildIndex = -1')
     );
 
     updateTimelineForBuilder();
@@ -309,8 +310,8 @@ function updateBuildInspector(results, builder, dygraph, index)
 
     // Revision link(s)
     if (!shouldShowWebKitRevisionsOnly())
-        addRow('Chromium change:', chromiumRevisionLink(results, index));
-    addRow('WebKit change:', webKitRevisionLink(results, index));
+        addRow('Chromium change:', ui.html.chromiumRevisionLink(results, index));
+    addRow('WebKit change:', ui.html.webKitRevisionLink(results, index));
 
     // Test status/counts
     addRow('', '');
index 252ac81..40406c0 100644 (file)
@@ -111,6 +111,7 @@ td {
 <script src="loader.js"></script>
 <script src="string.js"></script>
 <script src="dashboard_base.js"></script>
+<script src="ui.js"></script>
 <script src='webtreemap.js'></script>
 
 <div id='header-container'></div>
@@ -230,7 +231,7 @@ var g_webTree;
 
 function generatePage()
 {
-    $('header-container').innerHTML = htmlForTestTypeSwitcher();
+    $('header-container').innerHTML = ui.html.testTypeSwitcher();
 
     g_isGeneratingPage = true;
 
diff --git a/Tools/TestResultServer/static-dashboards/ui.js b/Tools/TestResultServer/static-dashboards/ui.js
new file mode 100644 (file)
index 0000000..cb4eba9
--- /dev/null
@@ -0,0 +1,199 @@
+// Copyright (C) 2013 Google Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//         * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//         * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//         * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+var ui = ui || {};
+
+(function() {
+
+ui.popup = {};
+
+ui.popup.hide = function()
+{
+    var popup = $('popup');
+    if (popup) {
+        popup.parentNode.removeChild(popup);
+        document.removeEventListener('mousedown', ui.popup._handleMouseDown, false);
+    }
+}
+
+ui.popup.show = function(target, html)
+{
+    var popup = $('popup');
+    if (!popup) {
+        popup = document.createElement('div');
+        popup.id = 'popup';
+        document.body.appendChild(popup);
+        document.addEventListener('mousedown', ui.popup._handleMouseDown, false);
+    }
+
+    // Set html first so that we can get accurate size metrics on the popup.
+    popup.innerHTML = html;
+
+    var targetRect = target.getBoundingClientRect();
+
+    var x = Math.min(targetRect.left - 10, document.documentElement.clientWidth - popup.offsetWidth);
+    x = Math.max(0, x);
+    popup.style.left = x + document.body.scrollLeft + 'px';
+
+    var y = targetRect.top + targetRect.height;
+    if (y + popup.offsetHeight > document.documentElement.clientHeight)
+        y = targetRect.top - popup.offsetHeight;
+    y = Math.max(0, y);
+    popup.style.top = y + document.body.scrollTop + 'px';
+}
+
+ui.popup._handleMouseDown = function(e) {
+    // Clear the open popup, unless the click was inside the popup.
+    var popup = $('popup');
+    if (popup && e.target != popup && !(popup.compareDocumentPosition(e.target) & 16)) 
+        ui.popup.hide();    
+}
+
+ui.html = {};
+
+ui.html.checkbox = function(queryParameter, label, isChecked, opt_extraJavaScript)
+{
+    var js = opt_extraJavaScript || '';
+    return '<label style="padding-left: 2em">' +
+        '<input type="checkbox" onchange="toggleQueryParameter(\'' + queryParameter + '\');' + js + '" ' +
+            (isChecked ? 'checked' : '') + '>' + label +
+        '</label> ';
+}
+
+ui.html.select = function(label, queryParameter, options)
+{
+    var html = '<label style="padding-left: 2em">' + label + ': ' +
+        '<select onchange="setQueryParameter(\'' + queryParameter + '\', this[this.selectedIndex].value)">';
+
+    for (var i = 0; i < options.length; i++) {
+        var value = options[i];
+        html += '<option value="' + value + '" ' +
+            (queryParameterValue(queryParameter) == value ? 'selected' : '') +
+            '>' + value + '</option>'
+    }
+    html += '</select></label> ';
+    return html;
+}
+
+// Returns the HTML for the select element to switch to different testTypes.
+ui.html.testTypeSwitcher = function(opt_noBuilderMenu, opt_extraHtml, opt_includeNoneBuilder)
+{
+    var html = '<div style="border-bottom:1px dashed">';
+    html += '' +
+        ui.html._dashboardLink('Stats', 'aggregate_results.html') +
+        ui.html._dashboardLink('Timeline', 'timeline_explorer.html') +
+        ui.html._dashboardLink('Results', 'flakiness_dashboard.html') +
+        ui.html._dashboardLink('Treemap', 'treemap.html');
+
+    html += ui.html.select('Test type', 'testType', TEST_TYPES);
+
+    if (!opt_noBuilderMenu) {
+        var buildersForMenu = Object.keys(currentBuilders());
+        if (opt_includeNoneBuilder)
+            buildersForMenu.unshift('--------------');
+        html += ui.html.select('Builder', 'builder', buildersForMenu);
+    }
+
+    html += ui.html.select('Group', 'group',
+        Object.keys(currentBuilderGroupCategory()));
+
+    if (!isTreeMap())
+        html += ui.html.checkbox('showAllRuns', 'Show all runs', g_crossDashboardState.showAllRuns);
+
+    if (opt_extraHtml)
+        html += opt_extraHtml;
+    return html + '</div>';
+}
+
+ui.html._loadDashboard = function(fileName)
+{
+    var pathName = window.location.pathname;
+    pathName = pathName.substring(0, pathName.lastIndexOf('/') + 1);
+    window.location = pathName + fileName + window.location.hash;
+}
+
+ui.html._topLink = function(html, onClick, isSelected)
+{
+    var cssText = isSelected ? 'font-weight: bold;' : 'color:blue;text-decoration:underline;cursor:pointer;';
+    cssText += 'margin: 0 5px;';
+    return '<span style="' + cssText + '" onclick="' + onClick + '">' + html + '</span>';
+}
+
+ui.html._dashboardLink = function(html, fileName)
+{
+    var pathName = window.location.pathname;
+    var currentFileName = pathName.substring(pathName.lastIndexOf('/') + 1);
+    var isSelected = currentFileName == fileName;
+    var onClick = 'ui.html._loadDashboard(\'' + fileName + '\')';
+    return ui.html._topLink(html, onClick, isSelected);
+}
+
+ui.html._revisionLink = function(results, index, key, singleUrlTemplate, rangeUrlTemplate)
+{
+    var currentRevision = parseInt(results[key][index], 10);
+    var previousRevision = parseInt(results[key][index + 1], 10);
+
+    function singleUrl()
+    {
+        return singleUrlTemplate.replace('<rev>', currentRevision);
+    }
+
+    function rangeUrl()
+    {
+        return rangeUrlTemplate.replace('<rev1>', currentRevision).replace('<rev2>', previousRevision + 1);
+    }
+
+    if (currentRevision == previousRevision)
+        return 'At <a href="' + singleUrl() + '">r' + currentRevision    + '</a>';
+    else if (currentRevision - previousRevision == 1)
+        return '<a href="' + singleUrl() + '">r' + currentRevision    + '</a>';
+    else
+        return '<a href="' + rangeUrl() + '">r' + (previousRevision + 1) + ' to r' + currentRevision + '</a>';
+}
+
+ui.html.chromiumRevisionLink = function(results, index)
+{
+    return ui.html._revisionLink(
+        results,
+        index,
+        CHROME_REVISIONS_KEY,
+        'http://src.chromium.org/viewvc/chrome?view=rev&revision=<rev>',
+        'http://build.chromium.org/f/chromium/perf/dashboard/ui/changelog.html?url=/trunk/src&range=<rev2>:<rev1>&mode=html');
+}
+
+ui.html.webKitRevisionLink = function(results, index)
+{
+    return ui.html._revisionLink(
+        results,
+        index,
+        WEBKIT_REVISIONS_KEY,
+        'http://trac.webkit.org/changeset/<rev>',
+        'http://trac.webkit.org/log/trunk/?rev=<rev1>&stop_rev=<rev2>&limit=100&verbose=on');
+}
+
+})();
\ No newline at end of file