Merge lp:~deryck/launchpad/buglistings-preload-people-901122 into lp:launchpad

Proposed by Deryck Hodge on 2012-03-12
Status: Merged
Approved by: Aaron Bentley on 2012-03-19
Approved revision: no longer in the source branch.
Merged at revision: 14983
Proposed branch: lp:~deryck/launchpad/buglistings-preload-people-901122
Merge into: lp:launchpad
Diff against target: 284 lines (+83/-24)
7 files modified
lib/lp/bugs/browser/bugtask.py (+13/-5)
lib/lp/bugs/browser/cvereport.py (+5/-3)
lib/lp/bugs/browser/tests/test_bugtask.py (+26/-11)
lib/lp/bugs/interfaces/bugtask.py (+6/-0)
lib/lp/bugs/model/bugtask.py (+13/-0)
lib/lp/registry/browser/milestone.py (+13/-2)
lib/lp/registry/browser/tests/test_milestone.py (+7/-3)
To merge this branch: bzr merge lp:~deryck/launchpad/buglistings-preload-people-901122
Reviewer Review Type Date Requested Status
Aaron Bentley (community) 2012-03-12 Approve on 2012-03-19
Review via email: mp+97041@code.launchpad.net

Commit Message

Add getBugTaskPeople method to IBugTaskSet to help with pre-loading bugtask-related people objects.

Description of the Change

This branch updates IBugTaskSet to provide a new method for getting people related to a set of bugtasks. This is useful in the new buglistings work so that we can show assignee and bug reporter without doing a lot more queries.

The bulk of the work is to add this helper method that gets all related people in one query. Then the call sites that deal with bugtask people are updated to make use of this. A few tests also had to be updated.

I did not do a pre-imp call, since this work mirrors the earlier pre-loading work I did. The same pattern is followed here as when I loaded tags, which followed the pattern used for loading bug badge properties.

I did do a bit more of a refactor to the test helper method make_bug_task_listing_item, since we now needed to be conscious of both tags and people. Rather than passing in those attributes at call sites, you can now pass in the bugtask and the method uses the provided methods on IBugTaskSet to get at the related data. This cleans up the tests better and also ensures the methods on BugTaskSet are indirectly tested.

I also chose to update the milestone browser code to follow the bugtask browser code, which means milestone pages now issue two more queries than before. However, this brings the two spots together and we could now add in data about assignee, bug reporter, or tags in milestone pages, without timing out the pages.

We are lint free here, too.

To post a comment you must log in.
Deryck Hodge (deryck) wrote :

I update the branch to use IPersonSet.getPrecachedPersonsFromIDs instead of a hand crafted query, per Robert's suggestion on IRC. This produces the same query count as my original new query.

FWIW, I mentioned to Robert on IRC that I thought I had seen more queries by listing out ownerID via

[bugtask.bug.ownerID for bugtask in bugtasks]

Turns out I was seeing more queries, but not from that. When you click around a bugtask search results list, we update the URL with query parameters. When you come to the page via these query strings, that view issues a couple more queries than without the query string. But these increased counts are consistent, no matter the data size -- 2 more than without the query string. Given that, I didn't dig too much into that issue, especially since I wanted to finish this off and get it landed.

Aaron Bentley (abentley) wrote :

Looks good.

review: Approve
Robert Collins (lifeless) wrote :

