Merge lp:~lifeless/launchpad/bug-793809 into lp:launchpad

Proposed by Robert Collins
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 13171
Proposed branch: lp:~lifeless/launchpad/bug-793809
Merge into: lp:launchpad
Diff against target: 416 lines (+126/-100)
11 files modified
lib/lp/bugs/browser/bugtarget.py (+4/-15)
lib/lp/bugs/doc/bug-tags.txt (+20/-21)
lib/lp/bugs/interfaces/bugtarget.py (+9/-7)
lib/lp/bugs/model/bug.py (+43/-30)
lib/lp/registry/model/distribution.py (+7/-3)
lib/lp/registry/model/distributionsourcepackage.py (+7/-5)
lib/lp/registry/model/distroseries.py (+10/-3)
lib/lp/registry/model/product.py (+6/-3)
lib/lp/registry/model/productseries.py (+5/-2)
lib/lp/registry/model/projectgroup.py (+8/-6)
lib/lp/registry/model/sourcepackage.py (+7/-5)
To merge this branch: bzr merge lp:~lifeless/launchpad/bug-793809
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+63635@code.launchpad.net

Commit message

[r=wgrant][bug=793809] Use BugSummary to do tag portlet calculations

Description of the change

Per the linked bug generating bug tag portlets is extraordinarily slow. This is meant to be addressed by querying bugsummary, which this branch implements.

I have changed the interface to match our needs allowing a faster query (the with clause on teams for visibility).

Other than that I can see a path to reduce the duplicate code that we use here, but its unrelated to fixing this bug, so I've focused on getting the bug fixed ;)

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

21 + tags = self.context.getUsedBugTagsWithOpenCounts(self.user, 10,
22 + official_tags)

Nice cleanup. But bad indentation.

33 + official_tags = self.context.official_bug_tags

Sure this doesn't need to be materialised?

131 + :param tag_limit: The number of tags to return (excludes those found by
132 + matching include_tags). If 0 then all tags are returned. If
133 + non-zero then the most frequently used tags are returned.

0? Why not None?

172 +def get_bug_tags_open_count(context_condition, user, tag_limit=0,
173 + include_tags=None):

Bad indentation.

200 + store = store.with_(SQL(
201 + "teams AS ("
202 + "SELECT team from TeamParticipation WHERE person=%s)" % user.id))

String formatting in SQL: Australia says no.

Also, calling that "store" is a bit too much of a lie for my tastes.

228 + BugSummary.viewed_by_id.is_in(SQL("SELECT team FROM teams"))

What's the benefit of the WITH clause here? I doubt there is any performance gain, and including the full thing here is shorter and clearer.

review: Approve (code)
Revision history for this message
Robert Collins (lifeless) wrote :

> 21 + tags = self.context.getUsedBugTagsWithOpenCounts(self.user,
> 10,
> 22 + official_tags)
>
> Nice cleanup. But bad indentation.

How so ?

> 33 + official_tags = self.context.official_bug_tags
>
> Sure this doesn't need to be materialised?

The core function returns a list.

> 131 + :param tag_limit: The number of tags to return (excludes
> those found by
> 132 + matching include_tags). If 0 then all tags are returned.
> If
> 133 + non-zero then the most frequently used tags are returned.
>
> 0? Why not None?

Shrug; can change it to that, but 0 would be noddy. Using 0 as the flag avoids bad behaviour if a limit of 0 is passed in.

> 172 +def get_bug_tags_open_count(context_condition, user, tag_limit=0,
> 173 + include_tags=None):
>
> Bad indentation.

How so ?

> 200 + store = store.with_(SQL(
> 201 + "teams AS ("
> 202 + "SELECT team from TeamParticipation WHERE person=%s)" %
> user.id))
>
> String formatting in SQL: Australia says no.
>
> Also, calling that "store" is a bit too much of a lie for my tastes.

It meets the core contract and is better understood than 'resultset_factory'.

