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