Merge lp:~nuclearbob/qlbr/rtm-merge into lp:qlbr

Proposed by Max Brustkern
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
Reviewer Review Type Date Requested Status
Canonical Platform QA Team Pending
Review via email: mp+239040@code.launchpad.net

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.

To post a comment you must log in.
lp:~nuclearbob/qlbr/rtm-merge updated
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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">&nbsp;</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">&nbsp;</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">&nbsp;</li>
534+ <li id="toggle">
535+ <a id="open" class="open" href="#">&nbsp;Options</a>
536+ <a id="close" style="display: none;" class="close" href="#">&nbsp;Close&nbsp;&nbsp;</a>
537+ </li>
538+ <li class="right">&nbsp;</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">&nbsp;</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+ &nbsp;
618+ Themes:&nbsp;&nbsp;
619+ <a href='#' onclick="setActiveStyleSheet('dark'); return false;">DARK</a>
620+ &nbsp;
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+]

Subscribers

People subscribed via source and target branches

to all changes: