You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
9.7 KiB
330 lines
9.7 KiB
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
|