Merge lp:~sylvain-pineau/checkbox/show_reports into lp:checkbox

Proposed by Sylvain Pineau
Status: Merged
Merged at revision: 1273
Proposed branch: lp:~sylvain-pineau/checkbox/show_reports
Merge into: lp:checkbox
Diff against target: 776 lines (+472/-86)
6 files modified
checkbox_cli/cli_interface.py (+97/-1)
checkbox_gtk/gtk_interface.py (+1/-1)
checkbox_urwid/urwid_interface.py (+323/-84)
debian/changelog (+1/-0)
plugins/launchpad_report.py (+48/-0)
plugins/report_prompt.py (+2/-0)
To merge this branch: bzr merge lp:~sylvain-pineau/checkbox/show_reports
Reviewer Review Type Date Requested Status
Marc Tardif (community) Approve
Sylvain Pineau (community) Needs Resubmitting
Ara Pulido (community) Needs Fixing
Review via email: mp+93856@code.launchpad.net

Description of the change

This branch provides additional screens for the gtk/cli and urwid ui to display test results.
Used in conjunction with checkbox-oem, the gtk ui also provides a bug report screen.

These changes come from the "patchy" checkbox maintained by oem-qa and worked well with checkbox-oem.
Once this merge proposal is accepted we could get rid of the oem patches branches for precise.

To post a comment you must log in.
Revision history for this message
Ara Pulido (ara) wrote :

There is some code (about the bug report) that needs to be removed as it won't be accepted FFe

review: Needs Fixing
1272. By Sylvain Pineau

Remove the gtk code of the bug report screen, useless without the corresponding plugin

Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

I've removed the bug report code making the change only related to the test report for all non qt interfaces

review: Needs Resubmitting
Revision history for this message
Marc Tardif (cr3) wrote :

I'm still looking at the code but, in the meanwhile, I'd appreciate if you could report a bug describing the problem this merge request fixes. Then, add the corresponding changelog entry in your branch and feel free to resubmit. Finally, link the bug to your branch. Thanks!

Revision history for this message
Marc Tardif (cr3) wrote :

On line 558 of checkbox_gtk/gtk_interface.py, you have the following diff:

- message_dialog.add_button(option, index)
+ button = getattr(Gtk, "STOCK_%s" % option.upper())
+ message_dialog.add_buttons(button, index)

The problem is that the message dialog is also used for prompting the user to restart or recover when running checkbox after the first time. However, there is not STOCK_RESTART nor STOCK_RECOVER attribute in Gtk. If you just revert those two lines to the previous single line, that should work.

review: Needs Fixing
Revision history for this message
Marc Tardif (cr3) wrote :

On line 728 of checkbox_urwid/urwid_interface.py, you have the following diff:

- self._collapse_children()
+ #self._collapse_children()

This gives the uneasy feeling that maybe the code was commented out by mistake. I'm not a big fan of commenting out code in general so, while you're making changes, I'd appreciate if you could remove the line completely. Thanks!

1273. By Sylvain Pineau

Updated changelog and fix after reviews

Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

Thanks for your comment Marc, I've fixed the branch.

review: Needs Resubmitting
Revision history for this message
Marc Tardif (cr3) wrote :

First, it seems that your last commit replaced some symlinks by accident:

bzr status -r 1272..
kind changed:
  configs@ (symlink => directory)
  data/websites/PNG_Color_Image_Ubuntu.png@ (symlink => file)
modified:
  checkbox_gtk/gtk_interface.py
  checkbox_urwid/urwid_interface.py
  debian/changelog

Second, I'm not seeing the show_report method in the user interface called anywhere. And, when I run the GTK interface, the view report button still shows the old report. Is the purpose that this code will only be called by checkbox-oem rather than checkbox?

review: Needs Fixing
1274. By Sylvain Pineau

Update plugins to take advantage of the report screen

Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

1) Even after rebranching the code, i don't see the symlink errors you mentioned.
2) It was an error, the report code is intended to be rendered by checkbox, not only checkbox-oem. I've updated the plugins to enable it.

review: Needs Resubmitting
1275. By Sylvain Pineau

Fix missing text parameter and use NotImplementedError to raise exception in show_report

Revision history for this message
Marc Tardif (cr3) wrote :

The first problem was my mistake, sorry about that. As for the second problem, I now see the report. However, do you think we should remove the "View report" link from the final screen? Otherwise, it seems strange to have two ways to view essentially the same information.

