← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~lifeless/launchpad/bug-717394-4 into lp:launchpad

 

Robert Collins has proposed merging lp:~lifeless/launchpad/bug-717394-4 into lp:launchpad with lp:~lifeless/launchpad/bug-717394-3 as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  #722429 bugs with milestones trigger just in time lookups in bug searches
  https://bugs.launchpad.net/bugs/722429

For more details, see:
https://code.launchpad.net/~lifeless/launchpad/bug-717394-4/+merge/50548

Bug searches were doing lazy evaluation of bug milestones during rendering the mouseover text. This is a problem in milestone heavy environments. Eager load the milestones for the current batch.
-- 
https://code.launchpad.net/~lifeless/launchpad/bug-717394-4/+merge/50548
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/launchpad/bug-717394-4 into lp:launchpad.
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml	2011-02-18 08:01:09 +0000
+++ lib/lp/bugs/configure.zcml	2011-02-21 04:27:58 +0000
@@ -193,6 +193,7 @@
                     date_fix_released
                     date_left_closed
                     date_closed
+                    milestoneID
                     productseriesID
                     task_age
                     bug_subscribers

=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py	2011-02-21 04:27:58 +0000
+++ lib/lp/bugs/interfaces/bugtask.py	2011-02-21 04:27:58 +0000
@@ -477,6 +477,7 @@
         readonly=True,
         vocabulary='Milestone',
         schema=Interface)) # IMilestone
+    milestoneID = Attribute('The id of the milestone.')
 
     # XXX kiko 2006-03-23:
     # The status and importance's vocabularies do not

=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py	2011-02-21 04:27:58 +0000
+++ lib/lp/bugs/model/bugtask.py	2011-02-21 04:27:58 +0000
@@ -145,7 +145,10 @@
     IDistroSeries,
     IDistroSeriesSet,
     )
-from lp.registry.interfaces.milestone import IProjectGroupMilestone
+from lp.registry.interfaces.milestone import (
+    IMilestoneSet,
+    IProjectGroupMilestone,
+    )
 from lp.registry.interfaces.person import (
     IPerson,
     validate_person,
@@ -1559,6 +1562,13 @@
             SpecificationBug.bugID.is_in(bug_ids)))
         bug_ids_with_branches = set(IStore(BugBranch).find(
                 BugBranch.bugID, BugBranch.bugID.is_in(bug_ids)))
+        # Badging looks up milestones too : eager load into the storm cache.
+        milestoneset = getUtility(IMilestoneSet)
+        # And trigger a load:
+        milestone_ids = set(map(attrgetter('milestoneID'), bugtasks))
+        milestone_ids.discard(None)
+        if milestone_ids:
+            list(milestoneset.getByIds(milestone_ids))
 
         # Check if the bugs are cached. If not, cache all uncached bugs
         # at once to avoid one query per bugtask. We could rely on the

=== modified file 'lib/lp/registry/browser/tests/test_milestone.py'
--- lib/lp/registry/browser/tests/test_milestone.py	2011-02-13 23:02:49 +0000
+++ lib/lp/registry/browser/tests/test_milestone.py	2011-02-21 04:27:58 +0000
@@ -234,12 +234,14 @@
     def test_bugtasks_queries(self):
         # The view.bugtasks attribute will make four queries:
         #  1. Load bugtasks and bugs.
-        #  2. Load assignees (Person, Account, and EmailAddress).
-        #  3. Load links to specifications.
-        #  4. Load links to branches.
+        #  2. Loads the target (sourcepackagename / product)
+        #  3. Load assignees (Person, Account, and EmailAddress).
+        #  4. Load links to specifications.
+        #  5. Load links to branches.
+        #  6. Loads milestones
         bugtask_count = 10
         self.assert_bugtasks_query_count(
-            self.milestone, bugtask_count, query_limit=6)
+            self.milestone, bugtask_count, query_limit=7)
 
     def test_milestone_eager_loading(self):
         # Verify that the number of queries does not increase with more
@@ -260,7 +262,7 @@
         # is very large already, if the test fails due to such a change,
         # please cut some more of the existing fat out of it rather than
         # increasing the cap.
