Merge lp:~nuclearbob/qlbr/rtm-merge into lp:qlbr
- rtm-merge
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~nuclearbob/qlbr/rtm-merge |
Merge into: | lp:qlbr |
Diff against target: |
1318 lines (+1046/-69) 10 files modified
qlbr/management/commands/clear_cache.py (+28/-0) qlbr/management/commands/drop_searches.py (+29/-0) qlbr/management/commands/get_task_data.py (+59/-12) qlbr/settings.py (+27/-3) qlbr/team_mapping.py (+169/-0) qlbr/templates/qlbr/qa-default.html (+448/-0) qlbr/templates/qlbr/rtm.html (+16/-15) qlbr/urls.py (+7/-3) qlbr/views.py (+152/-36) rtm_searches.json (+111/-0) |
To merge this branch: | bzr merge lp:~nuclearbob/qlbr/rtm-merge |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical Platform QA Team | Pending | ||
Review via email: mp+239040@code.launchpad.net |
Commit message
Description of the change
This branch merges the changes we've made for the RTM report into the main branch and adds the RTM view. There are definitely more improvements that could be made (a lot of code is shared between the two views still, for instance,) but I'd like feedback on this as a first draft, and we can figure out whether to improve this further before merging, or try to land it and then make additional improvements as time allows.
I'd like to get things back into lp:qlbr for purposes of juju and mojo deployment.
- 133. By Max Brustkern
-
Restored old searches
- 134. By Max Brustkern
-
calling bug_tags once
- 135. By Max Brustkern
-
Added some non-display team names
- 136. By Max Brustkern
-
Enhanced readability
- 137. By Max Brustkern
-
More view refactoring
- 138. By Max Brustkern
-
Removed duplicate duplicate handling
- 139. By Max Brustkern
-
Adding setting for credentials file
- 140. By Max Brustkern
-
Refined and organized team mapping
- 141. By Max Brustkern
-
Sorted projects
- 142. By Max Brustkern
-
Sorted projects
Unmerged revisions
- 142. By Max Brustkern
-
Sorted projects
- 141. By Max Brustkern
-
Sorted projects
- 140. By Max Brustkern
-
Refined and organized team mapping
- 139. By Max Brustkern
-
Adding setting for credentials file
- 138. By Max Brustkern
-
Removed duplicate duplicate handling
- 137. By Max Brustkern
-
More view refactoring
- 136. By Max Brustkern
-
Enhanced readability
- 135. By Max Brustkern
-
Added some non-display team names
- 134. By Max Brustkern
-
calling bug_tags once
- 133. By Max Brustkern
-
Restored old searches
Preview Diff
1 | === renamed file 'searches.json' => 'qa_searches.json' |
2 | === added file 'qlbr/management/commands/clear_cache.py' |
3 | --- qlbr/management/commands/clear_cache.py 1970-01-01 00:00:00 +0000 |
4 | +++ qlbr/management/commands/clear_cache.py 2014-10-22 15:29:30 +0000 |
5 | @@ -0,0 +1,28 @@ |
6 | +# QA Launchpad Bug Reports |
7 | +# Copyright 2014 Canonical Ltd. |
8 | + |
9 | +# This program is free software: you can redistribute it and/or modify it |
10 | +# under the terms of the GNU Affero General Public License version 3, as |
11 | +# published by the Free Software Foundation. |
12 | + |
13 | +# This program is distributed in the hope that it will be useful, but |
14 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
15 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
16 | +# PURPOSE. See the GNU Affero General Public License for more details. |
17 | + |
18 | +# You should have received a copy of the GNU Affero General Public License |
19 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | + |
21 | +"""Clear the cache. Mainly intended for charm use.""" |
22 | + |
23 | +from django.core.cache import cache |
24 | +from django.core.management.base import BaseCommand |
25 | + |
26 | + |
27 | +class Command(BaseCommand): |
28 | + |
29 | + """Clear the cache.""" |
30 | + |
31 | + def handle(self, *args, **options): |
32 | + """Clear the cache.""" |
33 | + cache.clear() |
34 | |
35 | === added file 'qlbr/management/commands/drop_searches.py' |
36 | --- qlbr/management/commands/drop_searches.py 1970-01-01 00:00:00 +0000 |
37 | +++ qlbr/management/commands/drop_searches.py 2014-10-22 15:29:30 +0000 |
38 | @@ -0,0 +1,29 @@ |
39 | +# QA Launchpad Bug Reports |
40 | +# Copyright 2014 Canonical Ltd. |
41 | + |
42 | +# This program is free software: you can redistribute it and/or modify it |
43 | +# under the terms of the GNU Affero General Public License version 3, as |
44 | +# published by the Free Software Foundation. |
45 | + |
46 | +# This program is distributed in the hope that it will be useful, but |
47 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
48 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
49 | +# PURPOSE. See the GNU Affero General Public License for more details. |
50 | + |
51 | +# You should have received a copy of the GNU Affero General Public License |
52 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
53 | + |
54 | +"""Delete all searches so they can be freshly loaded.""" |
55 | + |
56 | +from django.core.management.base import BaseCommand |
57 | + |
58 | +from qlbr.models import Search |
59 | + |
60 | + |
61 | +class Command(BaseCommand): |
62 | + |
63 | + """Delete all searches so they can be freshly loaded.""" |
64 | + |
65 | + def handle(self, *args, **options): |
66 | + """Delete all searches so they can be freshly loaded.""" |
67 | + Search.objects.all().delete() |
68 | |
69 | === modified file 'qlbr/management/commands/get_task_data.py' |
70 | --- qlbr/management/commands/get_task_data.py 2014-06-04 18:21:24 +0000 |
71 | +++ qlbr/management/commands/get_task_data.py 2014-10-22 15:29:30 +0000 |
72 | @@ -17,12 +17,19 @@ |
73 | |
74 | import datetime |
75 | import logging |
76 | +from optparse import make_option |
77 | |
78 | +from django.conf import settings |
79 | +from django.core.cache import cache |
80 | from django.core.exceptions import ObjectDoesNotExist |
81 | +from django.core.management.base import BaseCommand |
82 | + |
83 | +from launchpadlib.launchpad import Launchpad |
84 | |
85 | from qlbr import TZ |
86 | from qlbr.management.commands import LaunchpadCommand |
87 | from qlbr.models import Bug, Person, Search, SearchTag, Tag, Task |
88 | +from qlbr.settings import CACHEDIR |
89 | |
90 | logger = logging.getLogger(__name__) |
91 | RUN_START = datetime.datetime.now(TZ) |
92 | @@ -32,6 +39,20 @@ |
93 | |
94 | """Provide data slurping commands.""" |
95 | |
96 | + option_list = BaseCommand.option_list + ( |
97 | + make_option('--reload', |
98 | + action='store_true', |
99 | + dest='reload', |
100 | + default=False, |
101 | + help='Reload all task data', |
102 | + ), |
103 | + make_option('--credentials_file', |
104 | + action='store', |
105 | + dest='credentials_file', |
106 | + help='Credentials file for authenticated LP access.', |
107 | + ), |
108 | + ) |
109 | + |
110 | def get_person(self, person_link): |
111 | """Get a person from launchpad if we don't already have it. |
112 | |
113 | @@ -50,14 +71,15 @@ |
114 | logger.info('Retriveing %s from launchpad', person_link) |
115 | try: |
116 | lp_person = self.lp.load(person_link) |
117 | - d_person = Person(self_link=lp_person.self_link, |
118 | - web_link=lp_person.web_link, |
119 | - name=lp_person.name, |
120 | - display_name=lp_person.display_name) |
121 | - d_person.save() |
122 | - return d_person |
123 | except: |
124 | + logger.error('Could not load %s', person_link) |
125 | return None |
126 | + d_person = Person(self_link=lp_person.self_link, |
127 | + web_link=lp_person.web_link, |
128 | + name=lp_person.name, |
129 | + display_name=lp_person.display_name) |
130 | + d_person.save() |
131 | + return d_person |
132 | |
133 | def get_bug(self, bug_link, duplicate=False): |
134 | """Get a bug from launchpad. Refresh it if it's stale. |
135 | @@ -78,7 +100,11 @@ |
136 | logger.info('Updating %s from launchpad', bug_link) |
137 | except ObjectDoesNotExist: |
138 | logger.info('Retrieving %s from launchpad', bug_link) |
139 | - lp_bug = self.lp.load(bug_link) |
140 | + try: |
141 | + lp_bug = self.lp.load(bug_link) |
142 | + except: |
143 | + logger.error('Could not load %s', bug_link) |
144 | + return None |
145 | d_owner = self.get_person(lp_bug.owner_link) |
146 | d_duplicate_of = self.get_bug(lp_bug.duplicate_of_link, duplicate=True) |
147 | bug_values = {'web_link': lp_bug.web_link, |
148 | @@ -96,6 +122,7 @@ |
149 | **bug_values) |
150 | else: |
151 | d_bug = Bug.objects.get(self_link=lp_bug.self_link) |
152 | + d_bug.tags.clear() |
153 | for tag in lp_bug.tags: |
154 | d_tag, _created = Tag.objects.get_or_create(tag=tag) |
155 | d_bug.tags.add(d_tag) |
156 | @@ -131,7 +158,12 @@ |
157 | except ObjectDoesNotExist: |
158 | logger.info('Retrieving %s from launchpad', task_link) |
159 | if isinstance(task, basestring): |
160 | - task = self.lp.load(task) |
161 | + try: |
162 | + task = self.lp.load(task) |
163 | + except: |
164 | + logger.error('Could not load %s', task) |
165 | + Task.objects.get(self_link=task).delete() |
166 | + return None |
167 | d_bug = self.get_bug(task.bug_link) |
168 | d_owner = self.get_person(task.owner_link) |
169 | d_assignee = self.get_person(task.assignee_link) |
170 | @@ -183,10 +215,17 @@ |
171 | criteria.update(search.criteria_dict) |
172 | for project in search.projects.values_list('project', flat=True): |
173 | logger.info('Searching project: %s', project) |
174 | - lp_project = self.lp.projects[project] |
175 | - for task in lp_project.searchTasks(**criteria): |
176 | - self.get_task(task, search.search_tags.values_list( |
177 | - 'search_tag', flat=True)) |
178 | + try: |
179 | + lp_project = self.lp.projects[project] |
180 | + for task in lp_project.searchTasks(**criteria): |
181 | + self.get_task(task, search.search_tags.values_list( |
182 | + 'search_tag', flat=True)) |
183 | + except KeyError as e: |
184 | + print("Skipping project '%s': %s" % (project, e)) |
185 | + pass |
186 | + if self.reload: |
187 | + for task in Task.objects.values_list('self_link', flat=True): |
188 | + self.get_task(task) |
189 | search.last_update = RUN_START |
190 | search.save() |
191 | |
192 | @@ -196,4 +235,12 @@ |
193 | self.reload = True |
194 | else: |
195 | self.reload = False |
196 | + credentials_file = options.get('credentials_file', None) |
197 | + if not credentials_file: |
198 | + credentials_file = settings.CREDENTIALS_FILE |
199 | + if credentials_file: |
200 | + self.lp = Launchpad.login_with( |
201 | + 'QA Reports', 'production', CACHEDIR, |
202 | + credentials_file=credentials_file) |
203 | self.search_tasks() |
204 | + cache.clear() |
205 | |
206 | === modified file 'qlbr/settings.py' |
207 | --- qlbr/settings.py 2013-11-26 21:33:14 +0000 |
208 | +++ qlbr/settings.py 2014-10-22 15:29:30 +0000 |
209 | @@ -103,7 +103,7 @@ |
210 | STATICFILES_FINDERS = ( |
211 | 'django.contrib.staticfiles.finders.FileSystemFinder', |
212 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', |
213 | - #'django.contrib.staticfiles.finders.DefaultStorageFinder', |
214 | + # 'django.contrib.staticfiles.finders.DefaultStorageFinder', |
215 | ) |
216 | |
217 | # Make this unique, and don't share it with anybody. |
218 | @@ -113,7 +113,7 @@ |
219 | TEMPLATE_LOADERS = ( |
220 | 'django.template.loaders.filesystem.Loader', |
221 | 'django.template.loaders.app_directories.Loader', |
222 | - #'django.template.loaders.eggs.Loader', |
223 | + # 'django.template.loaders.eggs.Loader', |
224 | ) |
225 | |
226 | MIDDLEWARE_CLASSES = ( |
227 | @@ -124,6 +124,8 @@ |
228 | 'django.contrib.messages.middleware.MessageMiddleware', |
229 | # Uncomment the next line for simple clickjacking protection: |
230 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', |
231 | + 'django.middleware.cache.UpdateCacheMiddleware', |
232 | + 'django.middleware.cache.FetchFromCacheMiddleware', |
233 | ) |
234 | |
235 | ROOT_URLCONF = 'qlbr.urls' |
236 | @@ -186,10 +188,32 @@ |
237 | } |
238 | |
239 | # our launchpadlib cache |
240 | -CACHEDIR = "/home/ubuntu/.launchpadlib/cache/" |
241 | +CACHEDIR = os.path.expanduser("~/.launchpadlib/cache/") |
242 | +CREDENTIALS_FILE = os.path.expanduser("~/.launchpadlib/credentials") |
243 | + |
244 | +# Let's try memcached for caching |
245 | +CACHES = { |
246 | + 'default': { |
247 | + 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', |
248 | + } |
249 | +} |
250 | +CACHE_MIDDLEWARE_ALIADS = 'qlbr' |
251 | +CACHE_MIDDLEWARE_SECONDS = 600 |
252 | +CACHE_MIDDLEWARE_KEY_PREFIX = '' |
253 | + |
254 | +# Set the default view so we can override it locally |
255 | +DEFAULT_VIEW = 'qa' |
256 | |
257 | # If we have local settings, import 'em |
258 | try: |
259 | from local_settings import * # noqa |
260 | except ImportError: |
261 | pass |
262 | +try: |
263 | + from database_settings import * # noqa |
264 | +except ImportError: |
265 | + pass |
266 | +try: |
267 | + from cache_settings import * # noqa |
268 | +except ImportError: |
269 | + pass |
270 | |
271 | === added file 'qlbr/team_mapping.py' |
272 | --- qlbr/team_mapping.py 1970-01-01 00:00:00 +0000 |
273 | +++ qlbr/team_mapping.py 2014-10-22 15:29:30 +0000 |
274 | @@ -0,0 +1,169 @@ |
275 | +# QA Launchpad Bug Reports |
276 | +# Copyright 2013 Canonical Ltd. |
277 | + |
278 | +# This program is free software: you can redistribute it and/or modify it |
279 | +# under the terms of the GNU Affero General Public License version 3, as |
280 | +# published by the Free Software Foundation. |
281 | + |
282 | +# This program is distributed in the hope that it will be useful, but |
283 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
284 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
285 | +# PURPOSE. See the GNU Affero General Public License for more details. |
286 | + |
287 | +# You should have received a copy of the GNU Affero General Public License |
288 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
289 | + |
290 | +"""Provide team to project mappings.""" |
291 | + |
292 | +__all__ = [ |
293 | + 'TEAM_MAPPING', |
294 | + 'PROJECT_MAPPING', |
295 | + 'PROJECT_LIST', |
296 | + 'PROJECT_CONFLICTS', |
297 | +] |
298 | + |
299 | +TEAM_MAPPING = { |
300 | + 'Unity APIs': ( |
301 | + 'gounityscopes', |
302 | + 'indicatordatetime', |
303 | + 'indicatorlocation', |
304 | + 'indicatormessages', |
305 | + 'indicatornetwork', |
306 | + 'indicatorpower', |
307 | + 'indicatorsound', |
308 | + 'indicatortransfer', |
309 | + 'locationservice', |
310 | + 'mediascanner', |
311 | + 'mediascanner2', |
312 | + 'payservice', |
313 | + 'payui', |
314 | + 'thumbnailer', |
315 | + 'unityscopeclick', |
316 | + 'unityscopemediascanner', |
317 | + 'unityscopesapi', |
318 | + 'unityscopesshell', |
319 | + 'unityscopevimeo', |
320 | + 'unityscopeyoutube', |
321 | + 'urldispatcher', |
322 | + ), |
323 | + 'Phonedations': ( |
324 | + 'android', |
325 | + 'bluez', |
326 | + 'mediahub', |
327 | + 'mtp', |
328 | + 'networkmanager', |
329 | + 'nuntium', |
330 | + 'ofono', |
331 | + 'platformapi', |
332 | + 'powerd', |
333 | + 'pulseaudio', |
334 | + 'qtcreatorpluginubuntu', |
335 | + 'qtubuntucamera', |
336 | + 'qtubuntumedia', |
337 | + 'ubuntutouchsession', |
338 | + ), |
339 | + 'Shell/Mir': ( |
340 | + 'mir', |
341 | + 'qtmir', |
342 | + 'qtubuntu', |
343 | + 'unity8', |
344 | + 'unitymir', |
345 | + 'unitysystemcompositor', |
346 | + ), |
347 | + 'Apps': ( |
348 | + 'addressbookapp', |
349 | + 'addressbookservice', |
350 | + 'cameraapp', |
351 | + 'dialerapp', |
352 | + 'galleryapp', |
353 | + 'maliitframework', |
354 | + 'mediaplayerapp', |
355 | + 'messagingapp', |
356 | + 'notesapp', |
357 | + 'oempriority', |
358 | + 'qtorganizer5eds', |
359 | + 'qtpimopensourcesrc', |
360 | + 'syncmonitor', |
361 | + 'telepathyofono', |
362 | + 'telephonyservice', |
363 | + 'ubuntukeyboard', |
364 | + 'webbrowserapp', |
365 | + ), |
366 | + 'Webapps/OA': ( |
367 | + 'accountplugins', |
368 | + 'accountsservice', |
369 | + 'onlineaccounts', |
370 | + 'signon', |
371 | + 'signonui', |
372 | + 'ubuntusystemsettingsonlineaccounts', |
373 | + 'unitywebappsqml', |
374 | + 'webappscore', |
375 | + ), |
376 | + 'Security': ( |
377 | + 'apparmor', |
378 | + 'apparmoreasyprofubuntu', |
379 | + 'clickapparmor', |
380 | + 'oxide', |
381 | + 'oxideqt', |
382 | + ), |
383 | + 'Community': ( |
384 | + 'droppingletters', |
385 | + 'musicapp', |
386 | + 'remindersapp', |
387 | + 'sudokuapp', |
388 | + 'ubuntucalculatorapp', |
389 | + 'ubuntuclockapp', |
390 | + 'ubuntufilemanagerapp', |
391 | + 'ubunturssreaderapp', |
392 | + 'ubuntuterminalapp', |
393 | + 'ubuntuweatherapp', |
394 | + ), |
395 | + 'SDK': ( |
396 | + 'ubuntuuitoolkit', |
397 | + ), |
398 | + 'Design': ( |
399 | + 'ubuntuux', |
400 | + ), |
401 | + 'Foundations': ( |
402 | + 'click', |
403 | + 'livecdrootfs', |
404 | + ), |
405 | + 'SystemSettings': ( |
406 | + 'ubuntusystemsettings', |
407 | + ), |
408 | + 'Desktop': ( |
409 | + 'ubuntuonecredentials', |
410 | + ), |
411 | + 'OnlineServices': ( |
412 | + ), |
413 | + 'Barajas': ( |
414 | + 'barajas', |
415 | + ), |
416 | + 'Espoo': ( |
417 | + 'espoo', |
418 | + ), |
419 | + 'Dash Server': ( |
420 | + 'accountpolld', |
421 | + 'curucu', |
422 | + 'ubuntupush', |
423 | + 'ubuntupushclient', |
424 | + 'ubuntupushserver', |
425 | + 'ubunturestscopes', |
426 | + ), |
427 | +} |
428 | + |
429 | +PROJECT_MAPPING = {} |
430 | +for team in TEAM_MAPPING: |
431 | + for target in TEAM_MAPPING[team]: |
432 | + PROJECT_MAPPING[target] = team |
433 | + |
434 | +PROJECT_LIST = [target for target in PROJECT_MAPPING] |
435 | + |
436 | +PROJECT_CONFLICTS = {} |
437 | +for target in PROJECT_LIST: |
438 | + for target2 in PROJECT_LIST: |
439 | + if target in target2 and target is not target2: |
440 | + if target in PROJECT_CONFLICTS: |
441 | + PROJECT_CONFLICTS[target].append(target2) |
442 | + else: |
443 | + PROJECT_CONFLICTS[target] = [target2] |
444 | |
445 | === added file 'qlbr/templates/qlbr/qa-default.html' |
446 | --- qlbr/templates/qlbr/qa-default.html 1970-01-01 00:00:00 +0000 |
447 | +++ qlbr/templates/qlbr/qa-default.html 2014-10-22 15:29:30 +0000 |
448 | @@ -0,0 +1,448 @@ |
449 | +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
450 | +<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US"> |
451 | + |
452 | + <head> |
453 | + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> |
454 | + <title>{{ report_title }}</title> |
455 | + |
456 | + <link title="light" rel="stylesheet" href="http://people.canonical.com/~kernel/reports/css/light-style.css" type="text/css" media="print, projection, screen" /> |
457 | + <link title="dark" rel="stylesheet" href="http://people.canonical.com/~kernel/reports/css/dark-style.css" type="text/css" media="print, projection, screen" /> |
458 | + |
459 | + <script type="text/javascript" src="http://people.canonical.com/~kernel/reports/js/styleswitcher.js"></script> |
460 | + |
461 | + <link href='http://fonts.googleapis.com/css?family=Cantarell&subset=latin' rel='stylesheet' type='text/css'> |
462 | + <script type="text/javascript" src="http://people.canonical.com/~kernel/reports/js/jquery-latest.js"></script> |
463 | + <script type="text/javascript" src="http://people.canonical.com/~kernel/reports/js/jquery.tablesorter.js"></script> |
464 | + </head> |
465 | + |
466 | + <body class="bugbody"> |
467 | + <!-- Top Panel --> |
468 | + <div id="toppanel"> |
469 | + <!-- Sliding Panel --> |
470 | + <div id="panel"> |
471 | + <form name="filter"> |
472 | + <div class="content clearfix"> |
473 | + <table width="100%"> |
474 | + <tr valign="top"> |
475 | + <td valign="top" width="30%"> |
476 | + <p class="l2-section-heading">Importance</p> |
477 | + <table width="100%"> |
478 | + <tr><td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Critical" /> Critical </td> |
479 | + <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Low" /> Low </td></tr> |
480 | + <tr><td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="High" /> High </td> |
481 | + <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Wishlist" /> Wishlist </td></tr> |
482 | + <tr><td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Medium" /> Medium </td> |
483 | + <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Undecided" /> Undecided </td></tr> |
484 | + </table> |
485 | + </td> |
486 | + |
487 | + <td width="20"> </td> |
488 | + |
489 | + <td valign="top"> |
490 | + <p class="l2-section-heading">Status</p> |
491 | + <table width="100%"> |
492 | + <tr><td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" checked value="New" /> New </td> |
493 | + <td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" checked value="Incomplete" /> Incomplete </td></tr> |
494 | + <tr><td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" checked value="Confirmed" /> Confirmed </td> |
495 | + <td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" value="Fix Released" /> Fix Released </td></tr> |
496 | + <tr><td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" checked value="Triaged" /> Triaged </td> |
497 | + <td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" value="Won't Fix" /> Won't Fix </td></tr> |
498 | + <tr><td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" checked value="In Progress" /> In Progress </td> |
499 | + <td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" value="Opinion" /> Opinion </td></tr> |
500 | + <tr><td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" checked value="Fix Committed" /> Fix Committed </td> |
501 | + <td width="50%"> <input type="checkbox" name="status" onclick="status_handler(this, 'status', true)" value="Invalid" /> Invalid </td></tr> |
502 | + </table> |
503 | + </td> |
504 | + |
505 | + <td width="20"> </td> |
506 | + |
507 | + <td valign="top" width="30%"> |
508 | + <p class="l2-section-heading">Series</p> |
509 | + <table width="100%"> |
510 | + <tr><td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="raring" /> Raring </td> |
511 | + <td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="quantal" /> Quantal </td></tr> |
512 | + <tr><td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="precise" /> Precise </td> |
513 | + <td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="jaunty" /> Jaunty </td></tr> |
514 | + <tr><td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="oneiric" /> Oneiric </td> |
515 | + <td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="karmic" /> Karmic </td></tr> |
516 | + <tr><td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="natty" /> Natty </td> |
517 | + <td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="hardy" /> Hardy </td></tr> |
518 | + <tr><td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="maverick" /> Maverick </td> |
519 | + <td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="" /> Unknown </td></tr> |
520 | + <tr><td width="50%"> <input type="checkbox" name="series" onclick="series_handler(this, 'series', true)" checked value="lucid" /> Lucid </td></tr> |
521 | + </table> |
522 | + </td> |
523 | + |
524 | + </table> |
525 | + </div> |
526 | + </form> |
527 | + <div style="clear:both;"></div> |
528 | + </div> <!-- panel --> |
529 | + |
530 | + <!-- The tab on top --> |
531 | + <div class="tab"> |
532 | + <ul class="login"> |
533 | + <li class="left"> </li> |
534 | + <li id="toggle"> |
535 | + <a id="open" class="open" href="#"> Options</a> |
536 | + <a id="close" style="display: none;" class="close" href="#"> Close </a> |
537 | + </li> |
538 | + <li class="right"> </li> |
539 | + </ul> |
540 | + </div> <!-- / top --> |
541 | + </div> <!-- Top Panel --> |
542 | + |
543 | + <div class="outermost"> |
544 | + <div class="title"> |
545 | + {{ report_title }} |
546 | + </div> |
547 | + |
548 | + {% if datefilter %}<div> |
549 | + <form name="datefilter"> |
550 | + <table width="100%"> |
551 | + <tr><td colspan="4">Created within:</td></tr> |
552 | + <tr><td width="10"> </td> |
553 | + <td width="50"> <input type="radio" name="date" onclick="date_handler(this, 'date', true)" value="1" /> 1 Day </td> |
554 | + <td width="50"> <input type="radio" name="date" onclick="date_handler(this, 'date', true)" value="7" /> 1 Week </td> |
555 | + <td width="50"> <input type="radio" name="date" onclick="date_handler(this, 'date', true)" value="30" /> 1 Month </td> |
556 | + <td width="50"> <input type="radio" name="date" onclick="date_handler(this, 'date', true)" checked value="-1" /> Unlimited </td></tr> |
557 | + </table> |
558 | + </form> |
559 | + </div>{% endif %} |
560 | + |
561 | + {% for target_display_name, target in target_report_order %} |
562 | + <div class="section" id="{{ target }}"> |
563 | + <div class="section-heading">{{ target_display_name }}</div> |
564 | + <table id="{{ target }}" class="tablesorter" border="0" cellpadding="0" cellspacing="1" width="100%%"> |
565 | + <thead> |
566 | + <tr> |
567 | + <th width="40">Bug</th> |
568 | + <th>Summary</th> |
569 | + <th width="80">Importance</th> |
570 | + <th width="80">Status</th> |
571 | + <th width="140">Reporter</th> |
572 | + <th width="140">Assignee</th> |
573 | + <th width="200">Tags</th> |
574 | + <th width="140">Created</th> |
575 | + <th width="65">Duplicate</th> |
576 | + </tr> |
577 | + </thead> |
578 | + <tbody> |
579 | + </tbody> |
580 | + </table> |
581 | + </div> |
582 | + {% endfor %}<br /> |
583 | + <br /> |
584 | + <div> |
585 | + <br /> |
586 | + <hr /> |
587 | + <table width="100%%" cellspacing="0" cellpadding="0"> |
588 | + <thead> |
589 | + <tr> |
590 | + <td width="100">Column</td> |
591 | + <td>Description</td> |
592 | + </tr> |
593 | + </th> |
594 | + <tbody> |
595 | + <tr><td>Bug</td><td>The bug number and a link to the Launchpad task.</td></tr> |
596 | + <tr><td>Summary</td><td>The 'summary' or 'title' from the bug.</td></tr> |
597 | + <tr><td>Importance</td><td>The bug task's importance.</td></tr> |
598 | + <tr><td>Status</td><td>The bug task's status.</td></tr> |
599 | + <tr><td>Reporter</td><td>The person who reported the bug.</td></tr> |
600 | + <tr><td>Assignee</td><td>The person or team assigned to work on the bug.</td></tr> |
601 | + <tr><td>Tags/td><td>The tags applied to the bug.</td></tr> |
602 | + <tr><td>Created</td><td>The date the bug was created.</td></tr> |
603 | + <tr><td>Duplicate</td><td>The duplicate bug reported by the QA team.</td></tr> |
604 | + </tbody> |
605 | + </table> |
606 | + <br /> |
607 | + </div> |
608 | + <div> |
609 | + <br /> |
610 | + <hr /> |
611 | + <table width="100%%" cellspacing="0" cellpadding="0"> |
612 | + <tr> |
613 | + <td> |
614 | + {{ timestamp }} |
615 | + </td> |
616 | + <td align="right"> |
617 | + |
618 | + Themes: |
619 | + <a href='#' onclick="setActiveStyleSheet('dark'); return false;">DARK</a> |
620 | + |
621 | + <a href='#' onclick="setActiveStyleSheet('light'); return false;">LIGHT</a> |
622 | + </td> |
623 | + </tr> |
624 | + </table> |
625 | + <br /> |
626 | + </div> |
627 | + </div> <!-- Outermost --> |
628 | + </body> |
629 | + |
630 | + <script type="text/javascript"> |
631 | + // add parser through the tablesorter addParser method |
632 | + $.tablesorter.addParser({ |
633 | + // set a unique id |
634 | + id: 'age', |
635 | + is: function(s) { return false; }, |
636 | + format: function(s) { |
637 | + // format your data for normalization |
638 | + fields = s.split('.') |
639 | + days = parseInt(fields[0], 10) * (60 * 24); |
640 | + hours = parseInt(fields[1], 10) * 60; |
641 | + minutes = parseInt(fields[2]); |
642 | + total = minutes + hours + days |
643 | + return total; |
644 | + }, |
645 | + // set type, either numeric or text |
646 | + type: 'numeric' |
647 | + }); |
648 | + |
649 | + // add parser through the tablesorter addParser method |
650 | + $.tablesorter.addParser({ |
651 | + // set a unique id |
652 | + id: 'importance', |
653 | + is: function(s) { return false; }, |
654 | + format: function(s) { |
655 | + // format your data for normalization |
656 | + return s.toLowerCase().replace(/critical/,6).replace(/high/,5).replace(/medium/,4).replace(/low/,3).replace(/wishlist/,2).replace(/undecided/,1).replace(/unknown/,0); |
657 | + }, |
658 | + // set type, either numeric or text |
659 | + type: 'numeric' |
660 | + }); |
661 | + |
662 | + // add parser through the tablesorter addParser method |
663 | + $.tablesorter.addParser({ |
664 | + // set a unique id |
665 | + id: 'status', |
666 | + is: function(s) { return false; }, |
667 | + format: function(s) { |
668 | + // format your data for normalization |
669 | + return s.toLowerCase().replace(/new/,12).replace(/incomplete/,11).replace(/confirmed/,10).replace(/triaged/,9).replace(/in progress/,8).replace(/fix committed/,7).replace(/fix released/,6).replace(/invalid/,5).replace(/won't fix/,4).replace(/confirmed/,3).replace(/opinion/,2).replace(/expired/,1).replace(/unknown/,0); |
670 | + }, |
671 | + // set type, either numeric or text |
672 | + type: 'numeric' |
673 | + }); |
674 | + $(function() { |
675 | + {% for _target_display_name, target in target_report_order %} |
676 | + $("#{{ target }}").tablesorter({ |
677 | + headers: { |
678 | + 3: { |
679 | + sorter:'importance' |
680 | + }, |
681 | + 4: { |
682 | + sorter:'status' |
683 | + } |
684 | + }, |
685 | + widgets: ['zebra'] |
686 | + }); |
687 | + {% endfor %} |
688 | + }); |
689 | + </script> |
690 | + |
691 | + <script type="text/javascript"> |
692 | + var series = ["raring", "quantal", "precise", "jaunty", "oneiric", "karmic", "natty", "hardy", "maverick", "lucid", ""]; |
693 | + var importance = ["Critical", "Low", "High", "Wishlist", "Medium", "Undecided"]; |
694 | + var task_status = ["New", "Incomplete", "Confirmed", "Fix Released", "Triaged", "Won't Fix", "In Progress", "Opinion", "Fix Committed", "Invalid"]; |
695 | + var assignees = []; |
696 | + var date_filter = -1; |
697 | + var jd = {% autoescape off %}{{ tasks_json_dump }}{% endautoescape %}; |
698 | + var first_time = true; |
699 | + |
700 | + var importance_color = { |
701 | + "Unknown" : "importance-unknown", |
702 | + "Critical" : "importance-critical", |
703 | + "High" : "importance-high", |
704 | + "Medium" : "importance-medium", |
705 | + "Low" : "importance-low", |
706 | + "Wishlist" : "importance-wishlist", |
707 | + "Undecided" : "importance-undecided" |
708 | + }; |
709 | + |
710 | + var status_color = { |
711 | + "New" : "status-new", |
712 | + "Incomplete" : "status-incomplete", |
713 | + "Confirmed" : "status-confirmed", |
714 | + "Triaged" : "status-triaged", |
715 | + "In Progress" : "status-in_progress", |
716 | + "Fix Committed" : "status-fix_committed", |
717 | + "Fix Released" : "status-fix_released", |
718 | + "Invalid" : "status-invalid", |
719 | + "Won't Fix" : "status-wont_fix", |
720 | + "Opinion" : "status-opinion", |
721 | + "Expired" : "status-expired", |
722 | + "Unknown" : "status-unknown" |
723 | + }; |
724 | + |
725 | + var targets_id_list = []; |
726 | + var targets_name_list = []; |
727 | + {% for _target_display_name, target in target_report_order %}targets_id_list.push("{{ target }}"); |
728 | + targets_name_list.push("{{ target }}"); |
729 | + {% endfor %} |
730 | + |
731 | + function series_handler(chkbx, grp, update_table) { |
732 | + series = []; |
733 | + for (i = 0; i < document.filter.length; i++) { |
734 | + if (document.filter[i].name == "series") { |
735 | + if (document.filter[i].checked) { |
736 | + series.push(document.filter[i].value); |
737 | + } |
738 | + } |
739 | + } |
740 | + |
741 | + if (update_table) { |
742 | + update_tables(); |
743 | + } |
744 | + } |
745 | + |
746 | + function importance_handler(chkbx, grp, update_table) { |
747 | + importance = []; |
748 | + for (i = 0; i < document.filter.length; i++) { |
749 | + if (document.filter[i].name == "importance") { |
750 | + if (document.filter[i].checked) { |
751 | + importance.push(document.filter[i].value); |
752 | + } |
753 | + } |
754 | + } |
755 | + |
756 | + if (update_table) { |
757 | + update_tables(); |
758 | + } |
759 | + } |
760 | + |
761 | + function assignee_handler(chkbx, grp, update_table) { |
762 | + assignees = []; |
763 | + for (i = 0; i < document.filter.length; i++) { |
764 | + if (document.filter[i].name == "assignees") { |
765 | + if (document.filter[i].checked) { |
766 | + assignees.push(document.filter[i].value); |
767 | + } |
768 | + } |
769 | + } |
770 | + |
771 | + if (update_table) { |
772 | + update_tables(); |
773 | + } |
774 | + } |
775 | + |
776 | + function status_handler(chkbx, grp, update_table) { |
777 | + task_status = []; |
778 | + for (i = 0; i < document.filter.length; i++) { |
779 | + if (document.filter[i].name == "status") { |
780 | + if (document.filter[i].checked) { |
781 | + task_status.push(document.filter[i].value); |
782 | + } |
783 | + } |
784 | + } |
785 | + |
786 | + if (update_table) { |
787 | + update_tables(); |
788 | + } |
789 | + } |
790 | + |
791 | + function date_handler(chkbx, grp, update_table) { |
792 | + date_filter = -1; |
793 | + for (i = 0; i < document.datefilter.length; i++) { |
794 | + if (document.datefilter[i].name == "date") { |
795 | + if (document.datefilter[i].checked) { |
796 | + date_filter = parseInt(document.datefilter[i].value); |
797 | + } |
798 | + } |
799 | + } |
800 | + |
801 | + if (update_table) { |
802 | + update_tables(); |
803 | + } |
804 | + } |
805 | + |
806 | + function update_tables() { |
807 | + var tables = { |
808 | + {% for _target_display_name, target in target_report_order %}"{{ target }}" : false, |
809 | + {% endfor %}}; |
810 | + |
811 | + var oddness = { |
812 | + {% for _target_display_name, target in target_report_order %}"{{ target }}" : true, |
813 | + {% endfor %}}; |
814 | + |
815 | + $.each(jd, function(bid, tasks) { |
816 | + $.each(tasks, function(index, task) { |
817 | + var fail = false; |
818 | + |
819 | + if (!fail && importance.indexOf(task.importance) == -1) { |
820 | + fail = true; |
821 | + } |
822 | + |
823 | + if (!fail && task_status.indexOf(task.status) == -1) { |
824 | + fail = true; |
825 | + } |
826 | + |
827 | +{% if datefilter %} |
828 | + if (!fail && date_filter != -1) { |
829 | + if (task.age > date_filter) { |
830 | + fail = true; |
831 | + } |
832 | + } |
833 | +{% endif %} |
834 | + s = ""; |
835 | + if (!fail) { |
836 | + if (oddness[task.target]) { |
837 | + s += "<tr class=\"odd\">"; |
838 | + oddness[task.target] = false; |
839 | + } else { |
840 | + s += "<tr class=\"even\">"; |
841 | + oddness[task.target] = true; |
842 | + } |
843 | + s += "<td><a href=\"" + task.web_link + "\">" + bid + "</a></td>"; |
844 | + s += "<td>" + task.title + "</td>"; |
845 | + s += "<td class=\"" + importance_color[task.importance] + "\">" + task.importance + "</td>"; |
846 | + s += "<td class=\"" + status_color[task.status] + "\">" + task.status + "</td>"; |
847 | + s += "<td>" + task.owner + "</td>"; |
848 | + s += "<td>" + task.assignee + "</td>"; |
849 | + s += "<td>" + task.taglist + "</td>"; |
850 | + s += "<td>" + task.date_created + "</td>"; |
851 | + s += "<td>" + task.duplicate_link + "</td>"; |
852 | + s += "</tr>"; |
853 | + tables[task.target] += s; |
854 | + } |
855 | + }); |
856 | + }); |
857 | + |
858 | + $.each(tables, function(target, val) { |
859 | + if (tables[target]) { |
860 | + $("#" + target + " tbody").html(tables[target]); |
861 | + $("#" + target).show() |
862 | + $("#" + target).trigger("update"); |
863 | + } else { |
864 | + $("#" + target).hide() |
865 | + $("#" + target).trigger("update"); |
866 | + } |
867 | + }); |
868 | + if (first_time) { |
869 | + first_time = false; |
870 | + sortList = [[3,1], [4,1]]; |
871 | + $.each(tables, function(target, val) { |
872 | + $("#" + target).trigger("sorton", [sortList]); |
873 | + }); |
874 | + } |
875 | + } |
876 | + |
877 | + $(document).ready(function(){ |
878 | + // Expand Panel |
879 | + $("#open").click(function(){ $("div#panel").slideDown("slow"); }); |
880 | + |
881 | + // Collapse Panel |
882 | + $("#close").click(function(){ $("div#panel").slideUp("slow"); }); |
883 | + |
884 | + // Switch buttons on the tab from "Options" to "Close" |
885 | + $("#toggle a").click(function () { $("#toggle a").toggle(); }); |
886 | + |
887 | + series_handler(null, null, false); |
888 | + importance_handler(null, null, false); |
889 | + status_handler(null, null, false); |
890 | + {% if datefilter %}date_handler(null, null, false); |
891 | + {% endif %}update_tables(); |
892 | + }); |
893 | + </script> |
894 | + |
895 | +</html> |
896 | +<!-- vi:set ts=4 sw=4 expandtab syntax=django: --> |
897 | |
898 | === renamed file 'qlbr/templates/qlbr/qa-default.html' => 'qlbr/templates/qlbr/rtm.html' |
899 | --- qlbr/templates/qlbr/qa-default.html 2013-11-20 15:39:37 +0000 |
900 | +++ qlbr/templates/qlbr/rtm.html 2014-10-22 15:29:30 +0000 |
901 | @@ -13,6 +13,7 @@ |
902 | <link href='http://fonts.googleapis.com/css?family=Cantarell&subset=latin' rel='stylesheet' type='text/css'> |
903 | <script type="text/javascript" src="http://people.canonical.com/~kernel/reports/js/jquery-latest.js"></script> |
904 | <script type="text/javascript" src="http://people.canonical.com/~kernel/reports/js/jquery.tablesorter.js"></script> |
905 | + <script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script> |
906 | </head> |
907 | |
908 | <body class="bugbody"> |
909 | @@ -28,11 +29,11 @@ |
910 | <p class="l2-section-heading">Importance</p> |
911 | <table width="100%"> |
912 | <tr><td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Critical" /> Critical </td> |
913 | - <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Low" /> Low </td></tr> |
914 | + <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" value="Low" /> Low </td></tr> |
915 | <tr><td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="High" /> High </td> |
916 | - <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Wishlist" /> Wishlist </td></tr> |
917 | - <tr><td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Medium" /> Medium </td> |
918 | - <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" checked value="Undecided" /> Undecided </td></tr> |
919 | + <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" value="Wishlist" /> Wishlist </td></tr> |
920 | + <tr><td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" value="Medium" /> Medium </td> |
921 | + <td width="50%"> <input type="checkbox" name="importance" onclick="importance_handler(this, 'importance', true)" value="Undecided" /> Undecided </td></tr> |
922 | </table> |
923 | </td> |
924 | |
925 | @@ -110,21 +111,21 @@ |
926 | </form> |
927 | </div>{% endif %} |
928 | |
929 | - {% for target_display_name, target in target_report_order %} |
930 | + {% for target_display_name, target, total, critical, high in target_report_order %} |
931 | <div class="section" id="{{ target }}"> |
932 | - <div class="section-heading">{{ target_display_name }}</div> |
933 | + <div class="section-heading">{{ target_display_name }} (Total: {{ total }} Critical: {{ critical }} High: {{ high }})</div> |
934 | <table id="{{ target }}" class="tablesorter" border="0" cellpadding="0" cellspacing="1" width="100%%"> |
935 | <thead> |
936 | <tr> |
937 | - <th width="40">Bug</th> |
938 | - <th>Summary</th> |
939 | - <th width="80">Importance</th> |
940 | - <th width="80">Status</th> |
941 | - <th width="140">Reporter</th> |
942 | - <th width="140">Assignee</th> |
943 | - <th width="200">Tags</th> |
944 | - <th width="140">Created</th> |
945 | - <th width="65">Duplicate</th> |
946 | + <th class=header width="40">Bug</th> |
947 | + <th class=header width="600">Summary</th> |
948 | + <th class=header width="80">Importance</th> |
949 | + <th class=header width="80">Status</th> |
950 | + <th class=header width="140">Reporter</th> |
951 | + <th class=header width="140">Assignee</th> |
952 | + <th class=header width="200">Tags</th> |
953 | + <th class=header width="140">Created</th> |
954 | + <th class=header width="65">Duplicate</th> |
955 | </tr> |
956 | </thead> |
957 | <tbody> |
958 | |
959 | === modified file 'qlbr/urls.py' |
960 | --- qlbr/urls.py 2013-12-03 20:12:05 +0000 |
961 | +++ qlbr/urls.py 2014-10-22 15:29:30 +0000 |
962 | @@ -17,6 +17,7 @@ |
963 | |
964 | import datetime |
965 | |
966 | +from django.conf import settings |
967 | from django.conf.urls import patterns, include, url |
968 | |
969 | from django.contrib import admin |
970 | @@ -24,9 +25,12 @@ |
971 | |
972 | urlpatterns = patterns( |
973 | '', |
974 | - url(r'^$', 'qlbr.views.index', name='index'), |
975 | - url(r'^untriaged$', 'qlbr.views.index', {'triaged': False}, name='index'), |
976 | - url(r'^today$', 'qlbr.views.index', {'task_filter': |
977 | + url(r'^$', 'qlbr.views.{}'.format(settings.DEFAULT_VIEW), |
978 | + name=settings.DEFAULT_VIEW), |
979 | + url(r'^qa/$', 'qlbr.views.qa', name='qa'), |
980 | + url(r'^rtm/$', 'qlbr.views.rtm', name='rtm'), |
981 | + url(r'^untriaged$', 'qlbr.views.qa', {'triaged': False}, name='qa'), |
982 | + url(r'^today$', 'qlbr.views.qa', {'task_filter': |
983 | {'bug__date_last_updated__gt': |
984 | datetime.datetime.now()-datetime.timedelta(1)}}), |
985 | url(r'^admin/doc/', include('django.contrib.admindocs.urls')), |
986 | |
987 | === modified file 'qlbr/views.py' |
988 | --- qlbr/views.py 2014-06-04 18:42:23 +0000 |
989 | +++ qlbr/views.py 2014-10-22 15:29:30 +0000 |
990 | @@ -19,13 +19,78 @@ |
991 | import json |
992 | import re |
993 | |
994 | +from django.http import HttpResponse |
995 | from django.shortcuts import render |
996 | |
997 | from qlbr import TZ |
998 | from qlbr.models import Task |
999 | - |
1000 | - |
1001 | -def index(request, task_filter=None, triaged=None): |
1002 | +from qlbr.team_mapping import ( |
1003 | + TEAM_MAPPING, |
1004 | + PROJECT_MAPPING, |
1005 | +) |
1006 | + |
1007 | +OPEN_STATUSES = [ |
1008 | + 'New', |
1009 | + 'Confirmed', |
1010 | + 'Triaged', |
1011 | + 'In Progress', |
1012 | + 'Fix Committed', |
1013 | + 'Incomplete', |
1014 | +] |
1015 | + |
1016 | + |
1017 | +def bug_tags(): |
1018 | + bug_tags = {} |
1019 | + for bug, tag in Task.objects.distinct().values_list('bug__bug_id', |
1020 | + 'bug__tags'): |
1021 | + if bug in bug_tags: |
1022 | + bug_tags[bug] += ' ' |
1023 | + bug_tags[bug] += tag |
1024 | + else: |
1025 | + bug_tags[bug] = tag |
1026 | + return bug_tags |
1027 | + |
1028 | + |
1029 | +BUG_TAGS = bug_tags() |
1030 | + |
1031 | + |
1032 | +def task_data(task, bug_tags=BUG_TAGS, triaged=False): |
1033 | + data = { |
1034 | + 'title': task.bug.title, |
1035 | + 'date_created': task.date_created.isoformat(), |
1036 | + 'age': (datetime.datetime.now(TZ) - task.date_created).days, |
1037 | + 'importance': task.importance, |
1038 | + 'status': task.status, |
1039 | + 'web_link': task.web_link, |
1040 | + 'duplicate_link': '', |
1041 | + 'owner': task.owner.display_name, |
1042 | + } |
1043 | + |
1044 | + # Pilfered from collect-bug-data in lp:arsenal |
1045 | + m = re.search('(\S+)\s\(.*\)', task.bug_target_name) |
1046 | + if m is not None: |
1047 | + data['target'] = m.group(1) |
1048 | + else: |
1049 | + data['target'] = task.bug_target_name |
1050 | + m = re.search('(\S+)\s\(.*\)', task.bug_target_display_name) |
1051 | + if m is not None: |
1052 | + data['target_display_name'] = m.group(1) |
1053 | + else: |
1054 | + data['target_display_name'] = task.bug_target_display_name |
1055 | + |
1056 | + if triaged: |
1057 | + data['triaged'] = task.triaged |
1058 | + if task.bug.duplicate_of: |
1059 | + data.update(task_data(task.bug.duplicate_of.task_set.first(), |
1060 | + bug_tags)) |
1061 | + data['duplicate_link'] = '<a href="{}">{}</a>'.format(task.web_link, |
1062 | + task.bug.bug_id) |
1063 | + data['taglist'] = bug_tags[task.bug.bug_id] |
1064 | + data['original_target_display_name'] = task.bug_target_display_name, |
1065 | + return data |
1066 | + |
1067 | + |
1068 | +def qa(request, task_filter=None, triaged=None): |
1069 | """Main view of all bugs. |
1070 | |
1071 | :returns: view of all bugs |
1072 | @@ -39,42 +104,10 @@ |
1073 | task_iterator = Task.objects.filter(**task_filter).iterator() |
1074 | for task in task_iterator: |
1075 | bugid = task.bug.bug_id |
1076 | - report_task = { |
1077 | - 'title': task.bug.title, |
1078 | - 'taglist': task.bug.tag_list, |
1079 | - 'date_created': task.date_created.isoformat(), |
1080 | - 'age': (datetime.datetime.now(TZ) - task.date_created).days, |
1081 | - 'importance': task.importance, |
1082 | - 'status': task.status, |
1083 | - 'web_link': task.web_link, |
1084 | - 'duplicate_link': '', |
1085 | - 'owner': task.owner.display_name, |
1086 | - 'triaged': task.triaged |
1087 | - } |
1088 | + report_task = task_data(task) |
1089 | if triaged is not None: |
1090 | if task.triaged != triaged: |
1091 | continue |
1092 | - dupe_of = task.bug.duplicate_of |
1093 | - if dupe_of: |
1094 | - report_task['duplicate_link'] = ('<a href="{}">{}</a>' |
1095 | - .format(task.web_link, bugid)) |
1096 | - report_task['owner'] += ' (dup)' |
1097 | - bugid = dupe_of.bug_id |
1098 | - report_task['web_link'] = dupe_of.web_link |
1099 | - report_task['triaged'] = dupe_of.triaged |
1100 | - report_task['status'] = dupe_of.task_set.first().status |
1101 | - |
1102 | - # Pilfered from collect-bug-data in lp:arsenal |
1103 | - m = re.search('(\S+)\s\(.*\)', task.bug_target_name) |
1104 | - if m is not None: |
1105 | - report_task['target'] = m.group(1) |
1106 | - else: |
1107 | - report_task['target'] = task.bug_target_name |
1108 | - m = re.search('(\S+)\s\(.*\)', task.bug_target_display_name) |
1109 | - if m is not None: |
1110 | - report_task['target_display_name'] = m.group(1) |
1111 | - else: |
1112 | - report_task['target_display_name'] = task.bug_target_display_name |
1113 | |
1114 | report_task['target'] = report_task['target'].replace('.', '') |
1115 | if task.assignee: |
1116 | @@ -105,3 +138,86 @@ |
1117 | 'target_report_order': target_report_order, |
1118 | 'tasks_json_dump': tasks_json_dump} |
1119 | return render(request, 'qlbr/qa-default.html', context) |
1120 | + |
1121 | + |
1122 | +def rtm(request, task_filter=None): |
1123 | + """RTM bug report. |
1124 | + |
1125 | + :returns: RTM bugs view |
1126 | + :rtype" HttpResponse |
1127 | + |
1128 | + """ |
1129 | + tasks = {} |
1130 | + task_objects = Task.objects.select_related( |
1131 | + 'owner__display_name', |
1132 | + 'assignee__display_name', |
1133 | + 'bug__duplicate_of', |
1134 | + ) |
1135 | + if task_filter: |
1136 | + task_objects = task_objects.filter(**task_filter) |
1137 | + task_objects = task_objects.filter(bug__tags__tag='rtm14') |
1138 | + if 'tag' in request.REQUEST: |
1139 | + for tag in request.REQUEST['tag'].split(): |
1140 | + task_objects = task_objects.filter(bug__tags__tag=tag) |
1141 | + task_iterator = task_objects.iterator() |
1142 | + for task in task_iterator: |
1143 | + bugid = task.bug.bug_id |
1144 | + report_task = task_data(task) |
1145 | + if task.assignee: |
1146 | + report_task['assignee'] = task.assignee.display_name |
1147 | + else: |
1148 | + report_task['assignee'] = 'Unassigned' |
1149 | + if bugid not in tasks: |
1150 | + tasks[bugid] = [] |
1151 | + |
1152 | + target = report_task['target'].replace(' (Ubuntu)', '') |
1153 | + target = target.replace(' ', '').replace('-', '') |
1154 | + target = target.lower() |
1155 | + if target in PROJECT_MAPPING: |
1156 | + team = PROJECT_MAPPING[target] |
1157 | + else: |
1158 | + team = 'Other' |
1159 | + report_task['target'] = team.replace(' ', '').replace('/', '') |
1160 | + tasks[bugid].append(report_task) |
1161 | + targets = {task['target'] for task in tasks[bugid]} |
1162 | + if 'Other' in targets and targets != {'Other'}: |
1163 | + task_list = list( |
1164 | + filter(lambda t: t['target'] != 'Other', tasks[bugid])) |
1165 | + if any(task['status'] in OPEN_STATUSES and |
1166 | + task['target'] != 'Other' for task in task_list): |
1167 | + tasks[bugid] = task_list |
1168 | + |
1169 | + team_stats = {'Other': ['Other', 'Other', 0, 0, 0]} |
1170 | + for team in TEAM_MAPPING: |
1171 | + team_sanitized = team.replace(' ', '').replace('/', '') |
1172 | + team_stats[team_sanitized] = [ |
1173 | + team, |
1174 | + team_sanitized, |
1175 | + 0, |
1176 | + 0, |
1177 | + 0, |
1178 | + ] |
1179 | + for bug_id in tasks: |
1180 | + for task in tasks[bug_id]: |
1181 | + if task['status'] in OPEN_STATUSES: |
1182 | + team = task['target'] |
1183 | + team_stats[team][2] += 1 |
1184 | + if task['importance'] == 'Critical': |
1185 | + team_stats[team][3] += 1 |
1186 | + if task['importance'] == 'High': |
1187 | + team_stats[team][4] += 1 |
1188 | + |
1189 | + team_report_order = [] |
1190 | + for team in sorted(list(team_stats)): |
1191 | + team_report_order.append(team_stats[team]) |
1192 | + tasks_json_dump = json.dumps(tasks, sort_keys=True, indent=4) |
1193 | + context = {'report_title': 'RTM Bugs', |
1194 | + 'tasks': tasks, |
1195 | + 'timestamp': datetime.datetime.now().isoformat(), |
1196 | + 'datefilter': True, |
1197 | + 'target_report_order': team_report_order, |
1198 | + 'tasks_json_dump': tasks_json_dump} |
1199 | + if 'json' in request.REQUEST: |
1200 | + return HttpResponse(tasks_json_dump, mimetype='application/json') |
1201 | + else: |
1202 | + return render(request, 'qlbr/rtm.html', context) |
1203 | |
1204 | === added file 'rtm_searches.json' |
1205 | --- rtm_searches.json 1970-01-01 00:00:00 +0000 |
1206 | +++ rtm_searches.json 2014-10-22 15:29:30 +0000 |
1207 | @@ -0,0 +1,111 @@ |
1208 | +[ |
1209 | + { |
1210 | + "name": "RTM14", |
1211 | + "search_tags": [], |
1212 | + "projects": [ |
1213 | + "ubuntu", |
1214 | + "account-plugins", |
1215 | + "account-polld", |
1216 | + "accountsservice", |
1217 | + "address-book-app", |
1218 | + "address-book-service", |
1219 | + "android", |
1220 | + "apparmor", |
1221 | + "apparmor-easyprof-ubuntu", |
1222 | + "barajas", |
1223 | + "bluez", |
1224 | + "camera-app", |
1225 | + "click", |
1226 | + "click-apparmor", |
1227 | + "curucu", |
1228 | + "dialer-app", |
1229 | + "dropping-letters", |
1230 | + "espoo", |
1231 | + "gallery-app", |
1232 | + "go-unityscopes", |
1233 | + "indicator-datetime", |
1234 | + "indicator-location", |
1235 | + "indicator-messages", |
1236 | + "indicator-network", |
1237 | + "indicator-power", |
1238 | + "indicator-sound", |
1239 | + "indicator-transfer", |
1240 | + "livecd-rootfs", |
1241 | + "location-service", |
1242 | + "maliit-framework", |
1243 | + "media-hub", |
1244 | + "mediaplayer-app", |
1245 | + "mediascanner", |
1246 | + "mediascanner2", |
1247 | + "messaging-app", |
1248 | + "mir", |
1249 | + "mtp", |
1250 | + "music-app", |
1251 | + "network-manager", |
1252 | + "notes-app", |
1253 | + "nuntium", |
1254 | + "oem-priority", |
1255 | + "ofono", |
1256 | + "online-accounts", |
1257 | + "oxide", |
1258 | + "oxide-qt", |
1259 | + "pay-service", |
1260 | + "pay-ui", |
1261 | + "platform-api", |
1262 | + "powerd", |
1263 | + "pulseaudio", |
1264 | + "qtcreator-plugin-ubuntu", |
1265 | + "qtmir", |
1266 | + "qtorganizer5-eds", |
1267 | + "qtpim-opensource-src", |
1268 | + "qtubuntu", |
1269 | + "qtubuntu-camera", |
1270 | + "qtubuntu-media", |
1271 | + "reminders-app", |
1272 | + "signon", |
1273 | + "signon-ui", |
1274 | + "sudoku-app", |
1275 | + "sync-monitor", |
1276 | + "telepathy-ofono", |
1277 | + "telephony-service", |
1278 | + "thumbnailer", |
1279 | + "ubuntu-calculator-app", |
1280 | + "ubuntu-clock-app", |
1281 | + "ubuntu-filemanager-app", |
1282 | + "ubuntu-keyboard", |
1283 | + "ubuntu-push", |
1284 | + "ubuntu-push-client", |
1285 | + "ubuntu-push-server", |
1286 | + "ubuntu-rest-scopes", |
1287 | + "ubuntu-rssreader-app", |
1288 | + "ubuntu-system-settings", |
1289 | + "ubuntu-system-settings-online-accounts", |
1290 | + "ubuntu-terminal-app", |
1291 | + "ubuntu-touch-session", |
1292 | + "ubuntu-translations", |
1293 | + "ubuntu-ui-toolkit", |
1294 | + "ubuntu-ux", |
1295 | + "ubuntu-weather-app", |
1296 | + "ubuntudeveloperportal", |
1297 | + "ubuntuone-credentials", |
1298 | + "unity-mir", |
1299 | + "unity-scope-click", |
1300 | + "unity-scope-mediascanner", |
1301 | + "unity-scope-vimeo", |
1302 | + "unity-scope-youtube", |
1303 | + "unity-scopes-api", |
1304 | + "unity-scopes-shell", |
1305 | + "unity-system-compositor", |
1306 | + "unity-webapps-qml", |
1307 | + "unity8", |
1308 | + "url-dispatcher", |
1309 | + "webapps-core", |
1310 | + "webbrowser-app" |
1311 | + ], |
1312 | + "criteria": { |
1313 | + "tags": [ |
1314 | + "rtm14" |
1315 | + ] |
1316 | + } |
1317 | + } |
1318 | +] |