← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/bugtaskflat-by-default-3 into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/bugtaskflat-by-default-3 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/bugtaskflat-by-default-3/+merge/105445

This branch ports a couple of things away from BugSet.searchAsUser, allowing it to be deleted. It was the second-last holdout for non-BugTaskFlat get_bug_privacy_filter usage.

latest_bugs gets replaced with a getMostRecentlyReportedBugs similar to getMostRecentlyFixedBugs, with its definition slightly changed: it's now the 5 distinct bugs with the most recently created tasks, rather than the 5 most recently created bugs. bug_count gets replaced with a SELECT MAX(id) FROM Bug, sufficiently similar that nobody should notice the different. Both are satisfiable far more rapidly than the queries they replace, and they let us remove deprecated non-BugTaskFlat code.

I also simplified getMostRecentlyFixedBugs while I was there. It previously filtered on date_closed with backoff until it found enough bugs, but with BugTaskFlat it's now fast enough to sort without a filter.
-- 
https://code.launchpad.net/~wgrant/launchpad/bugtaskflat-by-default-3/+merge/105445
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/bugtaskflat-by-default-3 into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bug.py'
--- lib/lp/bugs/browser/bug.py	2012-05-02 04:44:01 +0000
+++ lib/lp/bugs/browser/bug.py	2012-05-11 07:07:20 +0000
@@ -25,10 +25,6 @@
     'MaloneView',
     ]
 
-from datetime import (
-    datetime,
-    timedelta,
-    )
 from email.MIMEMultipart import MIMEMultipart
 from email.MIMEText import MIMEText
 import re
@@ -45,7 +41,6 @@
     )
 from lazr.restful.interface import copy_field
 from lazr.restful.interfaces import IJSONRequestCache
-import pytz
 from simplejson import dumps
 from zope import formlib
 from zope.app.form.browser import TextWidget
@@ -105,10 +100,7 @@
 from lp.services.librarian.browser import ProxiedLibraryFileAlias
 from lp.services.mail.mailwrapper import MailWrapper
 from lp.services.propertycache import cachedproperty