review: Needs Information
1276. By Sylvain Pineau

Remove the gtk ui implementation to just keep cli/urwid and avoid duplication with the submission.xml link

Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

The scope of the proposal is now limited to the cli/urwid interface, given that the gtk interface allows the tester to open the submission.xml to review the results.

review: Needs Resubmitting
Revision history for this message
Marc Tardif (cr3) wrote :

1. I unliked bug #937764 from this merge request which only seems to relate to bug #937657.

2. I would appreciate if you could rephrase bug #937657 as a problem rather than a feature request.

3. I updated the changelog entry to be less intimidating: Display results report in non-graphical interfaces.

4. I changed checking for the show_report attribute in plugins/report_prompt.py to catching the NotImplementError silently in plugins/launchpad_report.py instead.

Finally, I merged and pushed the rest of your changes.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'checkbox_cli/cli_interface.py'
--- checkbox_cli/cli_interface.py 2012-02-09 16:03:54 +0000
+++ checkbox_cli/cli_interface.py 2012-02-22 17:22:19 +0000
@@ -160,6 +160,94 @@
160 self.options.append(option)160 self.options.append(option)
161161
162162
163class CLIReportDialog(CLIDialog):
164 """
165 Display test results
166 """
167 STATUS = {'pass': '{0}',
168 'fail': '{0}'}
169
170 def __init__(self, text, results):
171 super(CLIReportDialog, self).__init__(text)
172 self.results = results
173
174 def run(self):
175 """
176 Show root of the tree
177 and provide the ability to further display subtress
178 """
179 root = self.results
180 title = self.text
181 self._display(title, root)
182
183 def _is_suite(self, root):
184 """
185 Return True if root contains a suite
186 that is, a job containing other jobs
187 """
188 return all(issubclass(type(value), dict)
189 for value in root.itervalues())
190
191 def _display(self, title, root):
192 """
193 Display dialog until user decides to exit
194 (recursively for subtrees)
195 """
196 while True:
197 self.put_newline()
198 self.put_newline()
199 self.put_line(title)
200 self.put_newline()
201
202 keys = []
203 options = []
204 def add_option(option, key=None):
205 """
206 Add option to list
207 and generate automatic key value
208 if not provided
209 """
210 if key is None:
211 key = string.lowercase[len(keys)]
212 keys.append(key)
213 options.append(option)
214
215 for job_name, job_data in sorted(root.iteritems()):
216 if self._is_suite(job_data):
217 add_option(job_name)
218 self.put_line('{key}: {option}'
219 .format(key=keys[-1],
220 option=options[-1]))
221 else:
222 job_status = job_data.get('status')
223 status_string = (self.STATUS.get(job_status, '{0}')
224 .format(job_status))
225 self.put_line(' {name} [{status}]'
226 .format(name=job_name,
227 status=status_string))
228
229 add_option(_("Space when finished"), " ")
230 self.put_line('{key}: {option}'
231 .format(key=keys[-1],
232 option=options[-1]))
233
234 response = self.get(_("Please choose (%s): ") % ("/".join(keys)))
235
236 if response != ' ':
237 try:
238 selected_option = options[keys.index(response)]
239 except ValueError:
240 # Display again menu
241 continue
242
243 # Display new menu with the contents of the selected option
244 self._display(selected_option, root[selected_option])
245 else:
246 # Exit from this menu display
247 # (display again parent menu or exit)
248 break
249
250
163class CLITextDialog(CLIDialog):251class CLITextDialog(CLIDialog):
164252
165 limit = 255253 limit = 255
@@ -271,7 +359,6 @@
271359
272 def show_tree(self, text, options={}, default={}):360 def show_tree(self, text, options={}, default={}):
273 keys = sorted(options.keys())361 keys = sorted(options.keys())
274 values = [options[k] for k in keys]
275362
276 dialog = CLIChoiceDialog(text)363 dialog = CLIChoiceDialog(text)
277 for option in keys:364 for option in keys:
@@ -314,6 +401,15 @@
314401
315 return results402 return results
316403
404
405 def show_report(self, text, results):
406 """
407 Show test case results in a tree hierarchy
408 """
409 dialog = CLIReportDialog(text, results)
410 dialog.run()
411
412
317 def show_test(self, test, runner):413 def show_test(self, test, runner):
318 options = list([ANSWER_TO_OPTION[a] for a in ALL_ANSWERS])414 options = list([ANSWER_TO_OPTION[a] for a in ALL_ANSWERS])
319 if "command" in test:415 if "command" in test:
320416
=== modified file 'checkbox_gtk/gtk_interface.py'
--- checkbox_gtk/gtk_interface.py 2012-02-09 16:03:54 +0000
+++ checkbox_gtk/gtk_interface.py 2012-02-22 17:22:19 +0000
@@ -116,7 +116,7 @@
116 for radio_button, value in map.items():116 for radio_button, value in map.items():
117 if self._get_widget(radio_button).get_active():117 if self._get_widget(radio_button).get_active():
118 return value118 return value
119 raise Exception, "Failed to map radio_button."119 raise Exception("Failed to map radio_button.")
120120
121 def _get_label(self, name):121 def _get_label(self, name):
122 widget = self._get_widget(name)122 widget = self._get_widget(name)
123123
=== modified file 'checkbox_urwid/urwid_interface.py'
--- checkbox_urwid/urwid_interface.py 2012-02-09 16:03:54 +0000
+++ checkbox_urwid/urwid_interface.py 2012-02-22 17:22:19 +0000
@@ -19,6 +19,7 @@
19import urwid19import urwid
2020
21import re, string21import re, string
22from operator import itemgetter
22from gettext import gettext as _23from gettext import gettext as _
2324
24from checkbox.user_interface import (UserInterface, NEXT, PREV,25from checkbox.user_interface import (UserInterface, NEXT, PREV,
@@ -37,7 +38,12 @@
37 ('button focused', 'white', 'dark blue'),38 ('button focused', 'white', 'dark blue'),
38 ('highlight', 'black', 'dark cyan'),39 ('highlight', 'black', 'dark cyan'),
39 ('highlight focused', 'white', 'dark blue'),40 ('highlight focused', 'white', 'dark blue'),
41 ('fail', 'light red', 'dark cyan'),
42 ('pass', 'light green', 'dark cyan'),
43 ('result', 'light gray', 'dark cyan'),
40 )44 )
45 PALETTE_MEMBERS = set(color_spec[0]
46 for color_spec in PALETTE)
41 header = None47 header = None
42 footer = None48 footer = None
4349
@@ -368,12 +374,12 @@
368 """374 """
369 Create a tree node and all its children375 Create a tree node and all its children
370 """376 """
371 widget = TreeNodeWidget(name, parent)377 widget = SelectableTreeNodeWidget(name, parent)
372 urwid.signals.connect_signal(widget, 'change',378 urwid.signals.connect_signal(widget, 'change',
373 widget.changed_cb, self.walker)379 widget.changed_cb, self.walker)
374380
375 if isinstance(data, dict):381 if isinstance(data, dict):
376 items = sorted(data.iteritems(), key=lambda item: item[0])382 items = sorted(data.iteritems(), key=itemgetter(0))
377 for children_name, children_data in items:383 for children_name, children_data in items:
378 child_widget = self.create_tree(children_name, children_data, widget)384 child_widget = self.create_tree(children_name, children_data, widget)
379 widget.append(child_widget)385 widget.append(child_widget)
@@ -402,14 +408,15 @@
402408
403 # Show tree409 # Show tree
404 self.option_widgets = []410 self.option_widgets = []
405 items = sorted(self.options.iteritems(), key=lambda item: item[0])411 items = sorted(self.options.iteritems(),
412 key=itemgetter(0))
406 for name, data in items:413 for name, data in items:
407 widget = self.create_tree(name, data)414 widget = self.create_tree(name, data)
408 self.option_widgets.append(widget)415 self.option_widgets.append(widget)
409 self.walker.append(widget)416 self.walker.append(widget)
410417
411 self._set_default([node for node in self.walker418 self._set_default([node for node in self.walker
412 if isinstance(node, TreeNodeWidget)],419 if isinstance(node, SelectableTreeNodeWidget)],
413 self.default)420 self.default)
414421
415 # Show buttons422 # Show buttons
@@ -422,9 +429,131 @@
422 self.walker.append(buttons_box)429 self.walker.append(buttons_box)
423430
424431
432class ReportDialog(ChoiceDialog):
433 """
434 Display test results dialog
435 """
436 footer = urwid.AttrMap(urwid.Columns((urwid.Text('Arrow keys/Page Up/Page Down: Move'),
437 urwid.Text(''),
438 urwid.Text('+/-/Enter/Space: Expand/Collapse'))),
439 'footer')
440
441 def __init__(self, text, results):
442 Dialog.__init__(self, text)
443 self.results = results
444
445
446 def _get_tree_node(self, node):
447 """
448 Get tree node even if a column is wrapping it
449 """
450 if issubclass(type(node), TreeNodeWidget):
451 return node
452 elif issubclass(type(node), urwid.Columns):
453 for widget in child.widget_list:
454 if issubclass(type(widget), TreeNodeWidget):
455 return widget
456 return node
457
458
459 def expand_all_clicked_cb(self, button):
460 """
461 Expand all elements in the tree to see results
462 """
463 for tree_node in [self._get_tree_node(node)
464 for node in self.root_nodes]:
465 tree_node.expand(expand_all=True)
466
467
468 def collapse_all_clicked_cb(self, button):
469 """
470 Collapse all elements in the tree to see results
471 """
472 for tree_node in [self._get_tree_node(node)
473 for node in self.root_nodes]:
474 tree_node.collapse(collapse_all=True)
475
476
477 def next_button_clicked_cb(self, button):
478 """
479 Set direction, response and exit
480 """
481 self.direction = NEXT
482 raise urwid.ExitMainLoop
483
484
485 def previous_button_clicked_cb(self, button):
486 """
487 Set direction, response and exit
488 """
489 self.direction = PREV
490 raise urwid.ExitMainLoop
491
492
493 def create_tree(self, name, data, parent=None):
494 """
495 Create a tree node and all its children
496 """
497 widget = TreeNodeWidget(name, parent)
498 urwid.signals.connect_signal(widget, 'change',
499 widget.changed_cb, self.walker)
500
501 items = sorted(data.iteritems(), key=itemgetter(0))
502 for child_name, child_data in items:
503 is_suite = all(issubclass(type(value), dict)
504 for value in child_data.itervalues())
505
506 if is_suite:
507 child_widget = self.create_tree(child_name,
508 child_data,
509 widget)
510 else:
511 result=child_data['status']
512
513 # Use color specification for result
514 # if found or default one
515 attr = (result
516 if result in self.PALETTE_MEMBERS
517 else 'result')
518
519 child_widget = urwid.Columns(
520 (TreeNodeWidget(child_name, widget),
521 urwid.AttrMap(urwid.Text((attr, result)),
522 'highlight', 'highlight focused')))
523 widget.append(child_widget)
524
525 return widget
526
527
528 def show(self):
529 """
530 Display dialog text, options tree and buttons
531 """
532 # Show text
533 Dialog.show(self)
534
535 # Show tree
536 items = sorted(self.results.iteritems(),
537 key=itemgetter(0))
538 for name, data in items:
539 widget = self.create_tree(name, data)
540 self.walker.append(widget)
541
542 self.root_nodes = [node for node in self.walker]
543
544 # Show buttons
545 labels = ((_('Expand All'), self.expand_all_clicked_cb),
546 (_('Collapse All'), self.collapse_all_clicked_cb),
547 (_('Previous'), self.previous_button_clicked_cb),
548 (_('Next'), self.next_button_clicked_cb))
549 buttons_box = self.create_buttons(labels)
550 self.walker.append(urwid.Divider())
551 self.walker.append(buttons_box)
552
553
425class TreeNodeWidget(urwid.WidgetWrap):554class TreeNodeWidget(urwid.WidgetWrap):
426 """555 """
427 Implementation of a node in a tree that can be selected/deselected556 Implementation of a node in a tree that can be expanded/unexpanded
428 """557 """
429 signals = ['change']558 signals = ['change']
430559
@@ -436,17 +565,45 @@
436565
437 self.expanded = False566 self.expanded = False
438567
439 # Use a checkbox as internal representation of the widget568 w = self._get_widget()
440 self.checkbox = urwid.CheckBox(self._get_label())
441 w = urwid.AttrMap(self.checkbox, 'highlight', 'highlight focused')
442 super(TreeNodeWidget, self).__init__(w)569 super(TreeNodeWidget, self).__init__(w)
443570
444571
572 def _get_widget(self):
573 """
574 Create widget that is wrapped by this class
575 """
576 self.widget = urwid.Text(self._get_label())
577 w = urwid.AttrMap(self.widget, 'highlight', 'highlight focused')
578 return w
579
580
581 def _update_label(self):
582 """
583 Update text label
584 """
585 self.widget.set_text(self._get_label())
586
587
588 def _get_node(self, child):
589 """
590 Get TreeNode directly without traversing Columns
591 """
592 if issubclass(type(child), TreeNodeWidget):
593 return child
594 elif issubclass(type(child), urwid.Columns):
595 for widget in child.widget_list:
596 if issubclass(type(widget), TreeNodeWidget):
597 return widget
598 return child
599
600
445 def __iter__(self):601 def __iter__(self):
446 """602 """
447 Iterate over children nodes603 Iterate over children nodes
448 """604 """
449 return iter(self.children)605 return iter([self._get_node(child)
606 for child in self.children])
450607
451608
452 def __len__(self):609 def __len__(self):
@@ -476,69 +633,17 @@
476 return True633 return True
477634
478635
479 @property
480 def state(self):
481 """
482 Get state from checkbox widget
483 """
484 return self.checkbox.get_state()
485
486
487 @state.setter
488 def state(self, value):
489 """
490 Set state to checkbox widget
491 """
492 self.checkbox.set_state(value)
493
494
495 def set_ancestors_state(self, new_state):
496 """
497 Set the state of all ancestors consistently
498 """
499 # If child is set, then all ancestors must be set
500 if new_state:
501 parent = self.parent
502 while parent:
503 parent.state = new_state
504 parent = parent.parent
505 # If child is not set, then all ancestors mustn't be set
506 # unless another child of the ancestor is set
507 else:
508 parent = self.parent
509 while parent:
510 if any((child.state
511 for child in parent)):
512 break
513 parent.state = new_state
514 parent = parent.parent
515
516
517 def set_children_state(self, new_state):
518 """
519 Set the state of all children recursively
520 """
521 self.state = new_state
522 for child in self:
523 child.set_children_state(new_state)
524
525
526 def keypress(self, size, key):636 def keypress(self, size, key):
527 """637 """
528 Use key events to select checkbox and expand tree hierarchy638 Use key events to expand/collapse tree hierarchy
529 """639 """
530640 if self.children:
531 if key == ' ':641 if (key in ('+', 'enter', ' ')
532 new_state = not self.state642 and self.expanded == False):
533 self.state = new_state
534 self.set_children_state(new_state)
535 self.set_ancestors_state(new_state)
536 return None
537 elif self.children:
538 if key in ('+', 'enter') and self.expanded == False:
539 urwid.signals.emit_signal(self, 'change')643 urwid.signals.emit_signal(self, 'change')
540 return None644 return None
541 elif key in ('-', 'enter') and self.expanded == True:645 elif (key in ('-', 'enter', ' ')
646 and self.expanded == True):
542 urwid.signals.emit_signal(self, 'change')647 urwid.signals.emit_signal(self, 'change')
543 return None648 return None
544649
@@ -547,16 +652,10 @@
547652
548 def mouse_event(self, size, event, button, col, row, focus):653 def mouse_event(self, size, event, button, col, row, focus):
549 """654 """
550 Use mouse events to select checkbox and expand tree hierarchy655 Use mouse events to expand/collapse tree hierarchy
551 """656 """
552 # Left click event
553 if button == 1:
554 new_state = not self.state
555 self.state = new_state
556 self.set_children_state(new_state)
557 self.set_ancestors_state(new_state)
558 # Ignore button release event657 # Ignore button release event
559 elif button == 0:658 if button == 0:
560 pass659 pass
561 else:660 else:
562 urwid.signals.emit_signal(self, 'change')661 urwid.signals.emit_signal(self, 'change')
@@ -581,11 +680,15 @@
581 return label680 return label
582681
583682
584 def _update_label(self):683 def collapse(self, collapse_all=False):
585 """684 """
586 Update text label685 Collapse node
587 """686 """
588 self.checkbox.set_label(self._get_label())687 if self.expanded == True:
688 urwid.signals.emit_signal(self, 'change')
689
690 if collapse_all:
691 self._collapse_children()
589692
590693
591 def _collapse_children(self):694 def _collapse_children(self):
@@ -593,12 +696,24 @@
593 Collapse all children696 Collapse all children
594 """697 """
595 for child in self:698 for child in self:
699 child._collapse_children()
596 if child.expanded:700 if child.expanded:
597 child._collapse_children()
598 child.expanded = False701 child.expanded = False
599 child._update_label()702 child._update_label()
600703
601704
705 def expand(self, expand_all=False):
706 """
707 Expand node
708 """
709 if self.expanded == False:
710 urwid.signals.emit_signal(self, 'change')
711
712 if expand_all:
713 for child in self:
714 child.expand(expand_all)
715
716
602 def changed_cb(self, walker):717 def changed_cb(self, walker):
603 """718 """
604 Handle node expansion in the tree719 Handle node expansion in the tree
@@ -610,18 +725,134 @@
610 del_end_position = (del_start_position +725 del_end_position = (del_start_position +
611 len(self) - 1)726 len(self) - 1)
612 del walker[del_start_position:del_end_position]727 del walker[del_start_position:del_end_position]
613 self._collapse_children()
614 self.expanded = False728 self.expanded = False
615 else:729 else:
616 insert_position = position + 1730 insert_position = position + 1
617731
618 # Append widgets to the list732 # Append widgets to the list
619 walker[insert_position:insert_position] = self.children733 subtree = list(self._get_subtree())
734 walker[insert_position:insert_position] = subtree
620 self.expanded = True735 self.expanded = True
621736
622 self._update_label()737 self._update_label()
623738
624739
740 def _get_subtree(self):
741 """
742 Return subtree with expanded children
743 """
744 for child in self.children:
745 yield child
746
747 child_node = self._get_node(child)
748 if child_node.expanded:
749 for descendant in child._get_subtree():
750 yield descendant
751
752
753class SelectableTreeNodeWidget(TreeNodeWidget):
754 """
755 Implementation of a node in a tree that can be selected/deselected
756 """
757 def __init__(self, name, parent=None):
758 super(SelectableTreeNodeWidget, self).__init__(name, parent)
759
760
761 def _get_widget(self):
762 """
763 Create widget that is wrapped by this class
764 """
765 # Use a checkbox to preserve widget selection stat
766 self.widget = urwid.CheckBox(self._get_label())
767 w = urwid.AttrMap(self.widget, 'highlight', 'highlight focused')
768 return w
769
770
771 def _update_label(self):
772 """
773 Update text label
774 """
775 self.widget.set_label(self._get_label())
776
777
778 @property
779 def state(self):
780 """
781 Get state from checkbox widget
782 """
783 return self.widget.get_state()
784
785
786 @state.setter
787 def state(self, value):
788 """
789 Set state to checkbox widget
790 """
791 self.widget.set_state(value)
792
793
794 def set_ancestors_state(self, new_state):
795 """
796 Set the state of all ancestors consistently
797 """
798 # If child is set, then all ancestors must be set
799 if new_state:
800 parent = self.parent
801 while parent:
802 parent.state = new_state
803 parent = parent.parent
804 # If child is not set, then all ancestors mustn't be set
805 # unless another child of the ancestor is set
806 else:
807 parent = self.parent
808 while parent:
809 if any((child.state
810 for child in parent)):
811 break
812 parent.state = new_state
813 parent = parent.parent
814
815
816 def set_children_state(self, new_state):
817 """
818 Set the state of all children recursively
819 """
820 self.state = new_state
821 for child in self:
822 child.set_children_state(new_state)
823
824
825 def keypress(self, size, key):
826 """
827 Use key events to select checkbox and expand tree hierarchy
828 """
829
830 if key == ' ':
831 new_state = not self.state
832 self.state = new_state
833 self.set_children_state(new_state)
834 self.set_ancestors_state(new_state)
835 return None
836
837 return super(SelectableTreeNodeWidget, self).keypress(size, key)
838
839
840 def mouse_event(self, size, event, button, col, row, focus):
841 """
842 Use mouse events to select checkbox and expand tree hierarchy
843 """
844 # Left click event
845 if button == 1:
846 new_state = not self.state
847 self.state = new_state
848 self.set_children_state(new_state)
849 self.set_ancestors_state(new_state)
850 return True
851
852 return (super(SelectableTreeNodeWidget, self)
853 .mouse_event(size, event, button, col, row, focus))
854
855
625class ProgressDialog(Dialog):856class ProgressDialog(Dialog):
626 """857 """
627 Show progress through a bar858 Show progress through a bar
@@ -753,6 +984,14 @@
753 return dialog.response984 return dialog.response
754985
755986
987 def show_report(self, text, results):
988 """
989 Show test case results in a tree hierarchy
990 """
991 dialog = ReportDialog(text, results).run()
992 self.direction = dialog.direction
993
994
756 def show_test(self, test, runner):995 def show_test(self, test, runner):
757 """996 """
758 Show test description, radio buttons to set result997 Show test description, radio buttons to set result
@@ -792,7 +1031,7 @@
792 test['data'] = dialog.input1031 test['data'] = dialog.input
793 test['status'] = ANSWER_TO_STATUS[answer]1032 test['status'] = ANSWER_TO_STATUS[answer]
794 self.direction = dialog.direction1033 self.direction = dialog.direction
795 return self.response1034 return dialog.response
7961035
7971036
798 def show_info(self, text, options=[], default=None):1037 def show_info(self, text, options=[], default=None):
7991038
=== modified file 'debian/changelog'
--- debian/changelog 2012-02-20 08:22:59 +0000
+++ debian/changelog 2012-02-22 17:22:19 +0000
@@ -5,6 +5,7 @@
5 5
6 [Sylvain Pineau]6 [Sylvain Pineau]
7 * Fix depends fields in info and suspend test suites (LP: #934051) 7 * Fix depends fields in info and suspend test suites (LP: #934051)
8 * Display results report before upload (LP: #937657)
89
9 [Brendan Donegan]10 [Brendan Donegan]
10 * Typo in command for for miscellanea/virtualization-check (LP: #934243)11 * Typo in command for for miscellanea/virtualization-check (LP: #934243)
1112
=== modified file 'plugins/launchpad_report.py'
--- plugins/launchpad_report.py 2012-02-07 20:39:15 +0000
+++ plugins/launchpad_report.py 2012-02-22 17:22:19 +0000
@@ -66,6 +66,8 @@
66 ("report-package", self.report_package),66 ("report-package", self.report_package),
67 ("report-uname", self.report_uname),67 ("report-uname", self.report_uname),
68 ("report-system_id", self.report_system_id),68 ("report-system_id", self.report_system_id),
69 ("report-suites", self.report_suites),
70 ("report-review", self.report_review),
69 ("report-tests", self.report_tests)]:71 ("report-tests", self.report_tests)]:
70 self._manager.reactor.call_on(rt, rh)72 self._manager.reactor.call_on(rt, rh)
7173
@@ -136,6 +138,7 @@
136 self._report["summary"]["system_id"] = system_id138 self._report["summary"]["system_id"] = system_id
137139
138 def report_tests(self, tests):140 def report_tests(self, tests):
141 self.tests = tests
139 for test in tests:142 for test in tests:
140 question = {143 question = {
141 "name": test["name"],144 "name": test["name"],
@@ -167,5 +170,50 @@
167170
168 self._manager.reactor.fire("launchpad-report", self.filename)171 self._manager.reactor.fire("launchpad-report", self.filename)
169172
173 def report_review(self, interface):
174 """
175 Show test report in the interface
176 """
177 report = {}
178
179 def add_job(job):
180 is_suite = 'type' in job and job['type'] == 'suite'
181 if 'suite' in job:
182 suite_name = job['suite']
183 parent_node = add_job(self.suites[suite_name])
184
185 if is_suite:
186 if job['description'] in parent_node:
187 return parent_node[job['description']]
188
189 node = {}
190 parent_node[job['description']] = node
191 return node
192 parent_node[job['name']] = job
193 else:
194 if is_suite:
195 field = 'description'
196 else:
197 field = 'name'
198
199 if job[field] in report:
200 return report[job[field]]
201
202 node = {}
203 report[job[field]] = node
204 return node
205
206 for test in self.tests:
207 add_job(test)
208
209 interface.show_report("Test case results report", report)
210
211 def report_suites(self, suites):
212 """
213 Get tests results and store it
214 to display them later
215 """
216 self.suites = dict([(suite['name'], suite) for suite in suites])
217
170218
171factory = LaunchpadReport219factory = LaunchpadReport
172220
=== modified file 'plugins/report_prompt.py'
--- plugins/report_prompt.py 2011-09-08 14:52:33 +0000
+++ plugins/report_prompt.py 2012-02-22 17:22:19 +0000
@@ -40,6 +40,8 @@
40 self._manager.reactor.fire, "report")40 self._manager.reactor.fire, "report")
4141
42 self._manager.reactor.cancel_call(event_id)42 self._manager.reactor.cancel_call(event_id)
43 if hasattr(interface, 'show_report'):
44 self._manager.reactor.fire("report-review", interface)
4345
4446
45factory = ReportPrompt47factory = ReportPrompt

Subscribers

People subscribed via source and target branches