launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04000
[Merge] lp:~lifeless/launchpad/bug-793848 into lp:launchpad/db-devel
Robert Collins has proposed merging lp:~lifeless/launchpad/bug-793848 into lp:launchpad/db-devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #793848 in Launchpad itself: "Distribution:+bugtarget-portlet-bugfilters-stats timeouts"
https://bugs.launchpad.net/launchpad/+bug/793848
For more details, see:
https://code.launchpad.net/~lifeless/launchpad/bug-793848/+merge/65158
This branch gets the table scan component of the bug stats portlet using bugsummary.
It needs the new columns only available in db-devel; along the way I saw dead code that confused me, so I deleted it.
I've added a interface for things that are dimensions in the bugsummary, this lets me avoid code duplication between the tags counting code and countBugs. In fact, in a future branch we probably want to delete the special cased tag counting code.
--
https://code.launchpad.net/~lifeless/launchpad/bug-793848/+merge/65158
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/launchpad/bug-793848 into lp:launchpad/db-devel.
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py 2011-06-07 23:20:43 +0000
+++ lib/lp/bugs/browser/bugtarget.py 2011-06-20 07:11:33 +0000
@@ -24,6 +24,7 @@
import cgi
from cStringIO import StringIO
from datetime import datetime
+from functools import partial
from operator import itemgetter
import urllib
from urlparse import urljoin
@@ -1190,7 +1191,7 @@
return url
-class BugTargetBugListingView:
+class BugTargetBugListingView(LaunchpadView):
"""Helper methods for rendering bug listings."""
@property
@@ -1218,7 +1219,7 @@
elif IProductSeries(self.context, None):
milestone_resultset = self.context.product.milestones
else:
- raise AssertionError("series_list called with illegal context")
+ raise AssertionError("milestones_list called with illegal context")
return list(milestone_resultset)
@property
@@ -1230,25 +1231,28 @@
The count only considers bugs that the user would actually be
able to see in a listing.
"""
+ # Circular fail.
+ from lp.bugs.model.bugsummary import (
+ BugSummary,
+ CombineBugSummaryContraint,
+ )
series_buglistings = []
bug_task_set = getUtility(IBugTaskSet)
series_list = self.series_list
if not series_list:
return series_buglistings
- open_bugs = bug_task_set.open_bugtask_search
- open_bugs.setTarget(any(*series_list))
# This would be better as delegation not a case statement.
if IDistribution(self.context, None):
- backlink = BugTask.distroseriesID
+ backlink = BugSummary.distroseries_id
elif IProduct(self.context, None):
- backlink = BugTask.productseriesID
+ backlink = BugSummary.productseries_id
elif IDistroSeries(self.context, None):
- backlink = BugTask.distroseriesID
+ backlink = BugSummary.distroseries_id
elif IProductSeries(self.context, None):
- backlink = BugTask.productseriesID
+ backlink = BugSummary.productseries_id
else:
raise AssertionError("illegal context %r" % self.context)
- counts = bug_task_set.countBugs(open_bugs, (backlink,))
+ counts = bug_task_set.countBugs(self.user, series_list, (backlink,))
for series in series_list:
series_bug_count = counts.get((series.id,), 0)
if series_bug_count > 0:
@@ -1263,14 +1267,23 @@
@property
def milestone_buglistings(self):
"""Return a buglisting for each milestone."""
+ # Circular fail.
+ from lp.bugs.model.bugsummary import (
+ BugSummary,
+ CombineBugSummaryContraint,
+ )
milestone_buglistings = []
bug_task_set = getUtility(IBugTaskSet)
milestones = self.milestones_list
if not milestones:
return milestone_buglistings
- open_bugs = bug_task_set.open_bugtask_search
- open_bugs.setTarget(any(*milestones))
- counts = bug_task_set.countBugs(open_bugs, (BugTask.milestoneID,))
+ # Note: this isn't totally optimal as a query, but its the simplest to
+ # code; we can iterate if needed to provide one complex context to
+ # countBugs.
+ query_milestones = map(partial(
+ CombineBugSummaryContraint, self.context), milestones)
+ counts = bug_task_set.countBugs(
+ self.user, query_milestones, (BugSummary.milestone_id,))
for milestone in milestones:
milestone_bug_count = counts.get((milestone.id,), 0)
if milestone_bug_count > 0:
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2011-06-06 06:44:23 +0000
+++ lib/lp/bugs/browser/bugtask.py 2011-06-20 07:11:33 +0000
@@ -1820,17 +1820,12 @@
@cachedproperty
def _bug_stats(self):
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
bug_task_set = getUtility(IBugTaskSet)
- upstream_open_bugs = bug_task_set.open_bugtask_search
- upstream_open_bugs.setTarget(self.context)
- upstream_open_bugs.resolved_upstream = True
- fixed_upstream_clause = SQL(
- bug_task_set.buildUpstreamClause(upstream_open_bugs))
- open_bugs = bug_task_set.open_bugtask_search
- open_bugs.setTarget(self.context)
- groups = (BugTask.status, BugTask.importance,
- Bug.latest_patch_uploaded != None, fixed_upstream_clause)
- counts = bug_task_set.countBugs(open_bugs, groups)
+ groups = (BugSummary.status, BugSummary.importance,
+ BugSummary.has_patch, BugSummary.fixed_upstream)
+ counts = bug_task_set.countBugs(self.user, [self.context], groups)
# Sum the split out aggregates.
new = 0
open = 0
=== modified file 'lib/lp/bugs/interfaces/bugsummary.py'
--- lib/lp/bugs/interfaces/bugsummary.py 2011-06-14 15:06:36 +0000
+++ lib/lp/bugs/interfaces/bugsummary.py 2011-06-20 07:11:33 +0000
@@ -4,7 +4,10 @@
"""BugSummary interfaces."""
__metaclass__ = type
-__all__ = ['IBugSummary']
+__all__ = [
+ 'IBugSummary',
+ 'IBugSummaryDimension',
+ ]
from zope.interface import Interface
@@ -70,3 +73,16 @@
has_patch = Bool(readonly=True)
fixed_upstream = Bool(readonly=True)
+
+
+class IBugSummaryDimension(Interface):
+ """Interface for dimensions used in the BugSummary database class."""
+
+ def _getBugSummaryContextWhereClause():
+ """Return a storm clause to filter bugsummaries on this context.
+
+ This method is intentended for in-appserver use only.
+
+ :return: Either a storm clause to filter bugsummaries, or False if
+ there cannot be any matching bug summaries.
+ """
=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py 2011-06-07 03:30:25 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py 2011-06-20 07:11:33 +0000
@@ -271,15 +271,6 @@
hardware_is_linked_to_bug to True.
"""
- def getBugCounts(user, statuses=None):
- """Return a dict with the number of bugs in each possible status.
-
- :user: Only bugs the user has permission to view will be
- counted.
- :statuses: Only bugs with these statuses will be counted. If
- None, all statuses will be included.
- """
-
def getBugTaskWeightFunction():
"""Return a function that is used to weight the bug tasks.
=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py 2011-06-14 15:06:36 +0000
+++ lib/lp/bugs/interfaces/bugtask.py 2011-06-20 07:11:33 +0000
@@ -1519,12 +1519,17 @@
:param params: the BugTaskSearchParams to search on.
"""
- def countBugs(params, group_on):
- """Count bugs that match params, grouping by group_on.
-
- :param param: A BugTaskSearchParams object.
+ def countBugs(user, contexts, group_on):
+ """Count open bugs that match params, grouping by group_on.
+
+ This serves results from the bugsummary fact table: it is fast but not
+ completely precise. See the bug summary documentation for more detail.
+
+ :param user: The user to query on behalf of.
+ :param contexts: A list of contexts to search. Contexts must support
+ the IBugSummaryDimension interface.
:param group_on: The column(s) group on - .e.g (
- Bugtask.distroseriesID, BugTask.milestoneID) will cause
+ BugSummary.distroseries_id, BugSummary.milestone_id) will cause
grouping by distro series and then milestone.
:return: A dict {group_instance: count, ...}
"""
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2011-06-16 13:50:58 +0000
+++ lib/lp/bugs/model/bug.py 2011-06-20 07:11:33 +0000
@@ -271,8 +271,9 @@
The only change is that this function takes a SQL expression for limiting
the found tags.
:param context_condition: A Storm SQL expression, limiting the
- used tags to a specific context. Only the BugTask table may be
- used to choose the context.
+ used tags to a specific context. Only the BugSummary table may be
+ used to choose the context. If False then no query will be performed
+ (and {} returned).
"""
# Circular fail.
from lp.bugs.model.bugsummary import BugSummary
=== modified file 'lib/lp/bugs/model/bugsummary.py'
--- lib/lp/bugs/model/bugsummary.py 2011-06-14 15:06:36 +0000
+++ lib/lp/bugs/model/bugsummary.py 2011-06-20 07:11:33 +0000
@@ -4,9 +4,13 @@
"""BugSummary Storm database classes."""
__metaclass__ = type
-__all__ = ['BugSummary']
+__all__ = [
+ 'BugSummary'
+ 'CombineBugSummaryContraint',
+ ]
from storm.locals import (
+ And,
Bool,
Int,
Reference,
@@ -14,6 +18,7 @@
Unicode,
)
from zope.interface import implements
+from zope.security.proxy import removeSecurityProxy
from canonical.database.enumcol import EnumCol
from lp.bugs.interfaces.bugsummary import IBugSummary
@@ -68,3 +73,21 @@
has_patch = Bool()
fixed_upstream = Bool()
+
+
+class CombineBugSummaryContraint(object):
+ """A class to combine two separate bug summary constraints.
+
+ This is useful for querying on multiple related dimensions (e.g. milestone
+ + sourcepackage) - and essential when a dimension is not unique to a
+ context.
+ """
+
+ def __init__(self, *dimensions):
+ self.dimensions = map(
+ lambda x:removeSecurityProxy(x._getBugSummaryContextWhereClause()),
+ dimensions)
+
+ def _getBugSummaryContextWhereClause(self):
+ """See `IBugSummaryDimension`."""
+ return And(*self.dimensions)
=== modified file 'lib/lp/bugs/model/bugtarget.py'
--- lib/lp/bugs/model/bugtarget.py 2011-03-29 15:59:22 +0000
+++ lib/lp/bugs/model/bugtarget.py 2011-06-20 07:11:33 +0000
@@ -119,15 +119,15 @@
def _customizeSearchParams(self, search_params):
"""Customize `search_params` for a specific target."""
- raise NotImplementedError
-
- def _getBugTaskContextWhereClause(self):
- """Return an SQL snippet to filter bugtasks on this context."""
- raise NotImplementedError
-
- def _getBugTaskContextClause(self):
- """Return a SQL clause for selecting this target's bugtasks."""
- raise NotImplementedError(self._getBugTaskContextClause)
+ raise NotImplementedError(self._customizeSearchParams)
+
+ def _getBugSummaryContextWhereClause(self):
+ """Return a storm clause to filter bugsummaries on this context.
+
+ :return: Either a storm clause to filter bugsummaries, or False if
+ there cannot be any matching bug summaries.
+ """
+ raise NotImplementedError(self._getBugSummaryContextWhereClause)
@property
def closed_bugtasks(self):
@@ -208,32 +208,6 @@
"""See `IHasBugs`."""
return not self.all_bugtasks.is_empty()
- def getBugCounts(self, user, statuses=None):
- """See `IHasBugs`."""
- if statuses is None:
- statuses = BugTaskStatus.items
- statuses = list(statuses)
-
- count_column = """
- COUNT (CASE WHEN BugTask.status = %s
- THEN BugTask.id ELSE NULL END)"""
- select_columns = [count_column % sqlvalues(status)
- for status in statuses]
- conditions = [
- '(%s)' % self._getBugTaskContextClause(),
- 'BugTask.bug = Bug.id',
- 'Bug.duplicateof is NULL']
- privacy_filter = get_bug_privacy_filter(user)
- if privacy_filter:
- conditions.append(privacy_filter)
-
- cur = cursor()
- cur.execute(
- "SELECT %s FROM BugTask, Bug WHERE %s" % (
- ', '.join(select_columns), ' AND '.join(conditions)))
- counts = cur.fetchone()
- return dict(zip(statuses, counts))
-
def getBugTaskWeightFunction(self):
"""Default weight function is the simple one."""
return simple_weight_calculator
@@ -249,6 +223,13 @@
# IDistribution, IDistroSeries, IProjectGroup.
enable_bugfiling_duplicate_search = True
+ def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
+ """See IBugTarget."""
+ from lp.bugs.model.bug import get_bug_tags_open_count
+ return get_bug_tags_open_count(
+ self._getBugSummaryContextWhereClause(),
+ user, tag_limit=tag_limit, include_tags=include_tags)
+
class HasBugHeatMixin:
"""Standard functionality for objects implementing IHasBugHeat."""
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2011-05-28 04:09:11 +0000
+++ lib/lp/bugs/model/bugtask.py 2011-06-20 07:11:33 +0000
@@ -45,6 +45,7 @@
Or,
Select,
SQL,
+ Sum,
)
from storm.info import ClassAlias
from storm.store import (
@@ -2525,13 +2526,56 @@
"""See `IBugTaskSet`."""
return self._search(BugTask.bugID, [], None, params).result_set
- def countBugs(self, params, group_on):
+ def countBugs(self, user, contexts, group_on):
"""See `IBugTaskSet`."""
- resultset = self._search(
- group_on + (SQL("COUNT(Distinct BugTask.bug)"),),
- [], None, params).result_set
- # We group on the related field:
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
+ assert user is not None
+ conditions = []
+ # Open bug statuses
+ conditions.append(BugSummary.status.is_in(UNRESOLVED_BUGTASK_STATUSES))
+ # BugSummary does not include duplicates so no need to exclude.
+ context_conditions = []
+ for context in contexts:
+ condition = removeSecurityProxy(
+ context._getBugSummaryContextWhereClause())
+ if condition is not False:
+ context_conditions.append(condition)
+ if not context_conditions:
+ return {}
+ conditions.append(Or(*context_conditions))
+ # bugsummary by design requires either grouping by tag or excluding
+ # non-null tags.
+ # This is an awkward way of saying
+ # if BugSummary.tag not in group_on:
+ # - see bug 799602
+ group_on_tag = False
+ for column in group_on:
+ if column is BugSummary.tag:
+ group_on_tag = True
+ if not group_on_tag:
+ conditions.append(BugSummary.tag == None)
+ else:
+ conditions.append(BugSummary.tag != None)
+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
+ admin_team = getUtility(ILaunchpadCelebrities).admin
+ if user is not None and not user.inTeam(admin_team):
+ store = store.with_(SQL(
+ "teams AS ("
+ "SELECT team from TeamParticipation WHERE person=?)",
+ (user.id,)))
+ # Note that because admins can see every bug regardless of subscription
+ # they will see rather inflated counts. Admins get to deal.
+ if not user.inTeam(admin_team):
+ conditions.append(
+ Or(
+ BugSummary.viewed_by_id == None,
+ BugSummary.viewed_by_id.is_in(SQL("SELECT team FROM teams"))
+ ))
+ sum_count = Sum(BugSummary.count)
+ resultset = store.find(group_on + (sum_count,), *conditions)
resultset.group_by(*group_on)
+ resultset.having(sum_count != 0)
resultset.order_by()
result = {}
for row in resultset:
=== removed file 'lib/lp/bugs/tests/bugtarget-bugcount.txt'
--- lib/lp/bugs/tests/bugtarget-bugcount.txt 2010-10-09 16:36:22 +0000
+++ lib/lp/bugs/tests/bugtarget-bugcount.txt 1970-01-01 00:00:00 +0000
@@ -1,137 +0,0 @@
-= Bug Counts for IBugTarget =
-
-A IBugTarget has a method for getting the number of bugs in different
-statuses, getBugCounts. The method has one required parameter; the user
-used when searching for bugs (to ensure that private bugs the user
-doesn't have permission to view are excluded). None acts as the
-anonymous user, i.e. no private bugs will be included in the counts.
-
- >>> bug_counts = bugtarget.getBugCounts(None)
- >>> for status in sorted(bug_counts.keys()):
- ... print status.name
- NEW
- INCOMPLETE
- OPINION
- INVALID
- WONTFIX
- EXPIRED
- CONFIRMED
- TRIAGED
- INPROGRESS
- FIXCOMMITTED
- FIXRELEASED
- UNKNOWN
-
-It's possible to limit which statuses to return.
-
- >>> from lp.bugs.interfaces.bugtask import BugTaskStatus
- >>> bug_counts = bugtarget.getBugCounts(
- ... None, [BugTaskStatus.NEW, BugTaskStatus.CONFIRMED])
- >>> for status in sorted(bug_counts.keys()):
- ... print status.name
- NEW
- CONFIRMED
-
-If we file a few bugs in each status, the counts will increase.
-
- >>> def print_count_difference(new_counts, old_counts, status):
- ... difference = new_counts[status] - old_counts[status]
- ... if difference > 0:
- ... diff_message = "%s bug(s) more" % difference
- ... elif difference < 0:
- ... diff_message = "%s bug(s) less" % -difference
- ... else:
- ... diff_message = "no difference"
- ... print "%s: %s" % (status.name, diff_message)
-
- >>> login('test@xxxxxxxxxxxxx')
- >>> old_counts = bugtarget.getBugCounts(None)
- >>> from storm.store import Store
- >>> for status in sorted(BugTaskStatus.items):
- ... for index in range(5):
- ... bug = filebug(bugtarget, "Test Bug", status=status)
- ... Store.of(bug).flush()
- ... new_bug_counts = bugtarget.getBugCounts(None)
- ... print_count_difference(new_bug_counts, old_counts, status)
- NEW: 5 bug(s) more
- INCOMPLETE: 5 bug(s) more
- OPINION: 5 bug(s) more
- INVALID: 5 bug(s) more
- WONTFIX: 5 bug(s) more
- EXPIRED: 5 bug(s) more
- CONFIRMED: 5 bug(s) more
- TRIAGED: 5 bug(s) more
- INPROGRESS: 5 bug(s) more
- FIXCOMMITTED: 5 bug(s) more
- FIXRELEASED: 5 bug(s) more
- UNKNOWN: 5 bug(s) more
-
-If we take a bug that is in one status and changes it to another, the
-former status' count will of course decrease, and the latter's increase.
-
- >>> from canonical.launchpad.ftests import syncUpdate
- >>> bug = filebug(bugtarget, "Test Bug", status=BugTaskStatus.CONFIRMED)
- >>> Store.of(bug).flush()
- >>> old_counts = bugtarget.getBugCounts(None)
- >>> for bugtask in bug.bugtasks:
- ... if bugtask.target == bugtarget:
- ... break
- >>> bugtask.transitionToStatus(
- ... BugTaskStatus.INCOMPLETE, getUtility(ILaunchBag).user)
- >>> syncUpdate(bugtask)
- >>> new_bug_counts = bugtarget.getBugCounts(None)
- >>> print_count_difference(
- ... new_bug_counts, old_counts, BugTaskStatus.CONFIRMED)
- CONFIRMED: 1 bug(s) less
- >>> print_count_difference(
- ... new_bug_counts, old_counts, BugTaskStatus.INCOMPLETE)
- INCOMPLETE: 1 bug(s) more
-
-== Private Bugs ==
-
-Private bugs are included in the counts, only if the user has permission
-to view the bug. So if we set the INCOMPLETE bug above to be private,
-Sample Person (the bug reporter) will still get the bug included in the
-count, but not No Privileges Person or the anonymous user.
-
- >>> from lp.registry.interfaces.person import IPersonSet
- >>> old_counts = bugtarget.getBugCounts(None)
- >>> bug.setPrivate(True, getUtility(ILaunchBag).user)
- True
- >>> syncUpdate(bug)
- >>> sample_person_counts = bugtarget.getBugCounts(
- ... getUtility(IPersonSet).getByEmail('test@xxxxxxxxxxxxx'))
- >>> nopriv_counts = bugtarget.getBugCounts(
- ... getUtility(IPersonSet).getByEmail('no-priv@xxxxxxxxxxxxx'))
- >>> anon_counts = bugtarget.getBugCounts(None)
-
- >>> print_count_difference(
- ... sample_person_counts, old_counts, BugTaskStatus.INCOMPLETE)
- INCOMPLETE: no difference
-
- >>> print_count_difference(
- ... nopriv_counts, old_counts, BugTaskStatus.INCOMPLETE)
- INCOMPLETE: 1 bug(s) less
-
- >>> print_count_difference(anon_counts, old_counts, BugTaskStatus.INCOMPLETE)
- INCOMPLETE: 1 bug(s) less
-
- >>> bug.setPrivate(False, getUtility(ILaunchBag).user)
- True
- >>> syncUpdate(bug)
-
-== Duplicate Bugs ==
-
-Duplicate bugs are not included in the counts, so if some bug is marked
-as a duplicate of another bug, the count will decrease.
-
- >>> another_bug = filebug(
- ... bugtarget, "Another Test Bug", status=BugTaskStatus.NEW)
- >>> Store.of(another_bug).flush()
- >>> old_counts = bugtarget.getBugCounts(None)
- >>> another_bug.markAsDuplicate(bug)
- >>> syncUpdate(another_bug)
- >>> new_counts = bugtarget.getBugCounts(None)
- >>> print_count_difference(
- ... new_counts, old_counts, BugTaskStatus.NEW)
- NEW: 1 bug(s) less
=== modified file 'lib/lp/bugs/tests/test_bugtarget.py'
--- lib/lp/bugs/tests/test_bugtarget.py 2011-05-12 14:55:54 +0000
+++ lib/lp/bugs/tests/test_bugtarget.py 2011-06-20 07:11:33 +0000
@@ -5,8 +5,7 @@
This module runs the interface test against the Product, ProductSeries
ProjectGroup, DistributionSourcePackage, and DistroSeries implementations
-IBugTarget. It runs the bugtarget-bugcount.txt, and
-bugtarget-questiontarget.txt tests.
+IBugTarget. It runs the bugtarget-questiontarget.txt test.
"""
# pylint: disable-msg=C0103
@@ -291,14 +290,5 @@
layer=DatabaseFunctionalLayer)
suite.addTest(test)
- setUpMethods.append(sourcePackageSetUp)
- setUpMethods.append(projectSetUp)
-
- for setUpMethod in setUpMethods:
- test = LayeredDocFileSuite('bugtarget-bugcount.txt',
- setUp=setUpMethod, tearDown=tearDown,
- layer=DatabaseFunctionalLayer)
- suite.addTest(test)
-
suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
return suite
=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py 2011-03-23 16:28:51 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py 2011-06-20 07:11:33 +0000
@@ -35,6 +35,7 @@
BugTaskStatus,
IBugTaskSet,
)
+from lp.bugs.model.bugsummary import BugSummary
from lp.bugs.model.bugtask import BugTask
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.distributionsourcepackage import (
@@ -76,11 +77,13 @@
if self.group_on is None:
# Not a useful/valid permutation.
return
- params = self.getBugTaskSearchParams(user=None, multitarget=True)
+ self.getBugTaskSearchParams(user=None, multitarget=True)
# The test data has 3 bugs for searchtarget and 6 for searchtarget2.
expected = {(self.targetToGroup(self.searchtarget),): 3,
(self.targetToGroup(self.searchtarget2),): 6}
- self.assertEqual(expected, self.bugtask_set.countBugs(params,
+ user = self.factory.makePerson()
+ self.assertEqual(expected, self.bugtask_set.countBugs(user,
+ (self.searchtarget, self.searchtarget2),
group_on=self.group_on))
def test_search_all_bugtasks_for_target(self):
@@ -696,7 +699,7 @@
def setUp(self):
super(ProductTarget, self).setUp()
- self.group_on = (BugTask.productID,)
+ self.group_on = (BugSummary.product_id,)
self.searchtarget = self.factory.makeProduct()
self.owner = self.searchtarget.owner
self.makeBugTasks()
@@ -727,7 +730,7 @@
def setUp(self):
super(ProductSeriesTarget, self).setUp()
- self.group_on = (BugTask.productseriesID,)
+ self.group_on = (BugSummary.productseries_id,)
self.searchtarget = self.factory.makeProductSeries()
self.owner = self.searchtarget.owner
self.makeBugTasks()
@@ -884,7 +887,7 @@
def setUp(self):
super(MilestoneTarget, self).setUp()
self.product = self.factory.makeProduct()
- self.group_on = (BugTask.milestoneID,)
+ self.group_on = (BugSummary.milestone_id,)
self.searchtarget = self.factory.makeMilestone(product=self.product)
self.owner = self.product.owner
self.makeBugTasks(bugtarget=self.product)
@@ -930,7 +933,7 @@
def setUp(self):
super(DistributionTarget, self).setUp()
- self.group_on = (BugTask.distributionID,)
+ self.group_on = (BugSummary.distribution_id,)
self.searchtarget = self.factory.makeDistribution()
self.owner = self.searchtarget.owner
self.makeBugTasks()
@@ -968,7 +971,7 @@
def setUp(self):
super(DistroseriesTarget, self).setUp()
- self.group_on = (BugTask.distroseriesID,)
+ self.group_on = (BugSummary.distroseries_id,)
self.searchtarget = self.factory.makeDistroSeries()
self.owner = self.searchtarget.owner
self.makeBugTasks()
@@ -991,7 +994,7 @@
def setUp(self):
super(SourcePackageTarget, self).setUp()
- self.group_on = (BugTask.sourcepackagenameID,)
+ self.group_on = (BugSummary.sourcepackagename_id,)
self.searchtarget = self.factory.makeSourcePackage()
self.owner = self.searchtarget.distroseries.owner
self.makeBugTasks()
@@ -1028,7 +1031,7 @@
def setUp(self):
super(DistributionSourcePackageTarget, self).setUp()
- self.group_on = (BugTask.sourcepackagenameID,)
+ self.group_on = (BugSummary.sourcepackagename_id,)
self.searchtarget = self.factory.makeDistributionSourcePackage()
self.owner = self.searchtarget.distribution.owner
self.makeBugTasks()
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2011-06-06 20:56:43 +0000
+++ lib/lp/registry/configure.zcml 2011-06-20 07:11:33 +0000
@@ -201,6 +201,7 @@
class="lp.registry.model.distroseries.DistroSeries">
<allow
interface="lp.registry.interfaces.distroseries.IDistroSeriesPublic"/>
+ <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
<allow
interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
<allow
@@ -360,6 +361,7 @@
class="lp.registry.model.projectgroup.ProjectGroup">
<allow
interface="lp.registry.interfaces.projectgroup.IProjectGroupPublic"/>
+ <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
<allow
interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
<allow
@@ -448,6 +450,7 @@
<class
class="lp.registry.model.distributionsourcepackage.DistributionSourcePackage">
+ <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
<allow
interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
<allow
@@ -477,7 +480,6 @@
enable_bugfiling_duplicate_search
findRelatedArchivePublications
findRelatedArchives
- getBugCounts
getPersonsByEmail
getReleasesAndPublishingHistory
getUsedBugTags
@@ -986,6 +988,7 @@
createProductRelease
closeBugsAndBlueprints
destroySelf"/>
+ <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
<allow
interface="lp.bugs.interfaces.bugtarget.IHasBugs"/>
<allow
@@ -1143,6 +1146,7 @@
class="lp.registry.model.product.Product">
<allow
interface="lp.registry.interfaces.product.IProductPublic"/>
+ <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
<allow
interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
<allow
@@ -1392,6 +1396,7 @@
<require
permission="launchpad.Edit"
interface="lp.registry.interfaces.productseries.IProductSeriesEditRestricted"/>
+ <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
<allow
interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
<require
@@ -1460,6 +1465,7 @@
class="lp.registry.model.distribution.Distribution">
<allow
interface="lp.registry.interfaces.distribution.IDistributionPublic"/>
+ <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
<allow
interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
<require
@@ -1592,6 +1598,7 @@
class="lp.registry.model.sourcepackage.SourcePackage">
<allow
interface="lp.registry.interfaces.sourcepackage.ISourcePackagePublic"/>
+ <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
<allow
interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
<allow
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2011-06-08 23:31:41 +0000
+++ lib/lp/registry/model/distribution.py 2011-06-20 07:11:33 +0000
@@ -112,7 +112,6 @@
from lp.bugs.model.bug import (
BugSet,
get_bug_tags,
- get_bug_tags_open_count,
)
from lp.bugs.model.bugtarget import (
BugTargetBase,
@@ -637,9 +636,13 @@
"""See `IBugTarget`."""
return self.name
- def _getBugTaskContextWhereClause(self):
+ def _getBugSummaryContextWhereClause(self):
"""See BugTargetBase."""
- return "BugTask.distribution = %d" % self.id
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
+ return And(
+ BugSummary.distribution_id == self.id,
+ BugSummary.sourcepackagename_id == None)
def _customizeSearchParams(self, search_params):
"""Customize `search_params` for this distribution."""
@@ -649,15 +652,6 @@
"""See `IBugTarget`."""
return get_bug_tags("BugTask.distribution = %s" % sqlvalues(self))
- def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
- """See IBugTarget."""
- # Circular fail.
- from lp.bugs.model.bugsummary import BugSummary
- return get_bug_tags_open_count(
- And(BugSummary.distribution_id == self.id,
- BugSummary.sourcepackagename_id == None),
- user, tag_limit=tag_limit, include_tags=include_tags)
-
def getMirrorByName(self, name):
"""See `IDistribution`."""
return Store.of(self).find(
@@ -720,10 +714,6 @@
bug_params.setBugTarget(distribution=self)
return BugSet().createBug(bug_params)
- def _getBugTaskContextClause(self):
- """See BugTargetBase."""
- return 'BugTask.distribution = %s' % sqlvalues(self)
-
@property
def currentseries(self):
"""See `IDistribution`."""
=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
--- lib/lp/registry/model/distributionsourcepackage.py 2011-06-07 03:30:25 +0000
+++ lib/lp/registry/model/distributionsourcepackage.py 2011-06-20 07:11:33 +0000
@@ -43,7 +43,6 @@
from lp.bugs.model.bug import (
Bug,
BugSet,
- get_bug_tags_open_count,
)
from lp.bugs.model.bugtarget import (
BugTargetBase,
@@ -472,11 +471,13 @@
"""See `IDistributionSourcePackage`."""
return not self.__eq__(other)
- def _getBugTaskContextWhereClause(self):
+ def _getBugSummaryContextWhereClause(self):
"""See `BugTargetBase`."""
- return (
- "BugTask.distribution = %d AND BugTask.sourcepackagename = %d" % (
- self.distribution.id, self.sourcepackagename.id))
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
+ return And(
+ BugSummary.distribution == self.distribution,
+ BugSummary.sourcepackagename == self.sourcepackagename),
def _customizeSearchParams(self, search_params):
"""Customize `search_params` for this distribution source package."""
@@ -486,15 +487,6 @@
"""See `IBugTarget`."""
return self.distribution.getUsedBugTags()
- def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
- """See IBugTarget."""
- # Circular fail.
- from lp.bugs.model.bugsummary import BugSummary
- return get_bug_tags_open_count(
- And(BugSummary.distribution == self.distribution,
- BugSummary.sourcepackagename == self.sourcepackagename),
- user, tag_limit=tag_limit, include_tags=include_tags)
-
def _getOfficialTagClause(self):
return self.distribution._getOfficialTagClause()
@@ -510,12 +502,6 @@
sourcepackagename=self.sourcepackagename)
return BugSet().createBug(bug_params)
- def _getBugTaskContextClause(self):
- """See `BugTargetBase`."""
- return (
- 'BugTask.distribution = %s AND BugTask.sourcepackagename = %s' %
- sqlvalues(self.distribution, self.sourcepackagename))
-
def composeCustomLanguageCodeMatch(self):
"""See `HasCustomLanguageCodesMixin`."""
return And(
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2011-06-16 10:38:21 +0000
+++ lib/lp/registry/model/distroseries.py 2011-06-20 07:11:33 +0000
@@ -84,7 +84,6 @@
from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask
from lp.bugs.model.bug import (
get_bug_tags,
- get_bug_tags_open_count,
)
from lp.bugs.model.bugtarget import (
BugTargetBase,
@@ -841,18 +840,6 @@
"""See `IHasBugs`."""
return get_bug_tags("BugTask.distroseries = %s" % sqlvalues(self))
- def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0,
- include_tags=None):
- """See IBugTarget."""
- # Circular fail.
- from lp.bugs.model.bugsummary import BugSummary
- return get_bug_tags_open_count(
- And(
- BugSummary.distroseries_id == self.id,
- BugSummary.sourcepackagename_id == None
- ),
- user, tag_limit=tag_limit, include_tags=include_tags)
-
@property
def has_any_specifications(self):
"""See IHasSpecifications."""
@@ -1664,9 +1651,14 @@
"not-too-distant future. For now, you probably meant to file "
"the bug on the distribution instead.")
- def _getBugTaskContextClause(self):
+ def _getBugSummaryContextWhereClause(self):
"""See BugTargetBase."""
- return 'BugTask.distroseries = %s' % sqlvalues(self)
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
+ return And(
+ BugSummary.distroseries_id == self.id,
+ BugSummary.sourcepackagename_id == None
+ )
def copyTranslationsFromParent(self, transaction, logger=None):
"""See `IDistroSeries`."""
=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py 2011-03-23 16:28:51 +0000
+++ lib/lp/registry/model/milestone.py 2011-06-20 07:11:33 +0000
@@ -40,6 +40,7 @@
from canonical.launchpad.webapp.sorting import expand_numbers
from lp.app.errors import NotFoundError
from lp.blueprints.model.specification import Specification
+from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
from lp.bugs.interfaces.bugtarget import IHasBugs
from lp.bugs.interfaces.bugtask import (
BugTaskSearchParams,
@@ -128,7 +129,7 @@
class Milestone(SQLBase, StructuralSubscriptionTargetMixin, HasBugsBase):
- implements(IHasBugs, IMilestone)
+ implements(IHasBugs, IMilestone, IBugSummaryDimension)
# XXX: Guilherme Salgado 2007-03-27 bug=40978:
# Milestones should be associated with productseries/distroseriess
@@ -247,6 +248,12 @@
"associated with it.")
SQLBase.destroySelf(self)
+ def _getBugSummaryContextWhereClause(self):
+ """See BugTargetBase."""
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
+ return BugSummary.milestone_id == self.id
+
class MilestoneSet:
implements(IMilestoneSet)
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2011-06-07 03:30:25 +0000
+++ lib/lp/registry/model/product.py 2011-06-20 07:11:33 +0000
@@ -114,7 +114,6 @@
from lp.bugs.model.bug import (
BugSet,
get_bug_tags,
- get_bug_tags_open_count,
)
from lp.bugs.model.bugtarget import (
BugTargetBase,
@@ -799,14 +798,6 @@
"""See `IBugTarget`."""
return get_bug_tags("BugTask.product = %s" % sqlvalues(self))
- def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
- """See IBugTarget."""
- # Circular fail.
- from lp.bugs.model.bugsummary import BugSummary
- return get_bug_tags_open_count(
- BugSummary.product_id == self.id,
- user, tag_limit=tag_limit, include_tags=include_tags)
-
series = SQLMultipleJoin('ProductSeries', joinColumn='product',
orderBy='name')
@@ -957,9 +948,11 @@
bug_params.setBugTarget(product=self)
return BugSet().createBug(bug_params)
- def _getBugTaskContextClause(self):
+ def _getBugSummaryContextWhereClause(self):
"""See BugTargetBase."""
- return 'BugTask.product = %s' % sqlvalues(self)
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
+ return BugSummary.product_id == self.id
def searchQuestions(self, search_text=None,
status=QUESTION_STATUS_DEFAULT_SEARCH,
=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py 2011-06-07 03:30:25 +0000
+++ lib/lp/registry/model/productseries.py 2011-06-20 07:11:33 +0000
@@ -66,7 +66,6 @@
from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask
from lp.bugs.model.bug import (
get_bug_tags,
- get_bug_tags_open_count,
)
from lp.bugs.model.bugtarget import (
BugTargetBase,
@@ -446,21 +445,15 @@
"""See IBugTarget."""
return get_bug_tags("BugTask.productseries = %s" % sqlvalues(self))
- def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
- """See IBugTarget."""
- # Circular fail.
- from lp.bugs.model.bugsummary import BugSummary
- return get_bug_tags_open_count(
- BugSummary.productseries_id == self.id, user, tag_limit=tag_limit,
- include_tags=include_tags)
-
def createBug(self, bug_params):
"""See IBugTarget."""
raise NotImplementedError('Cannot file a bug against a productseries')
- def _getBugTaskContextClause(self):
+ def _getBugSummaryContextWhereClause(self):
"""See BugTargetBase."""
- return 'BugTask.productseries = %s' % sqlvalues(self)
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
+ return BugSummary.productseries_id == self.id
def getSpecification(self, name):
"""See ISpecificationTarget."""
=== modified file 'lib/lp/registry/model/projectgroup.py'
--- lib/lp/registry/model/projectgroup.py 2011-06-07 03:30:25 +0000
+++ lib/lp/registry/model/projectgroup.py 2011-06-20 07:11:33 +0000
@@ -69,7 +69,6 @@
from lp.bugs.interfaces.bugtarget import IHasBugHeat
from lp.bugs.model.bug import (
get_bug_tags,
- get_bug_tags_open_count,
)
from lp.bugs.model.bugtarget import (
BugTargetBase,
@@ -343,20 +342,14 @@
return get_bug_tags(
"BugTask.product IN (%s)" % ",".join(product_ids))
- def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
- """See IBugTarget."""
+ def _getBugSummaryContextWhereClause(self):
+ """See BugTargetBase."""
# Circular fail.
from lp.bugs.model.bugsummary import BugSummary
product_ids = [product.id for product in self.products]
if not product_ids:
- return {}
- return get_bug_tags_open_count(
- BugSummary.product_id.is_in(product_ids),
- user, tag_limit=tag_limit, include_tags=include_tags)
-
- def _getBugTaskContextClause(self):
- """See `HasBugsBase`."""
- return 'BugTask.product IN (%s)' % ','.join(sqlvalues(*self.products))
+ return False
+ return BugSummary.product_id.is_in(product_ids)
# IQuestionCollection
def searchQuestions(self, search_text=None,
=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2011-06-08 15:00:44 +0000
+++ lib/lp/registry/model/sourcepackage.py 2011-06-20 07:11:33 +0000
@@ -39,7 +39,6 @@
)
from lp.bugs.interfaces.bugtarget import IHasBugHeat
from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask
-from lp.bugs.model.bug import get_bug_tags_open_count
from lp.bugs.model.bugtarget import (
BugTargetBase,
HasBugHeatMixin,
@@ -500,15 +499,6 @@
"""See `IBugTarget`."""
return self.distroseries.getUsedBugTags()
- def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0, include_tags=None):
- """See IBugTarget."""
- # Circular fail.
- from lp.bugs.model.bugsummary import BugSummary
- return get_bug_tags_open_count(
- And(BugSummary.distroseries == self.distroseries,
- BugSummary.sourcepackagename == self.sourcepackagename),
- user, tag_limit=tag_limit, include_tags=include_tags)
-
@property
def max_bug_heat(self):
"""See `IHasBugs`."""
@@ -528,11 +518,13 @@
"future. For now, you probably meant to file the bug on the "
"distro-wide (i.e. not series-specific) source package.")
- def _getBugTaskContextClause(self):
+ def _getBugSummaryContextWhereClause(self):
"""See BugTargetBase."""
- return (
- 'BugTask.distroseries = %s AND BugTask.sourcepackagename = %s' %
- sqlvalues(self.distroseries, self.sourcepackagename))
+ # Circular fail.
+ from lp.bugs.model.bugsummary import BugSummary
+ return And(
+ BugSummary.distroseries == self.distroseries,
+ BugSummary.sourcepackagename == self.sourcepackagename)
def setPackaging(self, productseries, owner):
"""See `ISourcePackage`."""
Follow ups