-from lp.services.searchbuilder import (
-    any,
-    greater_than,
-    )
+from lp.services.searchbuilder import any
 from lp.services.webapp import (
     canonical_url,
     ContextMenu,
@@ -425,39 +417,19 @@
         else:
             return self.request.response.redirect(canonical_url(bug))
 
-    def getMostRecentlyFixedBugs(self, limit=5, when=None):
-        """Return the ten most recently fixed bugs."""
-        if when is None:
-            when = datetime.now(pytz.timezone('UTC'))
-        date_closed_limits = [
-            timedelta(days=1),
-            timedelta(days=7),
-            timedelta(days=30),
-            None,
-        ]
-        for date_closed_limit in date_closed_limits:
-            fixed_bugs = []
-            search_params = BugTaskSearchParams(
-                self.user, status=BugTaskStatus.FIXRELEASED,
-                orderby='-date_closed')
-            if date_closed_limit is not None:
-                search_params.date_closed = greater_than(
-                    when - date_closed_limit)
-            fixed_bugtasks = self.context.searchTasks(search_params)
-            # XXX: Bjorn Tillenius 2006-12-13:
-            #      We might end up returning less than :limit: bugs, but in
-            #      most cases we won't, and '4*limit' is here to prevent
-            #      this page from timing out in production. Later I'll fix
-            #      this properly by selecting bugs instead of bugtasks.
-            #      If fixed_bugtasks isn't sliced, it will take a long time
-            #      to iterate over it, even over just 10, because
-            #      Transaction.iterSelect() listifies the result.
-            for bugtask in fixed_bugtasks[:4 * limit]:
-                if bugtask.bug not in fixed_bugs:
-                    fixed_bugs.append(bugtask.bug)
-                    if len(fixed_bugs) >= limit:
-                        return fixed_bugs
-        return fixed_bugs
+    def getMostRecentlyFixedBugs(self, limit=5):
+        """Return the five most recently fixed bugs."""
+        params = BugTaskSearchParams(
+            self.user, status=BugTaskStatus.FIXRELEASED,
+            orderby='-date_closed')
+        return getUtility(IBugSet).getDistinctBugsForBugTasks(
+            self.context.searchTasks(params), self.user, limit)
+
+    def getMostRecentlyReportedBugs(self, limit=5):
+        """Return the five most recently reported bugs."""
+        params = BugTaskSearchParams(self.user, orderby='-datecreated')
+        return getUtility(IBugSet).getDistinctBugsForBugTasks(
+            self.context.searchTasks(params), self.user, limit)
 
     def getCveBugLinkCount(self):
         """Return the number of links between bugs and CVEs there are."""

=== removed file 'lib/lp/bugs/browser/tests/recently-fixed-bugs.txt'
--- lib/lp/bugs/browser/tests/recently-fixed-bugs.txt	2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/browser/tests/recently-fixed-bugs.txt	1970-01-01 00:00:00 +0000
@@ -1,104 +0,0 @@
-= Displaying recently fixed bugs =
-
-On the Bugs frontpage, the 5 most recently fixed bugs are displayed.
-There's a view class called MaloneView that helps getting the list of
-bugs.
-
-    >>> from lp.bugs.browser.bug import MaloneView
-    >>> class TestMaloneView(MaloneView):
-    ...     """Test version of MaloneView.
-    ...
-    ...     Override the user attribute, so we don't need the component
-    ...     architecture set up.
-    ...     """
-    ...     user = None
-
-MaloneView.getMostRecentlyFixedBugs() calls the context's searchTasks()
-method, getting all the FIXRELEASED bugs, ordered by date_closed, with
-the newly closed bugs first.
-
-    >>> class FakeBugTask:
-    ...     def __init__(self):
-    ...         self.bug = object()
-
-    >>> class FakeContext:
-    ...     def searchTasks(self, search_params):
-    ...         print "status: %s" % search_params.status.name
-    ...         print "order by: %s" % search_params.orderby
-    ...         return [FakeBugTask() for dummy in range(5)]
-    >>> view = TestMaloneView(FakeContext(), None)
-    >>> view.user = None
-    >>> fixed_bugs = view.getMostRecentlyFixedBugs()
-    status: FIXRELEASED
-    order by: -date_closed
-    >>> len(fixed_bugs)
-    5
-
-Searching through all the bug tasks can be quite slow. It's much faster
-(compare 4 seconds to less than 100 ms) to limit the search, so that
-only the very recently closed bugs are considered. It starts with
-searching for bugs that have been closed during the last day.
-
-    >>> import pytz
-    >>> from datetime import datetime, timedelta
-    >>> utc_now = datetime(2008, 8, 31, 12, 0, 0, tzinfo=pytz.timezone('UTC'))
-    >>> class LimitByDateClosedContext:
-    ...     def __init__(self, date_closed_limit):
-    ...         self.date_closed_limit = date_closed_limit
-    ...     def searchTasks(self, search_params):
-    ...         print "Searching for bugs with date_closed: %s" % (
-    ...             search_params.date_closed)
-    ...         if (search_params.date_closed is not None and
-    ...             search_params.date_closed.value < self.date_closed_limit):
-    ...             return [FakeBugTask() for dummy in range(5)]
-    ...         else:
-    ...             return [FakeBugTask() for dummy in range(4)]
-    >>> view = TestMaloneView(LimitByDateClosedContext(utc_now), None)
-    >>> fixed_bugs = view.getMostRecentlyFixedBugs(when=utc_now)
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 30, 12, 0, tzinfo=<UTC>))
-
-Since we can't guarantee that enough bugs have been closed during a
-certain time period, the search is limited with regard to date_closed
-using increasing ranges. If not enough bugs have been closed during the
-last day, the search is increased to a week.
-
-    >>> view = TestMaloneView(
-    ...    LimitByDateClosedContext(utc_now-timedelta(days=2)), None)
-    >>> fixed_bugs = view.getMostRecentlyFixedBugs(when=utc_now)
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 30, 12, 0, tzinfo=<UTC>))
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 24, 12, 0, tzinfo=<UTC>))
-
-If not enough bugs are closed during the last week, the search is
-extended to the last 30 days.
-
-    >>> view = TestMaloneView(
-    ...    LimitByDateClosedContext(utc_now-timedelta(days=8)), None)
-    >>> fixed_bugs = view.getMostRecentlyFixedBugs(when=utc_now)
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 30, 12, 0, tzinfo=<UTC>))
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 24, 12, 0, tzinfo=<UTC>))
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 1, 12, 0, tzinfo=<UTC>))
-
-And finally, if not enough bugs have been closed during the last 30
-days, the date_closed limit is removed.
-
-    >>> view = TestMaloneView(
-    ...    LimitByDateClosedContext(utc_now-timedelta(days=31)), None)
-    >>> fixed_bugs = view.getMostRecentlyFixedBugs(when=utc_now)
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 30, 12, 0, tzinfo=<UTC>))
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 24, 12, 0, tzinfo=<UTC>))
-    Searching for bugs with date_closed:
-        greater_than(datetime.datetime(2008, 8, 1, 12, 0, tzinfo=<UTC>))
-    Searching for bugs with date_closed: None
-
-It doesn't matter that less than 4 bugs were returned.
-
-    >>> len(fixed_bugs)
-    4

