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

Proposed by Christopher Lee
Status: Merged
Merged at revision: 30
Proposed branch: lp:~canonical-platform-qa/ubuntu-community-testing/initial-reporting
Merge into: lp:ubuntu-community-testing
Diff against target: 546 lines (+427/-11)
13 files modified
ubuntu_pt_community/__init__.py (+5/-1)
ubuntu_pt_community/api/v1.py (+4/-8)
ubuntu_pt_community/db/__init__.py (+5/-2)
ubuntu_pt_community/db/db.py (+6/-0)
ubuntu_pt_community/pages/__init__.py (+27/-0)
ubuntu_pt_community/pages/pages.py (+69/-0)
ubuntu_pt_community/pages/reports.py (+167/-0)
ubuntu_pt_community/templates/base.html (+30/-0)
ubuntu_pt_community/templates/results/all_testsuites.html (+23/-0)
ubuntu_pt_community/templates/results/index.html (+10/-0)
ubuntu_pt_community/templates/results/latest_uploads.html (+27/-0)
ubuntu_pt_community/tests/__init__.py (+17/-0)
ubuntu_pt_community/tests/test_reports.py (+37/-0)
To merge this branch: bzr merge lp:~canonical-platform-qa/ubuntu-community-testing/initial-reporting
Reviewer Review Type Date Requested Status
Brendan Donegan (community) Approve
Nicholas Skaggs (community) Approve
Review via email: mp+270480@code.launchpad.net

Commit message

Initial lot of reporting.

Description of the change

Initial reporting incl.
  - "Latest Uploads" - list of upload date with testsuites run and the users email
  - "All testsuites" - simple stats for each testsuite that we have upload details for (runs, pass/fail numbers, success rates).

These are the first run and we can build on it from here (incl. making it look better :-P as well as navigation etc.).

To post a comment you must log in.
Revision history for this message
Christopher Lee (veebers) wrote :

Just marked as WIP as I notice that somethings are incorrect. Adding tests and sorting out these issues.

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

This looks good from my perspective. Chris, it looks like only the results from today will display in the 'latest results?'. I didn't actually build and deploy this, so I might be mistaken :-)

If so however, I might see this as a problem when days rollover. When the day switches to a new day, I might not see my results, even though I submitted them say within the last hour. Could we just simply display the last X number of results? Or if you wish limit it to a more reasonable number of days, perhaps 3 instead of just 1. If you do use days, it might still be wise to limit the number of results displayed so we don't have a long list on the page.

Revision history for this message
Christopher Lee (veebers) wrote :

@balloons at the moment there is no limit to how many it displays and just shows all uploads with the latest at the top.

We can iterate on doing smarter things with this (i.e. pagination (using JIT requests), just show X days etc.)

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Ack, sounds fine to me.

review: Approve
Revision history for this message
Brendan Donegan (brendan-donegan) wrote :

Mostly looks great - some questions about use of docstrings though

review: Needs Information
41. By Christopher Lee

Better and additional docstrings.

42. By Christopher Lee

Fix flake8 errors.

Revision history for this message
Christopher Lee (veebers) wrote :

> Mostly looks great - some questions about use of docstrings though
Good catch with the docstrings. Have added more and improved the existing.

Regarding docstrings for 'private' methods; I see no issue with having them, They are a well known format which can streamline a developer reading them and contain details regarding the intention of the method itself.

Revision history for this message
Brendan Donegan (brendan-donegan) wrote :

