Merge lp:~canonical-platform-qa/ubuntu-community-testing/improved-reporting into lp:ubuntu-community-testing

Proposed by Christopher Lee on 2015-10-12
Status: Merged
Approved by: Christopher Lee on 2015-10-16
Approved revision: 50
Merged at revision: 35
Proposed branch: lp:~canonical-platform-qa/ubuntu-community-testing/improved-reporting
Merge into: lp:ubuntu-community-testing
Diff against target: 704 lines (+466/-124)
9 files modified
ubuntu_pt_community/pages/pages.py (+40/-9)
ubuntu_pt_community/pages/reports.py (+235/-91)
ubuntu_pt_community/templates/base.html (+1/-0)
ubuntu_pt_community/templates/results/all_testsuite_overview.html (+45/-0)
ubuntu_pt_community/templates/results/all_testsuites.html (+0/-23)
ubuntu_pt_community/templates/results/index.html (+1/-1)
ubuntu_pt_community/templates/results/overview_base.html (+33/-0)
ubuntu_pt_community/templates/results/test_drilldown_details.html (+69/-0)
ubuntu_pt_community/templates/results/testsuite_drilldown.html (+42/-0)
To merge this branch: bzr merge lp:~canonical-platform-qa/ubuntu-community-testing/improved-reporting
Reviewer Review Type Date Requested Status
Brendan Donegan (community) 2015-10-12 Needs Fixing on 2015-10-15
Review via email: mp+274093@code.launchpad.net

Commit message

Improved reporting for uploaded test results.

Description of the change

Better reporting for the uploaded results.