=== removed file 'lib/lp/bugs/browser/tests/test_recentlyfixedbugs.py'
--- lib/lp/bugs/browser/tests/test_recentlyfixedbugs.py	2011-12-22 05:09:10 +0000
+++ lib/lp/bugs/browser/tests/test_recentlyfixedbugs.py	1970-01-01 00:00:00 +0000
@@ -1,19 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Test harness for running the recently-fixed-bugs.txt tests."""
-
-__metaclass__ = type
-
-__all__ = []
-
-import unittest
-
-from lp.testing.systemdocs import LayeredDocFileSuite
-
-
-def test_suite():
-    suite = unittest.TestSuite()
-    test = LayeredDocFileSuite('recently-fixed-bugs.txt')
-    suite.addTest(test)
-    return suite

=== modified file 'lib/lp/bugs/doc/bug.txt'
--- lib/lp/bugs/doc/bug.txt	2012-05-02 12:45:53 +0000
+++ lib/lp/bugs/doc/bug.txt	2012-05-11 07:07:20 +0000
@@ -172,44 +172,6 @@
 security proxied, which doesn't make sense to test here.)
 
 
-Searching for Bugs
-------------------
-
-To search for bugs matching specific criteria, use IBugSet.searchAsUser:
-
-    >>> from lp.services.database.sqlbase import flush_database_updates
-    >>> from lp.services.webapp.interfaces import ILaunchBag
-
-    >>> def current_user():
-    ...     return getUtility(ILaunchBag).user
-
-    >>> login("foo.bar@xxxxxxxxxxxxx")
-
-    >>> from lp.registry.interfaces.product import IProductSet
-    >>> productset = getUtility(IProductSet)
-    >>> firefox = productset.get(4)
-    >>> firefox_test_bug = bugset.get(3)
-    >>> firefox_master_bug = factory.makeBug(product=firefox)
-    >>> firefox_test_bug.markAsDuplicate(firefox_master_bug)
-    >>> flush_database_updates()
-
-    >>> dups_of_bug_six = bugset.searchAsUser(
-    ...     duplicateof=firefox_master_bug, user=current_user())
-    >>> print dups_of_bug_six.count()
-    1
-    >>> dups_of_bug_six[0].id
-    3
-
-    >>> firefox_test_bug.markAsDuplicate(None)
-    >>> flush_database_updates()
-    >>> dups_of_bug_six = bugset.searchAsUser(
-    ...     duplicateof=firefox_crashes, user=current_user())
-    >>> print dups_of_bug_six.count()
-    0
-
-    >>> login(ANONYMOUS)
-
-
 Absolute URLs
 -------------
 
@@ -217,6 +179,7 @@
 include a URL to the bug inside the email.
 
     >>> from lp.services.webapp import canonical_url
+    >>> login(ANONYMOUS)
     >>> print canonical_url(firefox_crashes)
     http://.../bugs/6
 
@@ -235,6 +198,11 @@
 For the purposes of demonstration, we'll make the firefox crashing bug
 private. A bug cannot be made private by an anonymous user.
 
+    >>> from lp.services.webapp.interfaces import ILaunchBag
+    
+    >>> def current_user():
+    ...     return getUtility(ILaunchBag).user
+
     >>> firefox_crashes.private = True
     Traceback (most recent call last):
       ...
@@ -316,14 +284,18 @@
 Note that a search will return all public bugs, omitting bug 14 which is
 private:
 
+    >>> from lp.bugs.interfaces.bugtask import (
+    ...     IBugTaskSet, BugTaskSearchParams)
+    >>> from lp.bugs.model.bug import Bug
     >>> from lp.services.database.lpstorm import IStore
-    >>> from lp.bugs.model.bug import Bug
+
     >>> all_bugs = set(IStore(Bug).find(Bug).values(Bug.id))
 
+    >>> taskset = getUtility(IBugTaskSet)
     >>> def hidden_bugs():
     ...     found_bugs = set(
-    ...         bug.id for bug in bugset.searchAsUser(
-    ...             user=current_user()))
+    ...         task.bug.id for task in taskset.search(
+    ...             BugTaskSearchParams(current_user())))
     ...     return sorted(all_bugs - found_bugs)
 
     >>> login("test@xxxxxxxxxxxxx")
@@ -424,6 +396,9 @@
 
 When a public bug is filed:
 
+    >>> from lp.registry.interfaces.product import IProductSet
+    >>> productset = getUtility(IProductSet)
+    >>> firefox = productset.get(4)
     >>> foobar = personset.getByEmail('foo.bar@xxxxxxxxxxxxx')
     >>> params = CreateBugParams(
     ...     title="test firefox bug", comment="blah blah blah", owner=foobar)
@@ -855,7 +830,7 @@
 
     >>> print firefox_bug.duplicateof
     None
-    >>> firefox_bug.markAsDuplicate(firefox_master_bug)
+    >>> firefox_bug.markAsDuplicate(factory.makeBug())
 
     >>> bug_duplicateof_changed = ObjectModifiedEvent(
     ...     firefox_bug, bug_before_modification, ["duplicateof"])

=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py	2012-05-11 00:39:47 +0000
+++ lib/lp/bugs/interfaces/bug.py	2012-05-11 07:07:20 +0000
@@ -1199,13 +1199,6 @@
         If it can't be found, NotFoundError will be raised.
         """
 
