| import re |
| import reason_qualifier |
| |
| # pylint: disable=missing-docstring |
| |
| color_map = { |
| 'header' : '#e5e5c0', # greyish yellow |
| 'blank' : '#ffffff', # white |
| 'plain_text' : '#e5e5c0', # greyish yellow |
| 'borders' : '#bbbbbb', # grey |
| 'white' : '#ffffff', # white |
| 'green' : '#66ff66', # green |
| 'yellow' : '#fffc00', # yellow |
| 'red' : '#ff6666', # red |
| |
| #### additional keys for shaded color of a box |
| #### depending on stats of GOOD/FAIL |
| '100pct' : '#32CD32', # green, 94% to 100% of success |
| '95pct' : '#c0ff80', # step twrds yellow, 88% to 94% of success |
| '90pct' : '#ffff00', # yellow, 82% to 88% |
| '85pct' : '#ffc040', # 76% to 82% |
| '75pct' : '#ff4040', # red, 1% to 76% |
| '0pct' : '#d080d0', # violet, <1% of success |
| |
| } |
| |
| _brief_mode = False |
| |
| |
| def set_brief_mode(): |
| global _brief_mode |
| _brief_mode = True |
| |
| |
| def is_brief_mode(): |
| return _brief_mode |
| |
| |
| def color_keys_row(): |
| """ Returns one row table with samples of 'NNpct' colors |
| defined in the color_map |
| and numbers of corresponding %% |
| """ |
| ### This function does not require maintenance in case of |
| ### color_map augmenting - as long as |
| ### color keys for box shading have names that end with 'pct' |
| keys = filter(lambda key: key.endswith('pct'), color_map.keys()) |
| def num_pct(key): |
| return int(key.replace('pct','')) |
| keys.sort(key=num_pct) |
| html = '' |
| for key in keys: |
| html+= "\t\t\t<td bgcolor =%s> </td>\n"\ |
| % color_map[key] |
| hint = key.replace('pct',' %') |
| if hint[0]<>'0': ## anything but 0 % |
| hint = 'to ' + hint |
| html+= "\t\t\t<td> %s </td>\n" % hint |
| |
| html = """ |
| <table width = "500" border="0" cellpadding="2" cellspacing="2">\n |
| <tbody>\n |
| <tr>\n |
| %s |
| </tr>\n |
| </tbody> |
| </table><br> |
| """ % html |
| return html |
| |
| |
| def calculate_html(link, data, tooltip=None, row_label=None, column_label=None): |
| if not is_brief_mode(): |
| hover_text = '%s:%s' % (row_label, column_label) |
| if data: ## cell is not empty |
| hover_text += '<br>%s' % tooltip |
| else: |
| ## avoid "None" printed in empty cells |
| data = ' ' |
| html = ('<center><a class="info" href="%s">' |
| '%s<span>%s</span></a></center>' % |
| (link, data, hover_text)) |
| return html |
| # no hover if embedded into AFE but links shall redirect to new window |
| if data: ## cell is non empty |
| html = '<a href="%s" target="_blank">%s</a>' % (link, data) |
| return html |
| else: ## cell is empty |
| return ' ' |
| |
| |
| class box: |
| def __init__(self, data, color_key = None, header = False, link = None, |
| tooltip = None, row_label = None, column_label = None): |
| |
| ## in brief mode we display grid table only and nothing more |
| ## - mouse hovering feature is stubbed in brief mode |
| ## - any link opens new window or tab |
| |
| redirect = "" |
| if is_brief_mode(): |
| ## we are acting under AFE |
| ## any link shall open new window |
| redirect = " target=NEW" |
| |
| if data: |
| data = "<tt>%s</tt>" % data |
| |
| if link and not tooltip: |
| ## FlipAxis corner, column and row headers |
| self.data = ('<a href="%s"%s>%s</a>' % |
| (link, redirect, data)) |
| else: |
| self.data = calculate_html(link, data, tooltip, |
| row_label, column_label) |
| |
| if color_map.has_key(color_key): |
| self.color = color_map[color_key] |
| elif header: |
| self.color = color_map['header'] |
| elif data: |
| self.color = color_map['plain_text'] |
| else: |
| self.color = color_map['blank'] |
| self.header = header |
| |
| |
| def html(self): |
| if self.data: |
| data = self.data |
| else: |
| data = ' ' |
| |
| if self.header: |
| box_html = 'th' |
| else: |
| box_html = 'td' |
| |
| return "<%s bgcolor=%s>%s</%s>" % \ |
| (box_html, self.color, data, box_html) |
| |
| |
| def grade_from_status(status_idx, status): |
| # % of goodness |
| # GOOD (6) -> 1 |
| # TEST_NA (8) is not counted |
| # ## If the test doesn't PASS, it FAILS |
| # else -> 0 |
| |
| if status == status_idx['GOOD']: |
| return 1.0 |
| else: |
| return 0.0 |
| |
| |
| def average_grade_from_status_count(status_idx, status_count): |
| average_grade = 0 |
| total_count = 0 |
| for key in status_count.keys(): |
| if key not in (status_idx['TEST_NA'], status_idx['RUNNING']): |
| average_grade += (grade_from_status(status_idx, key) |
| * status_count[key]) |
| total_count += status_count[key] |
| if total_count != 0: |
| average_grade = average_grade / total_count |
| else: |
| average_grade = 0.0 |
| return average_grade |
| |
| |
| def shade_from_status_count(status_idx, status_count): |
| if not status_count: |
| return None |
| |
| ## average_grade defines a shade of the box |
| ## 0 -> violet |
| ## 0.76 -> red |
| ## 0.88-> yellow |
| ## 1.0 -> green |
| average_grade = average_grade_from_status_count(status_idx, status_count) |
| |
| ## find appropiate keyword from color_map |
| if average_grade<0.01: |
| shade = '0pct' |
| elif average_grade<0.75: |
| shade = '75pct' |
| elif average_grade<0.85: |
| shade = '85pct' |
| elif average_grade<0.90: |
| shade = '90pct' |
| elif average_grade<0.95: |
| shade = '95pct' |
| else: |
| shade = '100pct' |
| |
| return shade |
| |
| |
| def status_html(db, box_data, shade): |
| """ |
| status_count: dict mapping from status (integer key) to count |
| eg. { 'GOOD' : 4, 'FAIL' : 1 } |
| """ |
| status_count_subset = box_data.status_count.copy() |
| test_na = db.status_idx['TEST_NA'] |
| running = db.status_idx['RUNNING'] |
| good = db.status_idx['GOOD'] |
| |
| status_count_subset[test_na] = 0 # Don't count TEST_NA |
| status_count_subset[running] = 0 # Don't count RUNNING |
| html = "%d / %d " % (status_count_subset.get(good, 0), |
| sum(status_count_subset.values())) |
| if test_na in box_data.status_count.keys(): |
| html += ' (%d N/A)' % box_data.status_count[test_na] |
| if running in box_data.status_count.keys(): |
| html += ' (%d running)' % box_data.status_count[running] |
| |
| if box_data.reasons_list: |
| reasons_list = box_data.reasons_list |
| aggregated_reasons_list = \ |
| reason_qualifier.aggregate_reason_fields(reasons_list) |
| for reason in aggregated_reasons_list: |
| ## a bit of more postprocessing |
| ## to look nicer in a cell |
| ## in future: to do subtable within the cell |
| reason = reason.replace('<br>','\n') |
| reason = reason.replace('<','[').replace('>',']') |
| reason = reason.replace('|','\n').replace('&',' AND ') |
| reason = reason.replace('\n','<br>') |
| html += '<br>' + reason |
| |
| tooltip = "" |
| for status in sorted(box_data.status_count.keys(), reverse = True): |
| status_word = db.status_word[status] |
| tooltip += "%d %s " % (box_data.status_count[status], status_word) |
| return (html,tooltip) |
| |
| |
| def status_count_box(db, tests, link = None): |
| """ |
| Display a ratio of total number of GOOD tests |
| to total number of all tests in the group of tests. |
| More info (e.g. 10 GOOD, 2 WARN, 3 FAIL) is in tooltips |
| """ |
| if not tests: |
| return box(None, None) |
| |
| status_count = {} |
| for test in tests: |
| count = status_count.get(test.status_num, 0) |
| status_count[test.status_num] = count + 1 |
| return status_precounted_box(db, status_count, link) |
| |
| |
| def status_precounted_box(db, box_data, link = None, |
| x_label = None, y_label = None): |
| """ |
| Display a ratio of total number of GOOD tests |
| to total number of all tests in the group of tests. |
| More info (e.g. 10 GOOD, 2 WARN, 3 FAIL) is in tooltips |
| """ |
| status_count = box_data.status_count |
| if not status_count: |
| return box(None, None) |
| |
| shade = shade_from_status_count(db.status_idx, status_count) |
| html,tooltip = status_html(db, box_data, shade) |
| precounted_box = box(html, shade, False, link, tooltip, |
| x_label, y_label) |
| return precounted_box |
| |
| |
| def print_table(matrix): |
| """ |
| matrix: list of lists of boxes, giving a matrix of data |
| Each of the inner lists is a row, not a column. |
| |
| Display the given matrix of data as a table. |
| """ |
| |
| print ('<table bgcolor="%s" cellspacing="1" cellpadding="5" ' |
| 'style="margin-right: 200px;">') % ( |
| color_map['borders']) |
| for row in matrix: |
| print '<tr>' |
| for element in row: |
| print element.html() |
| print '</tr>' |
| print '</table>' |
| |
| |
| def print_main_header(): |
| hover_css="""\ |
| a.info{ |
| position:relative; /*this is the key*/ |
| z-index:1 |
| color:#000; |
| text-decoration:none} |
| |
| a.info:hover{z-index:25;} |
| |
| a.info span{display: none} |
| |
| a.info:hover span{ /*the span will display just on :hover state*/ |
| display:block; |
| position:absolute; |
| top:1em; left:1em; |
| min-width: 100px; |
| overflow: visible; |
| border:1px solid #036; |
| background-color:#fff; color:#000; |
| text-align: left |
| } |
| """ |
| print '<head><style type="text/css">' |
| print 'a { text-decoration: none }' |
| print hover_css |
| print '</style></head>' |
| print '<h2>' |
| print '<a href="compose_query.cgi">Functional</a>' |
| print '   ' |
| |
| |
| def group_name(group): |
| name = re.sub('_', '<br>', group.name) |
| if re.search('/', name): |
| (owner, machine) = name.split('/', 1) |
| name = owner + '<br>' + machine |
| return name |