-        page_query_limit = 34
+        page_query_limit = 35
         product = self.factory.makeProduct()
         login_person(product.owner)
         milestone = self.factory.makeMilestone(
@@ -359,9 +361,10 @@
         #  3. Load assignees (Person, Account, and EmailAddress).
         #  4. Load links to specifications.
         #  5. Load links to branches.
+        #  6. Loads milestones.
         bugtask_count = 10
         self.assert_bugtasks_query_count(
-            self.milestone, bugtask_count, query_limit=6)
+            self.milestone, bugtask_count, query_limit=7)
 
     def test_milestone_eager_loading(self):
         # Verify that the number of queries does not increase with more
@@ -419,17 +422,19 @@
         #  2. Check if the user is in the admin team.
         #  3. Check if the user is in the owner of the admin team.
         #  4. Load bugtasks and bugs.
-        #  5. Load assignees (Person, Account, and EmailAddress).
-        #  6. Load links to specifications.
-        #  7. Load links to branches.
+        #  5. load the source package names.
+        #  6. Load assignees (Person, Account, and EmailAddress).
+        #  7. Load links to specifications.
+        #  8. Load links to branches.
+        #  9. Load links to milestones.
         bugtask_count = 10
         self.assert_bugtasks_query_count(
-            self.milestone, bugtask_count, query_limit=9)
+            self.milestone, bugtask_count, query_limit=10)
 
     def test_milestone_eager_loading(self):
         # Verify that the number of queries does not increase with more
         # bugs with different assignees.
-        query_limit = 33
+        query_limit = 34
         self.add_bug(3)
         self.assert_milestone_page_query_count(
             self.milestone, query_limit=query_limit)

=== modified file 'lib/lp/registry/interfaces/milestone.py'
--- lib/lp/registry/interfaces/milestone.py	2011-01-21 08:12:29 +0000
+++ lib/lp/registry/interfaces/milestone.py	2011-02-21 04:27:58 +0000
@@ -229,6 +229,9 @@
         NotFoundError will be raised.
         """
 
+    def getByIds(milestoneids):
+        """Get the milestones for milestoneids."""
+
     def getByNameAndProduct(name, product, default=None):
         """Get a milestone by its name and product.
 

=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py	2011-01-21 08:12:29 +0000
+++ lib/lp/registry/model/milestone.py	2011-02-21 04:27:58 +0000
@@ -37,6 +37,7 @@
     SQLBase,
     sqlvalues,
     )
+from canonical.launchpad.interfaces.lpstorm import IStore
 from canonical.launchpad.webapp.sorting import expand_numbers
 from lp.app.errors import NotFoundError
 from lp.blueprints.model.specification import Specification
@@ -257,11 +258,16 @@
 
     def get(self, milestoneid):
         """See lp.registry.interfaces.milestone.IMilestoneSet."""
-        try:
-            return Milestone.get(milestoneid)
-        except SQLObjectNotFound:
+        result = list(self.getByIds([milestoneid]))
+        if not result:
             raise NotFoundError(
                 "Milestone with ID %d does not exist" % milestoneid)
+        return result[0]
+
+    def getByIds(self, milestoneids):
+        """See `IMilestoneSet`."""
+        return IStore(Milestone).find(Milestone,
+            Milestone.id.is_in(milestoneids))
 
     def getByNameAndProduct(self, name, product, default=None):
         """See lp.registry.interfaces.milestone.IMilestoneSet."""

=== modified file 'lib/lp/registry/tests/test_milestone.py'
--- lib/lp/registry/tests/test_milestone.py	2011-01-12 18:05:13 +0000
+++ lib/lp/registry/tests/test_milestone.py	2011-02-21 04:27:58 +0000
@@ -5,6 +5,7 @@
 
 __metaclass__ = type
 
+from operator import attrgetter
 import unittest
 
 from zope.component import getUtility
@@ -53,6 +54,17 @@
         self.assertEqual(milestone_set.get(1).id, 1)
         self.assertRaises(NotFoundError, milestone_set.get, 100000)
 
+    def testMilestoneSetGetIDs(self):
+        """Test of MilestoneSet.getByIds()"""
+        milestone_set = getUtility(IMilestoneSet)
+        milestones = milestone_set.getByIds([1,3])
+        ids = sorted(map(attrgetter('id'), milestones))
+        self.assertEqual([1, 3], ids)
+
+    def testMilestoneSetGetByIDs_ignores_missing(self):
+        milestone_set = getUtility(IMilestoneSet)
+        self.assertEqual([], list(milestone_set.getByIds([100000])))
+
     def testMilestoneSetGetByNameAndProduct(self):
         """Test of MilestoneSet.getByNameAndProduct()"""
         firefox = getUtility(IProductSet).getByName('firefox')