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