Merge lp:~gmb/launchpad/subscribers-timeout-bug-471974 into lp:launchpad

Proposed by Graham Binns
Status: Merged
Approved by: Graham Binns
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/subscribers-timeout-bug-471974
Merge into: lp:launchpad
Diff against target: 255 lines (+122/-17)
7 files modified
lib/canonical/launchpad/javascript/bugs/bugtask-index.js (+60/-9)
lib/lp/bugs/browser/bug.py (+0/-6)
lib/lp/bugs/browser/bugsubscription.py (+14/-0)
lib/lp/bugs/browser/configure.zcml (+5/-0)
lib/lp/bugs/browser/tests/bug-subscription-views.txt (+40/-0)
lib/lp/bugs/templates/bug-portlet-subscribers.pt (+3/-0)
lib/lp/bugs/templates/bugtask-index.pt (+0/-2)
To merge this branch: bzr merge lp:~gmb/launchpad/subscribers-timeout-bug-471974
Reviewer Review Type Date Requested Status
Gavin Panella (community) js Approve
Brad Crittenden (community) code Approve
Review via email: mp+14955@code.launchpad.net

Commit message

The subscriber-IDs-to-CSS-ids mapping will now be loaded when the bug subscribers portlet is loaded, rather than being loaded with the page and potentially causing a timeout.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (3.3 KiB)

This branch fixes bug 471974 by moving the loading of the JSON struct of
subscriber IDs out of the main bug page template. Instead, we'll now
lazily-load it when we actually need it - i.e. when we're loading the
subscribers portlet.

The lazy-loading involves requesting a JSON dump from the server, via a
view designed precisely for that task. This seems a bit hacky, but it's
the quickest way to solve the problem. We will investigate more elegant
solutions later.

The upshot of this is that we shouldn't see any more timeouts on the bug
page due to large numbers of subscribers (especially from dupes).

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/javascript/bugs/bugtask-index.js
  lib/lp/bugs/browser/bug.py
  lib/lp/bugs/browser/bugsubscription.py
  lib/lp/bugs/browser/configure.zcml
  lib/lp/bugs/templates/bug-portlet-subscribers-ids.pt
  lib/lp/bugs/templates/bug-portlet-subscribers.pt
  lib/lp/bugs/templates/bugtask-index.pt

== JSLint notices ==
jslint: No problem found in '/home/graham/canonical/lp-branches/subscribers-timeout-bug-471974/lib/canonical/launchpad/javascript/bugs/bugtask-index.js'.

jslint: 1 file to lint.

== Pylint notices ==

lib/lp/bugs/browser/bug.py
    28: [F0401] Unable to import 'email.MIMEMultipart' (No module named MIMEMultipart)
    29: [F0401] Unable to import 'email.MIMEText' (No module named MIMEText)
    43: [F0401] Unable to import 'lazr.enum' (No module named enum)
    44: [F0401] Unable to import 'lazr.lifecycle.event' (No module named lifecycle)
    45: [F0401] Unable to import 'lazr.lifecycle.snapshot' (No module named lifecycle)
    46: [F0401] Unable to import 'lazr.restful.interfaces' (No module named restful)

lib/lp/bugs/browser/bugsubscription.py
    16: [F0401] Unable to import 'lazr.delegates' (No module named delegates)
    17: [F0401] Unable to import 'lazr.lifecycle.event' (No module named lifecycle)

= Changes made =

== lib/canonical/launchpad/javascript/bugs/bugtask-index.js ==

 - I've added Javascript to load the subscriber_ids struct just before
   loading the contents of the subscribers portlet. This should stop the
   bug page from timing out.
 - Note that, should loading the subscriber ids from the JSON dump time
   out (which is theoretically possible), the portlet will still load.

== lib/lp/bugs/browser/bug.py ==

 - I've removed the subscriber_ids_json property from the BugViewMixin.
   It belongs specifically in our JSON-dumping view.

