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
=== modified file 'ubuntu_pt_community/pages/pages.py'
--- ubuntu_pt_community/pages/pages.py 2015-09-17 05:31:40 +0000
+++ ubuntu_pt_community/pages/pages.py 2015-10-13 04:38:18 +0000
@@ -27,11 +27,20 @@
2727
28class PageDefinition:28class PageDefinition:
2929
30 def __init__(self, name, route_name, route_func):30 def __init__(self, name, route_name, route_func, display=True):
31 """Encapsulate the report view details."""31 """Encapsulate the report view details.
32
33 :param name: Title name of the Page
34 :param route_name: Route string to use when defining the route
35 :param route_func: Function to bind to the route
36 :param display: Is this link displayable as is (i.e. not a composite
37 url)
38
39 """
32 self.name = name40 self.name = name
33 self.route_name = route_name41 self.route_name = route_name
34 self.route_func = route_func42 self.route_func = route_func
43 self.display = display
3544
36 @property45 @property
37 def route_function_name(self):46 def route_function_name(self):
@@ -43,16 +52,37 @@
43# them and adding routes.52# them and adding routes.
44Report_Pages = [53Report_Pages = [
45 PageDefinition(54 PageDefinition(
46 'All Results',
47 '/reports/all_results',
48 reports.view_all_results
49 ),
50
51 PageDefinition(
52 'Latest Uploads',55 'Latest Uploads',
53 '/reports/latest',56 '/reports/latest',
54 reports.view_latest_uploads57 reports.view_latest_uploads
55 ),58 ),
59
60 PageDefinition(
61 'All Tests Overview',
62 '/reports/overview',
63 reports.view_testsuite_overview
64 ),
65
66 PageDefinition(
67 'Testsuite Overview',
68 '/reports/overview/<app_under_test>',
69 reports.view_testsuite_overview,
70 display=False
71 ),
72
73 PageDefinition(
74 'Testsuite Overview',
75 '/reports/overview/<app_under_test>/<testsuite>',
76 reports.view_testsuite_drilldown,
77 display=False
78 ),
79
80 PageDefinition(
81 'Test Details',
82 '/reports/overview/<app_under_test>/<testsuite>/<test>',
83 reports.view_test_details,
84 display=False
85 ),
56]86]
5787
5888
@@ -66,4 +96,5 @@
6696
67def view_reports():97def view_reports():
68 """Render a simple list of links to available reports."""98 """Render a simple list of links to available reports."""
69 return render_template('results/index.html', reports=Report_Pages)99 index_reports = [report for report in Report_Pages if report.display]
100 return render_template('results/index.html', reports=index_reports)
70101
=== modified file 'ubuntu_pt_community/pages/reports.py'
--- ubuntu_pt_community/pages/reports.py 2015-09-29 03:40:29 +0000
+++ ubuntu_pt_community/pages/reports.py 2015-10-13 04:38:18 +0000
@@ -19,7 +19,9 @@
1919
20import json20import json
21import logging21import logging
22import re
22from collections import (23from collections import (
24 Counter,
23 defaultdict,25 defaultdict,
24 namedtuple,26 namedtuple,
25)27)
@@ -30,78 +32,77 @@
30logger = logging.getLogger(__name__)32logger = logging.getLogger(__name__)
3133
3234
33def view_all_results():35CommentDetail = namedtuple('CommentDetail', ['date', 'comment'])
34 """Render report displaying all uploaded testsuites with stats.36
3537
36 Stats displayed are number of runs, number of passes and fails and the % of38class RunAggregator:
37 successful runs.39 """Build up aggregate details for a testsuite/test."""
38 """40
39 results_collection = db.get_results_collection()41 def __init__(self):
40 all_uploads = results_collection.find()42 self.outcome_count = Counter()
41 simple_report_data = _get_simple_report_data(all_uploads)43 self.comments = []
42 return render_template(44 self._latest_outcome_date = None
43 'results/all_testsuites.html',45 self._latest_outcome = None
44 results=simple_report_data46
45 )47 def update(self, outcome, testdate, comment=None):
4648 """Update stored details for the test/testsuite.
4749
48def _get_simple_report_data(all_uploads):50 :param outcome: String representing a test-run outcome. (Only report on
49 """Produce a list of suite results.51 'pass', 'fail', 'skip'.)
5052 :param testdate: Datetime for the testrun.
51 :param all_uploads: list of dicts containing uploaded data details.53 :param comment: Comment string for the test (if present). Will not be
52 :returns: a list of TestsuiteResult objects.54 stored if it is None (defaults to none).
53 """55
54 TestsuiteResult = namedtuple(56 """
55 'TestsuiteResult',57 # Not all tests will have comments (or have comments we care about).
56 ['name', 'runs', 'passes', 'fails', 'success_rate']58 self.outcome_count[outcome] += 1
57 )59 if comment is not None and comment != "":
5860 self.comments.append(CommentDetail(testdate, comment))
59 # all_uploads will be a list of dicts61
60 # dict will have keys:62 self._update_latest_outcome(testdate, outcome)
61 # - results: a json string with the result details.63
62 # - user_email: email of the user who uploaded the results64 def _update_latest_outcome(self, testdate, outcome):
63 # - uploaded: a date of uploading65 try:
64 testsuites = defaultdict(list)66 if testdate > self._latest_outcome_date:
65 for upload in all_uploads:67 self._latest_outcome_date = testdate
66 upload_id = str(upload['_id'])68 self._latest_outcome = outcome
6769 except TypeError:
68 try:70 # first testdate comparison
69 results = _result_dict_from_document(upload)71 self._latest_outcome_date = testdate
70 except KeyError as e:72 self._latest_outcome = outcome
71 logger.warning(e)73
72 continue74 @property
7375 def latest_outcome(self):
74 try:76 """Return the most recent outcome for this testcase."""
75 only_tests = _get_only_testcases(results)77 return self._latest_outcome
76 except KeyError:78
77 logger.warning(79 @property
78 'Skipping {}: does not contain expected details'.format(80 def runs(self):
79 upload_id81 return sum(self.outcome_count.values())
80 )82
81 )83 @property
82 continue84 def passes(self):
8385 return self.outcome_count['pass']
84 for testname in only_tests:86
85 # outcome will be 'pass' or 'fail'87 @property
86 try:88 def fails(self):
87 testsuites[testname].append(only_tests[testname]['outcome'])89 return self.outcome_count['fail']
88 except KeyError:90
89 logger.error(91 @property
90 'Testsuite has not outcome. Document ID: ',92 def skips(self):
91 upload_id93 return self.outcome_count['skip']
92 )94
9395 @property
94 results = []96 def success_rate(self):
95 for suite in testsuites:97 return format(self.passes / self.runs * 100, '.2f')
96 runs = len(testsuites[suite])98
97 passes = len([t for t in testsuites[suite] if t == 'pass'])99 @property
98 fails = runs - passes100 def fail_rate(self):
99 success_rate = format(passes / runs * 100, '.2f')101 return format(self.fails / self.runs * 100, '.2f')
100102
101 results.append(103 @property
102 TestsuiteResult(suite, runs, passes, fails, success_rate)104 def skip_rate(self):
103 )105 return format(self.skips / self.runs * 100, '.2f')
104 return results
105106
106107
107def view_latest_uploads():108def view_latest_uploads():
@@ -113,27 +114,15 @@
113 # slight shim over the returned data to prepare it for presention.114 # slight shim over the returned data to prepare it for presention.
114 sanitised_uploads = []115 sanitised_uploads = []
115 for upload in all_uploads:116 for upload in all_uploads:
117 try:
118 testsuite_details = _get_only_testcases_from_upload(upload)
119 except KeyError:
120 logger.info('Skipping upload; issue encountered')
121 continue
122
123 uploaded_testsuite_names = testsuite_details.keys()
116 upload_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')124 upload_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
117 uploader_email = upload.get('user_email') or 'Anonymous'125 uploader_email = upload.get('user_email') or 'Anonymous'
118
119 try:
120 results = _result_dict_from_document(upload)
121 except KeyError as e:
122 logger.warning(e)
123 continue
124
125 try:
126 testsuite_details = _get_only_testcases(results)
127 except KeyError:
128 upload_id = str(upload.get('_id', 'Unknown'))
129 logger.warning(
130 'Skipping {}: does not contain expected details'.format(
131 upload_id
132 )
133 )
134 continue
135 uploaded_testsuite_names = testsuite_details.keys()
136
137 sanitised_uploads.append(126 sanitised_uploads.append(
138 dict(127 dict(
139 upload_date=upload_date,128 upload_date=upload_date,
@@ -148,6 +137,51 @@
148 )137 )
149138
150139
140def view_testsuite_overview(app_under_test=None):
141 results_collection = db.get_results_collection()
142 all_uploads = results_collection.find()
143 overview_details = _get_testsuite_overview(all_uploads, app_under_test)
144
145 return render_template(
146 'results/all_testsuite_overview.html',
147 results=overview_details,
148 app_under_test=app_under_test,
149 )
150
151
152def view_testsuite_drilldown(app_under_test, testsuite):
153 results_collection = db.get_results_collection()
154 all_uploads = results_collection.find()
155 overview_details = _get_testsuite_drilldown(
156 all_uploads,
157 app_under_test,
158 testsuite
159 )
160
161 return render_template(
162 'results/testsuite_drilldown.html',
163 results=overview_details,
164 app_under_test=app_under_test,
165 testsuite=testsuite,
166 )
167
168
169def view_test_details(app_under_test, testsuite, test):
170 results_collection = db.get_results_collection()
171 all_uploads = results_collection.find()
172 test_details = _get_test_drilldown_details(
173 all_uploads, app_under_test, testsuite, test
174 )
175
176 return render_template(
177 'results/test_drilldown_details.html',
178 test_details=test_details,
179 app_under_test=app_under_test,
180 testsuite=testsuite,
181 testname=test,
182 )
183
184
151def _result_dict_from_document(upload):185def _result_dict_from_document(upload):
152 """Return a dict of result details.186 """Return a dict of result details.
153187
@@ -190,3 +224,113 @@
190 except KeyError:224 except KeyError:
191 logger.warning('Upload details does not contain a resource_map')225 logger.warning('Upload details does not contain a resource_map')
192 raise226 raise
227
228
229def _get_testsuite_overview(all_uploads, app_under_test=None):
230 test_result_data = defaultdict(lambda: defaultdict(RunAggregator))
231
232 for upload in all_uploads:
233 try:
234 test_cases = _get_only_testcases_from_upload(upload)
235 except KeyError:
236 logger.info('Skipping upload; issue encountered')
237 continue
238
239 for full_testsuitename, results in test_cases.items():
240 app, testsuite, testcase = _split_testsuite_id(full_testsuitename)
241 if app_under_test and app_under_test != app:
242 continue
243 outcome = results['outcome']
244 testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
245 test_result_data[app][testsuite].update(outcome, testrun_date)
246
247 return test_result_data
248
249
250def _get_testsuite_drilldown(all_uploads, app_under_test, testsuite):
251 testsuite_result_data = defaultdict(RunAggregator)
252
253 for upload in all_uploads:
254 try:
255 test_cases = _get_only_testcases_from_upload(upload)
256 except KeyError:
257 logger.info('Skipping upload; issue encountered')
258 continue
259
260 testsuite_qualifier = '{app}::{testsuite}'.format(
261 app=app_under_test, testsuite=testsuite
262 )
263
264 for full_testsuitename, results in test_cases.items():
265 if not full_testsuitename.startswith(testsuite_qualifier):
266 continue
267 app, testsuite, testcase = _split_testsuite_id(full_testsuitename)
268
269 testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
270 outcome = results['outcome']
271 testsuite_result_data[testcase].update(outcome, testrun_date)
272
273 return testsuite_result_data
274
275
276def _get_test_drilldown_details(all_uploads, app_under_test, testsuite, test):
277 test_details_data = RunAggregator()
278
279 for upload in all_uploads:
280 try:
281 test_cases = _get_only_testcases_from_upload(upload)
282 except KeyError:
283 logger.info('Skipping upload; issue encountered')
284 continue
285
286 test_qualifier_str = '{app}::{testsuite}/{test}'
287 required_test = test_qualifier_str.format(
288 app=app_under_test, testsuite=testsuite, test=test
289 )
290
291 for full_testsuitename, results in test_cases.items():
292 app, this_testsuite, testcase = _split_testsuite_id(
293 full_testsuitename
294 )
295
296 this_test = test_qualifier_str.format(
297 app=app, testsuite=this_testsuite, test=testcase
298 )
299
300 if this_test != required_test:
301 continue
302
303 outcome = results['outcome']
304 testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
305 comment = results['comments']
306
307 test_details_data.update(outcome, testrun_date, comment)
308
309 return test_details_data
310
311
312def _split_testsuite_id(testsuite_string):
313 # Some tests will have a pt-id at the end
314 # app-under-test::testsuite/test/pt-id (pt-id optional)
315 elements = re.split('::|\/', testsuite_string)
316 # Ensure we only ever return 3 elements.
317 app, testsuite, test = elements[0:3]
318 return [app, testsuite, test]
319
320
321def _get_only_testcases_from_upload(upload):
322 upload_id = str(upload['_id'])
323 try:
324 results = _result_dict_from_document(upload)
325 except KeyError as e:
326 logger.warning(e)
327 raise
328 try:
329 return _get_only_testcases(results)
330 except KeyError:
331 logger.warning(
332 'Skipping {}: does not contain expected details'.format(
333 upload_id
334 )
335 )
336 raise
193337
=== modified file 'ubuntu_pt_community/templates/base.html'
--- ubuntu_pt_community/templates/base.html 2015-09-09 05:31:37 +0000
+++ ubuntu_pt_community/templates/base.html 2015-10-13 04:38:18 +0000
@@ -14,6 +14,7 @@
14 <body>14 <body>
1515
16 <div id="content" class="container">16 <div id="content" class="container">
17 {% block nav %}{% endblock %}
17 {% block content %}{% endblock %}18 {% block content %}{% endblock %}
18 </div>19 </div>
1920
2021
=== added file 'ubuntu_pt_community/templates/results/all_testsuite_overview.html'
--- ubuntu_pt_community/templates/results/all_testsuite_overview.html 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/templates/results/all_testsuite_overview.html 2015-10-13 04:38:18 +0000
@@ -0,0 +1,45 @@
1{% extends "results/overview_base.html" %}
2{% block title %}Testsuite Overview{% endblock %}
3
4{% block content %}
5<h1 class="page-header">Application Testsuite Overview</h1>
6
7<table class="table table-condensed table-bordered table-hover">
8 {% for app_under_test in results | sort %}
9 <tr>
10 <th colspan=6 class="info">
11 {% if results|length > 1 %}
12 <a href="{{url_for('view_testsuite_overview', app_under_test=app_under_test)}}">{{ app_under_test }}</a>
13 {% else %}
14 {{ app_under_test }}
15 {% endif %}
16
17 </th>
18 </tr>
19 <tr>
20 <th>Testsuite</th>
21 <th class="text-center">Runs</th>
22 <th class="text-center success">Pass</th>
23 <th class="text-center warning">Skip</th>
24 <th class="text-center danger">Fail</th>
25 <th class="text-center">Success Rate</th>
26 </tr>
27 {% for testsuite in results[app_under_test] | sort %}
28 <tr>
29 <td>
30 <a href="{{url_for('view_testsuite_drilldown', app_under_test=app_under_test, testsuite=testsuite)}}">{{testsuite}}</a>
31 </td>
32 <td class="text-center">{{results[app_under_test][testsuite].runs}}</td>
33 <td class="text-center success">{{results[app_under_test][testsuite].passes}}</td>
34 <td class="text-center warning">{{results[app_under_test][testsuite].skips}}</td>
35 <td class="text-center danger">{{results[app_under_test][testsuite].fails}}</td>
36 <td class="text-center">{{results[app_under_test][testsuite].success_rate}}%</td>
37 </tr>
38 {% endfor %}
39 <tr>
40 <td colspan=6>&nbsp;</td>
41 </tr>
42 {% endfor %}
43</table>
44
45{% endblock %}
046
=== removed file 'ubuntu_pt_community/templates/results/all_testsuites.html'
--- ubuntu_pt_community/templates/results/all_testsuites.html 2015-09-09 05:44:07 +0000
+++ ubuntu_pt_community/templates/results/all_testsuites.html 1970-01-01 00:00:00 +0000
@@ -1,23 +0,0 @@
1{% extends "base.html" %}
2{% block title %}All Result Details{% endblock %}
3
4{% block content %}
5<h1 class="page-header">All testsuite statistics</h1>
6
7<!-- Could probably also add a list of users/email address that have done this report. -->
8<table class="table table-striped table-bordered">
9 <tr>
10 <th>Testsuite</th>
11 <th>Total Runs</th>
12 <th>Success Rate</th>
13 </tr>
14 {% for report in results %}
15 <tr>
16 <td>{{report.name}}</td>
17 <td>{{report.runs}}</td>
18 <td>{{report.success_rate}}%</td>
19 </tr>
20 {% endfor %}
21</table>
22
23{% endblock %}
240
=== modified file 'ubuntu_pt_community/templates/results/index.html'
--- ubuntu_pt_community/templates/results/index.html 2015-09-09 05:44:07 +0000
+++ ubuntu_pt_community/templates/results/index.html 2015-10-13 04:38:18 +0000
@@ -1,7 +1,7 @@
1{% extends "base.html" %}1{% extends "base.html" %}
2{% block title %}Community Testing Reports{% endblock %}2{% block title %}Community Testing Reports{% endblock %}
3{% block content %}3{% block content %}
4<h1 class="page-header">List of available reports</h1>4<h1 class="page-header">Available reports</h1>
5<ul>5<ul>
6 {% for report in reports %}6 {% for report in reports %}
7 <li><a href="{{ url_for(report.route_function_name) }}">{{ report.name }}</a></li>7 <li><a href="{{ url_for(report.route_function_name) }}">{{ report.name }}</a></li>
88
=== added file 'ubuntu_pt_community/templates/results/overview_base.html'
--- ubuntu_pt_community/templates/results/overview_base.html 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/templates/results/overview_base.html 2015-10-13 04:38:18 +0000
@@ -0,0 +1,33 @@
1{% extends "base.html" %}
2
3{% block nav %}
4<ol class="breadcrumb">
5 {% set active = 'overview' %}
6 <li>
7 <a href="{{url_for('view_reports')}}">Reports</a>
8 </li>
9 {% if app_under_test is defined and not None and app_under_test is not none %}
10 {% set active = app_under_test %}
11 <li>
12 <a href="{{url_for('view_testsuite_overview')}}">overview</a>
13 </li>
14 {% if testsuite is defined and app_under_test is not none %}
15 {% set active = testsuite %}
16 <li>
17 <a href="{{url_for('view_testsuite_overview', app_under_test=app_under_test)}}">
18 {{app_under_test}}
19 </a>
20 </li>
21 {% if testname is defined %}
22 {% set active = testname %}
23 <li>
24 <a href="{{url_for('view_testsuite_drilldown', app_under_test=app_under_test, testsuite=testsuite)}}">
25 {{testsuite}}
26 </a>
27 </li>
28 {% endif %}
29 {% endif %}
30 {% endif %}
31 <li class="active">{{active}}</li>
32</ol>
33{% endblock %}
034
=== added file 'ubuntu_pt_community/templates/results/test_drilldown_details.html'
--- ubuntu_pt_community/templates/results/test_drilldown_details.html 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/templates/results/test_drilldown_details.html 2015-10-13 04:38:18 +0000
@@ -0,0 +1,69 @@
1{% extends "results/overview_base.html" %}
2{% block title %}Test Details{% endblock %}
3
4{% block content %}
5<h1 class="page-header">Test Details</h1>
6
7{% set col_count = 6 %}
8{% set outcome_highlight = {'pass': 'success', 'skip': 'warning', 'fail': 'danger'} %}
9<table class="table table-condensed table-bordered table-hover">
10 <tr>
11 <th colspan={{col_count}} class="info">{{ app_under_test }}::{{ testsuite }}::{{ testname }}</th>
12 </tr>
13 <tr>
14 <th class="text-center">Runs</th>
15 <th class="text-center success">Pass</th>
16 <th class="text-center warning">Skips</th>
17 <th class="text-center danger">Fail</th>
18 <th class="text-center">Success Rate</th>
19 <th class="text-center">Latest Outcome</th>
20 </tr>
21 <tr>
22 <td class="text-center">{{test_details.runs}}</td>
23 <td class="text-center success">{{test_details.passes}}</td>
24 <td class="text-center warning">{{test_details.skips}}</td>
25 <td class="text-center danger">{{test_details.fails}}</td>
26 <td class="text-center">{{test_details.success_rate}}%</td>
27 <td class="text-center {{outcome_highlight[test_details.latest_outcome]}}">
28 {{test_details.latest_outcome|capitalize}}
29 </td>
30 </tr>
31
32 <tr>
33 <th colspan={{col_count}}>Overview</th>
34 </tr>
35 <tr class="active">
36 <td colspan={{col_count}}>
37 <div class="progress" style="margin-bottom: 0px;">
38 <div class="progress-bar progress-bar-success" style="width: {{test_details.success_rate}}%">
39 <span class="sr-only">{{test_details.success_rate}}% success rate</span>
40 </div>
41 <div class="progress-bar progress-bar-warning" style="width: {{test_details.skip_rate}}%">
42 <span class="sr-only">{{test_details.skip_rate}}% skip rate</span>
43 </div>
44 <div class="progress-bar progress-bar-danger" style="width: {{test_details.fail_rate}}%">
45 <span class="sr-only">{{test_details.fail_rate}}% fail rate</span>
46 </div>
47 </div>
48 </td>
49 </tr>
50
51 {% if test_details.comments| length > 0 %}
52 <tr>
53 <th colspan={{col_count}}>Comments</th>
54 </tr>
55 <tr class="active">
56 <td colspan={{col_count}}>
57 {% for comment in test_details.comments %}
58 <blockquote>
59 <p>{{comment.comment}}</p>
60 <footer>{{comment.date}}</footer>
61 </blockquote>
62 {% endfor %}
63 </td>
64 </tr>
65 {% endif %}
66
67</table>
68
69{% endblock %}
070
=== added file 'ubuntu_pt_community/templates/results/testsuite_drilldown.html'
--- ubuntu_pt_community/templates/results/testsuite_drilldown.html 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/templates/results/testsuite_drilldown.html 2015-10-13 04:38:18 +0000
@@ -0,0 +1,42 @@
1{% extends "results/overview_base.html" %}
2{% block title %}Testsuite Drilldown{% endblock %}
3
4{% block content %}
5<h1 class="page-header">Testsuite Drilldown</h1>
6
7{% set col_count = 7 %}
8{% set outcome_highlight = {'pass': 'success', 'skip': 'warning', 'fail': 'danger'} %}
9<table class="table table-condensed table-bordered table-hover">
10 <tr>
11 <th colspan={{ col_count }} class="info">{{ app_under_test }}::{{ testsuite }}</th>
12 </tr>
13 <tr>
14 <th>Testsuite</th>
15 <th class="text-center">Runs</th>
16 <th class="text-center success">Pass</th>
17 <th class="text-center warning">Skip</th>
18 <th class="text-center danger">Fail</th>
19 <th class="text-center">Success Rate</th>
20 <th class="text-center">Latest Outcome</th>
21 </tr>
22 {% for test in results | sort %}
23 {% set current_test = results[test] %}
24 <tr>
25 <td>
26 <a href="{{url_for('view_test_details', app_under_test=app_under_test, testsuite=testsuite, test=test)}}">
27 {{test}}
28 </a>
29 </td>
30 <td class="text-center">{{current_test.runs}}</td>
31 <td class="text-center success">{{current_test.passes}}</td>
32 <td class="text-center warning">{{current_test.skips}}</td>
33 <td class="text-center danger">{{current_test.fails}}</td>
34 <td class="text-center">{{current_test.success_rate}}%</td>
35 <td class="text-center {{outcome_highlight[current_test.latest_outcome]}}">
36 {{current_test.latest_outcome|capitalize}}
37 </td>
38 </tr>
39
40 {% endfor %}
41</table>
42{% endblock %}

Subscribers

People subscribed via source and target branches