Merge lp:~wgrant/launchpad/bug-736002 into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 12808
Proposed branch: lp:~wgrant/launchpad/bug-736002
Merge into: lp:launchpad
Diff against target: 430 lines (+113/-117)
11 files modified
lib/lp/bugs/browser/bugtarget.py (+28/-53)
lib/lp/bugs/doc/bug-tags.txt (+20/-12)
lib/lp/bugs/interfaces/bugtarget.py (+3/-1)
lib/lp/bugs/model/bug.py (+43/-37)
lib/lp/registry/model/distribution.py (+3/-2)
lib/lp/registry/model/distributionsourcepackage.py (+2/-2)
lib/lp/registry/model/distroseries.py (+3/-2)
lib/lp/registry/model/product.py (+3/-2)
lib/lp/registry/model/productseries.py (+3/-2)
lib/lp/registry/model/projectgroup.py (+3/-2)
lib/lp/registry/model/sourcepackage.py (+2/-2)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-736002
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Review via email: mp+57278@code.launchpad.net

Commit message

[r=jtv][bug=736002] Speed up +bugtarget-portlet-tags-content.

Description of the change

This branch fixes tags_cloud_data to to issue only two queries, hopefully minimising timeouts on +bugtarget-portlet-tags-content.

It used to display the official tags and the top 10 unofficial tags. This proves to be fairly expensive, so it will now display the official tags and the top 10 tags, some of which may duplicate the official tag set.

I've changed it to query the context's official tags, then perform a single UNION query to get the top 10 and official tag counts. Both sides of the union are done together in a single pass, and I've also avoided a slow join over Bug, letting the query complete in <4s for Ubuntu on staging.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Looks good. On IRC you said that we don't expect anyone to have a problem with the functional change, or if they do, we can solve it in other ways later.

Another, very small thing, in lib/lp/bugs/browser/bugtask.py:

80 + tags = []
81 + for tag, count in raw_tags.iteritems():
82 + tag_dict = dict(
83 + tag=tag, count=count, url=self._getSearchURL(tag),
84 + factor=1 + (count / max_count))
85 + if tag in official_tags:
86 + tag_dict['factor'] += 0.5
87 + tags.append(tag_dict)

The "factor=1 + (count / maxcount)" is a bit misleading in terms of associativity. If you isolated the factor computation (including the "+= 0.5") in a function, this whole loop could be replaced with a list comprehension.

