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