-    def searchAsUser(user, duplicateof=None, orderBy=None, limit=None):
-        """Find bugs matching the search criteria provided.
-
-        To search as an anonymous user, the user argument passed
-        should be None.
-        """
-
     def queryByRemoteBug(bugtracker, remotebug):
         """Find one or None bugs for the BugWatch and bug tracker.
 

=== modified file 'lib/lp/bugs/interfaces/malone.py'
--- lib/lp/bugs/interfaces/malone.py	2012-04-26 23:21:35 +0000
+++ lib/lp/bugs/interfaces/malone.py	2012-05-11 07:07:20 +0000
@@ -45,7 +45,6 @@
         "products and distributions")
     bugtracker_count = Attribute("The number of bug trackers in Launchpad")
     top_bugtrackers = Attribute("The BugTrackers with the most watches.")
-    latest_bugs = Attribute("The latest 5 bugs filed.")
 
     @collection_default_content()
     def empty_list():

=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py	2012-05-11 00:39:47 +0000
+++ lib/lp/bugs/model/bug.py	2012-05-11 07:07:20 +0000
@@ -2050,7 +2050,7 @@
 
         If bug privacy rights are changed here, corresponding changes need
         to be made to the queries which screen for privacy.  See
-        Bug.searchAsUser and BugTask.get_bug_privacy_filter_with_decorator.
+        bugtasksearch's get_bug_privacy_filter.
         """
         from lp.bugs.interfaces.bugtask import BugTaskSearchParams
 
