Merge lp:~bac/launchpad/bug-799901 into lp:launchpad
- bug-799901
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Brad Crittenden |
Approved revision: | no longer in the source branch. |
Merged at revision: | 13487 |
Proposed branch: | lp:~bac/launchpad/bug-799901 |
Merge into: | lp:launchpad |
Diff against target: |
504 lines (+170/-48) 8 files modified
lib/lp/bugs/browser/bugsubscription.py (+21/-12) lib/lp/bugs/browser/tests/test_bugsubscription_views.py (+79/-12) lib/lp/bugs/doc/bugsubscription.txt (+6/-6) lib/lp/bugs/javascript/subscribers_list.js (+9/-5) lib/lp/bugs/javascript/tests/test_subscribers_list.js (+24/-3) lib/lp/bugs/model/bug.py (+3/-1) lib/lp/bugs/model/bugsubscription.py (+6/-4) lib/lp/bugs/model/tests/test_bug.py (+22/-5) |
To merge this branch: | bzr merge lp:~bac/launchpad/bug-799901 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Review via email: mp+68590@code.launchpad.net |
Commit message
[r=allenap][bug=799901] Add tooltip to bug subscribers list.
Description of the change
= Summary =
The new bug subscription work has a regression in that the tooltip showing who subscribed a person or team is missing. This branch adds that functionality back.
== Proposed fix ==
Add the tooltip for direct subscribers. The indirect subscribers (seen under "May be notified") don't have the required subscription information and were not previously decorated.
Note that I have chose to not include a tooltip when using "Subscribe someone else". Since the user just did the action it is forgiveable to not display the fact that he just did it. Adding that information would require another roundtrip to fetch the extra data.
== Pre-implementation notes ==
Chat with Gary.
== Implementation details ==
As above.
== Tests ==
bin/test -vvt test_bugsubscri
firefox lib/lp/
== Demo and Q/A ==
Go to https:/
= Launchpad lint =
Will fix this lint before landing.
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
./lib/lp/
725: Line exceeds 78 characters.
./lib/lp/
791: Line exceeds 78 characters.
Robert Collins (lifeless) wrote : | # |
Данило Шеган (danilo) wrote : | # |
Indeed, Robert is right. For that sole reason I implemented a separate method which gets all the data with one query for this page, and that method needs extending: IBug.getDirectS
It returns a ResultSet of (Person, Subscription) tuples, and should be extended to return (Person, SubscribedBy, Subscription) tuples where SubscribedBy is a ClassAlias(Person) as appropriate.
Don't worry about changing the method on the model because this is the only place it is used in.
Brad Crittenden (bac) wrote : | # |
Thanks for the suggestion Danilos. I've made the change you suggested. I did go ahead and update the model as Robert suggested just because I think it is better practice to do the comparisons the most efficient way even if the objects are cached so that someone else wouldn't pattern a new change off the inefficient approach.
I also added a test showing the query count for getting the direct subscribers is 1. This required a slight refactoring of the browser property.
Gavin Panella (allenap) wrote : | # |
Tip top :)
[1]
+ if IBug.providedBy
+ bug = self.context
+ elif IBugTask.
+ bug = self.context.bug
I know you only moved this, but I thought I'd mention that there is an
adapter from IBugTask to IBug, so the above can be written as:
bug = IBug(self.context)
[2]
+ harness = LaunchpadFormHa
Cool, I've not see that before.
[3]
+ # A subscriber_data_js returns JSON string of a list
That should probably read:
# subscriber_data_js returns a JSON string of a list
or something like that.
[4]
In the subscriber dict, assembled in direct_
subscribed_by /feels/ like it should be the person, rather than a
description. Consider doing instead something like:
}
That makes the data more generally useful, and it more closely
resembles the BugSubscription API.
Ach, it's not important, just an idea.
Данило Шеган (danilo) wrote : | # |
Another minor note from me (I've had the tab loaded, sorry :)): perhaps you can also move the display_
Otherwise, looks great!
Данило Шеган (danilo) wrote : | # |
Of course, it's too late for it now (I see it's merged already, and I haven't refreshed the tab recently). So, never mind it.
Preview Diff
1 | === modified file 'lib/lp/bugs/browser/bugsubscription.py' |
2 | --- lib/lp/bugs/browser/bugsubscription.py 2011-06-29 13:41:36 +0000 |
3 | +++ lib/lp/bugs/browser/bugsubscription.py 2011-07-21 18:09:56 +0000 |
4 | @@ -49,7 +49,6 @@ |
5 | from lp.bugs.enum import BugNotificationLevel |
6 | from lp.bugs.interfaces.bug import IBug |
7 | from lp.bugs.interfaces.bugsubscription import IBugSubscription |
8 | -from lp.bugs.interfaces.bugtask import IBugTask |
9 | from lp.bugs.model.personsubscriptioninfo import PersonSubscriptions |
10 | from lp.bugs.model.structuralsubscription import ( |
11 | get_structural_subscriptions_for_bug, |
12 | @@ -529,17 +528,19 @@ |
13 | class BugPortletSubscribersWithDetails(LaunchpadView): |
14 | """A view that returns a JSON dump of the subscriber details for a bug.""" |
15 | |
16 | - @property |
17 | - def subscriber_data_js(self): |
18 | - """Return subscriber_ids in a form suitable for JavaScript use.""" |
19 | + @cachedproperty |
20 | + def api_request(self): |
21 | + return IWebServiceClientRequest(self.request) |
22 | + |
23 | + def direct_subscriber_data(self, bug): |
24 | + """Get the direct subscriber data. |
25 | + |
26 | + This method is isolated from the subscriber_data_js so that query |
27 | + count testing can be done accurately and robustly. |
28 | + """ |
29 | data = [] |
30 | - if IBug.providedBy(self.context): |
31 | - bug = self.context |
32 | - elif IBugTask.providedBy(self.context): |
33 | - bug = self.context.bug |
34 | details = list(bug.getDirectSubscribersWithDetails()) |
35 | - api_request = IWebServiceClientRequest(self.request) |
36 | - for person, subscription in details: |
37 | + for person, subscribed_by, subscription in details: |
38 | can_edit = subscription.canBeUnsubscribedByUser(self.user) |
39 | if person == self.user or (person.private and not can_edit): |
40 | # Skip the current user viewing the page, |
41 | @@ -550,9 +551,10 @@ |
42 | 'name': person.name, |
43 | 'display_name': person.displayname, |
44 | 'web_link': canonical_url(person, rootsite='mainsite'), |
45 | - 'self_link': absoluteURL(person, api_request), |
46 | + 'self_link': absoluteURL(person, self.api_request), |
47 | 'is_team': person.is_team, |
48 | 'can_edit': can_edit, |
49 | + 'display_subscribed_by': subscription.display_subscribed_by, |
50 | } |
51 | record = { |
52 | 'subscriber': subscriber, |
53 | @@ -560,6 +562,13 @@ |
54 | removeSecurityProxy(subscription.bug_notification_level)), |
55 | } |
56 | data.append(record) |
57 | + return data |
58 | + |
59 | + @property |
60 | + def subscriber_data_js(self): |
61 | + """Return subscriber_ids in a form suitable for JavaScript use.""" |
62 | + bug = IBug(self.context) |
63 | + data = self.direct_subscriber_data(bug) |
64 | |
65 | others = list(bug.getIndirectSubscribers()) |
66 | for person in others: |
67 | @@ -570,7 +579,7 @@ |
68 | 'name': person.name, |
69 | 'display_name': person.displayname, |
70 | 'web_link': canonical_url(person, rootsite='mainsite'), |
71 | - 'self_link': absoluteURL(person, api_request), |
72 | + 'self_link': absoluteURL(person, self.api_request), |
73 | 'is_team': person.is_team, |
74 | 'can_edit': False, |
75 | } |
76 | |
77 | === modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py' |
78 | --- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-06-30 22:41:02 +0000 |
79 | +++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-07-21 18:09:56 +0000 |
80 | @@ -1,4 +1,4 @@ |
81 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
82 | +# Copyright 2010-2011 Canonical Ltd. This software is licensed under the |
83 | # GNU Affero General Public License version 3 (see the file LICENSE). |
84 | |
85 | """Tests for BugSubscription views.""" |
86 | @@ -6,7 +6,8 @@ |
87 | __metaclass__ = type |
88 | |
89 | from simplejson import dumps |
90 | - |
91 | +from storm.store import Store |
92 | +from testtools.matchers import Equals |
93 | from zope.component import getUtility |
94 | from zope.traversing.browser import absoluteURL |
95 | |
96 | @@ -23,8 +24,10 @@ |
97 | from lp.registry.interfaces.person import IPersonSet |
98 | from lp.testing import ( |
99 | person_logged_in, |
100 | + StormStatementRecorder, |
101 | TestCaseWithFactory, |
102 | ) |
103 | +from lp.testing.matchers import HasQueryCount |
104 | from lp.testing.sampledata import ADMIN_EMAIL |
105 | from lp.testing.views import create_initialized_view |
106 | |
107 | @@ -467,7 +470,7 @@ |
108 | self.assertEqual(dumps([]), harness.view.subscriber_data_js) |
109 | |
110 | def test_data_person_subscription(self): |
111 | - # A subscriber_data_js returns JSON string of a list |
112 | + # subscriber_data_js returns JSON string of a list |
113 | # containing all subscriber information needed for |
114 | # subscribers_list.js subscribers loading. |
115 | bug = self._makeBugWithNoSubscribers() |
116 | @@ -487,18 +490,69 @@ |
117 | 'can_edit': False, |
118 | 'web_link': canonical_url(subscriber), |
119 | 'self_link': absoluteURL(subscriber, api_request), |
120 | - }, |
121 | - 'subscription_level': "Lifecycle", |
122 | - } |
123 | - self.assertEqual( |
124 | - dumps([expected_result]), harness.view.subscriber_data_js) |
125 | + 'display_subscribed_by': 'Self-subscribed', |
126 | + }, |
127 | + 'subscription_level': "Lifecycle", |
128 | + } |
129 | + self.assertEqual( |
130 | + dumps([expected_result]), harness.view.subscriber_data_js) |
131 | + |
132 | + def test_data_person_subscription_other_subscriber(self): |
133 | + # Ensure subscriber_data_js does the correct thing when the person who |
134 | + # did the subscribing is someone else. |
135 | + bug = self._makeBugWithNoSubscribers() |
136 | + subscribed_by = self.factory.makePerson( |
137 | + name="someone", displayname='Someone') |
138 | + subscriber = self.factory.makePerson( |
139 | + name='user', displayname='Subscriber Name') |
140 | + with person_logged_in(subscriber): |
141 | + bug.subscribe(person=subscriber, |
142 | + subscribed_by=subscribed_by, |
143 | + level=BugNotificationLevel.LIFECYCLE) |
144 | + harness = LaunchpadFormHarness(bug, BugPortletSubscribersWithDetails) |
145 | + api_request = IWebServiceClientRequest(harness.request) |
146 | + |
147 | + expected_result = { |
148 | + 'subscriber': { |
149 | + 'name': 'user', |
150 | + 'display_name': 'Subscriber Name', |
151 | + 'is_team': False, |
152 | + 'can_edit': False, |
153 | + 'web_link': canonical_url(subscriber), |
154 | + 'self_link': absoluteURL(subscriber, api_request), |
155 | + 'display_subscribed_by': 'Subscribed by Someone (someone)', |
156 | + }, |
157 | + 'subscription_level': "Lifecycle", |
158 | + } |
159 | + self.assertEqual( |
160 | + dumps([expected_result]), harness.view.subscriber_data_js) |
161 | + |
162 | + def test_data_person_subscription_other_subscriber_query_count(self): |
163 | + # All subscriber data should be retrieved with a single query. |
164 | + bug = self._makeBugWithNoSubscribers() |
165 | + subscribed_by = self.factory.makePerson( |
166 | + name="someone", displayname='Someone') |
167 | + subscriber = self.factory.makePerson( |
168 | + name='user', displayname='Subscriber Name') |
169 | + with person_logged_in(subscriber): |
170 | + bug.subscribe(person=subscriber, |
171 | + subscribed_by=subscribed_by, |
172 | + level=BugNotificationLevel.LIFECYCLE) |
173 | + harness = LaunchpadFormHarness(bug, BugPortletSubscribersWithDetails) |
174 | + # Invoke the view method, ignoring the results. |
175 | + Store.of(bug).invalidate() |
176 | + with StormStatementRecorder() as recorder: |
177 | + harness.view.direct_subscriber_data(bug) |
178 | + self.assertThat(recorder, HasQueryCount(Equals(1))) |
179 | |
180 | def test_data_team_subscription(self): |
181 | # For a team subscription, subscriber_data_js has is_team set |
182 | # to true. |
183 | bug = self._makeBugWithNoSubscribers() |
184 | + teamowner = self.factory.makePerson( |
185 | + name="team-owner", displayname="Team Owner") |
186 | subscriber = self.factory.makeTeam( |
187 | - name='team', displayname='Team Name') |
188 | + name='team', displayname='Team Name', owner=teamowner) |
189 | with person_logged_in(subscriber.teamowner): |
190 | bug.subscribe(subscriber, subscriber.teamowner, |
191 | level=BugNotificationLevel.LIFECYCLE) |
192 | @@ -513,6 +567,8 @@ |
193 | 'can_edit': False, |
194 | 'web_link': canonical_url(subscriber), |
195 | 'self_link': absoluteURL(subscriber, api_request), |
196 | + 'display_subscribed_by': \ |
197 | + 'Subscribed by Team Owner (team-owner)', |
198 | }, |
199 | 'subscription_level': "Lifecycle", |
200 | } |
201 | @@ -523,8 +579,10 @@ |
202 | # For a team subscription, subscriber_data_js has can_edit |
203 | # set to true for team owner. |
204 | bug = self._makeBugWithNoSubscribers() |
205 | + teamowner = self.factory.makePerson( |
206 | + name="team-owner", displayname="Team Owner") |
207 | subscriber = self.factory.makeTeam( |
208 | - name='team', displayname='Team Name') |
209 | + name='team', displayname='Team Name', owner=teamowner) |
210 | with person_logged_in(subscriber.teamowner): |
211 | bug.subscribe(subscriber, subscriber.teamowner, |
212 | level=BugNotificationLevel.LIFECYCLE) |
213 | @@ -540,6 +598,8 @@ |
214 | 'can_edit': True, |
215 | 'web_link': canonical_url(subscriber), |
216 | 'self_link': absoluteURL(subscriber, api_request), |
217 | + 'display_subscribed_by': \ |
218 | + 'Subscribed by Team Owner (team-owner)', |
219 | }, |
220 | 'subscription_level': "Lifecycle", |
221 | } |
222 | @@ -552,8 +612,11 @@ |
223 | # set to true for team member. |
224 | bug = self._makeBugWithNoSubscribers() |
225 | member = self.factory.makePerson() |
226 | + teamowner = self.factory.makePerson( |
227 | + name="team-owner", displayname="Team Owner") |
228 | subscriber = self.factory.makeTeam( |
229 | - name='team', displayname='Team Name', members=[member]) |
230 | + name='team', displayname='Team Name', owner=teamowner, |
231 | + members=[member]) |
232 | with person_logged_in(subscriber.teamowner): |
233 | bug.subscribe(subscriber, subscriber.teamowner, |
234 | level=BugNotificationLevel.LIFECYCLE) |
235 | @@ -569,6 +632,8 @@ |
236 | 'can_edit': True, |
237 | 'web_link': canonical_url(subscriber), |
238 | 'self_link': absoluteURL(subscriber, api_request), |
239 | + 'display_subscribed_by': \ |
240 | + 'Subscribed by Team Owner (team-owner)', |
241 | }, |
242 | 'subscription_level': "Lifecycle", |
243 | } |
244 | @@ -598,6 +663,7 @@ |
245 | 'can_edit': True, |
246 | 'web_link': canonical_url(subscriber), |
247 | 'self_link': absoluteURL(subscriber, api_request), |
248 | + 'display_subscribed_by': 'Self-subscribed', |
249 | }, |
250 | 'subscription_level': "Lifecycle", |
251 | } |
252 | @@ -615,7 +681,7 @@ |
253 | subscriber = self.factory.makePerson( |
254 | name='user', displayname='Subscriber Name') |
255 | subscribed_by = self.factory.makePerson( |
256 | - name='someone', displayname='Subscribed By Name') |
257 | + name='someone', displayname='Someone') |
258 | with person_logged_in(subscriber): |
259 | bug.subscribe(subscriber, subscribed_by, |
260 | level=BugNotificationLevel.LIFECYCLE) |
261 | @@ -630,6 +696,7 @@ |
262 | 'can_edit': True, |
263 | 'web_link': canonical_url(subscriber), |
264 | 'self_link': absoluteURL(subscriber, api_request), |
265 | + 'display_subscribed_by': 'Subscribed by Someone (someone)', |
266 | }, |
267 | 'subscription_level': "Lifecycle", |
268 | } |
269 | |
270 | === modified file 'lib/lp/bugs/doc/bugsubscription.txt' |
271 | --- lib/lp/bugs/doc/bugsubscription.txt 2011-06-23 21:46:48 +0000 |
272 | +++ lib/lp/bugs/doc/bugsubscription.txt 2011-07-21 18:09:56 +0000 |
273 | @@ -888,14 +888,14 @@ |
274 | >>> ff_bug.subscribe(lifeless, mark) |
275 | <lp.bugs.model.bugsubscription.BugSubscription ...> |
276 | >>> subscriptions = [ |
277 | - ... "%s (%s)" % ( |
278 | + ... "%s: %s" % ( |
279 | ... subscription.person.displayname, |
280 | ... subscription.display_subscribed_by) |
281 | ... for subscription in ff_bug.getDirectSubscriptions()] |
282 | >>> for subscription in sorted(subscriptions): |
283 | ... print subscription |
284 | - Mark Shuttleworth (Self-subscribed) |
285 | - Robert Collins (Subscribed by Mark Shuttleworth) |
286 | + Mark Shuttleworth: Self-subscribed |
287 | + Robert Collins: Subscribed by Mark Shuttleworth (mark) |
288 | >>> params = CreateBugParams( |
289 | ... title="one more dupe test bug", |
290 | ... comment="one more dupe test description", |
291 | @@ -906,8 +906,8 @@ |
292 | >>> dupe_ff_bug.subscribe(foobar, lifeless) |
293 | <lp.bugs.model.bugsubscription.BugSubscription ...> |
294 | >>> for subscription in ff_bug.getSubscriptionsFromDuplicates(): |
295 | - ... print '%s (%s)' % ( |
296 | + ... print '%s: %s' % ( |
297 | ... subscription.person.displayname, |
298 | ... subscription.display_duplicate_subscribed_by) |
299 | - Scott James Remnant (Self-subscribed to bug ...) |
300 | - Foo Bar (Subscribed to bug ... by Robert Collins) |
301 | + Scott James Remnant: Self-subscribed to bug ... |
302 | + Foo Bar: Subscribed to bug ... by Robert Collins (lifeless) |
303 | |
304 | === modified file 'lib/lp/bugs/javascript/subscribers_list.js' |
305 | --- lib/lp/bugs/javascript/subscribers_list.js 2011-07-20 13:35:24 +0000 |
306 | +++ lib/lp/bugs/javascript/subscribers_list.js 2011-07-21 18:09:56 +0000 |
307 | @@ -192,8 +192,9 @@ |
308 | * "name": "foobar", |
309 | * "display_name": "Foo Bar", |
310 | * "can_edit": true/false, |
311 | - * "is_team": false/false, |
312 | - * "web_link": "https://launchpad.dev/~foobar" |
313 | + * "is_team": true/false, |
314 | + * "web_link": "https://launchpad.dev/~foobar", |
315 | + * "display_subscribed_by": "Matt Zimmerman (mdz)" |
316 | * }, |
317 | * "subscription_level": "Details"}, |
318 | * { "subscriber": ... } |
319 | @@ -322,7 +323,6 @@ |
320 | var subscriber = person.getAttrs(); |
321 | this.subscribers_list.addSubscriber(subscriber, 'Discussion'); |
322 | this.subscribers_list.indicateSubscriberActivity(subscriber); |
323 | - |
324 | var loader = this; |
325 | |
326 | function on_success() { |
327 | @@ -687,7 +687,7 @@ |
328 | * |
329 | * @method _createSubscriberNode |
330 | * @param subscriber {Object} Object containing `name`, `display_name` |
331 | - * `web_link` and `is_team` attributes. |
332 | + * `web_link`, `is_team` and `display_subscribed_by` attributes. |
333 | * @return {Object} Node containing a subscriber link. |
334 | */ |
335 | SubscribersList.prototype._createSubscriberNode = function(subscriber) { |
336 | @@ -712,6 +712,9 @@ |
337 | } else { |
338 | subscriber_text.addClass('person'); |
339 | } |
340 | + if (Y.Lang.isString(subscriber.display_subscribed_by)) { |
341 | + subscriber_link.set('title', subscriber.display_subscribed_by); |
342 | + } |
343 | subscriber_link.appendChild(subscriber_text); |
344 | |
345 | subscriber_node.appendChild(subscriber_link); |
346 | @@ -728,7 +731,8 @@ |
347 | * |
348 | * @method addSubscriber |
349 | * @param subscriber {Object} Object containing `name`, `display_name` |
350 | - * `web_link` and `is_team` attributes describing the subscriber. |
351 | + * `web_link`, `is_team`, and `display_subscribed_by` attributes describing |
352 | + * the subscriber. |
353 | * @param level {String} Level of the subscription. |
354 | * @param config {Object} Object containing potential 'unsubscribe' callback |
355 | * in the `unsubscribe_callback` attribute. |
356 | |
357 | === modified file 'lib/lp/bugs/javascript/tests/test_subscribers_list.js' |
358 | --- lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-07-20 13:35:24 +0000 |
359 | +++ lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-07-21 18:09:56 +0000 |
360 | @@ -780,18 +780,39 @@ |
361 | var subscriber = { |
362 | name: 'user', |
363 | display_name: 'User Name', |
364 | - web_link: 'http://launchpad.net/~user' |
365 | + web_link: 'http://launchpad.net/~user', |
366 | + display_subscribed_by: 'Subscribed by Someone (someone)' |
367 | }; |
368 | var node = subscribers_list._createSubscriberNode(subscriber); |
369 | Y.Assert.isTrue(node.hasClass('subscriber')); |
370 | |
371 | var link = node.one('a'); |
372 | Y.Assert.areEqual('http://launchpad.net/~user', link.get('href')); |
373 | - |
374 | + Y.Assert.areEqual( |
375 | + 'Subscribed by Someone (someone)', link.get('title')); |
376 | var text = link.one('span'); |
377 | Y.Assert.areEqual('User Name', text.get('text')); |
378 | Y.Assert.isTrue(text.hasClass('sprite')); |
379 | Y.Assert.isTrue(text.hasClass('person')); |
380 | + |
381 | + }, |
382 | + |
383 | + test_createSubscriberNode_missing_display_subscribed_by: function() { |
384 | + // When passed a subscriber object with no 'display_subscribed_by' |
385 | + // attribute then the title is simply not set but shows up |
386 | + // as a null string. |
387 | + var subscribers_list = setUpSubscribersList(this.root); |
388 | + var subscriber = { |
389 | + name: 'user', |
390 | + display_name: 'User Name', |
391 | + web_link: 'http://launchpad.net/~user' |
392 | + }; |
393 | + var node = subscribers_list._createSubscriberNode(subscriber); |
394 | + Y.Assert.isTrue(node.hasClass('subscriber')); |
395 | + |
396 | + var link = node.one('a'); |
397 | + Y.Assert.areEqual('http://launchpad.net/~user', link.get('href')); |
398 | + Y.Assert.areEqual('', link.get('title')); |
399 | }, |
400 | |
401 | test_createSubscriberNode_display_name_truncated: function() { |
402 | @@ -827,7 +848,7 @@ |
403 | |
404 | test_addSubscriber: function() { |
405 | // When there is no subscriber in the subscriber list, |
406 | - // new node is constructed and appropriate section is added. |
407 | + // a new node is constructed and the appropriate section is added. |
408 | var subscribers_list = setUpSubscribersList(this.root); |
409 | var node = subscribers_list.addSubscriber( |
410 | { name: 'user' }, 'Details'); |
411 | |
412 | === modified file 'lib/lp/bugs/model/bug.py' |
413 | --- lib/lp/bugs/model/bug.py 2011-07-19 20:34:27 +0000 |
414 | +++ lib/lp/bugs/model/bug.py 2011-07-21 18:09:56 +0000 |
415 | @@ -939,10 +939,12 @@ |
416 | |
417 | def getDirectSubscribersWithDetails(self): |
418 | """See `IBug`.""" |
419 | + SubscribedBy = ClassAlias(Person, name="subscribed_by") |
420 | results = Store.of(self).find( |
421 | - (Person, BugSubscription), |
422 | + (Person, SubscribedBy, BugSubscription), |
423 | BugSubscription.person_id == Person.id, |
424 | BugSubscription.bug_id == self.id, |
425 | + BugSubscription.subscribed_by_id == SubscribedBy.id, |
426 | Not(In(BugSubscription.person_id, |
427 | Select(BugMute.person_id, BugMute.bug_id == self.id))) |
428 | ).order_by(Person.displayname) |
429 | |
430 | === modified file 'lib/lp/bugs/model/bugsubscription.py' |
431 | --- lib/lp/bugs/model/bugsubscription.py 2011-06-23 04:55:46 +0000 |
432 | +++ lib/lp/bugs/model/bugsubscription.py 2011-07-21 18:09:56 +0000 |
433 | @@ -62,10 +62,11 @@ |
434 | @property |
435 | def display_subscribed_by(self): |
436 | """See `IBugSubscription`.""" |
437 | - if self.person == self.subscribed_by: |
438 | + if self.person_id == self.subscribed_by_id: |
439 | return u'Self-subscribed' |
440 | else: |
441 | - return u'Subscribed by %s' % self.subscribed_by.displayname |
442 | + return u'Subscribed by %s (%s)' % ( |
443 | + self.subscribed_by.displayname, self.subscribed_by.name) |
444 | |
445 | @property |
446 | def display_duplicate_subscribed_by(self): |
447 | @@ -73,8 +74,9 @@ |
448 | if self.person == self.subscribed_by: |
449 | return u'Self-subscribed to bug %s' % (self.bug_id) |
450 | else: |
451 | - return u'Subscribed to bug %s by %s' % (self.bug_id, |
452 | - self.subscribed_by.displayname) |
453 | + return u'Subscribed to bug %s by %s (%s)' % ( |
454 | + self.bug_id, self.subscribed_by.displayname, |
455 | + self.subscribed_by.name) |
456 | |
457 | def canBeUnsubscribedByUser(self, user): |
458 | """See `IBugSubscription`.""" |
459 | |
460 | === modified file 'lib/lp/bugs/model/tests/test_bug.py' |
461 | --- lib/lp/bugs/model/tests/test_bug.py 2011-07-19 14:42:52 +0000 |
462 | +++ lib/lp/bugs/model/tests/test_bug.py 2011-07-21 18:09:56 +0000 |
463 | @@ -205,9 +205,27 @@ |
464 | set(subscribers), set(direct_subscribers), |
465 | "Subscribers did not match expected value.") |
466 | |
467 | - def test_get_direct_subscribers_with_details(self): |
468 | - # getDirectSubscribersWithDetails() returns both |
469 | - # Person and BugSubscription records in one go. |
470 | + def test_get_direct_subscribers_with_details_other_subscriber(self): |
471 | + # getDirectSubscribersWithDetails() returns |
472 | + # Person and BugSubscription records in one go as well as the |
473 | + # BugSubscription.subscribed_by person. |
474 | + bug = self.factory.makeBug() |
475 | + with person_logged_in(bug.owner): |
476 | + # Unsubscribe bug owner so it doesn't taint the result. |
477 | + bug.unsubscribe(bug.owner, bug.owner) |
478 | + subscriber = self.factory.makePerson() |
479 | + subscribee = self.factory.makePerson() |
480 | + with person_logged_in(subscriber): |
481 | + subscription = bug.subscribe( |
482 | + subscribee, subscriber, level=BugNotificationLevel.LIFECYCLE) |
483 | + self.assertContentEqual( |
484 | + [(subscribee, subscriber, subscription)], |
485 | + bug.getDirectSubscribersWithDetails()) |
486 | + |
487 | + def test_get_direct_subscribers_with_details_self_subscribed(self): |
488 | + # getDirectSubscribersWithDetails() returns |
489 | + # Person and BugSubscription records in one go as well as the |
490 | + # BugSubscription.subscribed_by person. |
491 | bug = self.factory.makeBug() |
492 | with person_logged_in(bug.owner): |
493 | # Unsubscribe bug owner so it doesn't taint the result. |
494 | @@ -216,9 +234,8 @@ |
495 | with person_logged_in(subscriber): |
496 | subscription = bug.subscribe( |
497 | subscriber, subscriber, level=BugNotificationLevel.LIFECYCLE) |
498 | - |
499 | self.assertContentEqual( |
500 | - [(subscriber, subscription)], |
501 | + [(subscriber, subscriber, subscription)], |
502 | bug.getDirectSubscribersWithDetails()) |
503 | |
504 | def test_get_direct_subscribers_with_details_mute_excludes(self): |
I haven't read the diff here in whole, but I'd like to note that this appears as though it will trigger late evaluation - the display_ subscribed_ by property isn't eager loaded, and a bug with a couple hundred subscriptions will cause eager loading of a couple hundred person objects *unless* they are all self-subscribed. It will also be a little slow because its comparing objects not keys - the property does: _by.displayname
if self.person == self.subscribed_by:
return u'Self-subscribed'
else:
return u'Subscribed by %s' % self.subscribed
This would be a little more efficient as
if self.person_id == self.subscribed _by_id: _by.displayname
return u'Self-subscribed'
else:
return u'Subscribed by %s' % self.subscribed
but the big thing is eager loading: we need to populate the subscribing person (but only for views that need it) en masse, rather than just-in-time.
I don't know if this will cause timeouts, but this sort of thing is one of the key drivers for timeouts.