1 <%-- 2 ~ Copyright (c) 2016 Google Inc. All Rights Reserved. 3 ~ 4 ~ Licensed under the Apache License, Version 2.0 (the "License"); you 5 ~ may not use this file except in compliance with the License. You may 6 ~ obtain a copy of the License at 7 ~ 8 ~ http://www.apache.org/licenses/LICENSE-2.0 9 ~ 10 ~ Unless required by applicable law or agreed to in writing, software 11 ~ distributed under the License is distributed on an "AS IS" BASIS, 12 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13 ~ implied. See the License for the specific language governing 14 ~ permissions and limitations under the License. 15 --%> 16 <%@ page contentType='text/html;charset=UTF-8' language='java' %> 17 <%@ taglib prefix='fn' uri='http://java.sun.com/jsp/jstl/functions' %> 18 <%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core'%> 19 20 <html> 21 <%@ include file="header.jsp" %> 22 <link type='text/css' href='/css/show_table.css' rel='stylesheet'> 23 <script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script> 24 <script src='https://www.gstatic.com/external_hosted/moment/min/moment-with-locales.min.js'></script> 25 <script type='text/javascript'> 26 google.charts.load('current', {'packages':['table', 'corechart']}); 27 google.charts.setOnLoadCallback(drawGridTable); 28 google.charts.setOnLoadCallback(activateLogLinks); 29 google.charts.setOnLoadCallback(drawProfilingTable); 30 google.charts.setOnLoadCallback(drawPieChart); 31 google.charts.setOnLoadCallback(function() { 32 $('.gradient').removeClass('gradient'); 33 }); 34 35 $(document).ready(function() { 36 function verify() { 37 var oneChecked = ($('#presubmit').prop('checked') || 38 $('#postsubmit').prop('checked')); 39 if (!oneChecked) { 40 $('#refresh').addClass('disabled'); 41 } else { 42 $('#refresh').removeClass('disabled'); 43 } 44 } 45 $('#presubmit').prop('checked', ${showPresubmit} || false) 46 .change(verify); 47 $('#postsubmit').prop('checked', ${showPostsubmit} || false) 48 .change(verify); 49 $('#refresh').click(refresh); 50 $('#help-icon').click(function() { 51 $('#help-modal').openModal(); 52 }); 53 $('#input-box').keypress(function(e) { 54 if (e.which == 13) { 55 refresh(); 56 } 57 }); 58 59 // disable buttons on load 60 if (!${hasNewer}) { 61 $('#newer-button').toggleClass('disabled'); 62 } 63 if (!${hasOlder}) { 64 $('#older-button').toggleClass('disabled'); 65 } 66 $('#newer-button').click(prev); 67 $('#older-button').click(next); 68 }); 69 70 // Actives the log links to display the log info modal when clicked. 71 function activateLogLinks() { 72 $('.info-btn').click(function(e) { 73 showLog(${logInfoMap}[$(this).data('col')]); 74 }); 75 } 76 77 /** Displays a modal window with the specified log entries. 78 * 79 * @param logEntries Array of string arrays. Each entry in the outer array 80 * must contain (1) name string, and (2) url string. 81 */ 82 function showLog(logEntries) { 83 if (!logEntries || logEntries.length == 0) return; 84 85 var logList = $('<ul class="collection"></ul>'); 86 var entries = logEntries.reduce(function(acc, entry) { 87 if (!entry || entry.length == 0) return acc; 88 var link = '<a href="' + entry[1] + '"'; 89 link += 'class="collection-item">' + entry[0] + '</li>'; 90 return acc + link; 91 }, ''); 92 logList.html(entries); 93 var infoContainer = $('#info-modal>.modal-content>.info-container'); 94 infoContainer.empty(); 95 logList.appendTo(infoContainer); 96 $('#info-modal').openModal(); 97 } 98 99 // refresh the page to see the selected test types (pre-/post-submit) 100 function refresh() { 101 if($(this).hasClass('disabled')) return; 102 var link = '${pageContext.request.contextPath}' + 103 '/show_table?testName=${testName}'; 104 var presubmit = $('#presubmit').prop('checked'); 105 var postsubmit = $('#postsubmit').prop('checked'); 106 if (presubmit) { 107 link += '&showPresubmit='; 108 } 109 if (postsubmit) { 110 link += '&showPostsubmit='; 111 } 112 if (${unfiltered} && postsubmit && presubmit) { 113 link += '&unfiltered='; 114 } 115 var searchString = $('#input-box').val(); 116 if (searchString) { 117 link += '&search=' + encodeURIComponent(searchString); 118 } 119 window.open(link,'_self'); 120 } 121 122 // view older data 123 function next() { 124 if($(this).hasClass('disabled')) return; 125 var endTime = ${startTime}; 126 var link = '${pageContext.request.contextPath}' + 127 '/show_table?testName=${testName}&endTime=' + endTime; 128 if ($('#presubmit').prop('checked')) { 129 link += '&showPresubmit='; 130 } 131 if ($('#postsubmit').prop('checked')) { 132 link += '&showPostsubmit='; 133 } 134 if (${unfiltered}) { 135 link += '&unfiltered='; 136 } 137 var searchString = '${searchString}'; 138 if (searchString) { 139 link += '&search=' + encodeURIComponent(searchString); 140 } 141 window.open(link,'_self'); 142 } 143 144 // view newer data 145 function prev() { 146 if($(this).hasClass('disabled')) return; 147 var startTime = ${endTime}; 148 var link = '${pageContext.request.contextPath}' + 149 '/show_table?testName=${testName}&startTime=' + startTime; 150 if ($('#presubmit').prop('checked')) { 151 link += '&showPresubmit='; 152 } 153 if ($('#postsubmit').prop('checked')) { 154 link += '&showPostsubmit='; 155 } 156 if (${unfiltered}) { 157 link += '&unfiltered='; 158 } 159 var searchString = '${searchString}'; 160 if (searchString) { 161 link += '&search=' + encodeURIComponent(searchString); 162 } 163 window.open(link,'_self'); 164 } 165 166 // table for profiling data 167 function drawProfilingTable() { 168 169 } 170 171 // to draw pie chart 172 function drawPieChart() { 173 var topBuildResultCounts = ${topBuildResultCounts}; 174 if (topBuildResultCounts.length < 1) { 175 return; 176 } 177 var resultNames = ${resultNamesJson}; 178 var rows = resultNames.map(function(res, i) { 179 nickname = res.replace('TEST_CASE_RESULT_', '').replace('_', ' ') 180 .trim().toLowerCase(); 181 return [nickname, parseInt(topBuildResultCounts[i])]; 182 }); 183 rows.unshift(['Result', 'Count']); 184 185 // Get CSS color definitions (or default to white) 186 var colors = resultNames.map(function(res) { 187 return $('.' + res).css('background-color') || 'white'; 188 }); 189 190 var data = google.visualization.arrayToDataTable(rows); 191 var options = { 192 is3D: false, 193 colors: colors, 194 fontName: 'Roboto', 195 fontSize: '14px', 196 legend: 'none', 197 tooltip: {showColorCode: true, ignoreBounds: true}, 198 chartArea: {height: '90%'} 199 }; 200 201 var chart = new google.visualization.PieChart(document.getElementById('pie-chart-div')); 202 chart.draw(data, options); 203 } 204 205 // table for grid data 206 function drawGridTable() { 207 var data = new google.visualization.DataTable(); 208 209 // Add column headers. 210 headerRow = ${headerRow}; 211 headerRow.forEach(function(d, i) { 212 var classNames = 'table-header-content'; 213 if (i == 0) classNames += ' table-header-legend'; 214 data.addColumn('string', '<span class="' + classNames + '">' + 215 d + '</span>'); 216 }); 217 218 var timeGrid = ${timeGrid}; 219 var durationGrid = ${durationGrid}; 220 var summaryGrid = ${summaryGrid}; 221 var resultsGrid = ${resultsGrid}; 222 223 // Format time grid to a formatted date 224 timeGrid = timeGrid.map(function(row) { 225 return row.map(function(cell, j) { 226 if (j == 0) return cell; 227 var time = moment(cell/1000); 228 // If today, don't display the date 229 if (time.isSame(moment(), 'd')) { 230 return time.format('H:mm:ssZZ'); 231 } else { 232 return time.format('M/D/YY H:mm:ssZZ'); 233 } 234 }); 235 }); 236 237 // Format duration grid to HH:mm:ss.SSS 238 durationGrid = durationGrid.map(function(row) { 239 return row.map(function(cell, j) { 240 if (j == 0) return cell; 241 return moment.utc(cell/1000).format("HH:mm:ss.SSS"); 242 }); 243 }); 244 245 // add rows to the data. 246 data.addRows(timeGrid); 247 data.addRows(durationGrid); 248 data.addRows(summaryGrid); 249 data.addRows(resultsGrid); 250 251 var table = new google.visualization.Table(document.getElementById('grid-table-div')); 252 var classNames = { 253 headerRow : 'table-header', 254 headerCell : 'table-header-cell' 255 }; 256 var options = { 257 showRowNumber: false, 258 alternatingRowStyle: true, 259 allowHtml: true, 260 frozenColumns: 1, 261 cssClassNames: classNames, 262 sort: 'disable' 263 }; 264 table.draw(data, options); 265 } 266 </script> 267 268 <body> 269 <div class='wide container'> 270 <div class='row'> 271 <div class='col s12'> 272 <div class='card' id='filter-wrapper'> 273 <div id='search-icon-wrapper'> 274 <i class='material-icons' id='search-icon'>search</i> 275 </div> 276 <div class='input-field' id='search-wrapper'> 277 <input value='${searchString}' type='text' id='input-box'> 278 <label for='input-box'>Search for test results</label> 279 </div> 280 <div id='help-icon-wrapper'> 281 <i class='material-icons' id='help-icon'>help</i> 282 </div> 283 <div id='build-type-div' class='right'> 284 <input type='checkbox' id='presubmit' /> 285 <label for='presubmit'>Presubmit</label> 286 <input type='checkbox' id='postsubmit' /> 287 <label for='postsubmit'>Postsubmit</label> 288 <a id='refresh' class='btn-floating btn-medium red right waves-effect waves-light'> 289 <i class='medium material-icons'>cached</i> 290 </a> 291 </div> 292 </div> 293 </div> 294 <div class='col s7'> 295 <div class='col s12 card center-align'> 296 <div id='legend-wrapper'> 297 <c:forEach items='${resultNames}' var='res'> 298 <div class='center-align legend-entry'> 299 <c:set var='trimmed' value='${fn:replace(res, "TEST_CASE_RESULT_", "")}'/> 300 <c:set var='nickname' value='${fn:replace(trimmed, "_", " ")}'/> 301 <label for='${res}'>${nickname}</label> 302 <div id='${res}' class='${res} legend-bubble'></div> 303 </div> 304 </c:forEach> 305 </div> 306 </div> 307 <div id='profiling-container' class='col s12'> 308 <c:choose> 309 <c:when test='${empty profilingPointNames}'> 310 <div id='error-div' class='center-align card'><h5>${error}</h5></div> 311 </c:when> 312 <c:otherwise> 313 <ul id='profiling-body' class='collapsible' data-collapsible='accordion'> 314 <li> 315 <div class='collapsible-header'><i class='material-icons'>timeline</i>Profiling Graphs</div> 316 <div class='collapsible-body'> 317 <ul id='profiling-list' class='collection'> 318 <c:forEach items='${profilingPointNames}' var='pt'> 319 <c:set var='profPointArgs' value='testName=${testName}&profilingPoint=${pt}'/> 320 <c:set var='timeArgs' value='endTime=${endTime}'/> 321 <a href='/show_graph?${profPointArgs}&${timeArgs}' 322 class='collection-item profiling-point-name'>${pt} 323 </a> 324 </c:forEach> 325 </ul> 326 </div> 327 </li> 328 <li> 329 <a class='collapsible-link' href='/show_performance_digest?testName=${testName}'> 330 <div class='collapsible-header'><i class='material-icons'>toc</i>Performance Digest</div> 331 </a> 332 </li> 333 </ul> 334 </c:otherwise> 335 </c:choose> 336 </div> 337 </div> 338 <div class='col s5 valign-wrapper'> 339 <!-- pie chart --> 340 <div id='pie-chart-wrapper' class='col s12 valign center-align card'> 341 <h6 class='pie-chart-title'>Test Status for Device Build ID: ${topBuildId}</h6> 342 <div id='pie-chart-div'></div> 343 </div> 344 </div> 345 </div> 346 347 <div class='col s12'> 348 <div id='chart-holder' class='col s12 card'> 349 <!-- Grid tables--> 350 <div id='grid-table-div'></div> 351 </div> 352 </div> 353 <div id='newer-wrapper' class='page-button-wrapper fixed-action-btn'> 354 <a id='newer-button' class='btn-floating btn red waves-effect'> 355 <i class='large material-icons'>keyboard_arrow_left</i> 356 </a> 357 </div> 358 <div id='older-wrapper' class='page-button-wrapper fixed-action-btn'> 359 <a id='older-button' class='btn-floating btn red waves-effect'> 360 <i class='large material-icons'>keyboard_arrow_right</i> 361 </a> 362 </div> 363 </div> 364 <div id="help-modal" class="modal"> 365 <div class="modal-content"> 366 <h4>${searchHelpHeader}</h4> 367 <p>${searchHelpBody}</p> 368 </div> 369 <div class="modal-footer"> 370 <a href="#!" class="modal-action modal-close waves-effect btn-flat">Close</a> 371 </div> 372 </div> 373 <div id="info-modal" class="modal"> 374 <div class="modal-content"> 375 <h4>Logs</h4> 376 <div class="info-container"></div> 377 </div> 378 <div class="modal-footer"> 379 <a href="#!" class="modal-action modal-close waves-effect btn-flat">Close</a> 380 </div> 381 </div> 382 <%@ include file="footer.jsp" %> 383 </body> 384 </html> 385