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 29 var ui = ui || {}; 30 31 (function() { 32 33 ui.popup = {}; 34 35 ui.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 44 ui.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 70 ui.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 77 ui.html = {}; 78 79 ui.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 88 ui.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 96 ui.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 111 ui.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. 130 ui.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 147 ui.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 154 ui.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 161 ui.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 170 ui.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 190 ui.html.chromiumRevisionLink = function(testResults, index) 191 { 192 return ui.html._revisionLink(results.CHROME_REVISIONS, testResults, index); 193 } 194 195 ui.html.blinkRevisionLink = function(testResults, index) 196 { 197 return ui.html._revisionLink(results.BLINK_REVISIONS, testResults, index); 198 } 199 200 201 ui.Errors = function() { 202 this._messages = ''; 203 // Element to display the errors within. 204 this._containerElement = null; 205 } 206 207 ui.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