== lib/lp/bugs/browser/bugsubscription.py ==

 - I've added a BugPortletSubscribersIds view. This contains a
   subscriber_ids_json property, which returns a JSON dump of the
   subscriber IDs for the current bug.

== lib/lp/bugs/browser/configure.zcml ==

 - I've added the ZCML for the new view.

== lib/lp/bugs/templates/bug-portlet-subscribers-ids.pt ==

 - I've added this view to contain the JSON dump.

== lib/lp/bugs/templates/bug-portlet-subscribers.pt ==

 - I've added a link to the bug-portlet-subscribers-ids URL so that we
   can grab it using JS and load the JSON dump from it.

== lib...

Read more...

Revision history for this message
Brad Crittenden (bac) wrote :

Hi Graham,

Thanks for this fix. I note there are two issues. One, the icon and subscriber name now appear on different lines and for the life of me I cannot figure out why.

Also I'm bothered that a change of this size has no tests. You could do a view test to check the results of your new view. What about Windmill tests?

Perhaps it is just the current state of affairs but the fragility of this change bothers me.

I've removed the 'js' portion from this review as you may want to get an expert to do that portion.

review: Needs Information (code)
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (3.3 KiB)

On Tue, Nov 17, 2009 at 08:08:18PM -0000, Brad Crittenden wrote:
> Review: Needs Information code
> Hi Graham,

Hi Brad, thanks for the review.

> Thanks for this fix. I note there are two issues. One, the icon and
> subscriber name now appear on different lines and for the life of me I
> cannot figure out why.

That's not a result of this branch; you can see the same behaviour on
edge at the moment. I'll file a bug about it.

>
> Also I'm bothered that a change of this size has no tests. You could
> do a view test to check the results of your new view. What about
> Windmill tests?
>

You're quite right. I've added a view test for the JSON dump (See below
for diff). The changes should still pass the current windmill test
suite, though IIRC the inline subscribers part of the tests is currently
failing anyway, so it's difficult to tell. I'll run it through windmill
before landing.

> Perhaps it is just the current state of affairs but the fragility of
> this change bothers me.
>

I don't think the change is particularly fragile. I designed it
specifically so that if fetching the IDs fails we'll just fall back to
loading the portlet anyway. The only time someone will have a noticeable
problem in that case is if they try to subscribe and unsubscribe quickly
in succession (in which case the subscribe / unsubscribe link may not
update properly) or if they try to unsubscribe from a dupe, in which
case they'll have to do it via the old +subscribe form rather than via
AJAX.

Can you be more specific about the fragility that concerns you?

> I've removed the 'js' portion from this review as you may want to get
> an expert to do that portion.

I'll ask Gavin to take a look since he's OCR today.

Incremental diff of changes
---------------------------

=== added file 'lib/lp/bugs/browser/tests/bug-subscription-views.txt'
--- lib/lp/bugs/browser/tests/bug-subscription-views.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/browser/tests/bug-subscription-views.txt 2009-11-18 11:50:28 +0000
@@ -0,0 +1,40 @@
+Bug subscription views
+======================
+
+Getting subscriber CSS IDs
+--------------------------
+
+It's possible to get a mapping of bug subscriber names to CSS IDs using
+the +bug-portlet-subscribers-ids view.
+
+ >>> from lp.bugs.interfaces.bug import IBugSet
+ >>> bug_15 = getUtility(IBugSet).get(15)
+
+ >>> subscriber_ids_view = create_initialized_view(
+ ... bug_15, '+bug-portlet-subscribers-ids')
+
+The view's subscriber_ids_js property returns a JSON struct of the
+person name to CSS ID mappings for the bug's subscribers.
+
+ >>> print subscriber_ids_view.subscriber_ids_js
+ {"name16": "subscriber-16"}
+
+If bug 15 is marked as a duplicate of another bug, its subscribers will
+be included in that bugs subscriber_ids_js mapping.
+
+ >>> bug_13 = getUtility(IBugSet).get(13)
+ >>> subscriber_ids_view = create_initialized_view(
+ ... bug_13, '+bug-portlet-subscribers-ids')
+
+ >>> print subscriber_ids_view.subscriber_ids_js
+ {"name12": "subscriber-12"}
+
+ >>> login('<email address hidden>')
+ >>> bug_15.duplicateof = bug_13
+ >>> bug_13 = getUtility(IBugSet).get(13)
+
+ >>> subscriber...

