1# Lint as: python2, python3 2from __future__ import absolute_import 3from __future__ import division 4from __future__ import print_function 5import re 6import reason_qualifier 7 8# pylint: disable=missing-docstring 9 10color_map = { 11 'header' : '#e5e5c0', # greyish yellow 12 'blank' : '#ffffff', # white 13 'plain_text' : '#e5e5c0', # greyish yellow 14 'borders' : '#bbbbbb', # grey 15 'white' : '#ffffff', # white 16 'green' : '#66ff66', # green 17 'yellow' : '#fffc00', # yellow 18 'red' : '#ff6666', # red 19 20 #### additional keys for shaded color of a box 21 #### depending on stats of GOOD/FAIL 22 '100pct' : '#32CD32', # green, 94% to 100% of success 23 '95pct' : '#c0ff80', # step twrds yellow, 88% to 94% of success 24 '90pct' : '#ffff00', # yellow, 82% to 88% 25 '85pct' : '#ffc040', # 76% to 82% 26 '75pct' : '#ff4040', # red, 1% to 76% 27 '0pct' : '#d080d0', # violet, <1% of success 28 29} 30 31_brief_mode = False 32 33 34def set_brief_mode(): 35 global _brief_mode 36 _brief_mode = True 37 38 39def is_brief_mode(): 40 return _brief_mode 41 42 43def color_keys_row(): 44 """ Returns one row table with samples of 'NNpct' colors 45 defined in the color_map 46 and numbers of corresponding %% 47 """ 48 ### This function does not require maintenance in case of 49 ### color_map augmenting - as long as 50 ### color keys for box shading have names that end with 'pct' 51 keys = [key for key in color_map.keys() if key.endswith('pct')] 52 def num_pct(key): 53 return int(key.replace('pct','')) 54 keys.sort(key=num_pct) 55 html = '' 56 for key in keys: 57 html+= "\t\t\t<td bgcolor =%s> </td>\n"\ 58 % color_map[key] 59 hint = key.replace('pct',' %') 60 if hint[0]!='0': ## anything but 0 % 61 hint = 'to ' + hint 62 html+= "\t\t\t<td> %s </td>\n" % hint 63 64 html = """ 65<table width = "500" border="0" cellpadding="2" cellspacing="2">\n 66 <tbody>\n 67 <tr>\n 68%s 69 </tr>\n 70 </tbody> 71</table><br> 72""" % html 73 return html 74 75 76def calculate_html(link, data, tooltip=None, row_label=None, column_label=None): 77 if not is_brief_mode(): 78 hover_text = '%s:%s' % (row_label, column_label) 79 if data: ## cell is not empty 80 hover_text += '<br>%s' % tooltip 81 else: 82 ## avoid "None" printed in empty cells 83 data = ' ' 84 html = ('<center><a class="info" href="%s">' 85 '%s<span>%s</span></a></center>' % 86 (link, data, hover_text)) 87 return html 88 # no hover if embedded into AFE but links shall redirect to new window 89 if data: ## cell is non empty 90 html = '<a href="%s" target="_blank">%s</a>' % (link, data) 91 return html 92 else: ## cell is empty 93 return ' ' 94 95 96class box: 97 def __init__(self, data, color_key = None, header = False, link = None, 98 tooltip = None, row_label = None, column_label = None): 99 100 ## in brief mode we display grid table only and nothing more 101 ## - mouse hovering feature is stubbed in brief mode 102 ## - any link opens new window or tab 103 104 redirect = "" 105 if is_brief_mode(): 106 ## we are acting under AFE 107 ## any link shall open new window 108 redirect = " target=NEW" 109 110 if data: 111 data = "<tt>%s</tt>" % data 112 113 if link and not tooltip: 114 ## FlipAxis corner, column and row headers 115 self.data = ('<a href="%s"%s>%s</a>' % 116 (link, redirect, data)) 117 else: 118 self.data = calculate_html(link, data, tooltip, 119 row_label, column_label) 120 121 if color_key in color_map: 122 self.color = color_map[color_key] 123 elif header: 124 self.color = color_map['header'] 125 elif data: 126 self.color = color_map['plain_text'] 127 else: 128 self.color = color_map['blank'] 129 self.header = header 130 131 132 def html(self): 133 if self.data: 134 data = self.data 135 else: 136 data = ' ' 137 138 if self.header: 139 box_html = 'th' 140 else: 141 box_html = 'td' 142 143 return "<%s bgcolor=%s>%s</%s>" % \ 144 (box_html, self.color, data, box_html) 145 146 147def grade_from_status(status_idx, status): 148 # % of goodness 149 # GOOD (6) -> 1 150 # TEST_NA (8) is not counted 151 # ## If the test doesn't PASS, it FAILS 152 # else -> 0 153 154 if status == status_idx['GOOD']: 155 return 1.0 156 else: 157 return 0.0 158 159 160def average_grade_from_status_count(status_idx, status_count): 161 average_grade = 0 162 total_count = 0 163 for key in status_count.keys(): 164 if key not in (status_idx['TEST_NA'], status_idx['RUNNING']): 165 average_grade += (grade_from_status(status_idx, key) 166 * status_count[key]) 167 total_count += status_count[key] 168 if total_count != 0: 169 average_grade = average_grade / total_count 170 else: 171 average_grade = 0.0 172 return average_grade 173 174 175def shade_from_status_count(status_idx, status_count): 176 if not status_count: 177 return None 178 179 ## average_grade defines a shade of the box 180 ## 0 -> violet 181 ## 0.76 -> red 182 ## 0.88-> yellow 183 ## 1.0 -> green 184 average_grade = average_grade_from_status_count(status_idx, status_count) 185 186 ## find appropiate keyword from color_map 187 if average_grade<0.01: 188 shade = '0pct' 189 elif average_grade<0.75: 190 shade = '75pct' 191 elif average_grade<0.85: 192 shade = '85pct' 193 elif average_grade<0.90: 194 shade = '90pct' 195 elif average_grade<0.95: 196 shade = '95pct' 197 else: 198 shade = '100pct' 199 200 return shade 201 202 203def status_html(db, box_data, shade): 204 """ 205 status_count: dict mapping from status (integer key) to count 206 eg. { 'GOOD' : 4, 'FAIL' : 1 } 207 """ 208 status_count_subset = box_data.status_count.copy() 209 test_na = db.status_idx['TEST_NA'] 210 running = db.status_idx['RUNNING'] 211 good = db.status_idx['GOOD'] 212 213 status_count_subset[test_na] = 0 # Don't count TEST_NA 214 status_count_subset[running] = 0 # Don't count RUNNING 215 html = "%d / %d " % (status_count_subset.get(good, 0), 216 sum(status_count_subset.values())) 217 if test_na in box_data.status_count.keys(): 218 html += ' (%d N/A)' % box_data.status_count[test_na] 219 if running in box_data.status_count.keys(): 220 html += ' (%d running)' % box_data.status_count[running] 221 222 if box_data.reasons_list: 223 reasons_list = box_data.reasons_list 224 aggregated_reasons_list = \ 225 reason_qualifier.aggregate_reason_fields(reasons_list) 226 for reason in aggregated_reasons_list: 227 ## a bit of more postprocessing 228 ## to look nicer in a cell 229 ## in future: to do subtable within the cell 230 reason = reason.replace('<br>','\n') 231 reason = reason.replace('<','[').replace('>',']') 232 reason = reason.replace('|','\n').replace('&',' AND ') 233 reason = reason.replace('\n','<br>') 234 html += '<br>' + reason 235 236 tooltip = "" 237 for status in sorted(box_data.status_count.keys(), reverse = True): 238 status_word = db.status_word[status] 239 tooltip += "%d %s " % (box_data.status_count[status], status_word) 240 return (html,tooltip) 241 242 243def status_count_box(db, tests, link = None): 244 """ 245 Display a ratio of total number of GOOD tests 246 to total number of all tests in the group of tests. 247 More info (e.g. 10 GOOD, 2 WARN, 3 FAIL) is in tooltips 248 """ 249 if not tests: 250 return box(None, None) 251 252 status_count = {} 253 for test in tests: 254 count = status_count.get(test.status_num, 0) 255 status_count[test.status_num] = count + 1 256 return status_precounted_box(db, status_count, link) 257 258 259def status_precounted_box(db, box_data, link = None, 260 x_label = None, y_label = None): 261 """ 262 Display a ratio of total number of GOOD tests 263 to total number of all tests in the group of tests. 264 More info (e.g. 10 GOOD, 2 WARN, 3 FAIL) is in tooltips 265 """ 266 status_count = box_data.status_count 267 if not status_count: 268 return box(None, None) 269 270 shade = shade_from_status_count(db.status_idx, status_count) 271 html,tooltip = status_html(db, box_data, shade) 272 precounted_box = box(html, shade, False, link, tooltip, 273 x_label, y_label) 274 return precounted_box 275 276 277def print_table(matrix): 278 """ 279 matrix: list of lists of boxes, giving a matrix of data 280 Each of the inner lists is a row, not a column. 281 282 Display the given matrix of data as a table. 283 """ 284 285 print(('<table bgcolor="%s" cellspacing="1" cellpadding="5" ' 286 'style="margin-right: 200px;">') % ( 287 color_map['borders'])) 288 for row in matrix: 289 print('<tr>') 290 for element in row: 291 print(element.html()) 292 print('</tr>') 293 print('</table>') 294 295 296def print_main_header(): 297 hover_css="""\ 298a.info{ 299position:relative; /*this is the key*/ 300z-index:1 301color:#000; 302text-decoration:none} 303 304a.info:hover{z-index:25;} 305 306a.info span{display: none} 307 308a.info:hover span{ /*the span will display just on :hover state*/ 309display:block; 310position:absolute; 311top:1em; left:1em; 312min-width: 100px; 313overflow: visible; 314border:1px solid #036; 315background-color:#fff; color:#000; 316text-align: left 317} 318""" 319 print('<head><style type="text/css">') 320 print('a { text-decoration: none }') 321 print(hover_css) 322 print('</style></head>') 323 print('<h2>') 324 print('<a href="compose_query.cgi">Functional</a>') 325 print('   ') 326 327 328def group_name(group): 329 name = re.sub('_', '<br>', group.name) 330 if re.search('/', name): 331 (owner, machine) = name.split('/', 1) 332 name = owner + '<br>' + machine 333 return name 334