I have a small question - it looks like this increases the queries for
milestone views *without* needing too (because the tests were passing
before). That smells like waste - we're doing work the user doesn't
need. Of course, I'm probably wrong, but interesting mind wants to
know :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugtask.py'
2--- lib/lp/bugs/browser/bugtask.py 2012-02-29 19:44:28 +0000
3+++ lib/lp/bugs/browser/bugtask.py 2012-03-20 12:58:20 +0000
4@@ -2161,14 +2161,15 @@
5 delegates(IBugTask, 'bugtask')
6
7 def __init__(self, bugtask, has_bug_branch,
8- has_specification, has_patch, tags, request=None,
9- target_context=None):
10+ has_specification, has_patch, tags,
11+ people, request=None, target_context=None):
12 self.bugtask = bugtask
13 self.review_action_widget = None
14 self.has_bug_branch = has_bug_branch
15 self.has_specification = has_specification
16 self.has_patch = has_patch
17 self.tags = tags
18+ self.people = people
19 self.request = request
20 self.target_context = target_context
21
22@@ -2207,8 +2208,9 @@
23 else:
24 milestone_name = None
25 assignee = None
26- if self.assignee is not None:
27- assignee = self.assignee.displayname
28+ if self.assigneeID is not None:
29+ assignee = self.people[self.assigneeID].displayname
30+ reporter = self.people[self.bug.ownerID]
31
32 base_tag_url = "%s/?field.tag=" % canonical_url(
33 self.bugtask.target,
34@@ -2227,7 +2229,7 @@
35 'importance_class': 'importance' + self.importance.name,
36 'last_updated': last_updated,
37 'milestone_name': milestone_name,
38- 'reporter': self.bug.owner.displayname,
39+ 'reporter': reporter.displayname,
40 'status': self.status.title,
41 'status_class': 'status' + self.status.name,
42 'tags': [{'url': base_tag_url + urllib.quote(tag), 'tag': tag}
43@@ -2281,6 +2283,11 @@
44 """Return a dict matching bugtask to it's tags."""
45 return getUtility(IBugTaskSet).getBugTaskTags(self.currentBatch())
46
47+ @cachedproperty
48+ def bugtask_people(self):
49+ """Return mapping of people related to this bugtask set."""
50+ return getUtility(IBugTaskSet).getBugTaskPeople(self.currentBatch())
51+
52 def getCookieName(self):
53 """Return the cookie name used in bug listings js code."""
54 cookie_name_template = '%s-buglist-fields'
55@@ -2331,6 +2338,7 @@
56 badge_property['has_specification'],
57 badge_property['has_patch'],
58 tags,
59+ self.bugtask_people,
60 request=self.request,
61 target_context=target_context)
62
63
64=== modified file 'lib/lp/bugs/browser/cvereport.py'
65--- lib/lp/bugs/browser/cvereport.py 2012-02-29 17:03:40 +0000
66+++ lib/lp/bugs/browser/cvereport.py 2012-03-20 12:58:20 +0000
67@@ -78,8 +78,9 @@
68 self.resolved_cve_bugtasks = []
69 return
70
71- badge_properties = getUtility(IBugTaskSet).getBugTaskBadgeProperties(
72- bugtasks)
73+ bugtask_set = getUtility(IBugTaskSet)
74+ badge_properties = bugtask_set.getBugTaskBadgeProperties(bugtasks)
75+ people = bugtask_set.getBugTaskPeople(bugtasks)
76
77 open_bugtaskcves = {}
78 resolved_bugtaskcves = {}
79@@ -92,7 +93,8 @@
80 has_bug_branch=badges['has_branch'],
81 has_specification=badges['has_specification'],
82 has_patch=badges['has_patch'],
83- tags=())
84+ tags=(),
85+ people=people)
86 if bugtask.status in RESOLVED_BUGTASK_STATUSES:
87 current_bugtaskcves = resolved_bugtaskcves
88 else:
89
90=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
91--- lib/lp/bugs/browser/tests/test_bugtask.py 2012-03-08 11:51:36 +0000
92+++ lib/lp/bugs/browser/tests/test_bugtask.py 2012-03-20 12:58:20 +0000
93@@ -1793,22 +1793,31 @@
94 batched_view.activity_and_comments)
95
96
97-def make_bug_task_listing_item(factory, tags=()):
98- owner = factory.makePerson()
99- bug = factory.makeBug(
100- owner=owner, private=True, security_related=True)
101- with person_logged_in(owner):
102- bugtask = bug.default_bugtask
103+def make_bug_task_listing_item(factory, bugtask=None):
104+ if bugtask is None:
105+ owner = factory.makePerson()
106+ bug = factory.makeBug(
107+ owner=owner, private=True, security_related=True)
108+ with person_logged_in(owner):
109+ bugtask = bug.default_bugtask
110+ else:
111+ owner = bugtask.bug.owner
112+ bugtask = removeSecurityProxy(bugtask)
113 bug_task_set = getUtility(IBugTaskSet)
114 bug_badge_properties = bug_task_set.getBugTaskBadgeProperties(
115 [bugtask])
116 badge_property = bug_badge_properties[bugtask]
117+ tags = bug_task_set.getBugTaskTags([bugtask])
118+ if tags != {}:
119+ tags = tags[bugtask.id]
120+ people = bug_task_set.getBugTaskPeople([bugtask])
121 return owner, BugTaskListingItem(
122 bugtask,
123 badge_property['has_branch'],
124 badge_property['has_specification'],
125 badge_property['has_patch'],
126 tags,
127+ people,
128 target_context=bugtask.target)
129
130
131@@ -2351,11 +2360,14 @@
132
133 def test_model_assignee(self):
134 """Model contains expected fields with expected values."""
135- owner, item = make_bug_task_listing_item(self.factory)
136+ assignee = self.factory.makePerson(displayname='Example Person')
137+ bug = self.factory.makeBug()
138+ with person_logged_in(bug.owner):
139+ removeSecurityProxy(bug).default_bugtask.transitionToAssignee(
140+ assignee)
141+ owner, item = make_bug_task_listing_item(
142+ self.factory, bugtask=bug.default_bugtask)
143 with person_logged_in(owner):
144- self.assertIs(None, item.model['assignee'])
145- assignee = self.factory.makePerson(displayname='Example Person')
146- item.bugtask.transitionToAssignee(assignee)
147 self.assertEqual('Example Person', item.model['assignee'])
148
149 def test_model_age(self):
150@@ -2368,8 +2380,11 @@
151
152 def test_model_tags(self):
153 """Model contains bug tags."""
154+ bug = self.factory.makeBug()
155 tags = ['tag1', 'tag2']
156- owner, item = make_bug_task_listing_item(self.factory, tags=tags)
157+ removeSecurityProxy(bug).tags = tags
158+ owner, item = make_bug_task_listing_item(
159+ self.factory, bug.default_bugtask)
160 with person_logged_in(owner):
161 self.assertEqual(2, len(item.model['tags']))
162 self.assertTrue('tag' in item.model['tags'][0].keys())
163
164=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
165--- lib/lp/bugs/interfaces/bugtask.py 2012-03-01 02:12:35 +0000
166+++ lib/lp/bugs/interfaces/bugtask.py 2012-03-20 12:58:20 +0000
167@@ -1481,6 +1481,12 @@
168 Return a dict mapping from bugtask to tag.
169 """
170
171+ def getBugTaskPeople(bugtasks):
172+ """Return a set of people related to bugtasks.
173+
174+ Return a dict mapping from Person.id to Person.
175+ """
176+
177 def getBugTaskBadgeProperties(bugtasks):
178 """Return whether the bugtasks should have badges.
179
180
181=== modified file 'lib/lp/bugs/model/bugtask.py'
182--- lib/lp/bugs/model/bugtask.py 2012-02-28 18:54:08 +0000
183+++ lib/lp/bugs/model/bugtask.py 2012-03-20 12:58:20 +0000
184@@ -43,8 +43,10 @@
185 And,
186 Join,
187 Or,
188+ Select,
189 SQL,
190 Sum,
191+ Union,
192 )
193 from storm.store import (
194 EmptyResultSet,
195@@ -1398,6 +1400,17 @@
196 tags_by_bugtask[bugtask_id].append(tag_name)
197 return dict(tags_by_bugtask)
198
199+ def getBugTaskPeople(self, bugtasks):
200+ """See `IBugTaskSet`"""
201+ # Avoid circular imports.
202+ from lp.registry.interfaces.person import IPersonSet
203+ people_ids = set(
204+ [bugtask.assigneeID for bugtask in bugtasks] +
205+ [bugtask.bug.ownerID for bugtask in bugtasks])
206+ people = getUtility(IPersonSet).getPrecachedPersonsFromIDs(people_ids)
207+ return dict(
208+ (person.id, person) for person in people)
209+
210 def getBugTaskBadgeProperties(self, bugtasks):
211 """See `IBugTaskSet`."""
212 # Import locally to avoid circular imports.
213
214=== modified file 'lib/lp/registry/browser/milestone.py'
215--- lib/lp/registry/browser/milestone.py 2012-02-29 17:03:40 +0000
216+++ lib/lp/registry/browser/milestone.py 2012-03-20 12:58:20 +0000
217@@ -251,16 +251,27 @@
218 return getUtility(IBugTaskSet).getBugTaskBadgeProperties(
219 self._bugtasks)
220
221+ @cachedproperty
222+ def _bug_task_tags(self):
223+ return getUtility(IBugTaskSet).getBugTaskTags(self._bugtasks)
224+
225+ @cachedproperty
226+ def _bug_task_people(self):
227+ """The people associated with a set of bug tasks."""
228+ return getUtility(IBugTaskSet).getBugTaskPeople(self._bugtasks)
229+
230 def _getListingItem(self, bugtask):
231 """Return a decorated bugtask for the bug listing."""
232 badge_property = self._bug_badge_properties[bugtask]
233- tags = ()
234+ tags = self._bug_task_tags.get(bugtask.id, ())
235+ people = self._bug_task_people
236 return BugTaskListingItem(
237 bugtask,
238 badge_property['has_branch'],
239 badge_property['has_specification'],
240 badge_property['has_patch'],
241- tags)
242+ tags,
243+ people)
244
245 @cachedproperty
246 def bugtasks(self):
247
248=== modified file 'lib/lp/registry/browser/tests/test_milestone.py'
249--- lib/lp/registry/browser/tests/test_milestone.py 2012-01-18 12:04:16 +0000
250+++ lib/lp/registry/browser/tests/test_milestone.py 2012-03-20 12:58:20 +0000
251@@ -288,9 +288,11 @@
252 # 4. Load links to specifications.
253 # 5. Load links to branches.
254 # 6. Loads milestones
255+ # 7. Loads tags
256+ # 8. All related people
257 bugtask_count = 10
258 self.assert_bugtasks_query_count(
259- self.milestone, bugtask_count, query_limit=7)
260+ self.milestone, bugtask_count, query_limit=9)
261
262 def test_milestone_eager_loading(self):
263 # Verify that the number of queries does not increase with more
264@@ -403,16 +405,18 @@
265 logout()
266
267 def test_bugtasks_queries(self):
268- # The view.bugtasks attribute will make five queries:
269+ # The view.bugtasks attribute will make several queries:
270 # 1. For each project in the group load all the dev focus series ids.
271 # 2. Load bugtasks and bugs.
272 # 3. Load assignees (Person, Account, and EmailAddress).
273 # 4. Load links to specifications.
274 # 5. Load links to branches.
275 # 6. Loads milestones.
276+ # 7. Loads tags.
277+ # 8. All related people.
278 bugtask_count = 10
279 self.assert_bugtasks_query_count(
280- self.milestone, bugtask_count, query_limit=7)
281+ self.milestone, bugtask_count, query_limit=9)
282
283 def test_milestone_eager_loading(self):
284 # Verify that the number of queries does not increase with more