> 228 + BugSummary.viewed_by_id.is_in(SQL("SELECT team FROM
> teams"))
>
> What's the benefit of the WITH clause here? I doubt there is any performance
> gain, and including the full thing here is shorter and clearer.

Its faster. 150ms difference when I was testing.

Revision history for this message
William Grant (wgrant) wrote :

Thanks!

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-06-06 06:44:23 +0000
+++ lib/lp/bugs/browser/bugtarget.py 2011-06-07 05:39:33 +0000
@@ -1405,19 +1405,8 @@
1405 def tags_cloud_data(self):1405 def tags_cloud_data(self):
1406 """The data for rendering a tags cloud"""1406 """The data for rendering a tags cloud"""
1407 official_tags = self.context.official_bug_tags1407 official_tags = self.context.official_bug_tags
14081408 tags = self.context.getUsedBugTagsWithOpenCounts(
1409 # Construct a dict of official and top 10 tags.1409 self.user, 10, official_tags)
1410 # getUsedBugTagsWithOpenCounts is expensive, so do the union in
1411 # SQL. Also preseed with 0 for all the official tags, as gUBTWOC
1412 # won't return unused ones.
1413 top_ten = removeSecurityProxy(
1414 self.context.getUsedBugTagsWithOpenCounts(self.user)[:10])
1415 official = removeSecurityProxy(
1416 self.context.getUsedBugTagsWithOpenCounts(
1417 self.user, official_tags))
1418 tags = dict((tag, 0) for tag in official_tags)
1419 tags.update(dict(top_ten.union(official)))
1420
1421 max_count = float(max([1] + tags.values()))1410 max_count = float(max([1] + tags.values()))
14221411
1423 return sorted(1412 return sorted(
@@ -1462,8 +1451,8 @@
1462 @property1451 @property
1463 def tags_js_data(self):1452 def tags_js_data(self):
1464 """Return the JSON representation of the bug tags."""1453 """Return the JSON representation of the bug tags."""
1465 used_tags = dict(self.context.getUsedBugTagsWithOpenCounts(self.user))1454 used_tags = self.context.getUsedBugTagsWithOpenCounts(self.user)
1466 official_tags = list(self.context.official_bug_tags)1455 official_tags = self.context.official_bug_tags
1467 return """<script type="text/javascript">1456 return """<script type="text/javascript">
1468 var used_bug_tags = %s;1457 var used_bug_tags = %s;
1469 var official_bug_tags = %s;1458 var official_bug_tags = %s;
14701459
=== modified file 'lib/lp/bugs/doc/bug-tags.txt'
--- lib/lp/bugs/doc/bug-tags.txt 2011-04-12 06:40:51 +0000
+++ lib/lp/bugs/doc/bug-tags.txt 2011-06-07 05:39:33 +0000
@@ -332,35 +332,34 @@
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 >>> list(firefox.getUsedBugTagsWithOpenCounts(None))335 >>> sorted(firefox.getUsedBugTagsWithOpenCounts(None).items())
336 [(u'doc', 1L), (u'sco', 1L), (u'svg', 1L)]336 [(u'doc', 1L), (u'sco', 1L), (u'svg', 1L)]
337337
338 >>> list(mozilla.getUsedBugTagsWithOpenCounts(None))338 >>> sorted(mozilla.getUsedBugTagsWithOpenCounts(None).items())
339 [(u'doc', 1L), (u'sco', 1L), (u'svg', 1L)]339 [(u'doc', 1L), (u'sco', 1L), (u'svg', 1L)]
340340
341 >>> list(ubuntu.getUsedBugTagsWithOpenCounts(None))341 >>> sorted(ubuntu.getUsedBugTagsWithOpenCounts(None).items())
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.345We can require that some tags be included in the output even when limiting the
346results.
346347
347 >>> list(ubuntu.getUsedBugTagsWithOpenCounts(348 >>> sorted(ubuntu.getUsedBugTagsWithOpenCounts(None,
348 ... None, wanted_tags=['pebcak', 'svg', 'fake']))349 ... tag_limit=1, include_tags=[u'pebcak', u'svg', u'fake']).items())
349 [(u'pebcak', 1L), (u'svg', 1L)]350 [(u'crash', 2L), (u'fake', 0), (u'pebcak', 1L), (u'svg', 1L)]
350 >>> list(ubuntu.getUsedBugTagsWithOpenCounts(None, wanted_tags=[]))
351 []
352351
353Source packages are a bit special, they return all the tags that are352Source packages are a bit special, they return all the tags that are
354used in the whole distribution, while the bug count includes only bugs353used in the whole distribution, while the bug count includes only bugs
355in the specific package.354in the specific package.
356355
357 >>> list(ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(None))356 >>> ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(None)
358 [(u'crash', 1L)]357 {u'crash': 1L}
359358
360 >>> list(debian_woody.getUsedBugTagsWithOpenCounts(None))359 >>> sorted(debian_woody.getUsedBugTagsWithOpenCounts(None).items())
361 [(u'dataloss', 1L), (u'layout-test', 1L), (u'pebcak', 1L)]360 [(u'dataloss', 1L), (u'layout-test', 1L), (u'pebcak', 1L)]
362361
363 >>> list(debian_woody_firefox.getUsedBugTagsWithOpenCounts(None))362 >>> sorted(debian_woody_firefox.getUsedBugTagsWithOpenCounts(None).items())
364 [(u'dataloss', 1L), (u'layout-test', 1L), (u'pebcak', 1L)]363 [(u'dataloss', 1L), (u'layout-test', 1L), (u'pebcak', 1L)]
365364
366Only bugs that the supplied user has access to will be counted:365Only bugs that the supplied user has access to will be counted:
@@ -370,14 +369,14 @@
370 True369 True
371 >>> flush_database_updates()370 >>> flush_database_updates()
372371
373 >>> list(ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(None))372 >>> ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(None)
374 []373 {}
375374
376 >>> sample_person = getUtility(ILaunchBag).user375 >>> sample_person = getUtility(ILaunchBag).user
377 >>> bug_nine.isSubscribed(sample_person)376 >>> bug_nine.isSubscribed(sample_person)
378 True377 True
379 >>> list(ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(sample_person))378 >>> ubuntu_thunderbird.getUsedBugTagsWithOpenCounts(sample_person)
380 [(u'crash', 1L)]379 {u'crash': 1L}
381380
382When context doesn't have any tags getUsedBugTags() returns a empty list.381When context doesn't have any tags getUsedBugTags() returns a empty list.
383382
384383
=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py 2011-04-12 06:21:39 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py 2011-06-07 05:39:33 +0000
@@ -403,15 +403,17 @@
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, wanted_tags=None):406 def getUsedBugTagsWithOpenCounts(user, tag_limit=0, include_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 :param user: The user who wants the report.
410 number of open bugs having that tag. Only the bugs that the user410 :param tag_limit: The number of tags to return (excludes those found by
411 has permission to see are counted, and only tags having open411 matching include_tags). If 0 then all tags are returned. If
412 bugs will be returned.412 non-zero then the most frequently used tags are returned.
413413 :param include_tags: A list of string tags to return irrespective of
414 If wanted_tags is specified, only those tags will be returned.414 usage. Tags in this list that have no open bugs are returned with a
415 count of 0. May be None if there are tags to require inclusion of.
416 :return: A dict from tag -> count.
415 """417 """
416418
417 def _getOfficialTagClause():419 def _getOfficialTagClause():
418420
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2011-06-03 10:38:25 +0000
+++ lib/lp/bugs/model/bug.py 2011-06-07 05:39:33 +0000
@@ -63,6 +63,7 @@
63 Select,63 Select,
64 SQL,64 SQL,
65 SQLRaw,65 SQLRaw,
66 Sum,
66 Union,67 Union,
67 )68 )
68from storm.info import ClassAlias69from storm.info import ClassAlias
@@ -242,11 +243,6 @@
242 tag = StringCol(notNull=True)243 tag = StringCol(notNull=True)
243244
244245
245# We need to always use the same Count instance or the
246# get_bug_tags_open_count is not UNIONable.
247tag_count_columns = (BugTag.tag, Count())
248
249
250def get_bug_tags(context_clause):246def get_bug_tags(context_clause):
251 """Return all the bug tags as a list of strings.247 """Return all the bug tags as a list of strings.
252248
@@ -266,39 +262,56 @@
266 return shortlist([row[0] for row in cur.fetchall()])262 return shortlist([row[0] for row in cur.fetchall()])
267263
268264
269def get_bug_tags_open_count(context_condition, user, wanted_tags=None):265def get_bug_tags_open_count(context_condition, user, tag_limit=0,
270 """Return all the used bug tags with their open bug count.266 include_tags=None):
271267 """Worker for IBugTarget.getUsedBugTagsWithOpenCounts.
268
269 See `IBugTarget` for details.
270
271 The only change is that this function takes a SQL expression for limiting
272 the found tags.
272 :param context_condition: A Storm SQL expression, limiting the273 :param context_condition: A Storm SQL expression, limiting the
273 used tags to a specific context. Only the BugTask table may be274 used tags to a specific context. Only the BugTask table may be
274 used to choose the context.275 used to choose the context.
275 :param user: The user performing the search.
276 :param wanted_tags: A set of tags within which to restrict the search.
277
278 :return: A list of tuples, (tag name, open bug count).
279 """276 """
280 tables = (277 # Circular fail.
281 BugTag,278 from lp.bugs.model.bugsummary import BugSummary
282 Join(BugTask, BugTask.bugID == BugTag.bugID),279 tags = {}
283 )280 if include_tags:
281 tags = dict((tag, 0) for tag in include_tags)
282 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
283 admin_team = getUtility(ILaunchpadCelebrities).admin
284 if user is not None and not user.inTeam(admin_team):
285 store = store.with_(SQL(
286 "teams AS ("
287 "SELECT team from TeamParticipation WHERE person=?)", (user.id,)))
284 where_conditions = [288 where_conditions = [
285 BugTask.status.is_in(UNRESOLVED_BUGTASK_STATUSES),289 BugSummary.status.is_in(UNRESOLVED_BUGTASK_STATUSES),
290 BugSummary.tag != None,
286 context_condition,291 context_condition,
287 ]292 ]
288 if wanted_tags is not None:293 if user is None:
289 where_conditions.append(BugTag.tag.is_in(wanted_tags))294 where_conditions.append(BugSummary.viewed_by_id == None)
290 privacy_filter = get_bug_privacy_filter(user)295 elif not user.inTeam(admin_team):
291 if privacy_filter:
292 # The EXISTS sub-select avoids a join against Bug, improving
293 # performance significantly.
294 where_conditions.append(296 where_conditions.append(
295 Exists(Select(297 Or(
296 columns=[True], tables=[Bug],298 BugSummary.viewed_by_id == None,
297 where=And(Bug.id == BugTag.bugID, SQLRaw(privacy_filter)))))299 BugSummary.viewed_by_id.is_in(SQL("SELECT team FROM teams"))
298 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)300 ))
299 return store.using(*tables).find(301 tag_count_columns = (BugSummary.tag, Sum(BugSummary.count))
300 tag_count_columns, *where_conditions).group_by(BugTag.tag).order_by(302 # Always query for used
301 Desc(Count()), BugTag.tag)303 def _query(*args):
304 return store.find(tag_count_columns, *(where_conditions + list(args))
305 ).group_by(BugSummary.tag).order_by(
306 Desc(Sum(BugSummary.count)), BugSummary.tag)
307 used = _query()
308 if tag_limit:
309 used = used[:tag_limit]
310 if include_tags:
311 # Union in a query for just include_tags.
312 used = used.union(_query(BugSummary.tag.is_in(include_tags)))
313 tags.update(dict(used))
314 return tags
302315
303316
304class BugBecameQuestionEvent:317class BugBecameQuestionEvent:
305318
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2011-05-27 21:12:25 +0000
+++ lib/lp/registry/model/distribution.py 2011-06-07 05:39:33 +0000
@@ -646,10 +646,14 @@
646 """See `IBugTarget`."""646 """See `IBugTarget`."""
647 return get_bug_tags("BugTask.distribution = %s" % sqlvalues(self))647 return get_bug_tags("BugTask.distribution = %s" % sqlvalues(self))
648648
649 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):649 def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
650 """See `IBugTarget`."""650 """See IBugTarget."""
651 # Circular fail.
652 from lp.bugs.model.bugsummary import BugSummary
651 return get_bug_tags_open_count(653 return get_bug_tags_open_count(
652 BugTask.distribution == self, user, wanted_tags=wanted_tags)654 And(BugSummary.distribution_id == self.id,
655 BugSummary.sourcepackagename_id == None),
656 user, tag_limit=tag_limit, include_tags=include_tags)
653657
654 def getMirrorByName(self, name):658 def getMirrorByName(self, name):
655 """See `IDistribution`."""659 """See `IDistribution`."""
656660
=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
--- lib/lp/registry/model/distributionsourcepackage.py 2011-05-12 04:18:32 +0000
+++ lib/lp/registry/model/distributionsourcepackage.py 2011-06-07 05:39:33 +0000
@@ -486,12 +486,14 @@
486 """See `IBugTarget`."""486 """See `IBugTarget`."""
487 return self.distribution.getUsedBugTags()487 return self.distribution.getUsedBugTags()
488488
489 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):489 def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
490 """See `IBugTarget`."""490 """See IBugTarget."""
491 # Circular fail.
492 from lp.bugs.model.bugsummary import BugSummary
491 return get_bug_tags_open_count(493 return get_bug_tags_open_count(
492 And(BugTask.distribution == self.distribution,494 And(BugSummary.distribution == self.distribution,
493 BugTask.sourcepackagename == self.sourcepackagename),495 BugSummary.sourcepackagename == self.sourcepackagename),
494 user, wanted_tags=wanted_tags)496 user, tag_limit=tag_limit, include_tags=include_tags)
495497
496 def _getOfficialTagClause(self):498 def _getOfficialTagClause(self):
497 return self.distribution._getOfficialTagClause()499 return self.distribution._getOfficialTagClause()
498500
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2011-05-31 15:45:19 +0000
+++ lib/lp/registry/model/distroseries.py 2011-06-07 05:39:33 +0000
@@ -28,6 +28,7 @@
28 StringCol,28 StringCol,
29 )29 )
30from storm.locals import (30from storm.locals import (
31 And,
31 Desc,32 Desc,
32 Join,33 Join,
33 SQL,34 SQL,
@@ -853,10 +854,16 @@
853 """See `IHasBugs`."""854 """See `IHasBugs`."""
854 return get_bug_tags("BugTask.distroseries = %s" % sqlvalues(self))855 return get_bug_tags("BugTask.distroseries = %s" % sqlvalues(self))
855856
856 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):857 def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
857 """See `IHasBugs`."""858 """See IBugTarget."""
859 # Circular fail.
860 from lp.bugs.model.bugsummary import BugSummary
858 return get_bug_tags_open_count(861 return get_bug_tags_open_count(
859 BugTask.distroseries == self, user, wanted_tags=wanted_tags)862 And(
863 BugSummary.distroseries_id == self.id,
864 BugSummary.sourcepackagename_id == None
865 ),
866 user, tag_limit=tag_limit, include_tags=include_tags)
860867
861 @property868 @property
862 def has_any_specifications(self):869 def has_any_specifications(self):
863870
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2011-05-27 21:12:25 +0000
+++ lib/lp/registry/model/product.py 2011-06-07 05:39:33 +0000
@@ -799,10 +799,13 @@
799 """See `IBugTarget`."""799 """See `IBugTarget`."""
800 return get_bug_tags("BugTask.product = %s" % sqlvalues(self))800 return get_bug_tags("BugTask.product = %s" % sqlvalues(self))
801801
802 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):802 def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
803 """See `IBugTarget`."""803 """See IBugTarget."""
804 # Circular fail.
805 from lp.bugs.model.bugsummary import BugSummary
804 return get_bug_tags_open_count(806 return get_bug_tags_open_count(
805 BugTask.product == self, user, wanted_tags=wanted_tags)807 BugSummary.product_id == self.id,
808 user, tag_limit=tag_limit, include_tags=include_tags)
806809
807 series = SQLMultipleJoin('ProductSeries', joinColumn='product',810 series = SQLMultipleJoin('ProductSeries', joinColumn='product',
808 orderBy='name')811 orderBy='name')
809812
=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py 2011-05-27 21:12:25 +0000
+++ lib/lp/registry/model/productseries.py 2011-06-07 05:39:33 +0000
@@ -446,10 +446,13 @@
446 """See IBugTarget."""446 """See IBugTarget."""
447 return get_bug_tags("BugTask.productseries = %s" % sqlvalues(self))447 return get_bug_tags("BugTask.productseries = %s" % sqlvalues(self))
448448
449 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):449 def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
450 """See IBugTarget."""450 """See IBugTarget."""
451 # Circular fail.
452 from lp.bugs.model.bugsummary import BugSummary
451 return get_bug_tags_open_count(453 return get_bug_tags_open_count(
452 BugTask.productseries == self, user, wanted_tags=wanted_tags)454 BugSummary.productseries_id == self.id, user, tag_limit=tag_limit,
455 include_tags=include_tags)
453456
454 def createBug(self, bug_params):457 def createBug(self, bug_params):
455 """See IBugTarget."""458 """See IBugTarget."""
456459
=== modified file 'lib/lp/registry/model/projectgroup.py'
--- lib/lp/registry/model/projectgroup.py 2011-04-26 16:22:11 +0000
+++ lib/lp/registry/model/projectgroup.py 2011-06-07 05:39:33 +0000
@@ -343,14 +343,16 @@
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, wanted_tags=None):346 def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
347 """See `IHasBugs`."""347 """See IBugTarget."""
348 if not self.products:348 # Circular fail.
349 return []349 from lp.bugs.model.bugsummary import BugSummary
350 product_ids = [product.id for product in self.products]350 product_ids = [product.id for product in self.products]
351 if not product_ids:
352 return {}
351 return get_bug_tags_open_count(353 return get_bug_tags_open_count(
352 BugTask.productID.is_in(product_ids), user,354 BugSummary.product_id.is_in(product_ids),
353 wanted_tags=wanted_tags)355 user, tag_limit=tag_limit, include_tags=include_tags)
354356
355 def _getBugTaskContextClause(self):357 def _getBugTaskContextClause(self):
356 """See `HasBugsBase`."""358 """See `HasBugsBase`."""
357359
=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2011-05-14 15:03:04 +0000
+++ lib/lp/registry/model/sourcepackage.py 2011-06-07 05:39:33 +0000
@@ -494,12 +494,14 @@
494 """See `IBugTarget`."""494 """See `IBugTarget`."""
495 return self.distroseries.getUsedBugTags()495 return self.distroseries.getUsedBugTags()
496496
497 def getUsedBugTagsWithOpenCounts(self, user, wanted_tags=None):497 def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
498 """See `IBugTarget`."""498 """See IBugTarget."""
499 # Circular fail.
500 from lp.bugs.model.bugsummary import BugSummary
499 return get_bug_tags_open_count(501 return get_bug_tags_open_count(
500 And(BugTask.distroseries == self.distroseries,502 And(BugSummary.distroseries == self.distroseries,
501 BugTask.sourcepackagename == self.sourcepackagename),503 BugSummary.sourcepackagename == self.sourcepackagename),
502 user, wanted_tags=wanted_tags)504 user, tag_limit=tag_limit, include_tags=include_tags)
503505
504 @property506 @property
505 def max_bug_heat(self):507 def max_bug_heat(self):