@@ -2667,27 +2667,6 @@
                     "Unable to locate bug with nickname %s." % bugid)
         return bug
 
-    def searchAsUser(self, user, duplicateof=None, orderBy=None, limit=None):
-        """See `IBugSet`."""
-        from lp.bugs.model.bugtasksearch import get_bug_privacy_filter
-
-        where_clauses = []
-        if duplicateof:
-            where_clauses.append("Bug.duplicateof = %d" % duplicateof.id)
-
-        privacy_filter = get_bug_privacy_filter(user)
-        if privacy_filter:
-            where_clauses.append(privacy_filter)
-
-        other_params = {}
-        if orderBy:
-            other_params['orderBy'] = orderBy
-        if limit:
-            other_params['limit'] = limit
-
-        return Bug.select(
-            ' AND '.join(where_clauses), **other_params)
-
     def queryByRemoteBug(self, bugtracker, remotebug):
         """See `IBugSet`."""
         bug = Bug.selectFirst("""

=== modified file 'lib/lp/bugs/templates/malone-index.pt'
--- lib/lp/bugs/templates/malone-index.pt	2011-05-26 22:12:51 +0000
+++ lib/lp/bugs/templates/malone-index.pt	2012-05-11 07:07:20 +0000
@@ -59,7 +59,7 @@
         <div class="portlet">
           <h2>Recently reported</h2>
           <ul id="reported-bugs">
-            <li tal:repeat="bug context/latest_bugs"
+            <li tal:repeat="bug view/getMostRecentlyReportedBugs"
                 tal:replace="structure bug/@@+listing-detailed" />
           </ul>
         </div>

=== modified file 'lib/lp/systemhomes.py'
--- lib/lp/systemhomes.py	2012-04-26 23:21:35 +0000
+++ lib/lp/systemhomes.py	2012-05-11 07:07:20 +0000
@@ -21,6 +21,7 @@
 
 from lazr.restful import ServiceRootResource
 from lazr.restful.interfaces import ITopLevelEntryLink
+from storm.expr import Max
 from zope.component import getUtility
 from zope.interface import implements
 
@@ -40,6 +41,7 @@
     IMaloneApplication,
     IPrivateMaloneApplication,
     )
+from lp.bugs.model.bug import Bug
 from lp.code.interfaces.codehosting import (
     IBazaarApplication,
     ICodehostingApplication,
@@ -67,6 +69,7 @@
     IProductSet,
     )
 from lp.services.config import config
+from lp.services.database.lpstorm import IStore
 from lp.services.feeds.interfaces.application import IFeedsApplication
 from lp.services.statistics.interfaces.statistic import ILaunchpadStatisticSet
 from lp.services.webapp.interfaces import (
@@ -146,8 +149,7 @@
 
     @property
     def bug_count(self):
-        user = getUtility(ILaunchBag).user
-        return getUtility(IBugSet).searchAsUser(user=user).count()
+        return IStore(Bug).find(Max(Bug.id)).one()
 
     @property
     def bugwatch_count(self):
@@ -175,12 +177,6 @@
     def top_bugtrackers(self):
         return getUtility(IBugTrackerSet).getMostActiveBugTrackers(limit=5)
 
-    @property
-    def latest_bugs(self):
-        user = getUtility(ILaunchBag).user
-        return getUtility(IBugSet).searchAsUser(
-            user=user, orderBy=['-datecreated'], limit=5)
-
     def empty_list(self):
         """See `IMaloneApplication`."""
         return []


Follow ups