Ok looks better now. +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ubuntu_pt_community/__init__.py'
--- ubuntu_pt_community/__init__.py 2015-08-18 00:55:26 +0000
+++ ubuntu_pt_community/__init__.py 2015-09-17 05:33:50 +0000
@@ -18,7 +18,10 @@
18import logging18import logging
19import sys19import sys
2020
21from ubuntu_pt_community import api21from ubuntu_pt_community import (
22 api,
23 pages,
24)
2225
23from flask import Flask26from flask import Flask
2427
@@ -40,3 +43,4 @@
40app = Flask(__name__)43app = Flask(__name__)
4144
42api.define_api_routes(app)45api.define_api_routes(app)
46pages.define_page_routes(app)
4347
=== modified file 'ubuntu_pt_community/api/v1.py'
--- ubuntu_pt_community/api/v1.py 2015-08-18 10:58:22 +0000
+++ ubuntu_pt_community/api/v1.py 2015-09-17 05:33:50 +0000
@@ -28,7 +28,9 @@
28from qakit.practitest.report_checkbox_results_to_practitest import (28from qakit.practitest.report_checkbox_results_to_practitest import (
29 upload_results29 upload_results
30)30)
31from ubuntu_pt_community import auth, db31from ubuntu_pt_community import auth
32from ubuntu_pt_community.db import get_results_collection
33
3234
33logger = logging.getLogger(__name__)35logger = logging.getLogger(__name__)
3436
@@ -86,7 +88,7 @@
86 )88 )
8789
88 try:90 try:
89 collection = get_results_database()91 collection = get_results_collection()
90 insert_id = collection.insert(details)92 insert_id = collection.insert(details)
91 logger.info(93 logger.info(
92 'Inserted details for {} with id {}'.format(94 'Inserted details for {} with id {}'.format(
@@ -103,12 +105,6 @@
103 logger.error('Failed to get database connection: ', e)105 logger.error('Failed to get database connection: ', e)
104106
105107
106def get_results_database():
107 database_name = 'community_practitest'
108 collection_name = 'uploaded_results'
109 return db.get_collection(database_name, collection_name)
110
111
112def get_user_email_address(request):108def get_user_email_address(request):
113 try:109 try:
114 return request.form['uploader_email']110 return request.form['uploader_email']
115111
=== modified file 'ubuntu_pt_community/db/__init__.py'
--- ubuntu_pt_community/db/__init__.py 2015-08-13 05:52:38 +0000
+++ ubuntu_pt_community/db/__init__.py 2015-09-17 05:33:50 +0000
@@ -16,6 +16,9 @@
16# along with this program. If not, see <http://www.gnu.org/licenses/>.16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17#17#
1818
19from ubuntu_pt_community.db.db import get_collection19from ubuntu_pt_community.db.db import (
20 get_collection,
21 get_results_collection,
22)
2023
21__all__ = ['get_collection']24__all__ = ['get_collection', 'get_results_collection']
2225
=== modified file 'ubuntu_pt_community/db/db.py'
--- ubuntu_pt_community/db/db.py 2015-08-17 06:19:29 +0000
+++ ubuntu_pt_community/db/db.py 2015-09-17 05:33:50 +0000
@@ -48,6 +48,12 @@
48 return db[collection_name]48 return db[collection_name]
4949
5050
51def get_results_collection():
52 database_name = 'community_practitest'
53 collection_name = 'uploaded_results'
54 return get_collection(database_name, collection_name)
55
56
51def get_config_file_path():57def get_config_file_path():
52 try:58 try:
53 return os.path.join(59 return os.path.join(
5460
=== added directory 'ubuntu_pt_community/pages'
=== added file 'ubuntu_pt_community/pages/__init__.py'
--- ubuntu_pt_community/pages/__init__.py 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/pages/__init__.py 2015-09-17 05:33:50 +0000
@@ -0,0 +1,27 @@
1#
2# Ubuntu PractiTest Community results processor
3# Copyright (C) 2015 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18
19from ubuntu_pt_community.pages import pages
20
21"""Prepare the available api routes."""
22
23__all__ = ['define_page_routes']
24
25
26def define_page_routes(webapp):
27 pages.define_routes(webapp)
028
=== added file 'ubuntu_pt_community/pages/pages.py'
--- ubuntu_pt_community/pages/pages.py 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/pages/pages.py 2015-09-17 05:33:50 +0000
@@ -0,0 +1,69 @@
1#
2# Ubuntu PractiTest Community results processor
3# Copyright (C) 2015 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18
19import logging
20from flask import render_template
21
22from ubuntu_pt_community.pages import reports
23
24
25logger = logging.getLogger(__name__)
26
27
28class PageDefinition:
29
30 def __init__(self, name, route_name, route_func):
31 """Encapsulate the report view details."""
32 self.name = name
33 self.route_name = route_name
34 self.route_func = route_func
35
36 @property
37 def route_function_name(self):
38 """Return the name of the route function for use_url."""
39 return self.route_func.__name__
40
41
42# Keep a list of report pages so we don't have to duplicate things when listing
43# them and adding routes.
44Report_Pages = [
45 PageDefinition(
46 'All Results',
47 '/reports/all_results',
48 reports.view_all_results
49 ),
50
51 PageDefinition(
52 'Latest Uploads',
53 '/reports/latest',
54 reports.view_latest_uploads
55 ),
56]
57
58
59def define_routes(webapp):
60 """Setup all routes for available reports (incl. list of reports."""
61 webapp.add_url_rule('/reports', view_func=view_reports)
62
63 for page in Report_Pages:
64 webapp.add_url_rule(page.route_name, view_func=page.route_func)
65
66
67def view_reports():
68 """Render a simple list of links to available reports."""
69 return render_template('results/index.html', reports=Report_Pages)
070
=== added file 'ubuntu_pt_community/pages/reports.py'
--- ubuntu_pt_community/pages/reports.py 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/pages/reports.py 2015-09-17 05:33:50 +0000
@@ -0,0 +1,167 @@
1#
2# Ubuntu PractiTest Community results processor
3
4# Copyright (C) 2015 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19
20import json
21import logging
22from collections import (
23 defaultdict,
24 namedtuple,
25)
26from flask import render_template
27
28from ubuntu_pt_community import db
29
30logger = logging.getLogger(__name__)
31
32
33def view_all_results():
34 """Render report displaying all uploaded testsuites with stats.
35
36 Stats displayed are number of runs, number of passes and fails and the % of
37 successful runs.
38 """
39 results_collection = db.get_results_collection()
40 all_uploads = results_collection.find()
41 simple_report_data = _get_simple_report_data(all_uploads)
42 return render_template(
43 'results/all_testsuites.html',
44 results=simple_report_data
45 )
46
47
48def _get_simple_report_data(all_uploads):
49 """Produce a list of suite results.
50
51 :param all_uploads: list of dicts containing uploaded data details.
52 :returns: a list of TestsuiteResult objects.
53 """
54 TestsuiteResult = namedtuple(
55 'TestsuiteResult',
56 ['name', 'runs', 'passes', 'fails', 'success_rate']
57 )
58
59 # all_uploads will be a list of dicts
60 # dict will have keys:
61 # - results: a json string with the result details.
62 # - user_email: email of the user who uploaded the results
63 # - uploaded: a date of uploading
64 testsuites = defaultdict(list)
65 for upload in all_uploads:
66 upload_id = str(upload['_id'])
67
68 try:
69 results = _result_dict_from_document(upload)
70 except KeyError as e:
71 logger.warning(e)
72 continue
73
74 only_tests = _get_only_testcases(results)
75
76 for testname in only_tests:
77 # outcome will be 'pass' or 'fail'
78 try:
79 testsuites[testname].append(only_tests[testname]['outcome'])
80 except KeyError:
81 logger.error(
82 'Testsuite has not outcome. Document ID: ',
83 upload_id
84 )
85
86 results = []
87 for suite in testsuites:
88 runs = len(testsuites[suite])
89 passes = len([t for t in testsuites[suite] if t == 'pass'])
90 fails = runs - passes
91 success_rate = format(passes / runs * 100, '.2f')
92
93 results.append(
94 TestsuiteResult(suite, runs, passes, fails, success_rate)
95 )
96 return results
97
98
99def view_latest_uploads():
100 """Render report displaying a list of uploaded results, latest first."""
101 results_collection = db.get_results_collection()
102 # Sorted by latest first. . .
103 all_uploads = results_collection.find().sort('uploaded', -1)
104
105 # slight shim over the returned data to prepare it for presention.
106 sanitised_uploads = []
107 for upload in all_uploads:
108 upload_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S')
109 uploader_email = upload.get('user_email') or 'Anonymous'
110
111 try:
112 results = _result_dict_from_document(upload)
113 except KeyError as e:
114 logger.warning(e)
115 continue
116
117 testsuite_details = _get_only_testcases(results)
118 uploaded_testsuite_names = testsuite_details.keys()
119
120 sanitised_uploads.append(
121 dict(
122 upload_date=upload_date,
123 testsuites=uploaded_testsuite_names,
124 uploader_email=uploader_email
125 )
126 )
127
128 return render_template(
129 'results/latest_uploads.html',
130 all_uploads=sanitised_uploads
131 )
132
133
134def _result_dict_from_document(upload):
135 """Return a dict of result details.
136
137 Parses the json result string and constructs a dict containing the details.
138
139 :param upload: dict containing upload details. Must contain the key
140 'results' to be successful.
141 :raises KeyError: if no 'results' are found in the upload dict.
142 """
143 try:
144 results_json = upload['results']
145 return json.loads(results_json.decode('utf-8'))
146 except KeyError:
147 upload_id = str(upload.get('_id'), 'No ID.')
148 logger.warning(
149 'Skipping document. No results or result_map available',
150 upload_id,
151 )
152
153
154def _get_only_testcases(upload_details):
155 """Return details only for testsuites.
156
157 Uploaded results can contain detials that aren't testsuite related
158 (i.e. system details tests were run on.) Extract only the details that are
159 for testsuitse.
160
161 :param uploaded_details: dict containing keys 'resource_map' and
162 'result_map'.
163 """
164 removal_keys = upload_details['resource_map'].keys()
165
166 return {k: v for k, v in upload_details['result_map'].items()
167 if k not in removal_keys}
0168
=== added directory 'ubuntu_pt_community/templates'
=== added file 'ubuntu_pt_community/templates/base.html'
--- ubuntu_pt_community/templates/base.html 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/templates/base.html 2015-09-17 05:33:50 +0000
@@ -0,0 +1,30 @@
1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7
8 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
9
10 {% block head %}
11 <title>{% block title %}{% endblock %}</title>
12 {% endblock %}
13 </head>
14 <body>
15
16 <div id="content" class="container">
17 {% block content %}{% endblock %}
18 </div>
19
20 <footer class="footer">
21 <div class="container">
22 <p class="text-muted">
23 {% block footer %}
24 &copy; Copyright 2015 <a href="https://canonical.com">Canonical Ltd.</a>
25 {% endblock %}
26 </p>
27 </div>
28 </footer>
29 </body>
30</html>
031
=== added directory 'ubuntu_pt_community/templates/results'
=== added file 'ubuntu_pt_community/templates/results/all_testsuites.html'
--- ubuntu_pt_community/templates/results/all_testsuites.html 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/templates/results/all_testsuites.html 2015-09-17 05:33:50 +0000
@@ -0,0 +1,23 @@
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 %}
024
=== added file 'ubuntu_pt_community/templates/results/index.html'
--- ubuntu_pt_community/templates/results/index.html 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/templates/results/index.html 2015-09-17 05:33:50 +0000
@@ -0,0 +1,10 @@
1{% extends "base.html" %}
2{% block title %}Community Testing Reports{% endblock %}
3{% block content %}
4<h1 class="page-header">List of available reports</h1>
5<ul>
6 {% for report in reports %}
7 <li><a href="{{ url_for(report.route_function_name) }}">{{ report.name }}</a></li>
8 {% endfor %}
9</ul>
10{% endblock %}
011
=== added file 'ubuntu_pt_community/templates/results/latest_uploads.html'
--- ubuntu_pt_community/templates/results/latest_uploads.html 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/templates/results/latest_uploads.html 2015-09-17 05:33:50 +0000
@@ -0,0 +1,27 @@
1{% extends "base.html" %}
2{% block title %}Latest Uploads{% endblock %}
3
4{% block content %}
5<h1 class="page-header">Latest uploads</h1>
6
7<table class="table table-striped table-bordered">
8 <tr>
9 <th>Date Uploaded</th>
10 <th>Testsuites run</th>
11 <th>Uploader</th>
12 </tr>
13 {% for upload in all_uploads %}
14 <tr>
15 <td>{{ upload.upload_date }}</td>
16 <td>
17 <ul>
18 {% for testsuite in upload.testsuites %}
19 <li>{{ testsuite }}</li>
20 {% endfor %}
21 </ul>
22 </td>
23 <td>{{ upload.uploader_email }}</td>
24 </tr>
25 {% endfor%}
26</table>
27{% endblock %}
028
=== added directory 'ubuntu_pt_community/tests'
=== added file 'ubuntu_pt_community/tests/__init__.py'
--- ubuntu_pt_community/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/tests/__init__.py 2015-09-17 05:33:50 +0000
@@ -0,0 +1,17 @@
1#
2# Ubuntu PractiTest Community results processor
3# Copyright (C) 2015 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17#
018
=== added file 'ubuntu_pt_community/tests/test_reports.py'
--- ubuntu_pt_community/tests/test_reports.py 1970-01-01 00:00:00 +0000
+++ ubuntu_pt_community/tests/test_reports.py 2015-09-17 05:33:50 +0000
@@ -0,0 +1,37 @@
1#
2# Ubuntu PractiTest Community results processor
3# Copyright (C) 2015 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18
19import testtools
20
21from ubuntu_pt_community.pages import reports
22
23
24class ReportHelpersTestCase(testtools.TestCase):
25
26 def test_get_only_testcases_returns_only_testcases(self):
27 testdict = dict(
28 result_map=dict(
29 testcase1=True,
30 testcase2=True,
31 resourcedetails1=False,
32 ),
33 resource_map=dict(resourcedetails1=False)
34 )
35
36 results = reports._get_only_testcases(testdict)
37 self.assertDictEqual(results, dict(testcase1=True, testcase2=True))

Subscribers

People subscribed via source and target branches