• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>&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
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 = '&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
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 = '&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
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&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
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 '&nbsp&nbsp&nbsp'
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