Merge lp:~james-w/python-oops-tools/recent-oopses into lp:python-oops-tools
- recent-oopses
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | James Westby |
Approved revision: | no longer in the source branch. |
Merged at revision: | 49 |
Proposed branch: | lp:~james-w/python-oops-tools/recent-oopses |
Merge into: | lp:python-oops-tools |
Diff against target: |
232 lines (+163/-3) 6 files modified
src/oopstools/oops/static/oops.css (+7/-1) src/oopstools/oops/templates/report.html (+36/-0) src/oopstools/oops/test/__init__.py (+0/-2) src/oopstools/oops/test/test_report.py (+77/-0) src/oopstools/oops/test/testcase.py (+11/-0) src/oopstools/oops/views.py (+32/-0) |
To merge this branch: | bzr merge lp:~james-w/python-oops-tools/recent-oopses |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | code | Approve | |
j.c.sackett (community) | Approve | ||
Review via email: mp+137055@code.launchpad.net |
Commit message
Show the (paginated) oopses for each report.
Description of the change
Hi,
Showing the day-old reports is of limited use, because you don't know
if there are issues with the site at this moment. To try and help with that
show the oopses, sorted with the most recent first, on the report page.
It uses pagination to keep the number of oopses displayed small.
Thanks,
James
Launchpad PQM Bot (launchpad-pqm) wrote : | # |
The attempt to merge lp:~james-w/python-oops-tools/recent-oopses into lp:python-oops-tools failed.Below is the output from the failed tests.
find . -type f -name '*.py[co]' -exec rm -f {} \;
rm -f bin/buildout
rm -f src/oopstools/
bzr clean-tree --unknown --force
bzr up download-cache
python bootstrap.py \
--setup-
--download-
Download error: [Errno 111] Connection refused -- Some packages may not be found!
Download error: [Errno 111] Connection refused -- Some packages may not be found!
Generated script '/home/
bin/buildout configuration:
Develop: '/home/
Uninstalling filetemplates.
Updating scripts.
Installing filetemplates.
Updating django.
Updating docs.
Updating tags.
bin/test
Creating test database for alias 'default'...
Destroying test database for alias 'default'...
W: line 4 [buildbot-staging]: Deprecated key 'location' used
I: This option will be removed in the future; please update your configuration
W: line 2 [pqm]: Deprecated key 'location' used
I: This option will be removed in the future; please update your configuration
W: line 3 [pqm-oops-tools]: Deprecated key 'location' used
I: This option will be removed in the future; please update your configuration
W: line 1 [precise]: Deprecated key 'location' used
I: This option will be removed in the future; please update your configuration
I: [pqm-oops-tools chroot] Running command: "cd /home/pqm/
Nothing to delete.
+N dist/Twisted-
+N dist/lazr.
+N dist/lazr.
+N dist/lazr.
+N dist/lazr.
+N dist/lazr.
-D dist/lazr.
-D dist/lazr.
-D dist/lazr.
-D dist/lazr.
All changes applied successfully.
Updated to revision 531 of branch http://
/home/pqm/
DeprecationWa
/home/pqm/
DeprecationWa
/home/pqm/
category=
.......
=======
ERROR: test_recent_oopses (test_report.
-------
Traceback (most recent call last):
File "/home/pqm/...
Curtis Hovey (sinzui) wrote : | # |
Thank you for addressing the upcall issue.
Launchpad PQM Bot (launchpad-pqm) wrote : | # |
The attempt to merge lp:~james-w/python-oops-tools/recent-oopses into lp:python-oops-tools failed.Below is the output from the failed tests.
find . -type f -name '*.py[co]' -exec rm -f {} \;
rm -f bin/buildout
rm -f src/oopstools/
bzr clean-tree --unknown --force
bzr up download-cache
python bootstrap.py \
--setup-
--download-
Download error: [Errno 111] Connection refused -- Some packages may not be found!
Download error: [Errno 111] Connection refused -- Some packages may not be found!
Generated script '/home/
bin/buildout configuration:
Develop: '/home/
Uninstalling filetemplates.
Updating scripts.
Installing filetemplates.
Updating django.
Updating docs.
Updating tags.
bin/test
Creating test database for alias 'default'...
W: line 4 [buildbot-staging]: Deprecated key 'location' used
I: This option will be removed in the future; please update your configuration
W: line 2 [pqm]: Deprecated key 'location' used
I: This option will be removed in the future; please update your configuration
W: line 3 [pqm-oops-tools]: Deprecated key 'location' used
I: This option will be removed in the future; please update your configuration
W: line 1 [precise]: Deprecated key 'location' used
I: This option will be removed in the future; please update your configuration
I: [pqm-oops-tools chroot] Running command: "cd /home/pqm/
Nothing to delete.
Tree is up to date at revision 531 of branch http://
Traceback (most recent call last):
File "bin/test", line 58, in <module>
djangorecip
File "/home/
management.
File "/home/
utility.
File "/home/
self.
File "/home/
self.
File "/home/
output = self.handle(*args, **options)
File "/home/
super(Command, self).handle(*args, **kwargs)
File "/home/
failures = test_runner.
File "/home/
old_config = self.setup_
File "/home/
- 49. By James Westby
-
Show the (paginated) oopses for each report.
Preview Diff
1 | === modified file 'src/oopstools/oops/static/oops.css' | |||
2 | --- src/oopstools/oops/static/oops.css 2011-10-13 20:18:51 +0000 | |||
3 | +++ src/oopstools/oops/static/oops.css 2012-12-04 16:05:33 +0000 | |||
4 | @@ -7,7 +7,13 @@ | |||
5 | 7 | padding:0px; | 7 | padding:0px; |
6 | 8 | } | 8 | } |
7 | 9 | table { | 9 | table { |
9 | 10 | border: 1px #000; | 10 | border: 1px solid #000; |
10 | 11 | } | ||
11 | 12 | td { | ||
12 | 13 | padding: 4px; | ||
13 | 14 | } | ||
14 | 15 | tr:nth-child(2n) { | ||
15 | 16 | background-color: #eee; | ||
16 | 11 | } | 17 | } |
17 | 12 | p,h1,pre { | 18 | p,h1,pre { |
18 | 13 | margin:0px 10px 10px 10px; | 19 | margin:0px 10px 10px 10px; |
19 | 14 | 20 | ||
20 | === modified file 'src/oopstools/oops/templates/report.html' | |||
21 | --- src/oopstools/oops/templates/report.html 2011-10-13 20:18:51 +0000 | |||
22 | +++ src/oopstools/oops/templates/report.html 2012-12-04 16:05:33 +0000 | |||
23 | @@ -13,5 +13,41 @@ | |||
24 | 13 | <li><a href="{{SUMMARY_URI}}/{{report.name}}-{{date|date:"Y-m-d"}}.html">{{report.title}} {{date|date:"Y-m-d"}}</a></li> | 13 | <li><a href="{{SUMMARY_URI}}/{{report.name}}-{{date|date:"Y-m-d"}}.html">{{report.title}} {{date|date:"Y-m-d"}}</a></li> |
25 | 14 | {% endfor %} | 14 | {% endfor %} |
26 | 15 | <ul> | 15 | <ul> |
27 | 16 | <h2>Recent Oopses</h2> | ||
28 | 17 | <table> | ||
29 | 18 | <th><tr> | ||
30 | 19 | <td>Date/Time</td> | ||
31 | 20 | <td>HTTP Method</td> | ||
32 | 21 | <td>URL</td> | ||
33 | 22 | <td>Duration (ms)</td> | ||
34 | 23 | <td>Exception Type</td> | ||
35 | 24 | <td>Oops</td> | ||
36 | 25 | </tr></th> | ||
37 | 26 | {% for oops in recent.object_list %} | ||
38 | 27 | <tr> | ||
39 | 28 | <td>{{oops.date|date:"Y-m-d H:i:s"}}</td> | ||
40 | 29 | <td>{{oops.http_method}}</td> | ||
41 | 30 | <td>{% if oops.url %}<a href="{{oops.url}}">{{oops.url}}</a>{% endif %}</td> | ||
42 | 31 | <td>{{oops.total_time}}</td> | ||
43 | 32 | <td>{{oops.exception_type}}</td> | ||
44 | 33 | <td><a href="{% url oopstools.oops.views.index %}?oopsid={{oops.oopsid}}">{{oops.oopsid}}</a></td> | ||
45 | 34 | </tr> | ||
46 | 35 | {% endfor %} | ||
47 | 36 | </table> | ||
48 | 37 | <div class="pagination"> | ||
49 | 38 | <span class="step-links"> | ||
50 | 39 | {% if recent.has_previous %} | ||
51 | 40 | <a href="?page={{ recent.previous_page_number }}">newer</a> | ||
52 | 41 | {% endif %} | ||
53 | 42 | |||
54 | 43 | <span class="current"> | ||
55 | 44 | Page {{ recent.number }} of {{ recent.paginator.num_pages }}. | ||
56 | 45 | </span> | ||
57 | 46 | |||
58 | 47 | {% if recent.has_next %} | ||
59 | 48 | <a href="?page={{ recent.next_page_number }}">older</a> | ||
60 | 49 | {% endif %} | ||
61 | 50 | </span> | ||
62 | 51 | </div> | ||
63 | 16 | </body> | 52 | </body> |
64 | 17 | </html> | 53 | </html> |
65 | 18 | 54 | ||
66 | === modified file 'src/oopstools/oops/test/__init__.py' | |||
67 | --- src/oopstools/oops/test/__init__.py 2011-10-13 20:18:51 +0000 | |||
68 | +++ src/oopstools/oops/test/__init__.py 2012-12-04 16:05:33 +0000 | |||
69 | @@ -12,5 +12,3 @@ | |||
70 | 12 | # | 12 | # |
71 | 13 | # You should have received a copy of the GNU Affero General Public License | 13 | # You should have received a copy of the GNU Affero General Public License |
72 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
73 | 15 | |||
74 | 16 | |||
75 | 17 | 15 | ||
76 | === added file 'src/oopstools/oops/test/test_report.py' | |||
77 | --- src/oopstools/oops/test/test_report.py 1970-01-01 00:00:00 +0000 | |||
78 | +++ src/oopstools/oops/test/test_report.py 2012-12-04 16:05:33 +0000 | |||
79 | @@ -0,0 +1,77 @@ | |||
80 | 1 | # Copyright 2005-2012 Canonical Ltd. All rights reserved. | ||
81 | 2 | # | ||
82 | 3 | # This program is free software: you can redistribute it and/or modify | ||
83 | 4 | # it under the terms of the GNU Affero General Public License as published by | ||
84 | 5 | # the Free Software Foundation, either version 3 of the License, or | ||
85 | 6 | # (at your option) any later version. | ||
86 | 7 | # | ||
87 | 8 | # This program is distributed in the hope that it will be useful, | ||
88 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
89 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
90 | 11 | # GNU Affero General Public License for more details. | ||
91 | 12 | # | ||
92 | 13 | # You should have received a copy of the GNU Affero General Public License | ||
93 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
94 | 15 | |||
95 | 16 | from datetime import datetime | ||
96 | 17 | from operator import attrgetter | ||
97 | 18 | |||
98 | 19 | from oopstools.oops.models import ( | ||
99 | 20 | AppInstance, | ||
100 | 21 | Classification, | ||
101 | 22 | Infestation, | ||
102 | 23 | Oops, | ||
103 | 24 | Prefix, | ||
104 | 25 | Report, | ||
105 | 26 | ) | ||
106 | 27 | from oopstools.oops.test.testcase import TestCase | ||
107 | 28 | |||
108 | 29 | |||
109 | 30 | class ReportTests(TestCase): | ||
110 | 31 | |||
111 | 32 | def test_no_report(self): | ||
112 | 33 | resp = self.client.get('/reports/some-report/') | ||
113 | 34 | self.assertEqual(404, resp.status_code) | ||
114 | 35 | |||
115 | 36 | def test_inactive_report(self): | ||
116 | 37 | report_name = 'areport' | ||
117 | 38 | Report.objects.create(name=report_name, active=False) | ||
118 | 39 | resp = self.client.get('/reports/%s/' % (report_name,)) | ||
119 | 40 | self.assertEqual(404, resp.status_code) | ||
120 | 41 | |||
121 | 42 | def test_no_recent_oopses(self): | ||
122 | 43 | report_name = 'areport' | ||
123 | 44 | Report.objects.create(name=report_name, active=True) | ||
124 | 45 | resp = self.client.get('/reports/%s/' % (report_name,)) | ||
125 | 46 | self.assertEqual(200, resp.status_code) | ||
126 | 47 | self.assertQuerysetEqual(resp.context['recent'].object_list, []) | ||
127 | 48 | |||
128 | 49 | def make_oops(self, prefix): | ||
129 | 50 | classification = Classification.objects.create( | ||
130 | 51 | title=self.getUniqueString(prefix="classification")) | ||
131 | 52 | infestation = Infestation.objects.create( | ||
132 | 53 | exception_type=self.getUniqueString(prefix="exc_type"), | ||
133 | 54 | exception_value=self.getUniqueString(prefix="exc_value")) | ||
134 | 55 | return Oops.objects.create( | ||
135 | 56 | prefix=prefix, classification=classification, | ||
136 | 57 | oopsinfestation=infestation, statements_count=100, | ||
137 | 58 | appinstance=prefix.appinstance, total_time=3, | ||
138 | 59 | date=datetime.now()) | ||
139 | 60 | |||
140 | 61 | def make_prefix(self): | ||
141 | 62 | appinstance = AppInstance.objects.create( | ||
142 | 63 | title=self.getUniqueString(prefix="appinstance")) | ||
143 | 64 | return Prefix.objects.create( | ||
144 | 65 | value=self.getUniqueString(prefix="prefix"), | ||
145 | 66 | appinstance=appinstance) | ||
146 | 67 | |||
147 | 68 | def test_recent_oopses(self): | ||
148 | 69 | report_name = 'areport' | ||
149 | 70 | report = Report.objects.create(name=report_name, active=True) | ||
150 | 71 | prefix = self.make_prefix() | ||
151 | 72 | report.prefixes.add(prefix) | ||
152 | 73 | oops = self.make_oops(prefix) | ||
153 | 74 | resp = self.client.get('/reports/%s/' % (report_name,)) | ||
154 | 75 | self.assertEqual(200, resp.status_code) | ||
155 | 76 | self.assertQuerysetEqual( | ||
156 | 77 | resp.context['recent'].object_list, [oops.pk], transform=attrgetter('pk')) | ||
157 | 0 | 78 | ||
158 | === added file 'src/oopstools/oops/test/testcase.py' | |||
159 | --- src/oopstools/oops/test/testcase.py 1970-01-01 00:00:00 +0000 | |||
160 | +++ src/oopstools/oops/test/testcase.py 2012-12-04 16:05:33 +0000 | |||
161 | @@ -0,0 +1,11 @@ | |||
162 | 1 | from django.test import TestCase as DjangoTestCase | ||
163 | 2 | from testtools import TestCase as TesttoolsTestCase | ||
164 | 3 | |||
165 | 4 | |||
166 | 5 | class TestCase(DjangoTestCase, TesttoolsTestCase): | ||
167 | 6 | |||
168 | 7 | def __init__(self, *args, **kwargs): | ||
169 | 8 | # python2.6 doesn't up-call, so TesttoolsTestCase.__init__ | ||
170 | 9 | # never gets called unless we do this. | ||
171 | 10 | DjangoTestCase.__init__(self, *args, **kwargs) | ||
172 | 11 | TesttoolsTestCase.__init__(self, *args, **kwargs) | ||
173 | 0 | 12 | ||
174 | === modified file 'src/oopstools/oops/views.py' | |||
175 | --- src/oopstools/oops/views.py 2011-11-03 00:08:18 +0000 | |||
176 | +++ src/oopstools/oops/views.py 2012-12-04 16:05:33 +0000 | |||
177 | @@ -18,6 +18,7 @@ | |||
178 | 18 | from datetime import datetime, timedelta | 18 | from datetime import datetime, timedelta |
179 | 19 | 19 | ||
180 | 20 | from django.conf import settings | 20 | from django.conf import settings |
181 | 21 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | ||
182 | 21 | from django.http import Http404, HttpResponseRedirect | 22 | from django.http import Http404, HttpResponseRedirect |
183 | 22 | from django.shortcuts import render_to_response | 23 | from django.shortcuts import render_to_response |
184 | 23 | 24 | ||
185 | @@ -96,6 +97,32 @@ | |||
186 | 96 | "prefixes": prefixes}) | 97 | "prefixes": prefixes}) |
187 | 97 | 98 | ||
188 | 98 | 99 | ||
189 | 100 | def get_page_from_query_args(paginator, query_args): | ||
190 | 101 | """Get the page from ``paginator`` specified in the query params. | ||
191 | 102 | |||
192 | 103 | If the request specified ``page`` in the query params, then that | ||
193 | 104 | page of the paginator will be retrieved. | ||
194 | 105 | |||
195 | 106 | This function aims to return a page regardless of the page requested, | ||
196 | 107 | e.g. requesting a page beyond the last page will just return the | ||
197 | 108 | last page. | ||
198 | 109 | |||
199 | 110 | :param paginator: a ``Paginator`` object. | ||
200 | 111 | :param query_args: a ``dict`` of query params. | ||
201 | 112 | :return: a ``Page`` from the ``Paginator``. | ||
202 | 113 | """ | ||
203 | 114 | page_num = query_args.get('page', 1) | ||
204 | 115 | try: | ||
205 | 116 | page = paginator.page(page_num) | ||
206 | 117 | except PageNotAnInteger: | ||
207 | 118 | # If page is not an integer, deliver first page. | ||
208 | 119 | page = paginator.page(1) | ||
209 | 120 | except EmptyPage: | ||
210 | 121 | # If page is out of range (e.g. 9999), deliver last page of results. | ||
211 | 122 | page = paginator.page(paginator.num_pages) | ||
212 | 123 | return page | ||
213 | 124 | |||
214 | 125 | |||
215 | 99 | def report(request, report_name): | 126 | def report(request, report_name): |
216 | 100 | try: | 127 | try: |
217 | 101 | r = Report.objects.get(name=report_name, active=True) | 128 | r = Report.objects.get(name=report_name, active=True) |
218 | @@ -108,10 +135,15 @@ | |||
219 | 108 | dates = [] | 135 | dates = [] |
220 | 109 | for day in range(1, 11): | 136 | for day in range(1, 11): |
221 | 110 | dates.append(now - timedelta(day)) | 137 | dates.append(now - timedelta(day)) |
222 | 138 | oopses = Oops.objects.filter( | ||
223 | 139 | prefix__report=r).order_by('-date') | ||
224 | 140 | paginator = Paginator(oopses, 50) | ||
225 | 141 | recent_oopses = get_page_from_query_args(paginator, request.GET) | ||
226 | 111 | data = { | 142 | data = { |
227 | 112 | 'report': r, | 143 | 'report': r, |
228 | 113 | 'dates': dates, | 144 | 'dates': dates, |
229 | 114 | 'SUMMARY_URI': settings.SUMMARY_URI, | 145 | 'SUMMARY_URI': settings.SUMMARY_URI, |
230 | 146 | 'recent': recent_oopses, | ||
231 | 115 | } | 147 | } |
232 | 116 | return render_to_response("report.html", dictionary=data) | 148 | return render_to_response("report.html", dictionary=data) |
233 | 117 | 149 |
Looks good. Thanks, James.