Read more...

Revision history for this message
Graham Binns (gmb) wrote :
Download full text (5.4 KiB)

Here's an incremental diff of the changes that Gavin suggested on IRC:

=== modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js'
--- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-11-17 16:00:21 +0000
+++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-11-18 15:20:12 +0000
@@ -54,8 +54,7 @@
 Y.augment(PortletTarget, Y.Event.Target);
 Y.bugs.portlet = new PortletTarget();
 Y.bugs.portlet.subscribe('bugs:portletloaded', function() {
- setup_subscription_link_handlers();
- load_subscribers_from_duplicates();
+ load_subscriber_ids();
 });
 Y.bugs.portlet.subscribe('bugs:dupeportletloaded', function() {
     setup_unsubscribe_icon_handlers();
@@ -78,6 +77,22 @@
     setup_unsubscribe_icon_handlers();
 });

+/* If loading the subscriber IDs JSON has succeeded, set up the
+ * subscription link handlers and load the subscribers from dupes.
+ */
+Y.bugs.portlet.subscribe('bugs:portletsubscriberidsloaded', function() {
+ setup_subscription_link_handlers();
+ load_subscribers_from_duplicates();
+});
+
+/* If loading the subscriber IDs JSON fails we still need to load the
+ * subscribers from duplicates but we don't set up the subscription link
+ * handlers.
+ */
+Y.bugs.portlet.subscribe('bugs:portletsubscriberidsfailed', function() {
+ load_subscribers_from_duplicates();
+});
+
 /*
  * Subscribing someone else requires loading a grayed out
  * username into the DOM until the subscribe action completes.
@@ -1806,35 +1821,44 @@
         Y.bugs.portlet.fire('bugs:portletloaded');
     }

- function load_portlet() {
- var config = {on: {success: setup_portlet,
- failure: hide_spinner}};
- var url = Y.get(
- '#subscribers-content-link').getAttribute('href').replace(
- 'bugs.', '');
- Y.io(url, config);
- }
+ var config = {on: {success: setup_portlet,
+ failure: hide_spinner}};
+ var url = Y.get(
+ '#subscribers-content-link').getAttribute('href').replace(
+ 'bugs.', '');
+ Y.io(url, config);
+};

- function load_subscriber_ids(transactionid, response, args) {
+function load_subscriber_ids() {
+ function on_success(transactionid, response, args) {
         try {
- var subscriber_ids_node = Y.Node.create(
- response.responseText);
- var subscriber_ids_json = subscriber_ids_node.get('innerHTML');
- subscriber_ids = Y.JSON.parse(subscriber_ids_json);
+ subscriber_ids = Y.JSON.parse(response.responseText);
+ Y.log("Loaded subscriber IDs.");
+
+ // Fire a custom event to trigger the setting-up of the
+ // subscription handlers.
+ Y.bugs.portlet.fire('bugs:portletsubscriberidsloaded');
         } catch (e) {
             Y.log("Unable to load subscriber ids.", "error");
+
+ // Fire an event to signal failure. This ensures that the
+ // subscribers-from-dupes still get loaded into the portlet.
+ Y.bugs.portlet.fire('bugs:portletsubscriberidsfailed');
         }
-
- load_portlet();
- }
-
- var config = {on: {success: lo...

Read more...

Revision history for this message
Brad Crittenden (bac) wrote :

Thanks for adding the view test Graham. I'm satisfied with the code portion and I know Gavin and you have a handle on the JS parts.

review: Approve (code)
Revision history for this message
Gavin Panella (allenap) :
review: Approve (js)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js'
2--- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-11-14 21:14:40 +0000
3+++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-11-18 15:53:11 +0000
4@@ -39,6 +39,9 @@
5 var privacy_spinner;
6 var link_branch_link;
7
8+// The set of subscriber CSS IDs as a JSON struct.
9+var subscriber_ids;
10+
11 /*
12 * An object representing the bugtask subscribers portlet.
13 *
14@@ -51,8 +54,7 @@
15 Y.augment(PortletTarget, Y.Event.Target);
16 Y.bugs.portlet = new PortletTarget();
17 Y.bugs.portlet.subscribe('bugs:portletloaded', function() {
18- setup_subscription_link_handlers();
19- load_subscribers_from_duplicates();
20+ load_subscriber_ids();
21 });
22 Y.bugs.portlet.subscribe('bugs:dupeportletloaded', function() {
23 setup_unsubscribe_icon_handlers();
24@@ -75,6 +77,22 @@
25 setup_unsubscribe_icon_handlers();
26 });
27
28+/* If loading the subscriber IDs JSON has succeeded, set up the
29+ * subscription link handlers and load the subscribers from dupes.
30+ */
31+Y.bugs.portlet.subscribe('bugs:portletsubscriberidsloaded', function() {
32+ setup_subscription_link_handlers();
33+ load_subscribers_from_duplicates();
34+});
35+
36+/* If loading the subscriber IDs JSON fails we still need to load the
37+ * subscribers from duplicates but we don't set up the subscription link
38+ * handlers.
39+ */
40+Y.bugs.portlet.subscribe('bugs:portletsubscriberidsfailed', function() {
41+ load_subscribers_from_duplicates();
42+});
43+
44 /*
45 * Subscribing someone else requires loading a grayed out
46 * username into the DOM until the subscribe action completes.
47@@ -1792,7 +1810,7 @@
48 }
49 }
50
51- function on_success(transactionid, response, args) {
52+ function setup_portlet(transactionid, response, args) {
53 hide_spinner();
54 var portlet = Y.get('#portlet-subscribers');
55 portlet.set('innerHTML',
56@@ -1803,16 +1821,49 @@
57 Y.bugs.portlet.fire('bugs:portletloaded');
58 }
59
60- var config = {on: {success: on_success,
61+ var config = {on: {success: setup_portlet,
62 failure: hide_spinner}};
63 var url = Y.get(
64- '#subscribers-content-link').getAttribute('href').replace('bugs.', '');
65+ '#subscribers-content-link').getAttribute('href').replace(
66+ 'bugs.', '');
67 Y.io(url, config);
68 };
69
70-}, '0.1', {requires: ['base', 'oop', 'node', 'event', 'io-base', 'substitute',
71- 'widget-position-ext', 'lazr.formoverlay', 'lazr.anim',
72- 'lazr.base', 'lazr.overlay', 'lazr.choiceedit',
73- 'lp.picker', 'lp.client.plugins', 'lp.subscriber',
74+function load_subscriber_ids() {
75+ function on_success(transactionid, response, args) {
76+ try {
77+ subscriber_ids = Y.JSON.parse(response.responseText);
78+ Y.log("Loaded subscriber IDs.");
79+
80+ // Fire a custom event to trigger the setting-up of the
81+ // subscription handlers.
82+ Y.bugs.portlet.fire('bugs:portletsubscriberidsloaded');
83+ } catch (e) {
84+ Y.log("Unable to load subscriber ids.", "error");
85+
86+ // Fire an event to signal failure. This ensures that the
87+ // subscribers-from-dupes still get loaded into the portlet.
88+ Y.bugs.portlet.fire('bugs:portletsubscriberidsfailed');
89+ }
90+ }
91+
92+ function on_failure() {
93+ // Fire an event to signal failure. This ensures that the
94+ // subscribers-from-dupes still get loaded into the portlet.
95+ Y.bugs.portlet.fire('bugs:portletsubscriberidsfailed');
96+ }
97+
98+ var config = {on: {success: on_success,
99+ failure: on_failure}};
100+ var url = Y.get(
101+ '#subscribers-ids-link').getAttribute('href');
102+ Y.io(url, config);
103+}
104+
105+}, '0.1', {requires: ['base', 'oop', 'node', 'event', 'io-base', 'json-parse',
106+ 'substitute', 'widget-position-ext',
107+ 'lazr.formoverlay', 'lazr.anim', 'lazr.base',
108+ 'lazr.overlay', 'lazr.choiceedit', 'lp.picker',
109+ 'lp.client.plugins', 'lp.subscriber',
110 'lp.errors']});
111
112
113=== modified file 'lib/lp/bugs/browser/bug.py'
114--- lib/lp/bugs/browser/bug.py 2009-10-30 16:28:41 +0000
115+++ lib/lp/bugs/browser/bug.py 2009-11-18 15:53:11 +0000
116@@ -30,7 +30,6 @@
117 import re
118
119 import pytz
120-from simplejson import dumps
121
122 from zope.app.form.browser import TextWidget
123 from zope.component import adapter, getUtility
124@@ -433,11 +432,6 @@
125 ids[sub.name] = 'subscriber-%s' % sub.id
126 return ids
127
128- @property
129- def subscriber_ids_js(self):
130- """Return subscriber_ids in a form suitable for JavaScript use."""
131- return dumps(self.subscriber_ids)
132-
133 def subscription_class(self, subscribed_person):
134 """Return a set of CSS class names based on subscription status.
135
136
137=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
138--- lib/lp/bugs/browser/bugsubscription.py 2009-11-05 19:01:12 +0000
139+++ lib/lp/bugs/browser/bugsubscription.py 2009-11-18 15:53:11 +0000
140@@ -10,6 +10,7 @@
141 'BugSubscriptionAddView',
142 ]
143
144+from simplejson import dumps
145 from zope.event import notify
146
147 from lazr.delegates import delegates
148@@ -97,6 +98,19 @@
149 for subscription in self.context.getSubscriptionsFromDuplicates()]
150
151
152+class BugPortletSubcribersIds(LaunchpadView, BugViewMixin):
153+ """A view that returns a JSON dump of the subscriber IDs for a bug."""
154+
155+ @property
156+ def subscriber_ids_js(self):
157+ """Return subscriber_ids in a form suitable for JavaScript use."""
158+ return dumps(self.subscriber_ids)
159+
160+ def render(self):
161+ """Override the default render() to return only JSON."""
162+ return self.subscriber_ids_js
163+
164+
165 class SubscriptionAttrDecorator:
166 """A BugSubscription with added attributes for HTML/JS."""
167 delegates(IBugSubscription, 'subscription')
168
169=== modified file 'lib/lp/bugs/browser/configure.zcml'
170--- lib/lp/bugs/browser/configure.zcml 2009-11-10 11:31:06 +0000
171+++ lib/lp/bugs/browser/configure.zcml 2009-11-18 15:53:11 +0000
172@@ -1004,6 +1004,11 @@
173 class="lp.bugs.browser.bugsubscription.BugPortletDuplicateSubcribersContents"
174 template="../templates/bug-portlet-dupe-subscribers-content.pt"
175 permission="zope.Public"/>
176+ <browser:page
177+ for="lp.bugs.interfaces.bug.IBug"
178+ name="+bug-portlet-subscribers-ids"
179+ class="lp.bugs.browser.bugsubscription.BugPortletSubcribersIds"
180+ permission="zope.Public"/>
181 <browser:navigation
182 module="lp.bugs.browser.bug"
183 classes="
184
185=== added file 'lib/lp/bugs/browser/tests/bug-subscription-views.txt'
186--- lib/lp/bugs/browser/tests/bug-subscription-views.txt 1970-01-01 00:00:00 +0000
187+++ lib/lp/bugs/browser/tests/bug-subscription-views.txt 2009-11-18 15:53:11 +0000
188@@ -0,0 +1,40 @@
189+Bug subscription views
190+======================
191+
192+Getting subscriber CSS IDs
193+--------------------------
194+
195+It's possible to get a mapping of bug subscriber names to CSS IDs using
196+the +bug-portlet-subscribers-ids view.
197+
198+ >>> from lp.bugs.interfaces.bug import IBugSet
199+ >>> bug_15 = getUtility(IBugSet).get(15)
200+
201+ >>> subscriber_ids_view = create_initialized_view(
202+ ... bug_15, '+bug-portlet-subscribers-ids')
203+
204+The view's subscriber_ids_js property returns a JSON struct of the
205+person name to CSS ID mappings for the bug's subscribers.
206+
207+ >>> print subscriber_ids_view.subscriber_ids_js
208+ {"name16": "subscriber-16"}
209+
210+If bug 15 is marked as a duplicate of another bug, its subscribers will
211+be included in that bugs subscriber_ids_js mapping.
212+
213+ >>> bug_13 = getUtility(IBugSet).get(13)
214+ >>> subscriber_ids_view = create_initialized_view(
215+ ... bug_13, '+bug-portlet-subscribers-ids')
216+
217+ >>> print subscriber_ids_view.subscriber_ids_js
218+ {"name12": "subscriber-12"}
219+
220+ >>> login('foo.bar@canonical.com')
221+ >>> bug_15.duplicateof = bug_13
222+ >>> bug_13 = getUtility(IBugSet).get(13)
223+
224+ >>> subscriber_ids_view = create_initialized_view(
225+ ... bug_13, '+bug-portlet-subscribers-ids')
226+
227+ >>> print subscriber_ids_view.subscriber_ids_js
228+ {"name12": "subscriber-12", "name16": "subscriber-16"}
229
230=== modified file 'lib/lp/bugs/templates/bug-portlet-subscribers.pt'
231--- lib/lp/bugs/templates/bug-portlet-subscribers.pt 2009-11-05 14:56:01 +0000
232+++ lib/lp/bugs/templates/bug-portlet-subscribers.pt 2009-11-18 15:53:11 +0000
233@@ -14,6 +14,9 @@
234 <div id="sub-unsub-spinner">Subscribing...</div>
235 <div tal:content="structure context_menu/addsubscriber/render" />
236 </div>
237+ <a id="subscribers-ids-link"
238+ tal:define="bug context/bug|context"
239+ tal:attributes="href bug/fmt:url/+bug-portlet-subscribers-ids"></a>
240 <a id="subscribers-content-link"
241 tal:define="bug context/bug|context"
242 tal:attributes="href bug/fmt:url/+bug-portlet-subscribers-content"></a>
243
244=== modified file 'lib/lp/bugs/templates/bugtask-index.pt'
245--- lib/lp/bugs/templates/bugtask-index.pt 2009-10-28 06:08:11 +0000
246+++ lib/lp/bugs/templates/bugtask-index.pt 2009-11-18 15:53:11 +0000
247@@ -10,8 +10,6 @@
248 <script type='text/javascript' tal:content="string:var yui_base='${yui}';" />
249 <script type='text/javascript' id='available-official-tags-js'
250 tal:content="view/available_official_tags_js" />
251- <script type="text/javascript"
252- tal:content="string:var subscriber_ids = ${view/subscriber_ids_js};" />
253 <tal:devmode condition="devmode">
254 <script type="text/javascript"
255 tal:define="lp_js string:${icingroot}/build"