Reporting types included (incl. reference to the image lines in the follow up branch.)

  - [1] All application testsuite review
    -> Shows breakdown of all application and their tests (pass/skip/fail
  - [2] Singular application testsuite (same as above but shows single application (i.e. 2015.com.ubuntu.clock)
  - [3] Testsuite overview (click on an apps testsuite name to get to.)
    -> Drilldown of the testsuite incl P/S/F
  - [4] Test Drilldown (click on test name to get to this report)
    -> Lowest level to go, shows P/S/F, comments. W/ plans to linkify comments referencing bugs.

To post a comment you must log in.
I Ahmad (iahmad) wrote :

I think there should be someway to see the latest test results at both testsuite and test case level.

50. By Christopher Lee on 2015-10-13

Added latest outcome to report.

Christopher Lee (veebers) wrote :

> I think there should be someway to see the latest test results at both
> testsuite and test case level.
Added this at revno 50. Images showing this:

  - testsuite details: http://people.canonical.com/~leecj2/reporting/reporting-testsuite-details-latestoutcome.png
  - test drilldown: http://people.canonical.com/~leecj2/reporting/reporting-test-drilldown-latestoutcome.png

I Ahmad (iahmad) wrote :

ok reporting looks good for now but it would be nice to see the date for last run, device it ran on, related bug links etc

I Ahmad (iahmad) wrote :

and of course the information about the image/build and a graph/chart too.

Christopher Lee (veebers) wrote :

@iahmad, sounds good all do-able. I would suggest getting this landed as is (well, within it's current scope) and iterate to add those additional things (with a defined scope, i.e. what does the graph/chart need to show.)

Brendan Donegan (brendan-donegan) wrote :

Technically speaking the part of a provider name before the : is just an FQDN, so it doesn't really make much sense on its own. Therefore I'd have the top level of reporting be the full name of the provider, breaking down into individual testplans/whitelists. For example for clock app:

2014.com.ubuntu.clock:clock-tests
-> clock-app-alarm
-> clock-app-setting
-> clock-app-stopwatch
-> clock-app-worldcity

Also the testsuite overview seems to be mixing job plainbox execution unit (pxu) file names with test plan or whitelist file names. I'd have to look at the raw data to see what the reason for this is though.

review: Needs Fixing
Christopher Lee (veebers) wrote :

> Technically speaking the part of a provider name before the : is just an FQDN,
> so it doesn't really make much sense on its own. Therefore I'd have the top
> level of reporting be the full name of the provider, breaking down into
> individual testplans/whitelists. For example for clock app:
>
> 2014.com.ubuntu.clock:clock-tests
> -> clock-app-alarm
> -> clock-app-setting
> -> clock-app-stopwatch
> -> clock-app-worldcity
I understand that the FQDN doesn't really make much sense in itself, but it's used here to collect the tests under a related header. For instance in your example there is no definition of 'clock-tests' (as the results come in named '2015.com.ubuntu.music::music-library/search-songs' for instance). So while it's somewhat arbitrary it allows for an overview view of all the tests.
Otherwise for the main overview there would be a _lot_ of tables (e.g. one for 2015.com.ubuntu.music::music-library and 2015.com.ubuntu.music::music-playlists).

As balloons has acked the existing reports and is really eager to see something I'm going to do the potentially rude thing and deploy this as is so we have something now and we can iterate on the specifics and improve it from there.

> Also the testsuite overview seems to be mixing job plainbox execution unit
> (pxu) file names with test plan or whitelist file names. I'd have to look at
> the raw data to see what the reason for this is though.
This is more than likely due to the example results that I've uploaded during my testing. I have a task to clear this out of the database and it should resolve the issue you're seeing. (You mention this due to the appearance of '2015.com.canonical' in the reports right?).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntu_pt_community/pages/pages.py'
2--- ubuntu_pt_community/pages/pages.py 2015-09-17 05:31:40 +0000
3+++ ubuntu_pt_community/pages/pages.py 2015-10-13 04:38:18 +0000
4@@ -27,11 +27,20 @@
5
6 class PageDefinition:
7
8- def __init__(self, name, route_name, route_func):
9- """Encapsulate the report view details."""
10+ def __init__(self, name, route_name, route_func, display=True):
11+ """Encapsulate the report view details.
12+
13+ :param name: Title name of the Page
14+ :param route_name: Route string to use when defining the route
15+ :param route_func: Function to bind to the route
16+ :param display: Is this link displayable as is (i.e. not a composite
17+ url)
18+
19+ """
20 self.name = name
21 self.route_name = route_name
22 self.route_func = route_func
23+ self.display = display
24
25 @property
26 def route_function_name(self):
27@@ -43,16 +52,37 @@
28 # them and adding routes.
29 Report_Pages = [
30 PageDefinition(
31- 'All Results',
32- '/reports/all_results',
33- reports.view_all_results
34- ),
35-
36- PageDefinition(
37 'Latest Uploads',
38 '/reports/latest',
39 reports.view_latest_uploads
40 ),
41+
42+ PageDefinition(
43+ 'All Tests Overview',
44+ '/reports/overview',
45+ reports.view_testsuite_overview
46+ ),
47+
48+ PageDefinition(
49+ 'Testsuite Overview',
50+ '/reports/overview/<app_under_test>',
51+ reports.view_testsuite_overview,
52+ display=False
53+ ),
54+
55+ PageDefinition(
56+ 'Testsuite Overview',
57+ '/reports/overview/<app_under_test>/<testsuite>',
58+ reports.view_testsuite_drilldown,
59+ display=False
60+ ),
61+
62+ PageDefinition(
63+ 'Test Details',
64+ '/reports/overview/<app_under_test>/<testsuite>/<test>',
65+ reports.view_test_details,
66+ display=False
67+ ),
68 ]
69
70
71@@ -66,4 +96,5 @@
72
73 def view_reports():
74 """Render a simple list of links to available reports."""
75- return render_template('results/index.html', reports=Report_Pages)
76+ index_reports = [report for report in Report_Pages if report.display]
77+ return render_template('results/index.html', reports=index_reports)
78
79=== modified file 'ubuntu_pt_community/pages/reports.py'
80--- ubuntu_pt_community/pages/reports.py 2015-09-29 03:40:29 +0000
81+++ ubuntu_pt_community/pages/reports.py 2015-10-13 04:38:18 +0000
82@@ -19,7 +19,9 @@
83
84 import json
85 import logging
86+import re
87 from collections import (
88+ Counter,
89 defaultdict,
90 namedtuple,
91 )
92@@ -30,78 +32,77 @@
93 logger = logging.getLogger(__name__)
94
95
96-def view_all_results():
97- """Render report displaying all uploaded testsuites with stats.
98-
99- Stats displayed are number of runs, number of passes and fails and the % of
100- successful runs.
101- """
102- results_collection = db.get_results_collection()
103- all_uploads = results_collection.find()
104- simple_report_data = _get_simple_report_data(all_uploads)
105- return render_template(
106- 'results/all_testsuites.html',
107- results=simple_report_data
108- )
109-
110-
111-def _get_simple_report_data(all_uploads):
112- """Produce a list of suite results.
113-
114- :param all_uploads: list of dicts containing uploaded data details.
115- :returns: a list of TestsuiteResult objects.
116- """
117- TestsuiteResult = namedtuple(
118- 'TestsuiteResult',
119- ['name', 'runs', 'passes', 'fails', 'success_rate']
120- )
121-
122- # all_uploads will be a list of dicts
123- # dict will have keys:
124- # - results: a json string with the result details.
125- # - user_email: email of the user who uploaded the results
126- # - uploaded: a date of uploading
127- testsuites = defaultdict(list)
128- for upload in all_uploads:
129- upload_id = str(upload['_id'])
130-
131- try:
132- results = _result_dict_from_document(upload)
133- except KeyError as e:
134- logger.warning(e)
135- continue
136-
137- try:
138- only_tests = _get_only_testcases(results)
139- except KeyError:
140- logger.warning(
141- 'Skipping {}: does not contain expected details'.format(
142- upload_id
143- )
144- )
145- continue
146-
147- for testname in only_tests:
148- # outcome will be 'pass' or 'fail'
149- try:
150- testsuites[testname].append(only_tests[testname]['outcome'])
151- except KeyError:
152- logger.error(
153- 'Testsuite has not outcome. Document ID: ',
154- upload_id
155- )
156-
157- results = []
158- for suite in testsuites:
159- runs = len(testsuites[suite])
160- passes = len([t for t in testsuites[suite] if t == 'pass'])
161- fails = runs - passes
162- success_rate = format(passes / runs * 100, '.2f')
163-
164- results.append(
165- TestsuiteResult(suite, runs, passes, fails, success_rate)
166- )
167- return results
168+CommentDetail = namedtuple('CommentDetail', ['date', 'comment'])
169+
170+
171+class RunAggregator:
172+ """Build up aggregate details for a testsuite/test."""
173+
174+ def __init__(self):
175+ self.outcome_count = Counter()
176+ self.comments = []
177+ self._latest_outcome_date = None
178+ self._latest_outcome = None
179+
180+ def update(self, outcome, testdate, comment=None):
181+ """Update stored details for the test/testsuite.
182+
183+ :param outcome: String representing a test-run outcome. (Only report on
184+ 'pass', 'fail', 'skip'.)
185+ :param testdate: Datetime for the testrun.
186+ :param comment: Comment string for the test (if present). Will not be
187+ stored if it is None (defaults to none).
188+
189+ """
190+ # Not all tests will have comments (or have comments we care about).
191+ self.outcome_count[outcome] += 1
192+ if comment is not None and comment != "":
193+ self.comments.append(CommentDetail(testdate, comment))
194+
195+ self._update_latest_outcome(testdate, outcome)
196+
197+ def _update_latest_outcome(self, testdate, outcome):
198+ try:
199+ if testdate > self._latest_outcome_date:
200+ self._latest_outcome_date = testdate
201+ self._latest_outcome = outcome
202+ except TypeError:
203+ # first testdate comparison
204+ self._latest_outcome_date = testdate
205+ self._latest_outcome = outcome
206+
207+ @property
208+ def latest_outcome(self):
209+ """Return the most recent outcome for this testcase."""
210+ return self._latest_outcome
211+
212+ @property
213+ def runs(self):
214+ return sum(self.outcome_count.values())
215+
216+ @property
217+ def passes(self):
218+ return self.outcome_count['pass']
219+
220+ @property
221+ def fails(self):
222+ return self.outcome_count['fail']
223+
224+ @property
225+ def skips(self):
226+ return self.outcome_count['skip']
227+
228+ @property
229+ def success_rate(self):
230+ return format(self.passes / self.runs * 100, '.2f')
231+
232+ @property
233+ def fail_rate(self):
234+ return format(self.fails / self.runs * 100, '.2f')
235+
236+ @property
237+ def skip_rate(self):
238+ return format(self.skips / self.runs * 100, '.2f')
239
240
241 def view_latest_uploads():
242@@ -113,27 +114,15 @@
243 # slight shim over the returned data to prepare it for presention.
244 sanitised_uploads = []
245 for upload in all_uploads:
246+ try:
247+ testsuite_details = _get_only_testcases_from_upload(upload)
248+ except KeyError:
249+ logger.info('Skipping upload; issue encountered')
250+ continue
251+
252+ uploaded_testsuite_names = testsuite_details.keys()
253 upload_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
254 uploader_email = upload.get('user_email') or 'Anonymous'
255-
256- try:
257- results = _result_dict_from_document(upload)
258- except KeyError as e:
259- logger.warning(e)
260- continue
261-
262- try:
263- testsuite_details = _get_only_testcases(results)
264- except KeyError:
265- upload_id = str(upload.get('_id', 'Unknown'))
266- logger.warning(
267- 'Skipping {}: does not contain expected details'.format(
268- upload_id
269- )
270- )
271- continue
272- uploaded_testsuite_names = testsuite_details.keys()
273-
274 sanitised_uploads.append(
275 dict(
276 upload_date=upload_date,
277@@ -148,6 +137,51 @@
278 )
279
280
281+def view_testsuite_overview(app_under_test=None):
282+ results_collection = db.get_results_collection()
283+ all_uploads = results_collection.find()
284+ overview_details = _get_testsuite_overview(all_uploads, app_under_test)
285+
286+ return render_template(
287+ 'results/all_testsuite_overview.html',
288+ results=overview_details,
289+ app_under_test=app_under_test,
290+ )
291+
292+
293+def view_testsuite_drilldown(app_under_test, testsuite):
294+ results_collection = db.get_results_collection()
295+ all_uploads = results_collection.find()
296+ overview_details = _get_testsuite_drilldown(
297+ all_uploads,
298+ app_under_test,
299+ testsuite
300+ )
301+
302+ return render_template(
303+ 'results/testsuite_drilldown.html',
304+ results=overview_details,
305+ app_under_test=app_under_test,
306+ testsuite=testsuite,
307+ )
308+
309+
310+def view_test_details(app_under_test, testsuite, test):
311+ results_collection = db.get_results_collection()
312+ all_uploads = results_collection.find()
313+ test_details = _get_test_drilldown_details(
314+ all_uploads, app_under_test, testsuite, test
315+ )
316+
317+ return render_template(
318+ 'results/test_drilldown_details.html',
319+ test_details=test_details,
320+ app_under_test=app_under_test,
321+ testsuite=testsuite,
322+ testname=test,
323+ )
324+
325+
326 def _result_dict_from_document(upload):
327 """Return a dict of result details.
328
329@@ -190,3 +224,113 @@
330 except KeyError:
331 logger.warning('Upload details does not contain a resource_map')
332 raise
333+
334+
335+def _get_testsuite_overview(all_uploads, app_under_test=None):
336+ test_result_data = defaultdict(lambda: defaultdict(RunAggregator))
337+
338+ for upload in all_uploads:
339+ try:
340+ test_cases = _get_only_testcases_from_upload(upload)
341+ except KeyError:
342+ logger.info('Skipping upload; issue encountered')
343+ continue
344+
345+ for full_testsuitename, results in test_cases.items():
346+ app, testsuite, testcase = _split_testsuite_id(full_testsuitename)
347+ if app_under_test and app_under_test != app:
348+ continue
349+ outcome = results['outcome']
350+ testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
351+ test_result_data[app][testsuite].update(outcome, testrun_date)
352+
353+ return test_result_data
354+
355+
356+def _get_testsuite_drilldown(all_uploads, app_under_test, testsuite):
357+ testsuite_result_data = defaultdict(RunAggregator)
358+
359+ for upload in all_uploads:
360+ try:
361+ test_cases = _get_only_testcases_from_upload(upload)
362+ except KeyError:
363+ logger.info('Skipping upload; issue encountered')
364+ continue
365+
366+ testsuite_qualifier = '{app}::{testsuite}'.format(
367+ app=app_under_test, testsuite=testsuite
368+ )
369+
370+ for full_testsuitename, results in test_cases.items():
371+ if not full_testsuitename.startswith(testsuite_qualifier):
372+ continue
373+ app, testsuite, testcase = _split_testsuite_id(full_testsuitename)
374+
375+ testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
376+ outcome = results['outcome']
377+ testsuite_result_data[testcase].update(outcome, testrun_date)
378+
379+ return testsuite_result_data
380+
381+
382+def _get_test_drilldown_details(all_uploads, app_under_test, testsuite, test):
383+ test_details_data = RunAggregator()
384+
385+ for upload in all_uploads:
386+ try:
387+ test_cases = _get_only_testcases_from_upload(upload)
388+ except KeyError:
389+ logger.info('Skipping upload; issue encountered')
390+ continue
391+
392+ test_qualifier_str = '{app}::{testsuite}/{test}'
393+ required_test = test_qualifier_str.format(
394+ app=app_under_test, testsuite=testsuite, test=test
395+ )
396+
397+ for full_testsuitename, results in test_cases.items():
398+ app, this_testsuite, testcase = _split_testsuite_id(
399+ full_testsuitename
400+ )
401+
402+ this_test = test_qualifier_str.format(
403+ app=app, testsuite=this_testsuite, test=testcase
404+ )
405+
406+ if this_test != required_test:
407+ continue
408+
409+ outcome = results['outcome']
410+ testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
411+ comment = results['comments']
412+
413+ test_details_data.update(outcome, testrun_date, comment)
414+
415+ return test_details_data
416+
417+
418+def _split_testsuite_id(testsuite_string):
419+ # Some tests will have a pt-id at the end
420+ # app-under-test::testsuite/test/pt-id (pt-id optional)
421+ elements = re.split('::|\/', testsuite_string)
422+ # Ensure we only ever return 3 elements.
423+ app, testsuite, test = elements[0:3]
424+ return [app, testsuite, test]
425+
426+
427+def _get_only_testcases_from_upload(upload):
428+ upload_id = str(upload['_id'])
429+ try:
430+ results = _result_dict_from_document(upload)
431+ except KeyError as e:
432+ logger.warning(e)
433+ raise
434+ try:
435+ return _get_only_testcases(results)
436+ except KeyError:
437+ logger.warning(
438+ 'Skipping {}: does not contain expected details'.format(
439+ upload_id
440+ )
441+ )
442+ raise
443
444=== modified file 'ubuntu_pt_community/templates/base.html'
445--- ubuntu_pt_community/templates/base.html 2015-09-09 05:31:37 +0000
446+++ ubuntu_pt_community/templates/base.html 2015-10-13 04:38:18 +0000
447@@ -14,6 +14,7 @@
448 <body>
449
450 <div id="content" class="container">
451+ {% block nav %}{% endblock %}
452 {% block content %}{% endblock %}
453 </div>
454
455
456=== added file 'ubuntu_pt_community/templates/results/all_testsuite_overview.html'
457--- ubuntu_pt_community/templates/results/all_testsuite_overview.html 1970-01-01 00:00:00 +0000
458+++ ubuntu_pt_community/templates/results/all_testsuite_overview.html 2015-10-13 04:38:18 +0000
459@@ -0,0 +1,45 @@
460+{% extends "results/overview_base.html" %}
461+{% block title %}Testsuite Overview{% endblock %}
462+
463+{% block content %}
464+<h1 class="page-header">Application Testsuite Overview</h1>
465+
466+<table class="table table-condensed table-bordered table-hover">
467+ {% for app_under_test in results | sort %}
468+ <tr>
469+ <th colspan=6 class="info">
470+ {% if results|length > 1 %}
471+ <a href="{{url_for('view_testsuite_overview', app_under_test=app_under_test)}}">{{ app_under_test }}</a>
472+ {% else %}
473+ {{ app_under_test }}
474+ {% endif %}
475+
476+ </th>
477+ </tr>
478+ <tr>
479+ <th>Testsuite</th>
480+ <th class="text-center">Runs</th>
481+ <th class="text-center success">Pass</th>
482+ <th class="text-center warning">Skip</th>
483+ <th class="text-center danger">Fail</th>
484+ <th class="text-center">Success Rate</th>
485+ </tr>
486+ {% for testsuite in results[app_under_test] | sort %}
487+ <tr>
488+ <td>
489+ <a href="{{url_for('view_testsuite_drilldown', app_under_test=app_under_test, testsuite=testsuite)}}">{{testsuite}}</a>
490+ </td>
491+ <td class="text-center">{{results[app_under_test][testsuite].runs}}</td>
492+ <td class="text-center success">{{results[app_under_test][testsuite].passes}}</td>
493+ <td class="text-center warning">{{results[app_under_test][testsuite].skips}}</td>
494+ <td class="text-center danger">{{results[app_under_test][testsuite].fails}}</td>
495+ <td class="text-center">{{results[app_under_test][testsuite].success_rate}}%</td>
496+ </tr>
497+ {% endfor %}
498+ <tr>
499+ <td colspan=6>&nbsp;</td>
500+ </tr>
501+ {% endfor %}
502+</table>
503+
504+{% endblock %}
505
506=== removed file 'ubuntu_pt_community/templates/results/all_testsuites.html'
507--- ubuntu_pt_community/templates/results/all_testsuites.html 2015-09-09 05:44:07 +0000
508+++ ubuntu_pt_community/templates/results/all_testsuites.html 1970-01-01 00:00:00 +0000
509@@ -1,23 +0,0 @@
510-{% extends "base.html" %}
511-{% block title %}All Result Details{% endblock %}
512-
513-{% block content %}
514-<h1 class="page-header">All testsuite statistics</h1>
515-
516-<!-- Could probably also add a list of users/email address that have done this report. -->
517-<table class="table table-striped table-bordered">
518- <tr>
519- <th>Testsuite</th>
520- <th>Total Runs</th>
521- <th>Success Rate</th>
522- </tr>
523- {% for report in results %}
524- <tr>
525- <td>{{report.name}}</td>
526- <td>{{report.runs}}</td>
527- <td>{{report.success_rate}}%</td>
528- </tr>
529- {% endfor %}
530-</table>
531-
532-{% endblock %}
533
534=== modified file 'ubuntu_pt_community/templates/results/index.html'
535--- ubuntu_pt_community/templates/results/index.html 2015-09-09 05:44:07 +0000
536+++ ubuntu_pt_community/templates/results/index.html 2015-10-13 04:38:18 +0000
537@@ -1,7 +1,7 @@
538 {% extends "base.html" %}
539 {% block title %}Community Testing Reports{% endblock %}
540 {% block content %}
541-<h1 class="page-header">List of available reports</h1>
542+<h1 class="page-header">Available reports</h1>
543 <ul>
544 {% for report in reports %}
545 <li><a href="{{ url_for(report.route_function_name) }}">{{ report.name }}</a></li>
546
547=== added file 'ubuntu_pt_community/templates/results/overview_base.html'
548--- ubuntu_pt_community/templates/results/overview_base.html 1970-01-01 00:00:00 +0000
549+++ ubuntu_pt_community/templates/results/overview_base.html 2015-10-13 04:38:18 +0000
550@@ -0,0 +1,33 @@
551+{% extends "base.html" %}
552+
553+{% block nav %}
554+<ol class="breadcrumb">
555+ {% set active = 'overview' %}
556+ <li>
557+ <a href="{{url_for('view_reports')}}">Reports</a>
558+ </li>
559+ {% if app_under_test is defined and not None and app_under_test is not none %}
560+ {% set active = app_under_test %}
561+ <li>
562+ <a href="{{url_for('view_testsuite_overview')}}">overview</a>
563+ </li>
564+ {% if testsuite is defined and app_under_test is not none %}
565+ {% set active = testsuite %}
566+ <li>
567+ <a href="{{url_for('view_testsuite_overview', app_under_test=app_under_test)}}">
568+ {{app_under_test}}
569+ </a>
570+ </li>
571+ {% if testname is defined %}
572+ {% set active = testname %}
573+ <li>
574+ <a href="{{url_for('view_testsuite_drilldown', app_under_test=app_under_test, testsuite=testsuite)}}">
575+ {{testsuite}}
576+ </a>
577+ </li>
578+ {% endif %}
579+ {% endif %}
580+ {% endif %}
581+ <li class="active">{{active}}</li>
582+</ol>
583+{% endblock %}
584
585=== added file 'ubuntu_pt_community/templates/results/test_drilldown_details.html'
586--- ubuntu_pt_community/templates/results/test_drilldown_details.html 1970-01-01 00:00:00 +0000
587+++ ubuntu_pt_community/templates/results/test_drilldown_details.html 2015-10-13 04:38:18 +0000
588@@ -0,0 +1,69 @@
589+{% extends "results/overview_base.html" %}
590+{% block title %}Test Details{% endblock %}
591+
592+{% block content %}
593+<h1 class="page-header">Test Details</h1>
594+
595+{% set col_count = 6 %}
596+{% set outcome_highlight = {'pass': 'success', 'skip': 'warning', 'fail': 'danger'} %}
597+<table class="table table-condensed table-bordered table-hover">
598+ <tr>
599+ <th colspan={{col_count}} class="info">{{ app_under_test }}::{{ testsuite }}::{{ testname }}</th>
600+ </tr>
601+ <tr>
602+ <th class="text-center">Runs</th>
603+ <th class="text-center success">Pass</th>
604+ <th class="text-center warning">Skips</th>
605+ <th class="text-center danger">Fail</th>
606+ <th class="text-center">Success Rate</th>
607+ <th class="text-center">Latest Outcome</th>
608+ </tr>
609+ <tr>
610+ <td class="text-center">{{test_details.runs}}</td>
611+ <td class="text-center success">{{test_details.passes}}</td>
612+ <td class="text-center warning">{{test_details.skips}}</td>
613+ <td class="text-center danger">{{test_details.fails}}</td>
614+ <td class="text-center">{{test_details.success_rate}}%</td>
615+ <td class="text-center {{outcome_highlight[test_details.latest_outcome]}}">
616+ {{test_details.latest_outcome|capitalize}}
617+ </td>
618+ </tr>
619+
620+ <tr>
621+ <th colspan={{col_count}}>Overview</th>
622+ </tr>
623+ <tr class="active">
624+ <td colspan={{col_count}}>
625+ <div class="progress" style="margin-bottom: 0px;">
626+ <div class="progress-bar progress-bar-success" style="width: {{test_details.success_rate}}%">
627+ <span class="sr-only">{{test_details.success_rate}}% success rate</span>
628+ </div>
629+ <div class="progress-bar progress-bar-warning" style="width: {{test_details.skip_rate}}%">
630+ <span class="sr-only">{{test_details.skip_rate}}% skip rate</span>
631+ </div>
632+ <div class="progress-bar progress-bar-danger" style="width: {{test_details.fail_rate}}%">
633+ <span class="sr-only">{{test_details.fail_rate}}% fail rate</span>
634+ </div>
635+ </div>
636+ </td>
637+ </tr>
638+
639+ {% if test_details.comments| length > 0 %}
640+ <tr>
641+ <th colspan={{col_count}}>Comments</th>
642+ </tr>
643+ <tr class="active">
644+ <td colspan={{col_count}}>
645+ {% for comment in test_details.comments %}
646+ <blockquote>
647+ <p>{{comment.comment}}</p>
648+ <footer>{{comment.date}}</footer>
649+ </blockquote>
650+ {% endfor %}
651+ </td>
652+ </tr>
653+ {% endif %}
654+
655+</table>
656+
657+{% endblock %}
658
659=== added file 'ubuntu_pt_community/templates/results/testsuite_drilldown.html'
660--- ubuntu_pt_community/templates/results/testsuite_drilldown.html 1970-01-01 00:00:00 +0000
661+++ ubuntu_pt_community/templates/results/testsuite_drilldown.html 2015-10-13 04:38:18 +0000
662@@ -0,0 +1,42 @@
663+{% extends "results/overview_base.html" %}
664+{% block title %}Testsuite Drilldown{% endblock %}
665+
666+{% block content %}
667+<h1 class="page-header">Testsuite Drilldown</h1>
668+
669+{% set col_count = 7 %}
670+{% set outcome_highlight = {'pass': 'success', 'skip': 'warning', 'fail': 'danger'} %}
671+<table class="table table-condensed table-bordered table-hover">
672+ <tr>
673+ <th colspan={{ col_count }} class="info">{{ app_under_test }}::{{ testsuite }}</th>
674+ </tr>
675+ <tr>
676+ <th>Testsuite</th>
677+ <th class="text-center">Runs</th>
678+ <th class="text-center success">Pass</th>
679+ <th class="text-center warning">Skip</th>
680+ <th class="text-center danger">Fail</th>
681+ <th class="text-center">Success Rate</th>
682+ <th class="text-center">Latest Outcome</th>
683+ </tr>
684+ {% for test in results | sort %}
685+ {% set current_test = results[test] %}
686+ <tr>
687+ <td>
688+ <a href="{{url_for('view_test_details', app_under_test=app_under_test, testsuite=testsuite, test=test)}}">
689+ {{test}}
690+ </a>
691+ </td>
692+ <td class="text-center">{{current_test.runs}}</td>
693+ <td class="text-center success">{{current_test.passes}}</td>
694+ <td class="text-center warning">{{current_test.skips}}</td>
695+ <td class="text-center danger">{{current_test.fails}}</td>
696+ <td class="text-center">{{current_test.success_rate}}%</td>
697+ <td class="text-center {{outcome_highlight[current_test.latest_outcome]}}">
698+ {{current_test.latest_outcome|capitalize}}
699+ </td>
700+ </tr>
701+
702+ {% endfor %}
703+</table>
704+{% endblock %}

Subscribers

People subscribed via source and target branches