Jeroen

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py 2011-03-31 08:05:07 +0000
+++ lib/lp/bugs/browser/bugtarget.py 2011-04-12 08:33:41 +0000
@@ -1373,63 +1373,38 @@
1373 # Use path_only here to reduce the size of the rendered page.1373 # Use path_only here to reduce the size of the rendered page.
1374 return "+bugs?field.tag=%s" % urllib.quote(tag)1374 return "+bugs?field.tag=%s" % urllib.quote(tag)
13751375
1376 def getUsedBugTagsWithURLs(self):1376 def _calculateFactor(self, tag, count, max_count, official_tags):
1377 """Return the bug tags and their search URLs."""1377 bonus = 1.5 if tag in official_tags else 1
1378 bug_tag_counts = self.context.getUsedBugTagsWithOpenCounts(self.user)1378 return (count / max_count) + bonus
1379 return [
1380 {'tag': tag, 'count': count, 'url': self._getSearchURL(tag)}
1381 for tag, count in bug_tag_counts]
1382
1383 @property
1384 def official_tags(self):
1385 """Get the official tags to diplay."""
1386 official_tags = set(self.context.official_bug_tags)
1387 tags = [tag for tag in self.getUsedBugTagsWithURLs()
1388 if tag['tag'] in official_tags]
1389 used_tags = set(tag['tag'] for tag in tags)
1390 tags.sort(key=itemgetter('count'), reverse=True)
1391 for tag in sorted(official_tags - used_tags):
1392 tags.append(
1393 {'tag': tag, 'count': 0, 'url': self._getSearchURL(tag)})
1394 return tags
1395
1396 @property
1397 def other_tags(self):
1398 """Get the unofficial tags to diplay."""
1399 official_tags = set(self.context.official_bug_tags)
1400 tags = [tag for tag in self.getUsedBugTagsWithURLs()
1401 if tag['tag'] not in official_tags]
1402 tags.sort(key=itemgetter('count'), reverse=True)
1403 return tags[:10]
14041379
1405 @property1380 @property
1406 def tags_cloud_data(self):1381 def tags_cloud_data(self):
1407 """The data for rendering a tags cloud"""1382 """The data for rendering a tags cloud"""
1408 official_tags = list(self.context.official_bug_tags)1383 official_tags = self.context.official_bug_tags
1409 tags = self.getUsedBugTagsWithURLs()1384
1410 other_tags = [tag for tag in tags if tag['tag'] not in official_tags]1385 # Construct a dict of official and top 10 tags.
1411 popular_tags = [tag['tag'] for tag in sorted(1386 # getUsedBugTagsWithOpenCounts is expensive, so do the union in
1412 other_tags, key=itemgetter('count'), reverse=True)[:10]]1387 # SQL. Also preseed with 0 for all the official tags, as gUBTWOC
1413 tags = [1388 # won't return unused ones.
1414 tag for tag in tags1389 top_ten = removeSecurityProxy(
1415 if tag['tag'] in official_tags + popular_tags]1390 self.context.getUsedBugTagsWithOpenCounts(self.user)[:10])
1416 all_tag_dicts = [tag['tag'] for tag in tags]1391 official = removeSecurityProxy(
1417 for official_tag in official_tags:1392 self.context.getUsedBugTagsWithOpenCounts(
1418 if official_tag not in all_tag_dicts:1393 self.user, official_tags))
1419 tags.append({1394 tags = dict((tag, 0) for tag in official_tags)
1420 'tag': official_tag,1395 tags.update(dict(top_ten.union(official)))
1421 'count': 0,1396
1422 'url': "+bugs?field.tag=%s" % urllib.quote(official_tag)})1397 max_count = float(max([1] + tags.values()))
1423 max_count = float(max([1] + [tag['count'] for tag in tags]))1398
1424 for tag in tags:1399 return sorted(
1425 if tag['tag'] in official_tags:1400 [dict(
1426 if tag['count'] == 0:1401 tag=tag,
1427 tag['factor'] = 1.51402 factor=self._calculateFactor(
1428 else:1403 tag, count, max_count, official_tags),
1429 tag['factor'] = 1.5 + (tag['count'] / max_count)1404 url=self._getSearchURL(tag),
1430 else:1405 )
1431 tag['factor'] = 1 + (tag['count'] / max_count)1406 for (tag, count) in tags.iteritems()],
1432 return sorted(tags, key=itemgetter('tag'))1407 key=itemgetter('tag'))
14331408
1434 @property1409 @property
1435 def show_manage_tags_link(self):1410 def show_manage_tags_link(self):
14361411
=== modified file 'lib/lp/bugs/doc/bug-tags.txt'
--- lib/lp/bugs/doc/bug-tags.txt 2011-01-11 09:40:05 +0000
+++ lib/lp/bugs/doc/bug-tags.txt 2011-04-12 08:33:41 +0000
@@ -332,27 +332,35 @@
332We can also get all the used tags, together with the number of open332We can also get all the used tags, together with the number of open
333bugs each tag has. Only tags having open bugs are returned.333bugs each tag has. Only tags having open bugs are returned.
334334
335 >>> firefox.getUsedBugTagsWithOpenCounts(None)335 >>> list(firefox.getUsedBugTagsWithOpenCounts(None))
336 [(u'doc', 1L), (u'sco', 1L), (u'svg', 1L)]336 [(u'doc', 1L), (u'sco', 1L), (u'svg', 1L)]
337337
338 >>> mozilla.getUsedBugTagsWithOpenCounts(None)338 >>> list(mozilla.getUsedBugTagsWithOpenCounts(None))
339 [(u'doc', 1L), (u'sco', 1L), (u'svg', 1L)]339 [(u'doc', 1L), (u'sco', 1L), (u'svg', 1L)]
340340
341 >>> ubuntu.getUsedBugTagsWithOpenCounts(None)341 >>> list(ubuntu.getUsedBugTagsWithOpenCounts(None))
342 [(u'crash', 2L), (u'dataloss', 1L), (u'pebcak', 1L),342 [(u'crash', 2L), (u'dataloss', 1L), (u'pebcak', 1L),
343 (u'sco', 1L), (u'svg', 1L)]343 (u'sco', 1L), (u'svg', 1L)]
344344
345We can even ask for the counts for a particular set of tags.
346
347 >>> list(ubuntu.getUsedBugTagsWithOpenCounts(
348 ... None, wanted_tags=['pebcak', 'svg', 'fake']))
349 [(u'pebcak', 1L), (u'svg', 1L)]
350 >>> list(ubuntu.getUsedBugTagsWithOpenCounts(None, wanted_tags=[]))
351 []
352
345Source packages are a bit special, they return all the tags that are353Source packages are a bit special, they return all the tags that are
346used in the whole distribution, while the bug count includes only bugs354used in the whole distribution, while the bug count includes only bugs
347in the specific package.355in the specific package.
348356
349 >>> ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(None)357 >>> list(ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(None))
350 [(u'crash', 1L)]358 [(u'crash', 1L)]
351359
352 >>> debian_woody.getUsedBugTagsWithOpenCounts(None)360 >>> list(debian_woody.getUsedBugTagsWithOpenCounts(None))
353 [(u'dataloss', 1L), (u'layout-test', 1L), (u'pebcak', 1L)]361 [(u'dataloss', 1L), (u'layout-test', 1L), (u'pebcak', 1L)]
354362
355 >>> debian_woody_firefox.getUsedBugTagsWithOpenCounts(None)363 >>> list(debian_woody_firefox.getUsedBugTagsWithOpenCounts(None))
356 [(u'dataloss', 1L), (u'layout-test', 1L), (u'pebcak', 1L)]364 [(u'dataloss', 1L), (u'layout-test', 1L), (u'pebcak', 1L)]
357365
358Only bugs that the supplied user has access to will be counted:366Only bugs that the supplied user has access to will be counted:
@@ -362,13 +370,13 @@
362 True370 True
363 >>> flush_database_updates()371 >>> flush_database_updates()
364372
365 >>> ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(None)373 >>> list(ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(None))
366 []374 []
367375
368 >>> sample_person = getUtility(ILaunchBag).user376 >>> sample_person = getUtility(ILaunchBag).user
369 >>> bug_nine.isSubscribed(sample_person)377 >>> bug_nine.isSubscribed(sample_person)
370 True378 True
371 >>> ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(sample_person)379 >>> list(ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(sample_person))
372 [(u'crash', 1L)]380 [(u'crash', 1L)]
373381
374When context doesn't have any tags getUsedBugTags() returns a empty list.382When context doesn't have any tags getUsedBugTags() returns a empty list.
375383
=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py 2011-03-28 20:54:50 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py 2011-04-12 08:33:41 +0000
@@ -403,13 +403,15 @@
403 def getUsedBugTags():403 def getUsedBugTags():
404 """Return the tags used by the context as a sorted list of strings."""404 """Return the tags used by the context as a sorted list of strings."""
405405
406 def getUsedBugTagsWithOpenCounts(user):406 def getUsedBugTagsWithOpenCounts(user, wanted_tags=None):
407 """Return name and bug count of tags having open bugs.407 """Return name and bug count of tags having open bugs.
408408
409 It returns a list of tuples contining the tag name, and the409 It returns a list of tuples contining the tag name, and the
410 number of open bugs having that tag. Only the bugs that the user410 number of open bugs having that tag. Only the bugs that the user
411 has permission to see are counted, and only tags having open411 has permission to see are counted, and only tags having open
412 bugs will be returned.412 bugs will be returned.
413
414 If wanted_tags is specified, only those tags will be returned.
413 """415 """
414416
415 def _getOfficialTagClause():417 def _getOfficialTagClause():
416418
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2011-04-07 16:23:24 +0000
+++ lib/lp/bugs/model/bug.py 2011-04-12 08:33:41 +0000
@@ -50,6 +50,9 @@
50from storm.expr import (50from storm.expr import (
51 And,51 And,
52 Count,52 Count,
53 Desc,
54 Exists,
55 Join,
53 LeftJoin,56 LeftJoin,
54 Max,57 Max,
55 Not,58 Not,
@@ -211,6 +214,29 @@
211 %(condition)s GROUP BY BugTag.tag ORDER BY BugTag.tag"""214 %(condition)s GROUP BY BugTag.tag ORDER BY BugTag.tag"""
212215
213216
217def snapshot_bug_params(bug_params):
218 """Return a snapshot of a `CreateBugParams` object."""
219 return Snapshot(
220 bug_params, names=[
221 "owner", "title", "comment", "description", "msg",
222 "datecreated", "security_related", "private",
223 "distribution", "sourcepackagename", "binarypackagename",
224 "product", "status", "subscribers", "tags",
225 "subscribe_owner", "filed_by"])
226
227
228class BugTag(SQLBase):
229 """A tag belonging to a bug."""
230
231 bug = ForeignKey(dbName='bug', foreignKey='Bug', notNull=True)
232 tag = StringCol(notNull=True)
233
234
235# We need to always use the same Count instance or the
236# get_bug_tags_open_count is not UNIONable.
237tag_count_columns = (BugTag.tag, Count())
238
239
214def get_bug_tags(context_clause):240def get_bug_tags(context_clause):
215 """Return all the bug tags as a list of strings.241 """Return all the bug tags as a list of strings.
216242
@@ -230,59 +256,39 @@
230 return shortlist([row[0] for row in cur.fetchall()])256 return shortlist([row[0] for row in cur.fetchall()])
231257
232258
233def get_bug_tags_open_count(context_condition, user):259def get_bug_tags_open_count(context_condition, user, wanted_tags=None):
234 """Return all the used bug tags with their open bug count.260 """Return all the used bug tags with their open bug count.
235261
236 :param context_condition: A Storm SQL expression, limiting the262 :param context_condition: A Storm SQL expression, limiting the
237 used tags to a specific context. Only the BugTask table may be263 used tags to a specific context. Only the BugTask table may be
238 used to choose the context.264 used to choose the context.
239 :param user: The user performing the search.265 :param user: The user performing the search.
266 :param wanted_tags: A set of tags within which to restrict the search.
240267
241 :return: A list of tuples, (tag name, open bug count).268 :return: A list of tuples, (tag name, open bug count).
242 """269 """
243 open_statuses_condition = BugTask.status.is_in(270 tables = (
244 UNRESOLVED_BUGTASK_STATUSES)
245 columns = [
246 BugTag.tag,
247 Count(),
248 ]
249 tables = [
250 BugTag,271 BugTag,
251 LeftJoin(Bug, Bug.id == BugTag.bugID),272 Join(BugTask, BugTask.bugID == BugTag.bugID),
252 LeftJoin(273 )
253 BugTask,
254 And(BugTask.bugID == Bug.id, open_statuses_condition)),
255 ]
256 where_conditions = [274 where_conditions = [
257 open_statuses_condition,275 BugTask.status.is_in(UNRESOLVED_BUGTASK_STATUSES),
258 context_condition,276 context_condition,
259 ]277 ]
278 if wanted_tags is not None:
279 where_conditions.append(BugTag.tag.is_in(wanted_tags))
260 privacy_filter = get_bug_privacy_filter(user)280 privacy_filter = get_bug_privacy_filter(user)
261 if privacy_filter:281 if privacy_filter:
262 where_conditions.append(SQLRaw(privacy_filter))282 # The EXISTS sub-select avoids a join against Bug, improving
283 # performance significantly.
284 where_conditions.append(
285 Exists(Select(
286 columns=[True], tables=[Bug],
287 where=And(Bug.id == BugTag.bugID, SQLRaw(privacy_filter)))))
263 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)288 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
264 result = store.execute(Select(289 return store.using(*tables).find(
265 columns=columns, where=And(*where_conditions), tables=tables,290 tag_count_columns, *where_conditions).group_by(BugTag.tag).order_by(
266 group_by=BugTag.tag, order_by=BugTag.tag))291 Desc(Count()), BugTag.tag)
267 return shortlist([(row[0], row[1]) for row in result.get_all()])
268
269
270def snapshot_bug_params(bug_params):
271 """Return a snapshot of a `CreateBugParams` object."""
272 return Snapshot(
273 bug_params, names=[
274 "owner", "title", "comment", "description", "msg",
275 "datecreated", "security_related", "private",
276 "distribution", "sourcepackagename", "binarypackagename",
277 "product", "status", "subscribers", "tags",
278 "subscribe_owner", "filed_by"])
279
280
281class BugTag(SQLBase):
282 """A tag belonging to a bug."""
283
284 bug = ForeignKey(dbName='bug', foreignKey='Bug', notNull=True)
285 tag = StringCol(notNull=True)
286292
287293
288class BugBecameQuestionEvent:294class BugBecameQuestionEvent:
289295
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2011-04-08 15:44:02 +0000
+++ lib/lp/registry/model/distribution.py 2011-04-12 08:33:41 +0000
@@ -649,9 +649,10 @@
649 """See `IBugTarget`."""649 """See `IBugTarget`."""
650 return get_bug_tags("BugTask.distribution = %s" % sqlvalues(self))650 return get_bug_tags("BugTask.distribution = %s" % sqlvalues(self))
651651
652 def getUsedBugTagsWithOpenCounts(self, user):652 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):
653 """See `IBugTarget`."""653 """See `IBugTarget`."""
654 return get_bug_tags_open_count(BugTask.distribution == self, user)654 return get_bug_tags_open_count(
655 BugTask.distribution == self, user, wanted_tags=wanted_tags)
655656
656 def getMirrorByName(self, name):657 def getMirrorByName(self, name):
657 """See `IDistribution`."""658 """See `IDistribution`."""
658659
=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
--- lib/lp/registry/model/distributionsourcepackage.py 2011-03-23 16:28:51 +0000
+++ lib/lp/registry/model/distributionsourcepackage.py 2011-04-12 08:33:41 +0000
@@ -488,12 +488,12 @@
488 """See `IBugTarget`."""488 """See `IBugTarget`."""
489 return self.distribution.getUsedBugTags()489 return self.distribution.getUsedBugTags()
490490
491 def getUsedBugTagsWithOpenCounts(self, user):491 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):
492 """See `IBugTarget`."""492 """See `IBugTarget`."""
493 return get_bug_tags_open_count(493 return get_bug_tags_open_count(
494 And(BugTask.distribution == self.distribution,494 And(BugTask.distribution == self.distribution,
495 BugTask.sourcepackagename == self.sourcepackagename),495 BugTask.sourcepackagename == self.sourcepackagename),
496 user)496 user, wanted_tags=wanted_tags)
497497
498 def _getOfficialTagClause(self):498 def _getOfficialTagClause(self):
499 return self.distribution._getOfficialTagClause()499 return self.distribution._getOfficialTagClause()
500500
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2011-04-08 15:44:02 +0000
+++ lib/lp/registry/model/distroseries.py 2011-04-12 08:33:41 +0000
@@ -833,9 +833,10 @@
833 """See `IHasBugs`."""833 """See `IHasBugs`."""
834 return get_bug_tags("BugTask.distroseries = %s" % sqlvalues(self))834 return get_bug_tags("BugTask.distroseries = %s" % sqlvalues(self))
835835
836 def getUsedBugTagsWithOpenCounts(self, user):836 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):
837 """See `IHasBugs`."""837 """See `IHasBugs`."""
838 return get_bug_tags_open_count(BugTask.distroseries == self, user)838 return get_bug_tags_open_count(
839 BugTask.distroseries == self, user, wanted_tags=wanted_tags)
839840
840 @property841 @property
841 def has_any_specifications(self):842 def has_any_specifications(self):
842843
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2011-03-28 20:54:50 +0000
+++ lib/lp/registry/model/product.py 2011-04-12 08:33:41 +0000
@@ -800,9 +800,10 @@
800 """See `IBugTarget`."""800 """See `IBugTarget`."""
801 return get_bug_tags("BugTask.product = %s" % sqlvalues(self))801 return get_bug_tags("BugTask.product = %s" % sqlvalues(self))
802802
803 def getUsedBugTagsWithOpenCounts(self, user):803 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):
804 """See `IBugTarget`."""804 """See `IBugTarget`."""
805 return get_bug_tags_open_count(BugTask.product == self, user)805 return get_bug_tags_open_count(
806 BugTask.product == self, user, wanted_tags=wanted_tags)
806807
807 series = SQLMultipleJoin('ProductSeries', joinColumn='product',808 series = SQLMultipleJoin('ProductSeries', joinColumn='product',
808 orderBy='name')809 orderBy='name')
809810
=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py 2011-03-28 20:54:50 +0000
+++ lib/lp/registry/model/productseries.py 2011-04-12 08:33:41 +0000
@@ -433,9 +433,10 @@
433 """See IBugTarget."""433 """See IBugTarget."""
434 return get_bug_tags("BugTask.productseries = %s" % sqlvalues(self))434 return get_bug_tags("BugTask.productseries = %s" % sqlvalues(self))
435435
436 def getUsedBugTagsWithOpenCounts(self, user):436 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):
437 """See IBugTarget."""437 """See IBugTarget."""
438 return get_bug_tags_open_count(BugTask.productseries == self, user)438 return get_bug_tags_open_count(
439 BugTask.productseries == self, user, wanted_tags=wanted_tags)
439440
440 def createBug(self, bug_params):441 def createBug(self, bug_params):
441 """See IBugTarget."""442 """See IBugTarget."""
442443
=== modified file 'lib/lp/registry/model/projectgroup.py'
--- lib/lp/registry/model/projectgroup.py 2011-01-21 08:30:55 +0000
+++ lib/lp/registry/model/projectgroup.py 2011-04-12 08:33:41 +0000
@@ -343,13 +343,14 @@
343 return get_bug_tags(343 return get_bug_tags(
344 "BugTask.product IN (%s)" % ",".join(product_ids))344 "BugTask.product IN (%s)" % ",".join(product_ids))
345345
346 def getUsedBugTagsWithOpenCounts(self, user):346 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):
347 """See `IHasBugs`."""347 """See `IHasBugs`."""
348 if not self.products:348 if not self.products:
349 return []349 return []
350 product_ids = [product.id for product in self.products]350 product_ids = [product.id for product in self.products]
351 return get_bug_tags_open_count(351 return get_bug_tags_open_count(
352 BugTask.productID.is_in(product_ids), user)352 BugTask.productID.is_in(product_ids), user,
353 wanted_tags=wanted_tags)
353354
354 def _getBugTaskContextClause(self):355 def _getBugTaskContextClause(self):
355 """See `HasBugsBase`."""356 """See `HasBugsBase`."""
356357
=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2011-04-07 19:08:45 +0000
+++ lib/lp/registry/model/sourcepackage.py 2011-04-12 08:33:41 +0000
@@ -495,12 +495,12 @@
495 """See `IBugTarget`."""495 """See `IBugTarget`."""
496 return self.distroseries.getUsedBugTags()496 return self.distroseries.getUsedBugTags()
497497
498 def getUsedBugTagsWithOpenCounts(self, user):498 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):
499 """See `IBugTarget`."""499 """See `IBugTarget`."""
500 return get_bug_tags_open_count(500 return get_bug_tags_open_count(
501 And(BugTask.distroseries == self.distroseries,501 And(BugTask.distroseries == self.distroseries,
502 BugTask.sourcepackagename == self.sourcepackagename),502 BugTask.sourcepackagename == self.sourcepackagename),
503 user)503 user, wanted_tags=wanted_tags)
504504
505 @property505 @property
506 def max_bug_heat(self):506 def max_bug_heat(self):