Home | History | Annotate | Download | only in tko
      1 import re
      2 import reason_qualifier
      3 
      4 # pylint: disable=missing-docstring
      5 
      6 color_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 
     30 def set_brief_mode():
     31     global _brief_mode
     32     _brief_mode = True
     33 
     34 
     35 def is_brief_mode():
     36     return _brief_mode
     37 
     38 
     39 def 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>&nbsp;&nbsp;&nbsp;</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 
     72 def 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 = '&nbsp;'
     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 '&nbsp;'
     90 
     91 
     92 class 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 = '&nbsp'
    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 
    143 def 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 
    156 def 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 
    171 def 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 
    199 def 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&nbsp;/&nbsp;%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&nbsp;N/A)' % box_data.status_count[test_na]
    215     if running in box_data.status_count.keys():
    216         html += ' (%d&nbsp;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 
    239 def 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 
    255 def 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 
    273 def 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 
    292 def 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 
    307 def print_main_header():
    308     hover_css="""\
    309 a.info{
    310 position:relative; /*this is the key*/
    311 z-index:1
    312 color:#000;
    313 text-decoration:none}
    314 
    315 a.info:hover{z-index:25;}
    316 
    317 a.info span{display: none}
    318 
    319 a.info:hover span{ /*the span will display just on :hover state*/
    320 display:block;
    321 position:absolute;
    322 top:1em; left:1em;
    323 min-width: 100px;
    324 overflow: visible;
    325 border:1px solid #036;
    326 background-color:#fff; color:#000;
    327 text-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 '&nbsp&nbsp&nbsp'
    337 
    338 
    339 def 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 
    346 def 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