launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04849
[Merge] lp:~gary/launchpad/bug811447 into lp:launchpad
Gary Poster has proposed merging lp:~gary/launchpad/bug811447 into lp:launchpad with lp:~gary/launchpad/bug838869 as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #811447 in Launchpad itself: "BugTask:+index timeout - death by sql in PersonSubscriptions(user, bug)"
https://bugs.launchpad.net/launchpad/+bug/811447
For more details, see:
https://code.launchpad.net/~gary/launchpad/bug811447/+merge/73704
This branch fixes bug 811447 by drastically reducing the number of SQL needed for users who are team administrators and view a bug with many duplicates to which one or more of their teams is subscribed. It now takes a single query. This is done by converting the resultset of administrated teams to a list of administrated team ids, and then that list is consulted, rather than the result set.
Lint is happy.
This depends on lp:~gary/launchpad/bug838869 for a tool I used as test fixture.
Thank you.
--
https://code.launchpad.net/~gary/launchpad/bug811447/+merge/73704
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~gary/launchpad/bug811447 into lp:launchpad.
=== modified file 'lib/lp/bugs/model/personsubscriptioninfo.py'
--- lib/lp/bugs/model/personsubscriptioninfo.py 2011-05-17 12:49:03 +0000
+++ lib/lp/bugs/model/personsubscriptioninfo.py 2011-09-01 17:23:38 +0000
@@ -59,9 +59,9 @@
implements(IAbstractSubscriptionInfoCollection)
- def __init__(self, person, administrated_teams):
+ def __init__(self, person, administrated_team_ids):
self.person = person
- self.administrated_teams = administrated_teams
+ self.administrated_team_ids = administrated_team_ids
self.personal = []
self.as_team_member = []
self.as_team_admin = []
@@ -72,7 +72,7 @@
collection = self.personal
else:
assert principal.isTeam(), (principal, self.person)
- if principal in self.administrated_teams:
+ if principal.id in self.administrated_team_ids:
collection = self.as_team_admin
else:
collection = self.as_team_member
@@ -88,9 +88,9 @@
implements(IVirtualSubscriptionInfoCollection)
- def __init__(self, person, administrated_teams):
+ def __init__(self, person, administrated_team_ids):
super(VirtualSubscriptionInfoCollection, self).__init__(
- person, administrated_teams)
+ person, administrated_team_ids)
self._principal_pillar_to_info = {}
def _add_item_to_collection(self,
@@ -110,9 +110,9 @@
implements(IRealSubscriptionInfoCollection)
- def __init__(self, person, administrated_teams):
+ def __init__(self, person, administrated_team_ids):
super(RealSubscriptionInfoCollection, self).__init__(
- person, administrated_teams)
+ person, administrated_team_ids)
self._principal_bug_to_infos = {}
def _add_item_to_collection(self, collection, principal,
@@ -189,13 +189,13 @@
TeamParticipation.teamID == Person.id)
direct = RealSubscriptionInfoCollection(
- self.person, self.administrated_teams)
+ self.person, self.administrated_team_ids)
duplicates = RealSubscriptionInfoCollection(
- self.person, self.administrated_teams)
+ self.person, self.administrated_team_ids)
bugs = set()
for subscription, subscribed_bug, subscriber in info:
bugs.add(subscribed_bug)
- if subscribed_bug != bug:
+ if subscribed_bug.id != bug.id:
# This is a subscription through a duplicate.
collection = duplicates
else:
@@ -232,7 +232,8 @@
def loadSubscriptionsFor(self, person, bug):
self.person = person
- self.administrated_teams = person.getAdministratedTeams()
+ self.administrated_team_ids = [
+ team.id for team in person.getAdministratedTeams()]
self.bug = bug
# First get direct and duplicate real subscriptions.
@@ -244,9 +245,9 @@
# Then get owner and assignee virtual subscriptions.
as_owner = VirtualSubscriptionInfoCollection(
- self.person, self.administrated_teams)
+ self.person, self.administrated_team_ids)
as_assignee = VirtualSubscriptionInfoCollection(
- self.person, self.administrated_teams)
+ self.person, self.administrated_team_ids)
for bugtask in bug.bugtasks:
pillar = self._getTaskPillar(bugtask)
owner = pillar.owner
@@ -301,7 +302,7 @@
}
direct = {}
from_duplicate = {}
- as_owner = {} # This is an owner of a pillar with no bug supervisor.
+ as_owner = {} # This is an owner of a pillar with no bug supervisor.
as_assignee = {}
subscription_data = {
'direct': direct,
=== modified file 'lib/lp/bugs/model/tests/test_personsubscriptioninfo.py'
--- lib/lp/bugs/model/tests/test_personsubscriptioninfo.py 2011-05-17 12:49:03 +0000
+++ lib/lp/bugs/model/tests/test_personsubscriptioninfo.py 2011-09-01 17:23:38 +0000
@@ -5,8 +5,10 @@
__metaclass__ = type
+from testtools.matchers import LessThan
from zope.security.proxy import removeSecurityProxy
+from canonical.launchpad.webapp.adapter import SQLLogger
from canonical.testing import DatabaseFunctionalLayer
from lp.bugs.interfaces.personsubscriptioninfo import (
IRealSubscriptionInfo,
@@ -33,6 +35,20 @@
self.bug = self.factory.makeBug()
self.subscriptions = PersonSubscriptions(self.subscriber, self.bug)
+ def makeDuplicates(self, count=1, subscriber=None):
+ if subscriber is None:
+ subscriber = self.subscriber
+ if subscriber.isTeam():
+ subscribed_by = subscriber.teamowner
+ else:
+ subscribed_by = subscriber
+ duplicates = [self.factory.makeBug() for i in range(count)]
+ with person_logged_in(subscribed_by):
+ for duplicate in duplicates:
+ duplicate.markAsDuplicate(self.bug)
+ duplicate.subscribe(subscriber, subscribed_by)
+ return duplicates
+
def assertCollectionsAreEmpty(self, except_=None):
names = ('direct', 'from_duplicate', 'as_owner', 'as_assignee')
assert except_ is None or except_ in names
@@ -284,10 +300,7 @@
def test_duplicate_direct(self):
# Subscribed directly to the duplicate bug.
- duplicate = self.factory.makeBug()
- with person_logged_in(self.subscriber):
- duplicate.markAsDuplicate(self.bug)
- duplicate.subscribe(self.subscriber, self.subscriber)
+ [duplicate] = self.makeDuplicates(count=1)
# Load a `PersonSubscriptionInfo`s for subscriber and a bug.
self.subscriptions.reload()
@@ -487,3 +500,36 @@
self.subscriptions.reload()
self.failUnless(self.subscriptions.muted)
+
+ def test_many_duplicate_team_admin_subscriptions_few_queries(self):
+ # This is related to bug 811447. The user is subscribed to a
+ # duplicate bug through team membership in which the user is an admin.
+ team = self.factory.makeTeam()
+ with person_logged_in(team.teamowner):
+ team.addMember(self.subscriber, team.teamowner,
+ status=TeamMembershipStatus.ADMIN)
+ self.makeDuplicates(count=1, subscriber=team)
+ logger = SQLLogger()
+ with logger:
+ self.subscriptions.reload()
+ # This should produce a very small number of queries.
+ count_with_one_subscribed_duplicate = len(logger.queries)
+ self.assertThat(count_with_one_subscribed_duplicate, LessThan(5))
+ # It should have the correct result.
+ self.assertCollectionsAreEmpty(except_='from_duplicate')
+ self.assertCollectionContents(
+ self.subscriptions.from_duplicate, as_team_admin=1)
+ # If we increase the number of duplicates subscribed via the team that
+ # the user administers...
+ self.makeDuplicates(count=4, subscriber=team)
+ with logger:
+ self.subscriptions.reload()
+ # ...then the query count should remain the same.
+ count_with_five_subscribed_duplicates = len(logger.queries)
+ self.assertEqual(
+ count_with_one_subscribed_duplicate,
+ count_with_five_subscribed_duplicates)
+ # We should still have the correct result.
+ self.assertCollectionsAreEmpty(except_='from_duplicate')
+ self.assertCollectionContents(
+ self.subscriptions.from_duplicate, as_team_admin=5)