1/** 2 * Copyright (c) 2017 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 17(function($, moment) { 18 19/** 20 * Display the log links in a modal window. 21 * @param linkList A list of [name, url] tuples representing log links. 22 */ 23function showLinks(container, linkList) { 24 if (!linkList || linkList.length == 0) return; 25 26 var logCollection = $('<ul class="collection"></ul>'); 27 var entries = linkList.reduce(function(acc, entry) { 28 if (!entry || entry.length == 0) return acc; 29 var link = '<a href="' + entry[1] + '"'; 30 link += 'class="collection-item">' + entry[0] + '</li>'; 31 return acc + link; 32 }, ''); 33 logCollection.html(entries); 34 35 if (container.find('#info-modal').length == 0) { 36 var modal = 37 $('<div id="info-modal" class="modal modal-fixed-footer"></div>'); 38 var content = $('<div class="modal-content"></div>'); 39 content.append('<h4>Links</h4>'); 40 content.append('<div class="info-container"></div>'); 41 content.appendTo(modal); 42 var footer = $('<div class="modal-footer"></div>'); 43 footer.append('<a class="btn-flat modal-close">Close</a></div>'); 44 footer.appendTo(modal); 45 modal.appendTo(container); 46 } 47 var infoContainer = $('#info-modal>.modal-content>.info-container'); 48 infoContainer.empty(); 49 logCollection.appendTo(infoContainer); 50 $('#info-modal').modal({dismissible: true}); 51 $('#info-modal').modal('open'); 52} 53 54/** 55 * Get the nickname for a test case result. 56 * 57 * Removes the result prefix and suffix, extracting only the result name. 58 * 59 * @param testCaseResult The string name of a VtsReportMessage.TestCaseResult. 60 * @returns the string nickname of the result. 61 */ 62function getNickname(testCaseResult) { 63 return testCaseResult.replace('TEST_CASE_RESULT_', '') 64 .replace('_RESULT', '') 65 .trim() 66 .toLowerCase(); 67} 68 69/** 70 * Get the badge color from ratio value. 71 * 72 * @param the percentage value. 73 * @returns the string of color for the badge. 74 */ 75function getBadgeColor(ratio) { 76 var color = "orange"; 77 if (ratio <= 20) { 78 color = "red"; 79 } else if (ratio >= 70) { 80 color = "green"; 81 } 82 return color; 83} 84 85/** 86 * Get the rounded value. 87 * 88 * @param the percentage value. 89 * @returns the rounded value from percentage value. 90 */ 91function getRoundValue(ratio) { 92 return Math.round(ratio * 1000) / 10; 93} 94 95/** 96 * Display test data in the body beneath a test run's metadata. 97 * @param container The jquery object in which to insert the test metadata. 98 * @param data The json object containing the columns to display. 99 * @param lineHeight The height of each list element. 100 */ 101function displayTestDetails(container, data, lineHeight) { 102 var nCol = data.length; 103 var width = 's' + (12 / nCol); 104 test = container; 105 var maxLines = 0; 106 data.forEach(function(column, index) { 107 if (column.data == undefined || column.name == undefined) { 108 return; 109 } 110 var classes = 'col test-col grey lighten-5 ' + width; 111 if (index != nCol - 1) { 112 classes += ' bordered'; 113 } 114 if (index == 0) { 115 classes += ' left-most'; 116 } 117 if (index == nCol - 1) { 118 classes += ' right-most'; 119 } 120 var colContainer = $('<div class="' + classes + '"></div>'); 121 var col = $('<div class="test-case-container"></div>'); 122 colContainer.appendTo(container); 123 var count = column.data.length; 124 var head = $('<h5 class="test-result-label white"></h5>') 125 .text(getNickname(column.name)) 126 .appendTo(colContainer) 127 .css('text-transform', 'capitalize'); 128 $('<div class="indicator right center"></div>') 129 .text(count) 130 .addClass(column.name) 131 .appendTo(head); 132 col.appendTo(colContainer); 133 var list = $('<ul></ul>').appendTo(col); 134 column.data.forEach(function(testCase) { 135 $('<li></li>') 136 .text(testCase) 137 .addClass('test-case') 138 .css('font-size', lineHeight - 2) 139 .css('line-height', lineHeight + 'px') 140 .appendTo(list); 141 }); 142 if (count > maxLines) { 143 maxLines = count; 144 } 145 }); 146 var containers = container.find('.test-case-container'); 147 containers.height(maxLines * lineHeight); 148} 149 150/** 151 * Click handler for displaying test run details. 152 * @param e The click event. 153 */ 154function testRunClick(e) { 155 var header = $(this); 156 var icon = header.find('.material-icons.expand-arrow'); 157 var container = header.parent().find('.test-results'); 158 var test = header.attr('test'); 159 var time = header.attr('time'); 160 var url = '/api/test_run?test=' + test + '×tamp=' + time; 161 if (header.parent().hasClass('active')) { 162 header.parent().removeClass('active'); 163 header.removeClass('active'); 164 icon.removeClass('rotate'); 165 header.siblings('.collapsible-body').stop(true, false).slideUp({ 166 duration: 100, 167 easing: 'easeOutQuart', 168 queue: false, 169 complete: function() { 170 header.css('height', ''); 171 } 172 }); 173 } else { 174 container.empty(); 175 header.parent().addClass('active'); 176 header.addClass('active'); 177 header.addClass('disabled'); 178 icon.addClass('rotate'); 179 $.get(url) 180 .done(function(data) { 181 displayTestDetails(container, data, 16); 182 header.siblings('.collapsible-body').stop(true, false).slideDown({ 183 duration: 100, 184 easing: 'easeOutQuart', 185 queue: false, 186 complete: function() { 187 header.css('height', ''); 188 } 189 }); 190 }) 191 .fail(function() { 192 icon.removeClass('rotate'); 193 }) 194 .always(function() { 195 header.removeClass('disabled'); 196 }); 197 } 198} 199 200/** 201 * Append a clickable indicator link to the container. 202 * @param container The jquery object to append the indicator to. 203 * @param content The text to display in the indicator. 204 * @param classes Additional space-delimited classes to add to the indicator. 205 * @param click The click handler to assign to the indicator. 206 * @returns The jquery object for the indicator. 207 */ 208function createClickableIndicator(container, content, classes, click) { 209 var link = $('<span></span>'); 210 link.addClass('indicator badge padded hoverable waves-effect'); 211 link.addClass(classes); 212 link.css('color', 'white'); 213 link.css('margin-left', '1px'); 214 link.append(content); 215 link.appendTo(container); 216 link.click(click); 217 return link; 218} 219 220function displayTestMetadata(container, metadataList, showTestNames = false) { 221 var popout = $('<ul></ul>'); 222 popout.attr('data-collapsible', 'expandable'); 223 popout.addClass('collapsible popout test-runs'); 224 popout.appendTo(container); 225 popout.unbind(); 226 metadataList.forEach(function(metadata) { 227 var li = $('<li class="test-run-container"></li>'); 228 li.appendTo(popout); 229 var div = $('<div></div>'); 230 var test = metadata.testRun.testName; 231 var startTime = metadata.testRun.startTimestamp; 232 var endTime = metadata.testRun.endTimestamp; 233 div.attr('test', test); 234 div.attr('time', startTime); 235 div.addClass('collapsible-header test-run'); 236 div.appendTo(li); 237 div.unbind().click(testRunClick); 238 var span = $('<span></span>'); 239 span.addClass('test-run-metadata'); 240 span.appendTo(div); 241 span.click(function() { 242 return false; 243 }); 244 if (showTestNames) { 245 $('<span class="test-run-label"></span>').text(test).appendTo(span); 246 span.append('<br>'); 247 } 248 if (metadata.deviceInfo) { 249 $('<b></b>').text(metadata.deviceInfo).appendTo(span); 250 span.append('<br>'); 251 } 252 if (metadata.abiInfo) { 253 $('<b></b>').text('ABI: ').appendTo(span) 254 span.append(metadata.abiInfo).append('<br>'); 255 } 256 $('<b></b>').text('VTS Build: ').appendTo(span) 257 span.append(metadata.testRun.testBuildId).append('<br>'); 258 $('<b></b>').text('Host: ').appendTo(span) 259 span.append(metadata.testRun.hostName).append('<br>'); 260 var timeString = 261 (moment().renderTime(startTime, false) + ' - ' + 262 moment().renderTime(endTime, true) + ' (' + 263 moment().renderDuration(endTime - startTime) + ')'); 264 span.append(timeString); 265 var indicator = $('<span></span>'); 266 var color = metadata.testRun.failCount > 0 ? 'red' : 'green'; 267 indicator.addClass('indicator badge ' + color); 268 indicator.css('color', 'white'); 269 indicator.append( 270 metadata.testRun.passCount + '/' + 271 (metadata.testRun.passCount + metadata.testRun.failCount)); 272 indicator.appendTo(div); 273 if (metadata.testRun.coveredLineCount != undefined && 274 metadata.testRun.totalLineCount != undefined) { 275 var url = ('/show_coverage?testName=' + test + '&startTime=' + startTime); 276 var covered = metadata.testRun.coveredLineCount; 277 var total = metadata.testRun.totalLineCount; 278 var covPct = getRoundValue(covered / total); 279 var color = getBadgeColor(covPct); 280 var coverage = 281 ('Coverage: ' + covered + '/' + total + ' (' + covPct + '%)'); 282 createClickableIndicator(div, coverage, color, function(evt) { 283 window.location.href = url; 284 return false; 285 }); 286 } 287 if (metadata.testRun.coveredApiCount != undefined && 288 metadata.testRun.totalApiCount != undefined) { 289 var covered = metadata.testRun.coveredApiCount; 290 var total = metadata.testRun.totalApiCount; 291 var covPct = getRoundValue(covered / total); 292 var color = getBadgeColor(covPct); 293 var apiCoverage = ('API Coverage: ' + covered + '/' + total + ' (' + covPct + '%)'); 294 createClickableIndicator(div, apiCoverage, color, function(evt) { 295 $('#apiCoverageModal') 296 .data('urlSafeKeyList', metadata.testRun.apiCoverageKeyList); 297 $('#apiCoverageModal').modal('open'); 298 return false; 299 }); 300 } 301 if (metadata.testRun.logLinks != undefined) { 302 createClickableIndicator(div, 'Links', 'grey lighten-1', function() { 303 showLinks(popout, metadata.testRun.logLinks); 304 return false; 305 }); 306 } 307 if ($('#coverageModalGraph').length) { 308 createClickableIndicator(div, 'Graph', 'grey lighten-1', function(evt) { 309 $('#coverageModalGraph').data('testname', test); 310 $('#coverageModalGraph').modal('open'); 311 $(evt.target).removeClass('grey'); 312 $(evt.target).addClass('blue'); 313 return false; 314 }); 315 } 316 317 var expand = $('<i></i>'); 318 expand.addClass('material-icons expand-arrow') 319 expand.text('expand_more'); 320 expand.appendTo(div); 321 var body = $('<div></div>') 322 .addClass('collapsible-body test-results row') 323 .appendTo(li); 324 if (metadata.testDetails != undefined) { 325 expand.addClass('rotate'); 326 li.addClass('active'); 327 div.addClass('active'); 328 displayTestDetails(body, metadata.testDetails, 16); 329 div.siblings('.collapsible-body').stop(true, false).slideDown({ 330 duration: 0, 331 queue: false, 332 complete: function() { 333 div.css('height', ''); 334 } 335 }); 336 } 337 }); 338} 339 340/** 341 * Display test metadata in a vertical popout. 342 * @param container The jquery object in which to insert the test metadata. 343 * @param metadataList The list of metadata objects to render on the display. 344 * @param showTestNames True to label each entry with the test module name. 345 */ 346$.fn.showTests = function(metadataList, showTestNames = false) { 347 displayTestMetadata($(this), metadataList, showTestNames); 348} 349})(jQuery, moment); 350