1// Copyright (C) 2013 Google Inc. All rights reserved. 2// 3// Redistribution and use in source and binary forms, with or without 4// modification, are permitted provided that the following conditions are 5// met: 6// 7// * Redistributions of source code must retain the above copyright 8// notice, this list of conditions and the following disclaimer. 9// * Redistributions in binary form must reproduce the above 10// copyright notice, this list of conditions and the following disclaimer 11// in the documentation and/or other materials provided with the 12// distribution. 13// * Neither the name of Google Inc. nor the names of its 14// contributors may be used to endorse or promote products derived from 15// this software without specific prior written permission. 16// 17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29var ui = ui || {}; 30 31(function() { 32 33ui.popup = {}; 34 35ui.popup.hide = function() 36{ 37 var popup = $('popup'); 38 if (popup) { 39 popup.parentNode.removeChild(popup); 40 document.removeEventListener('mousedown', ui.popup._handleMouseDown, false); 41 } 42} 43 44ui.popup.show = function(target, html) 45{ 46 var popup = $('popup'); 47 if (!popup) { 48 popup = document.createElement('div'); 49 popup.id = 'popup'; 50 document.body.appendChild(popup); 51 document.addEventListener('mousedown', ui.popup._handleMouseDown, false); 52 } 53 54 // Set html first so that we can get accurate size metrics on the popup. 55 popup.innerHTML = html; 56 57 var targetRect = target.getBoundingClientRect(); 58 59 var x = Math.min(targetRect.left - 10, document.documentElement.clientWidth - popup.offsetWidth); 60 x = Math.max(0, x); 61 popup.style.left = x + document.body.scrollLeft + 'px'; 62 63 var y = targetRect.top + targetRect.height; 64 if (y + popup.offsetHeight > document.documentElement.clientHeight) 65 y = targetRect.top - popup.offsetHeight; 66 y = Math.max(0, y); 67 popup.style.top = y + document.body.scrollTop + 'px'; 68} 69 70ui.popup._handleMouseDown = function(e) { 71 // Clear the open popup, unless the click was inside the popup. 72 var popup = $('popup'); 73 if (popup && e.target != popup && !(popup.compareDocumentPosition(e.target) & 16)) 74 ui.popup.hide(); 75} 76 77ui.html = {}; 78 79ui.html.checkbox = function(queryParameter, label, isChecked, opt_extraJavaScript) 80{ 81 var js = opt_extraJavaScript || ''; 82 return '<label style="padding-left: 2em">' + 83 '<input type="checkbox" onchange="g_history.toggleQueryParameter(\'' + queryParameter + '\');' + js + '" ' + 84 (isChecked ? 'checked' : '') + '>' + label + 85 '</label>'; 86} 87 88ui.html.range = function(queryParameter, label, min, max, initialValue) 89{ 90 return '<label>' + 91 label + 92 '<input type=range onchange="g_history.setQueryParameter(\'' + queryParameter + '\', this.value)" min=' + min + ' max=' + max + ' value=' + initialValue + '>' + 93 '</label>'; 94} 95 96ui.html.select = function(label, queryParameter, options) 97{ 98 var html = '<label style="padding-left: 2em">' + label + ': ' + 99 '<select onchange="g_history.setQueryParameter(\'' + queryParameter + '\', this[this.selectedIndex].value)">'; 100 101 for (var i = 0; i < options.length; i++) { 102 var value = options[i]; 103 html += '<option value="' + value + '" ' + 104 (g_history.queryParameterValue(queryParameter) == value ? 'selected' : '') + 105 '>' + value + '</option>' 106 } 107 html += '</select></label> '; 108 return html; 109} 110 111ui.html.navbar = function(opt_extraHtml) 112{ 113 var html = '<div style="border-bottom:1px dashed">'; 114 html = ui.html._dashboardLink('Overview', 'overview.html') + 115 ui.html._dashboardLink('Results', 'flakiness_dashboard.html') + 116 ui.html._dashboardLink('Times', 'treemap.html') + 117 ui.html._dashboardLink('Stats', 'aggregate_results.html') + 118 ui.html._dashboardLink('Stats Timeline', 'timeline_explorer.html'); 119 120 if (opt_extraHtml) 121 html += opt_extraHtml; 122 123 if (!history.isTreeMap()) 124 html += ui.html.checkbox('showAllRuns', 'Use all recorded runs', g_history.crossDashboardState.showAllRuns); 125 126 return html + '</div>'; 127} 128 129// Returns the HTML for the select element to switch to different testTypes. 130ui.html.testTypeSwitcher = function(opt_noBuilderMenu, opt_extraHtml, opt_includeNoneBuilder) 131{ 132 var html = ui.html.select('Test type', 'testType', builders.testTypes); 133 if (!opt_noBuilderMenu) { 134 var buildersForMenu = Object.keys(currentBuilders()); 135 if (opt_includeNoneBuilder) 136 buildersForMenu.unshift('--------------'); 137 html += ui.html.select('Builder', 'builder', buildersForMenu); 138 } 139 140 html += ui.html.select('Group', 'group', builders.groupNamesForTestType(g_history.crossDashboardState.testType)); 141 142 if (opt_extraHtml) 143 html += opt_extraHtml; 144 return ui.html.navbar(html); 145} 146 147ui.html._loadDashboard = function(fileName) 148{ 149 var pathName = window.location.pathname; 150 pathName = pathName.substring(0, pathName.lastIndexOf('/') + 1); 151 window.location = pathName + fileName + window.location.hash; 152} 153 154ui.html._topLink = function(html, onClick, isSelected) 155{ 156 var cssText = isSelected ? 'font-weight: bold;' : 'color:blue;text-decoration:underline;cursor:pointer;'; 157 cssText += 'margin: 0 5px;'; 158 return '<span style="' + cssText + '" onclick="' + onClick + '">' + html + '</span>'; 159} 160 161ui.html._dashboardLink = function(html, fileName) 162{ 163 var pathName = window.location.pathname; 164 var currentFileName = pathName.substring(pathName.lastIndexOf('/') + 1); 165 var isSelected = currentFileName == fileName; 166 var onClick = 'ui.html._loadDashboard(\'' + fileName + '\')'; 167 return ui.html._topLink(html, onClick, isSelected); 168} 169 170ui.html._revisionLink = function(resultsKey, testResults, index) 171{ 172 var currentRevision = parseInt(testResults[resultsKey][index], 10); 173 var previousRevision = parseInt(testResults[resultsKey][index + 1], 10); 174 175 var isChrome = resultsKey == results.CHROME_REVISIONS; 176 var singleUrl = 'http://src.chromium.org/viewvc/' + (isChrome ? 'chrome' : 'blink') + '?view=rev&revision=' + currentRevision; 177 178 if (currentRevision == previousRevision) 179 return 'At <a href="' + singleUrl + '">r' + currentRevision + '</a>'; 180 181 if (currentRevision - previousRevision == 1) 182 return '<a href="' + singleUrl + '">r' + currentRevision + '</a>'; 183 184 var rangeUrl = 'http://build.chromium.org/f/chromium/perf/dashboard/ui/changelog' + 185 (isChrome ? '' : '_blink') + '.html?url=/trunk' + (isChrome ? '/src' : '') + 186 '&range=' + (previousRevision + 1) + ':' + currentRevision + '&mode=html'; 187 return '<a href="' + rangeUrl + '">r' + (previousRevision + 1) + ' to r' + currentRevision + '</a>'; 188} 189 190ui.html.chromiumRevisionLink = function(testResults, index) 191{ 192 return ui.html._revisionLink(results.CHROME_REVISIONS, testResults, index); 193} 194 195ui.html.blinkRevisionLink = function(testResults, index) 196{ 197 return ui.html._revisionLink(results.BLINK_REVISIONS, testResults, index); 198} 199 200 201ui.Errors = function() { 202 this._messages = ''; 203 // Element to display the errors within. 204 this._containerElement = null; 205} 206 207ui.Errors.prototype = { 208 show: function() 209 { 210 if (!this._containerElement) { 211 this._containerElement = document.createElement('H2'); 212 this._containerElement.style.color = 'red'; 213 this._containerElement.id = 'errors'; 214 document.documentElement.insertBefore(this._containerElement, document.body); 215 } 216 217 this._containerElement.innerHTML = this._messages; 218 }, 219 // Record a new error message. 220 addError: function(message) 221 { 222 this._messages += message + '<br>'; 223 }, 224 hasErrors: function() 225 { 226 return !!this._messages; 227 } 228} 229 230})(); 231