Merge lp:~danilo/launchpad/bug-772754-other-subscribers-remove-cruft into lp:launchpad

Proposed by Данило Шеган
Status: Merged
Merged at revision: 13243
Proposed branch: lp:~danilo/launchpad/bug-772754-other-subscribers-remove-cruft
Merge into: lp:launchpad
Prerequisite: lp:~danilo/launchpad/bug-772754-other-subscribers-actions
Diff against target: 3223 lines (+122/-2659)
25 files modified
lib/lp/app/javascript/picker.js (+5/-3)
lib/lp/bugs/browser/bug.py (+0/-18)
lib/lp/bugs/browser/bugsubscription.py (+6/-59)
lib/lp/bugs/browser/configure.zcml (+0/-17)
lib/lp/bugs/browser/tests/bug-portlet-subscribers-content.txt (+0/-25)
lib/lp/bugs/browser/tests/bug-subscription-views.txt (+0/-40)
lib/lp/bugs/browser/tests/test_bug_views.py (+0/-11)
lib/lp/bugs/browser/tests/test_bugsubscription.py (+0/-23)
lib/lp/bugs/browser/tests/test_bugsubscription_views.py (+0/-53)
lib/lp/bugs/javascript/bugtask_index.js (+2/-4)
lib/lp/bugs/javascript/bugtask_index_portlets.js (+0/-897)
lib/lp/bugs/javascript/subscriber.js (+0/-390)
lib/lp/bugs/javascript/subscribers_list.js (+0/-66)
lib/lp/bugs/javascript/tests/test_subscriber.html (+0/-104)
lib/lp/bugs/javascript/tests/test_subscriber.js (+0/-288)
lib/lp/bugs/javascript/tests/test_subscribers_list.js (+1/-318)
lib/lp/bugs/stories/bug-privacy/05-set-bug-private-as-admin.txt (+7/-13)
lib/lp/bugs/stories/bugs/bug-add-subscriber.txt (+29/-32)
lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions.txt (+45/-125)
lib/lp/bugs/templates/bug-portlet-dupe-subscribers-content.pt (+0/-49)
lib/lp/bugs/templates/bug-portlet-subscribers-content.pt (+0/-74)
lib/lp/bugs/templates/bug-portlet-subscribers.pt (+0/-10)
lib/lp/bugs/templates/bug-portlet-subscription.pt (+0/-3)
lib/lp/bugs/templates/bugtask-index.pt (+1/-14)
lib/lp/bugs/tests/bug.py (+26/-23)
To merge this branch: bzr merge lp:~danilo/launchpad/bug-772754-other-subscribers-remove-cruft
Reviewer Review Type Date Requested Status
Данило Шеган (community) Approve
Review via email: mp+64188@code.launchpad.net

Description of the change

= Bug 772754: Other subscribers list, part 7 =

NOTE: Not much review will be needed (this is just removal of all the unneeded bits and pieces). Final step, all the tests are passing, and I'll be landing this branch when all the others have been reviewed.

This is a final part of ongoing work for providing the "other subscribers" list as indicated in mockup https://launchpadlibrarian.net/71552495/all-in-one.png attached to bug 772754 by Gary.

== Tests ==

bin/test -m lp.bugs

== Demo and Q/A ==

N/A

Lint is clean.

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

Hi Danilo. I'm using this branch for a UX review, so I'm going to make comments here that can probably be addressed in earlier branches if you like.

It looks good!

In the mockup, "Maybe notified" goes last in the portlet, while in your branch, at least in https://bugs.launchpad.dev/firefox/+bug/1 , it comes before "Notified of all changes". I still think that we should show categories from the most connected to the least--so "Notified of all changes" should come first, followed by METADATA, followed by LIFECYCLE, followed by "Maybe notified". If the change you made from the mockup was intentional, so be it--I won't be around for you to convince me :-) .

In the mockup, there is a lot of text describing what "Maybe notified" means. I do prefer to not have that text around, as you have done, but I am inclined to say that we should have a help link with that information. If you disagree, again, since I'll be absent, I'm a pushover.

I'd prefer the above two changes. This next comment is about a change that you made that I think is for the best. In the mockup, I showed pencil icons for teams that the user could modify. However, as you've noted in the past, we do not let teams change their subscription levels, so including that here would be scope creep. So, +1 on omitting that.

That's it. Thank you!

Revision history for this message
Данило Шеган (danilo) wrote :

Hi Gary, thanks for looking at this branch.

The first one should have already been fixed (there are even tests to prove it, but there was a bug in the ordering logic that allowed it to sometimes be misordered; both tests and code have been improved to catch this case). If it's not in this branch yet, sorry about it (that's one thing I wish pipelines made easier: pushing _all_ branches up).

As for the second, I omitted it on purpose: long text tends to not be read. As far as the help link, I'd like that, but I'll have to get help from Matt (or someone else) on the text, so I am leaving that for a next branch.

As for the third thing, the code is structured in a way that it should be easy to add another "action" (of which unsubscribe is only one option offered atm) which allows one to change the level of the subscription, but since we don't have that for teams yet anyway, I didn't want to implement it here. IOW, adding the icon/action should be easy, making it do the right thing is out of scope.

Revision history for this message
Данило Шеган (danilo) wrote :

Marking this one as approved, it's just a removal of all the unused stuff. All tests are passing in ec2.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/javascript/picker.js'
2--- lib/lp/app/javascript/picker.js 2011-06-09 20:57:53 +0000
3+++ lib/lp/app/javascript/picker.js 2011-06-15 11:10:20 +0000
4@@ -318,11 +318,13 @@
5 * @param {Node} text_input The input field to copy the selected match too.
6 */
7 namespace.connect_select_menu = function (select_menu, text_input) {
8- var copy_selected_value = function(e) {
9- text_input.value = select_menu.value;
10+ if (Y.Lang.isValue(select_menu)) {
11+ var copy_selected_value = function(e) {
12+ text_input.value = select_menu.value;
13 };
14- Y.on('change', copy_selected_value, select_menu);
15+ Y.on('change', copy_selected_value, select_menu);
16 };
17+};
18
19 /**
20 * Creates a picker widget that has already been rendered and hidden.
21
22=== modified file 'lib/lp/bugs/browser/bug.py'
23--- lib/lp/bugs/browser/bug.py 2011-06-15 11:09:59 +0000
24+++ lib/lp/bugs/browser/bug.py 2011-06-15 11:10:20 +0000
25@@ -477,24 +477,6 @@
26 """
27 return self.subscription_info.duplicate_subscriptions.subscribers
28
29- @cachedproperty
30- def subscriber_ids(self):
31- """Return a dictionary mapping a css_name to user name."""
32- subscribers = set().union(
33- self.direct_subscribers,
34- self.duplicate_subscribers)
35-
36- # The current user has to be in subscribers_id so
37- # in case the id is needed for a new subscription.
38- user = getUtility(ILaunchBag).user
39- if user is not None:
40- subscribers.add(user)
41-
42- ids = {}
43- for sub in subscribers:
44- ids[sub.name] = 'subscriber-%s' % sub.id
45- return ids
46-
47 def getSubscriptionClassForUser(self, subscribed_person):
48 """Return a set of CSS class names based on subscription status.
49
50
51=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
52--- lib/lp/bugs/browser/bugsubscription.py 2011-06-15 11:09:59 +0000
53+++ lib/lp/bugs/browser/bugsubscription.py 2011-06-15 11:10:20 +0000
54@@ -7,8 +7,7 @@
55 __all__ = [
56 'AdvancedSubscriptionMixin',
57 'BugMuteSelfView',
58- 'BugPortletDuplicateSubcribersContents',
59- 'BugPortletSubcribersContents',
60+ 'BugPortletSubscribersWithDetails',
61 'BugSubscriptionAddView',
62 'BugSubscriptionListView',
63 ]
64@@ -546,46 +545,6 @@
65 'dupe_links_string': dupe_links_string})
66
67
68-class BugPortletSubcribersContents(LaunchpadView, BugViewMixin):
69- """View for the contents for the subscribers portlet."""
70-
71- @property
72- def sorted_direct_subscriptions(self):
73- """Get the list of direct subscriptions to the bug.
74-
75- The list is sorted such that subscriptions you can unsubscribe appear
76- before all other subscriptions.
77- """
78- direct_subscriptions = [
79- SubscriptionAttrDecorator(subscription)
80- for subscription in self.context.getDirectSubscriptions().sorted]
81- can_unsubscribe = []
82- cannot_unsubscribe = []
83- for subscription in direct_subscriptions:
84- if not check_permission('launchpad.View', subscription.person):
85- continue
86- if subscription.person == self.user:
87- can_unsubscribe = [subscription] + can_unsubscribe
88- elif subscription.canBeUnsubscribedByUser(self.user):
89- can_unsubscribe.append(subscription)
90- else:
91- cannot_unsubscribe.append(subscription)
92- return can_unsubscribe + cannot_unsubscribe
93-
94-
95-class BugPortletDuplicateSubcribersContents(LaunchpadView, BugViewMixin):
96- """View for the contents for the subscribers-from-dupes portlet block."""
97-
98- @property
99- def sorted_subscriptions_from_dupes(self):
100- """Get the list of subscriptions to duplicates of this bug."""
101- return [
102- SubscriptionAttrDecorator(subscription)
103- for subscription in sorted(
104- self.context.getSubscriptionsFromDuplicates(),
105- key=(lambda subscription: subscription.person.displayname))]
106-
107-
108 class BugPortletSubscribersWithDetails(LaunchpadView):
109 """A view that returns a JSON dump of the subscriber details for a bug."""
110
111@@ -596,10 +555,12 @@
112 details = list(self.context.getDirectSubscribersWithDetails())
113 api_request = IWebServiceClientRequest(self.request)
114 for person, subscription in details:
115- if person == self.user:
116- # Skip the current user viewing the page.
117- continue
118 can_edit = self.user is not None and self.user.inTeam(person)
119+ if person == self.user or (person.private and not can_edit):
120+ # Skip the current user viewing the page,
121+ # and private teams user is not a member of.
122+ continue
123+
124 subscriber = {
125 'name': person.name,
126 'display_name': person.displayname,
127@@ -641,20 +602,6 @@
128 return self.subscriber_data_js
129
130
131-class BugPortletSubcribersIds(LaunchpadView, BugViewMixin):
132- """A view that returns a JSON dump of the subscriber IDs for a bug."""
133-
134- @property
135- def subscriber_ids_js(self):
136- """Return subscriber_ids in a form suitable for JavaScript use."""
137- return dumps(self.subscriber_ids)
138-
139- def render(self):
140- """Override the default render() to return only JSON."""
141- self.request.response.setHeader('content-type', 'application/json')
142- return self.subscriber_ids_js
143-
144-
145 class SubscriptionAttrDecorator:
146 """A BugSubscription with added attributes for HTML/JS."""
147 delegates(IBugSubscription, 'subscription')
148
149=== modified file 'lib/lp/bugs/browser/configure.zcml'
150--- lib/lp/bugs/browser/configure.zcml 2011-06-15 11:09:59 +0000
151+++ lib/lp/bugs/browser/configure.zcml 2011-06-15 11:10:20 +0000
152@@ -1085,23 +1085,6 @@
153 </browser:pages>
154 <browser:page
155 for="lp.bugs.interfaces.bug.IBug"
156- name="+bug-portlet-subscribers-content"
157- class="lp.bugs.browser.bugsubscription.BugPortletSubcribersContents"
158- template="../templates/bug-portlet-subscribers-content.pt"
159- permission="zope.Public"/>
160- <browser:page
161- for="lp.bugs.interfaces.bug.IBug"
162- name="+bug-portlet-dupe-subscribers-content"
163- class="lp.bugs.browser.bugsubscription.BugPortletDuplicateSubcribersContents"
164- template="../templates/bug-portlet-dupe-subscribers-content.pt"
165- permission="zope.Public"/>
166- <browser:page
167- for="lp.bugs.interfaces.bug.IBug"
168- name="+bug-portlet-subscribers-ids"
169- class="lp.bugs.browser.bugsubscription.BugPortletSubcribersIds"
170- permission="zope.Public"/>
171- <browser:page
172- for="lp.bugs.interfaces.bug.IBug"
173 name="+bug-portlet-subscribers-details"
174 class="
175 lp.bugs.browser.bugsubscription.BugPortletSubscribersWithDetails"
176
177=== removed file 'lib/lp/bugs/browser/tests/bug-portlet-subscribers-content.txt'
178--- lib/lp/bugs/browser/tests/bug-portlet-subscribers-content.txt 2010-09-14 15:32:53 +0000
179+++ lib/lp/bugs/browser/tests/bug-portlet-subscribers-content.txt 1970-01-01 00:00:00 +0000
180@@ -1,25 +0,0 @@
181-= Subscribers portlet content view =
182-
183-The view for the subscribers portlet exposes methods for getting the
184-subscription data ready for display. The list of subscribers is reordered so
185-that subscriptions that the current viewer can unsubscriber appear at the top
186-of the list.
187-
188- >>> from lp.bugs.browser.bugsubscription import (
189- ... BugPortletSubcribersContents)
190- >>> login('foo.bar@canonical.com')
191- >>> reporter = factory.makePerson(displayname='Bug Reporter')
192- >>> bug = factory.makeBug(owner=reporter)
193- >>> view = BugPortletSubcribersContents(bug, None)
194- >>> bug.subscribe(view.user, view.user)
195- <lp.bugs.model.bugsubscription.BugSubscription ...>
196- >>> bug.subscribe(view.user.teams_participated_in[0], view.user)
197- <lp.bugs.model.bugsubscription.BugSubscription ...>
198- >>> for subscription in view.sorted_direct_subscriptions:
199- ... print '%s %s' % (
200- ... subscription.person.displayname,
201- ... subscription.canBeUnsubscribedByUser(view.user))
202- Foo Bar True
203- Canonical Partner Developers True
204- Bug Reporter False
205-
206
207=== removed file 'lib/lp/bugs/browser/tests/bug-subscription-views.txt'
208--- lib/lp/bugs/browser/tests/bug-subscription-views.txt 2010-08-02 17:48:13 +0000
209+++ lib/lp/bugs/browser/tests/bug-subscription-views.txt 1970-01-01 00:00:00 +0000
210@@ -1,40 +0,0 @@
211-Bug subscription views
212-======================
213-
214-Getting subscriber CSS IDs
215---------------------------
216-
217-It's possible to get a mapping of bug subscriber names to CSS IDs using
218-the +bug-portlet-subscribers-ids view.
219-
220- >>> from lp.bugs.interfaces.bug import IBugSet
221- >>> bug_15 = getUtility(IBugSet).get(15)
222-
223- >>> subscriber_ids_view = create_initialized_view(
224- ... bug_15, '+bug-portlet-subscribers-ids')
225-
226-The view's subscriber_ids_js property returns a JSON struct of the
227-person name to CSS ID mappings for the bug's subscribers.
228-
229- >>> print subscriber_ids_view.subscriber_ids_js
230- {"name16": "subscriber-16"}
231-
232-If bug 15 is marked as a duplicate of another bug, its subscribers will
233-be included in that bugs subscriber_ids_js mapping.
234-
235- >>> bug_13 = getUtility(IBugSet).get(13)
236- >>> subscriber_ids_view = create_initialized_view(
237- ... bug_13, '+bug-portlet-subscribers-ids')
238-
239- >>> print subscriber_ids_view.subscriber_ids_js
240- {"name12": "subscriber-12"}
241-
242- >>> login('foo.bar@canonical.com')
243- >>> bug_15.markAsDuplicate(bug_13)
244- >>> bug_13 = getUtility(IBugSet).get(13)
245-
246- >>> subscriber_ids_view = create_initialized_view(
247- ... bug_13, '+bug-portlet-subscribers-ids')
248-
249- >>> print subscriber_ids_view.subscriber_ids_js
250- {"name12": "subscriber-12", "name16": "subscriber-16"}
251
252=== modified file 'lib/lp/bugs/browser/tests/test_bug_views.py'
253--- lib/lp/bugs/browser/tests/test_bug_views.py 2011-06-15 11:09:59 +0000
254+++ lib/lp/bugs/browser/tests/test_bug_views.py 2011-06-15 11:10:20 +0000
255@@ -109,17 +109,6 @@
256 self.assertTrue('menu-link-editsubscriptions' not in html)
257 self.assertTrue('/+subscriptions' not in html)
258
259- def test_mute_subscription_link_not_shown_with_no_feature_flag(self):
260- # Mute link is not shown when the feature flag is off.
261- person = self.factory.makePerson()
262- with person_logged_in(person):
263- with FeatureFixture({self.feature_flag_1: None}):
264- view = create_initialized_view(
265- self.bug, name="+portlet-subscription")
266- self.assertFalse(view.user_should_see_mute_link)
267- html = view.render()
268- self.assertFalse('mute_subscription' in html)
269-
270 def _hasCSSClass(self, html, element_id, css_class):
271 # Return True if element with ID `element_id` in `html` has
272 # a CSS class `css_class`.
273
274=== removed file 'lib/lp/bugs/browser/tests/test_bugsubscription.py'
275--- lib/lp/bugs/browser/tests/test_bugsubscription.py 2010-10-04 19:50:45 +0000
276+++ lib/lp/bugs/browser/tests/test_bugsubscription.py 1970-01-01 00:00:00 +0000
277@@ -1,23 +0,0 @@
278-# Copyright 2009 Canonical Ltd. This software is licensed under the
279-# GNU Affero General Public License version 3 (see the file LICENSE).
280-
281-import unittest
282-
283-from canonical.launchpad.testing.systemdocs import (
284- LayeredDocFileSuite,
285- setUp,
286- tearDown,
287- )
288-from canonical.testing.layers import LaunchpadFunctionalLayer
289-
290-
291-def test_suite():
292- suite = unittest.TestSuite()
293- suite.addTest(LayeredDocFileSuite(
294- 'bug-portlet-subscribers-content.txt', setUp=setUp, tearDown=tearDown,
295- layer=LaunchpadFunctionalLayer))
296- return suite
297-
298-
299-if __name__ == '__main__':
300- unittest.TextTestRunner().run(test_suite())
301
302=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
303--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-06-15 11:09:59 +0000
304+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-06-15 11:10:20 +0000
305@@ -14,7 +14,6 @@
306 from canonical.testing.layers import LaunchpadFunctionalLayer
307 from lazr.restful.interfaces import IWebServiceClientRequest
308 from lp.bugs.browser.bugsubscription import (
309- BugPortletSubcribersIds,
310 BugPortletSubscribersWithDetails,
311 BugSubscriptionListView,
312 BugSubscriptionSubscribeSelfView,
313@@ -343,58 +342,6 @@
314 'bug-notification-level-field', widget_class)
315
316
317-class BugSubscriptionAdvancedFeaturesPortletTestCase(TestCaseWithFactory):
318-
319- layer = LaunchpadFunctionalLayer
320- feature_flag = 'malone.advanced-subscriptions.enabled'
321-
322- def setUp(self):
323- super(BugSubscriptionAdvancedFeaturesPortletTestCase, self).setUp()
324- self.bug = self.factory.makeBug()
325- self.person = self.factory.makePerson()
326- self.target = self.bug.default_bugtask.target
327- subscriber = self.factory.makePerson()
328- with person_logged_in(self.person):
329- self.target.addBugSubscription(subscriber, subscriber)
330-
331- def get_contents(self, flag):
332- with person_logged_in(self.person):
333- with FeatureFixture({self.feature_flag: flag}):
334- bug_view = create_initialized_view(
335- self.bug, name="+bug-portlet-subscribers-content")
336- return bug_view.render()
337-
338- def test_also_notified_suppressed(self):
339- # If the advanced-subscription.enabled feature flag is on then the
340- # "Also notified" portion of the portlet is suppressed.
341- contents = self.get_contents(ON)
342- self.assertFalse('Also notified' in contents)
343-
344- def test_also_notified_not_suppressed(self):
345- # If the advanced-subscription.enabled feature flag is off then the
346- # "Also notified" portion of the portlet is shown.
347- contents = self.get_contents(OFF)
348- self.assertTrue('Also notified' in contents)
349-
350-
351-class BugPortletSubcribersIdsTests(TestCaseWithFactory):
352-
353- layer = LaunchpadFunctionalLayer
354-
355- def test_content_type(self):
356- bug = self.factory.makeBug()
357-
358- person = self.factory.makePerson()
359- with person_logged_in(person):
360- harness = LaunchpadFormHarness(
361- bug.default_bugtask, BugPortletSubcribersIds)
362- harness.view.render()
363-
364- self.assertEqual(
365- harness.request.response.getHeader('content-type'),
366- 'application/json')
367-
368-
369 class BugSubscriptionsListViewTestCase(TestCaseWithFactory):
370 """Tests for the BugSubscriptionsListView."""
371
372
373=== modified file 'lib/lp/bugs/javascript/bugtask_index.js'
374--- lib/lp/bugs/javascript/bugtask_index.js 2011-06-08 01:38:11 +0000
375+++ lib/lp/bugs/javascript/bugtask_index.js 2011-06-15 11:10:20 +0000
376@@ -40,8 +40,6 @@
377 var link_branch_link;
378
379 namespace.setup_bugtask_index = function() {
380- Y.lp.bugs.bugtask_index.portlets.setup_portlet_handlers();
381-
382 /*
383 * Display the privacy notification if the bug is private
384 */
385@@ -1115,5 +1113,5 @@
386 "lazr.formoverlay", "lazr.anim", "lazr.base",
387 "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
388 "lp.client", "escape",
389- "lp.client.plugins", "lp.bugs.bugtask_index.portlets",
390- "lp.bugs.subscriber", "lp.app.errors"]});
391+ "lp.client.plugins", "lp.bugs.subscriber",
392+ "lp.app.errors"]});
393
394=== removed file 'lib/lp/bugs/javascript/bugtask_index_portlets.js'
395--- lib/lp/bugs/javascript/bugtask_index_portlets.js 2011-06-15 11:09:59 +0000
396+++ lib/lp/bugs/javascript/bugtask_index_portlets.js 1970-01-01 00:00:00 +0000
397@@ -1,897 +0,0 @@
398-/* Copyright 2011 Canonical Ltd. This software is licensed under the
399- * GNU Affero General Public License version 3 (see the file LICENSE).
400- *
401- * Form overlay widgets and subscriber handling for bug pages.
402- *
403- * @module bugs
404- * @submodule bugtask_index.portlets
405- */
406-
407-YUI.add('lp.bugs.bugtask_index.portlets', function(Y) {
408-
409-var namespace = Y.namespace('lp.bugs.bugtask_index.portlets');
410-
411-// The launchpad js client used.
412-var lp_client;
413-
414-// The launchpad client entry for the current bug.
415-var lp_bug_entry;
416-
417-// The bug itself, taken from cache.
418-var bug_repr;
419-
420-// A boolean telling us whether advanced subscription features are to be
421-// used or not.
422-var use_advanced_subscriptions = false;
423-
424-var subscription_labels = Y.lp.bugs.subscriber.subscription_labels;
425-
426-submit_button_html =
427- '<button type="submit" name="field.actions.change" ' +
428- 'value="Change" class="lazr-pos lazr-btn" >OK</button>';
429-cancel_button_html =
430- '<button type="button" name="field.actions.cancel" ' +
431- 'class="lazr-neg lazr-btn" >Cancel</button>';
432-
433-// The set of subscriber CSS IDs as a JSON struct.
434-var subscriber_ids;
435-
436-/*
437- * An object representing the bugtask subscribers portlet.
438- *
439- * Since the portlet loads via XHR and inline subscribing
440- * depends on that portlet being loaded, setup a custom
441- * event object, to provide a hook for initializing subscription
442- * link callbacks after custom events.
443- */
444-var PortletTarget = function() {};
445-Y.augment(PortletTarget, Y.Event.Target);
446-namespace.portlet = new PortletTarget();
447-
448-/*
449- * Create the lp client and bug entry if we haven't done so already.
450- *
451- * @method setup_client_and_bug
452- */
453-function setup_client_and_bug() {
454- lp_client = new Y.lp.client.Launchpad();
455-
456- if (bug_repr === undefined) {
457- bug_repr = LP.cache.bug;
458- lp_bug_entry = new Y.lp.client.Entry(
459- lp_client, bug_repr, bug_repr.self_link);
460- }
461-}
462-
463-namespace.load_subscribers_portlet = function(
464- subscription_link, subscription_link_handler) {
465- if (Y.UA.ie) {
466- return null;
467- }
468-
469- Y.one('#subscribers-portlet-spinner').setStyle('display', 'block');
470-
471- function hide_spinner() {
472- Y.one('#subscribers-portlet-spinner').setStyle('display', 'none');
473- // Fire a custom event to notify that the initial click
474- // handler on subscription_link set above should be
475- // cleared.
476- if (namespace) {
477- namespace.portlet.fire(
478- 'bugs:portletloadfailed', subscription_link_handler);
479- }
480- }
481-
482- function setup_portlet(transactionid, response, args) {
483- hide_spinner();
484- Y.one('#portlet-subscribers')
485- .appendChild(Y.Node.create(response.responseText));
486-
487- // Fire a custom portlet loaded event to notify when
488- // it's safe to setup subscriber link callbacks.
489- namespace.portlet.fire('bugs:portletloaded');
490- }
491-
492- var config = {on: {success: setup_portlet,
493- failure: hide_spinner}};
494- var url = Y.one(
495- '#subscribers-content-link').getAttribute('href').replace(
496- 'bugs.', '');
497- Y.io(url, config);
498-};
499-
500-
501-namespace.setup_portlet_handlers = function() {
502- namespace.portlet.subscribe('bugs:portletloaded', function() {
503- load_subscriber_ids();
504- });
505- /*
506- * If the subscribers portlet fails to load, clear any
507- * click handlers, so the normal subscribe page can be reached.
508- */
509- namespace.portlet.subscribe('bugs:portletloadfailed', function(handler) {
510- handler.detach();
511- });
512- namespace.portlet.subscribe('bugs:dupeportletloaded', function() {
513- setup_subscription_link_handlers();
514- setup_unsubscribe_icon_handlers();
515- });
516- /* If the dupe subscribers portlet fails to load,
517- * be sure to try to handle any unsub icons that may
518- * exist for others.
519- */
520- namespace.portlet.subscribe(
521- 'bugs:dupeportletloadfailed',
522- function(handlers) {
523- setup_subscription_link_handlers();
524- setup_unsubscribe_icon_handlers();
525- });
526-
527- /* If loading the subscriber IDs JSON has succeeded, set up the
528- * subscription link handlers and load the subscribers from dupes.
529- */
530- namespace.portlet.subscribe(
531- 'bugs:portletsubscriberidsloaded',
532- function() {
533- load_subscribers_from_duplicates();
534- });
535-
536- /* If loading the subscriber IDs JSON fails we still need to load the
537- * subscribers from duplicates but we don't set up the subscription link
538- * handlers.
539- */
540- namespace.portlet.subscribe(
541- 'bugs:portletsubscriberidsfailed',
542- function() {
543- load_subscribers_from_duplicates();
544- });
545-
546- /*
547- * Subscribing someone else requires loading a grayed out
548- * username into the DOM until the subscribe action completes.
549- * There are a couple XHR requests in check_can_be_unsubscribed
550- * before the subscribe work can be done, so fire a custom event
551- * bugs:nameloaded and do the work here when the event fires.
552- */
553- namespace.portlet.subscribe('bugs:nameloaded', function(subscription) {
554- var error_handler = new Y.lp.client.ErrorHandler();
555- error_handler.clearProgressUI = function() {
556- var temp_link = Y.one('#temp-username');
557- if (temp_link) {
558- var temp_parent = temp_link.get('parentNode');
559- temp_parent.removeChild(temp_link);
560- }
561- };
562- error_handler.showError = function(error_msg) {
563- Y.lp.app.errors.display_error(
564- Y.one('.menu-link-addsubscriber'), error_msg);
565- };
566-
567- var config = {
568- on: {
569- success: function() {
570- var temp_link = Y.one('#temp-username');
571- var temp_spinner = Y.one('#temp-name-spinner');
572- temp_link.removeChild(temp_spinner);
573- var anim = Y.lazr.anim.green_flash({ node: temp_link });
574- anim.on('end', function() {
575- add_user_name_link(subscription);
576- var temp_parent = temp_link.get('parentNode');
577- temp_parent.removeChild(temp_link);
578- });
579- anim.run();
580- },
581- failure: error_handler.getFailureHandler()
582- },
583- parameters: {
584- person: Y.lp.client.get_absolute_uri(
585- subscription.get('person').get('escaped_uri')),
586- suppress_notify: false
587- }
588- };
589- lp_client.named_post(bug_repr.self_link, 'subscribe', config);
590- });
591-};
592-
593-function load_subscriber_ids() {
594- function on_success(transactionid, response, args) {
595- try {
596- subscriber_ids = Y.JSON.parse(response.responseText);
597-
598- // Fire a custom event to trigger the setting-up of the
599- // subscription handlers.
600- namespace.portlet.fire('bugs:portletsubscriberidsloaded');
601- } catch (e) {
602- // Fire an event to signal failure. This ensures that the
603- // subscribers-from-dupes still get loaded into the portlet.
604- namespace.portlet.fire('bugs:portletsubscriberidsfailed');
605- }
606- }
607-
608- function on_failure() {
609- // Fire an event to signal failure. This ensures that the
610- // subscribers-from-dupes still get loaded into the portlet.
611- namespace.portlet.fire('bugs:portletsubscriberidsfailed');
612- }
613-
614- var config = {on: {success: on_success,
615- failure: on_failure}};
616- var url = Y.one(
617- '#subscribers-ids-link').getAttribute('href');
618- Y.io(url, config);
619-}
620-
621-/*
622- * Set click handlers for unsubscribe remove icons.
623- *
624- * @method setup_unsubscribe_icon_handlers
625- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
626- */
627-function setup_unsubscribe_icon_handlers() {
628- var subscription = new Y.lp.bugs.subscriber.Subscription({
629- link: Y.one('.menu-link-subscription'),
630- spinner: Y.one('#sub-unsub-spinner'),
631- subscriber: new Y.lp.bugs.subscriber.Subscriber({
632- uri: LP.links.me,
633- subscriber_ids: subscriber_ids
634- })
635- });
636-
637- Y.on('click', function(e) {
638- e.halt();
639- unsubscribe_user_via_icon(e.target, subscription);
640- }, '.unsub-icon');
641-}
642-
643-/*
644- * Set up and return a Subscription object for the direct subscription
645- * link.
646- */
647-function get_subscribe_self_subscription() {
648- setup_client_and_bug();
649- var subscription = new Y.lp.bugs.subscriber.Subscription({
650- link: Y.one('.menu-link-subscription'),
651- spinner: Y.one('#sub-unsub-spinner'),
652- subscriber: new Y.lp.bugs.subscriber.Subscriber({
653- uri: LP.links.me,
654- subscriber_ids: subscriber_ids
655- })
656- });
657-
658- subscription.set('can_be_unsubscribed', true);
659- subscription.set('person', subscription.get('subscriber'));
660- subscription.set('is_team', false);
661- var css_name = subscription.get('person').get('css_name');
662- var direct_css_name = '#direct-' + css_name;
663- var direct_node = Y.one(direct_css_name);
664- var is_direct = direct_node !== null;
665- subscription.set('is_direct', is_direct);
666- var dupe_css_name = '#dupe-' + css_name;
667- var dupe_node = Y.one(dupe_css_name);
668- var has_dupes = dupe_node !== null;
669- subscription.set('has_dupes', has_dupes);
670- return subscription;
671-}
672-
673-
674-/*
675- * Set up and return a Subscription object for the team subscription
676- * link.
677- */
678-function get_team_subscription(team_uri) {
679- setup_client_and_bug();
680- var subscription = new Y.lp.bugs.subscriber.Subscription({
681- link: Y.one('.menu-link-subscription'),
682- spinner: Y.one('#sub-unsub-spinner'),
683- subscriber: new Y.lp.bugs.subscriber.Subscriber({
684- uri: team_uri,
685- subscriber_ids: subscriber_ids
686- })
687- });
688-
689- subscription.set('is_direct', true);
690- subscription.set('has_dupes', false);
691- subscription.set('can_be_unsubscribed', true);
692- subscription.set('person', subscription.get('subscriber'));
693- subscription.set('is_team', true);
694- return subscription;
695-}
696-
697-/*
698- * Initialize callbacks for subscribe/unsubscribe links.
699- *
700- * @method setup_subscription_link_handlers
701- */
702-function setup_subscription_link_handlers() {
703- if (LP.links.me === undefined) {
704- return;
705- }
706-
707- var subscription = get_subscribe_self_subscription();
708-
709-}
710-
711-function load_subscribers_from_duplicates() {
712- if (Y.UA.ie) {
713- return null;
714- }
715-
716- Y.one('#subscribers-portlet-dupe-spinner').setStyle(
717- 'display', 'block');
718-
719- function hide_spinner() {
720- Y.one('#subscribers-portlet-dupe-spinner').setStyle(
721- 'display', 'none');
722- }
723-
724- function on_failure(transactionid, response, args) {
725- hide_spinner();
726- // Fire a custom event to signal failure, so that
727- // any remaining unsub icons can be hooked up.
728- namespace.portlet.fire('bugs:dupeportletloadfailed');
729- }
730-
731- function on_success(transactionid, response, args) {
732- hide_spinner();
733-
734- var dupe_subscribers_container = Y.one(
735- '#subscribers-from-duplicates-container');
736- dupe_subscribers_container.set(
737- 'innerHTML',
738- dupe_subscribers_container.get('innerHTML') +
739- response.responseText);
740-
741- // Fire a custom portlet loaded event to notify when
742- // it's safe to setup dupe subscriber link callbacks.
743- namespace.portlet.fire('bugs:dupeportletloaded');
744- }
745-
746- var config = {on: {success: on_success,
747- failure: on_failure}};
748- var url = Y.one(
749- '#subscribers-from-dupes-content-link').getAttribute(
750- 'href').replace('bugs.', '');
751- Y.io(url, config);
752-}
753-
754-/*
755- * Add the user name to the subscriber's list.
756- *
757- * @method add_user_name_link
758- */
759-function add_user_name_link(subscription) {
760- // Be paranoid about display_name, since timeouts or other errors
761- // could mean display_name wasn't set on initialization.
762- subscription.get('person').set_display_name(function () {
763- _add_user_name_link(subscription);
764- });
765-}
766-
767-function _add_user_name_link(subscription) {
768- var person = subscription.get('person');
769- var link_node = build_user_link_html(subscription);
770- var subscribers = Y.one('#subscribers-links');
771- if (subscription.is_current_user_subscribing()) {
772- // If this is the current user, then top post the name and be done.
773- subscribers.insertBefore(link_node, subscribers.get('firstChild'));
774- } else {
775- var next = get_next_subscriber_node(subscription);
776- if (next) {
777- subscribers.insertBefore(link_node, next);
778- } else {
779- subscribers.appendChild(link_node);
780- }
781- }
782- // Handle the case of no previous subscribers.
783- var none_subscribers = Y.one('#none-subscribers');
784- if (none_subscribers) {
785- var none_parent = none_subscribers.get('parentNode');
786- none_parent.removeChild(none_subscribers);
787- }
788- // Highlight the new addition with a green flash.
789- Y.lazr.anim.green_flash({ node: link_node }).run();
790- // Set the click handler if adding a remove icon.
791- if (subscription.can_be_unsubscribed_by_user()) {
792- var remove_icon =
793- Y.one('#unsubscribe-icon-' + person.get('css_name'));
794- remove_icon.on('click', function(e) {
795- e.halt();
796- unsubscribe_user_via_icon(e.target, subscription);
797- });
798- }
799-}
800-
801-/*
802- * Unsubscribe a user from this bugtask when a remove icon is clicked.
803- *
804- * @method unsubscribe_user_via_icon
805- * @param icon {Node} The remove icon that was clicked.
806- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
807-*/
808-function unsubscribe_user_via_icon(icon, subscription) {
809- icon.set('src', '/@@/spinner');
810- var icon_parent = icon.get('parentNode');
811-
812- var user_uri = get_user_uri_from_icon(icon);
813- var person = new Y.lp.bugs.subscriber.Subscriber({
814- uri: user_uri,
815- subscriber_ids: subscriber_ids
816- });
817- subscription.set('person', person);
818-
819- // Determine if this is a dupe.
820- var is_dupe;
821- var icon_parent_div = icon_parent.get('parentNode');
822- var dupe_id = 'dupe-' + person.get('css_name');
823- if (icon_parent_div.get('id') === dupe_id) {
824- is_dupe = true;
825- } else {
826- is_dupe = false;
827- }
828-
829- var error_handler = new Y.lp.client.ErrorHandler();
830- error_handler.clearProgressUI = function () {
831- icon.set('src', '/@@/remove');
832- // Grab the icon again to reset to click handler.
833- var unsubscribe_icon = Y.one(
834- '#unsubscribe-icon-' + person.get('css_name'));
835- unsubscribe_icon.on('click', function(e) {
836- e.halt();
837- unsubscribe_user_via_icon(e.target, subscription);
838- });
839-
840- };
841- error_handler.showError = function (error_msg) {
842- var flash_node = Y.one('.' + person.get('css_name'));
843- Y.lp.app.errors.display_error(flash_node, error_msg);
844-
845- };
846-
847- var subscription_link = subscription.get('link');
848- var config = {
849- on: {
850- success: function(client) {
851- var num_person_links = Y.all(
852- '.' + person.get('css_name')).size();
853- Y.lp.bugs.subscribers_list.remove_user_link(person, is_dupe);
854- var has_direct, has_dupes;
855- if (num_person_links === 1 &&
856- subscription.is_current_user_subscribing()) {
857- // Current user has been completely unsubscribed.
858- subscription.disable_spinner(
859- subscription_labels.SUBSCRIBE);
860- has_direct = false;
861- has_dupes = false;
862- } else {
863- // If we removed the duplicate subscription,
864- // we are left with the direct one, and vice versa.
865- has_direct = is_dupe;
866- has_dupes = !is_dupe;
867- }
868- subscription.set('is_direct', has_direct);
869- subscription.set('has_dupes', has_dupes);
870- set_subscription_link_parent_class(
871- subscription_link, has_direct, has_dupes);
872- },
873-
874- failure: error_handler.getFailureHandler()
875- }
876- };
877-
878- if (!subscription.is_current_user_subscribing()) {
879- config.parameters = {
880- person: Y.lp.client.get_absolute_uri(user_uri)
881- };
882- }
883-
884- if (is_dupe) {
885- lp_client.named_post(
886- bug_repr.self_link, 'unsubscribeFromDupes', config);
887- } else {
888- lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
889- }
890-}
891-
892-/*
893- * Initialize click handler for the subscribe someone else link.
894- *
895- * @method setup_subscribe_someone_else_handler
896- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
897- */
898-function setup_subscribe_someone_else_handler(subscription) {
899- var config = {
900- header: 'Subscribe someone else',
901- step_title: 'Search',
902- picker_activator: '.menu-link-addsubscriber'
903- };
904-
905- config.save = function(result) {
906- subscribe_someone_else(result, subscription);
907- };
908- var picker = Y.lp.app.picker.create('ValidPersonOrTeam', config);
909-}
910-
911-/*
912- * Build the HTML for a user link for the subscribers list.
913- *
914- * @method build_user_link_html
915- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
916- * @return html {String} The HTML used for creating a subscriber link.
917- */
918-function build_user_link_html(subscription) {
919- var name = subscription.get('person').get('name');
920- var css_name = subscription.get('person').get('css_name');
921- var full_name = subscription.get('person').get('full_display_name');
922- var display_name = subscription.get('person').get('display_name');
923- var terms = {
924- name: name,
925- css_name: css_name,
926- display_name: display_name,
927- full_name: full_name
928- };
929-
930- if (subscription.is_current_user_subscribing()) {
931- terms.subscribed_by = 'themselves';
932- } else {
933- terms.subscribed_by = 'by ' + full_name;
934- }
935-
936- var html = Y.Node.create('<div><a></a></div>');
937- html.addClass(terms.css_name);
938-
939- if (subscription.has_duplicate_subscriptions()) {
940- html.set('id', 'dupe-' + terms.css_name);
941- } else {
942- html.set('id', 'direct-' + terms.css_name);
943- }
944-
945- html.one('a')
946- .set('href', '/~' + terms.name)
947- .set('name', terms.full_name)
948- .set('title', 'Subscribed ' + terms.subscribed_by);
949-
950- var span;
951- if (subscription.is_team()) {
952- span = '<span class="sprite team"></span>';
953- } else {
954- span = '<span class="sprite person"></span>';
955- }
956-
957- html.one('a')
958- .appendChild(Y.Node.create(span))
959- .appendChild(document.createTextNode(terms.display_name));
960-
961- // Add remove icon if the current user can unsubscribe the subscriber.
962- if (subscription.can_be_unsubscribed_by_user()) {
963- var icon_html = Y.Node.create(
964- '<a href="+subscribe">' +
965- '<img class="unsub-icon" src="/@@/remove" alt="Remove" /></a>');
966- icon_html
967- .set('id', 'unsubscribe-' + terms.css_name)
968- .set('title', 'Unsubscribe ' + terms.full_name);
969- icon_html.one('img')
970- .set('id', 'unsubscribe-icon-' + terms.css_name);
971- html.appendChild(icon_html);
972- }
973-
974- return html;
975-}
976-
977-/*
978- * Returns the next node in alphabetical order after the subscriber
979- * node now being added. No node is returned to append to end of list.
980- *
981- * The name can appear in one of two different lists. 1) The list of
982- * subscribers that can be unsubscribed by the current user, and
983- * 2) the list of subscribers that cannot be unsubscribed.
984- *
985- * @method get_next_subscriber_node
986- * @param subscription_link {Node} The sub/unsub link.
987- * @return {Node} The node appearing next in the subscriber list or
988- * undefined if no node is next.
989- */
990-function get_next_subscriber_node(subscription) {
991- var full_name = subscription.get('person').get('full_display_name');
992- var can_be_unsubscribed = subscription.can_be_unsubscribed_by_user();
993- var nodes_by_name = {};
994- var unsubscribables = [];
995- var not_unsubscribables = [];
996-
997- // Use the list of subscribers pulled from the DOM to have sortable
998- // lists of unsubscribable vs. not unsubscribable person links.
999- var all_subscribers = Y.all('#subscribers-links div');
1000- if (all_subscribers.size() > 0) {
1001- all_subscribers.each(function(sub_link) {
1002- if (sub_link.getAttribute('id') !== 'temp-username') {
1003- // User's displayname is found via the link's "name"
1004- // attribute.
1005- var sub_link_name = sub_link.one('a').getAttribute('name');
1006- nodes_by_name[sub_link_name] = sub_link;
1007- if (sub_link.one('img.unsub-icon')) {
1008- unsubscribables.push(sub_link_name);
1009- } else {
1010- not_unsubscribables.push(sub_link_name);
1011- }
1012- }
1013- });
1014-
1015- // Add the current subscription.
1016- if (can_be_unsubscribed) {
1017- unsubscribables.push(full_name);
1018- } else {
1019- not_unsubscribables.push(full_name);
1020- }
1021- unsubscribables.sort();
1022- not_unsubscribables.sort();
1023- } else {
1024- // If there is no all_subscribers, then we're dealing with
1025- // the printed None, so return.
1026- return;
1027- }
1028-
1029- var i;
1030- if ((!unsubscribables && !not_unsubscribables) ||
1031- // If A) neither list exists, B) the user belongs in the second
1032- // list but the second list doesn't exist, or C) user belongs in the
1033- // first list and the second doesn't exist, return no node to append.
1034- (!can_be_unsubscribed && !not_unsubscribables) ||
1035- (can_be_unsubscribed && unsubscribables && !not_unsubscribables)) {
1036- return;
1037- } else if (
1038- // If the user belongs in the first list, and the first list
1039- // doesn't exist, but the second one does, return the first node
1040- // in the second list.
1041- can_be_unsubscribed && !unsubscribables && not_unsubscribables) {
1042- return nodes_by_name[not_unsubscribables[0]];
1043- } else if (can_be_unsubscribed) {
1044- // If the user belongs in the first list, loop the list for position.
1045- for (i=0; i<unsubscribables.length; i++) {
1046- if (unsubscribables[i] === full_name) {
1047- if (i+1 < unsubscribables.length) {
1048- return nodes_by_name[unsubscribables[i+1]];
1049- // If the current link should go at the end of the first
1050- // list and we're at the end of that list, return the
1051- // first node of the second list. Due to earlier checks
1052- // we can be sure this list exists.
1053- } else if (i+1 >= unsubscribables.length) {
1054- return nodes_by_name[not_unsubscribables[0]];
1055- }
1056- }
1057- }
1058- } else if (!can_be_unsubscribed) {
1059- // If user belongs in the second list, loop the list for position.
1060- for (i=0; i<not_unsubscribables.length; i++) {
1061- if (not_unsubscribables[i] === full_name) {
1062- if (i+1 < not_unsubscribables.length) {
1063- return nodes_by_name[not_unsubscribables[i+1]];
1064- } else {
1065- return;
1066- }
1067- }
1068- }
1069- }
1070-}
1071-
1072-/*
1073- * Traverse the DOM of a given remove icon to find
1074- * the user's link. Returns a URI of the form "/~username".
1075- *
1076- * @method get_user_uri_from_icon
1077- * @param icon {Node} The node representing a remove icon.
1078- * @return user_uri {String} The user's uri, without the hostname.
1079- */
1080-function get_user_uri_from_icon(icon) {
1081- var parent_div = icon.get('parentNode').get('parentNode');
1082- // This should be parent_div.firstChild, but because of #text
1083- // and cross-browser issues, using the YUI query syntax is
1084- // safer here.
1085- var user_uri = parent_div.one('a').getAttribute('href');
1086-
1087- // Strip the domain off. We just want a path.
1088- var host_start = user_uri.indexOf('//');
1089- if (host_start !== -1) {
1090- var host_end = user_uri.indexOf('/', host_start+2);
1091- return user_uri.substring(host_end, user_uri.length);
1092- }
1093-
1094- return user_uri;
1095-}
1096-
1097-/*
1098- * Set the class on subscription link's parentNode.
1099- *
1100- * This is used to reset the class used by the
1101- * click handler to know which link was clicked.
1102- *
1103- * @method set_subscription_link_parent_class
1104- * @param subscription_link {Node} The sub/unsub link.
1105- * @param subscribed {Boolean} The sub/unsub'ed flag for the class.
1106- * @param dupe_subscribed {Boolean} The sub/unsub'ed flag for dupes
1107- * on the class.
1108- */
1109-function set_subscription_link_parent_class(
1110- user_link, subscribed, dupe_subscribed) {
1111-
1112- var parent = user_link.get('parentNode');
1113- if (subscribed) {
1114- parent.removeClass('subscribed-false');
1115- parent.addClass('subscribed-true');
1116- } else {
1117- parent.removeClass('subscribed-true');
1118- parent.addClass('subscribed-false');
1119- }
1120-
1121- if (dupe_subscribed) {
1122- parent.removeClass('dup-subscribed-false');
1123- parent.addClass('dup-subscribed-true');
1124- } else {
1125- parent.removeClass('dup-subscribed-true');
1126- parent.addClass('dup-subscribed-false');
1127- }
1128-}
1129-
1130-/*
1131- * Subscribe a person or team other than the current user.
1132- * This is a callback for the subscribe someone else picker.
1133- *
1134- * @method subscribe_someone_else
1135- * @result {Object} The object representing a person returned by the API.
1136- */
1137-function subscribe_someone_else(result, subscription) {
1138- var person = new Y.lp.bugs.subscriber.Subscriber({
1139- uri: result.api_uri,
1140- display_name: result.title,
1141- subscriber_ids: subscriber_ids
1142- });
1143- subscription.set('person', person);
1144-
1145- var error_handler = new Y.lp.client.ErrorHandler();
1146- error_handler.showError = function(error_msg) {
1147- Y.lp.app.errors.display_error(
1148- Y.one('.menu-link-addsubscriber'), error_msg);
1149- };
1150-
1151- if (subscription.is_already_subscribed()) {
1152- error_handler.showError(
1153- subscription.get('person').get('full_display_name') +
1154- ' has already been subscribed');
1155- } else {
1156- check_can_be_unsubscribed(subscription);
1157- }
1158-}
1159-
1160-/*
1161- * Check if the current user can unsubscribe the person
1162- * being subscribed.
1163- *
1164- * This must be done in JavaScript, since the subscription
1165- * hasn't completed yet, and so, can_be_unsubscribed_by_user
1166- * cannot be used.
1167- *
1168- * @method check_can_be_unsubscribed
1169- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
1170- */
1171-function check_can_be_unsubscribed(subscription) {
1172- var error_handler = new Y.lp.client.ErrorHandler();
1173- error_handler.showError = function (error_msg) {
1174- Y.lp.app.errors.display_error(
1175- Y.one('.menu-link-addsubscriber'), error_msg);
1176- };
1177-
1178- var config = {
1179- on: {
1180- success: function(result) {
1181- var is_team = result.get('is_team');
1182- subscription.set('is_team', is_team);
1183- var final_config = {
1184- on: {
1185- success: function(result) {
1186- var team_member = false;
1187- var i;
1188- for (i=0; i<result.entries.length; i++) {
1189- if (result.entries[i].get('member_link') ===
1190- Y.lp.client.get_absolute_uri(
1191- subscription.get(
1192- 'subscriber').get('uri'))) {
1193- team_member = true;
1194- }
1195- }
1196-
1197- if (team_member) {
1198- subscription.set('can_be_unsubscribed', true);
1199- add_temp_user_name(subscription);
1200- } else {
1201- subscription.set(
1202- 'can_be_unsubscribed', false);
1203- add_temp_user_name(subscription);
1204- }
1205- },
1206-
1207- failure: error_handler.getFailureHandler()
1208- }
1209- };
1210-
1211- if (is_team) {
1212- // Get a list of members to see if current user
1213- // is a team member.
1214- var members = result.get(
1215- 'members_details_collection_link');
1216- lp_client.get(members, final_config);
1217- } else {
1218- subscription.set('can_be_unsubscribed', false);
1219- add_temp_user_name(subscription);
1220- }
1221- },
1222-
1223- failure: error_handler.getFailureHandler()
1224- }
1225- };
1226- lp_client.get(Y.lp.client.get_absolute_uri(
1227- subscription.get('person').get('escaped_uri')), config);
1228-}
1229-
1230-/*
1231- * Add a grayed out, temporary user name when subscribing
1232- * someone else.
1233- *
1234- * @method add_temp_user_name
1235- * @param subscription_link {Node} The sub/unsub link.
1236- */
1237-function add_temp_user_name(subscription) {
1238- // Be paranoid about display_name, since timeouts or other errors
1239- // could mean display_name wasn't set on initialization.
1240- subscription.get('person').set_display_name(function () {
1241- _add_temp_user_name(subscription);
1242- });
1243-}
1244-
1245-function _add_temp_user_name(subscription) {
1246- var display_name = subscription.get('person').get('display_name');
1247- var img_src;
1248- if (subscription.is_team()) {
1249- img_src = '/@@/teamgray';
1250- } else {
1251- img_src = '/@@/persongray';
1252- }
1253-
1254- // The <span>...</span> below must *not* be <span/>. On FF (maybe
1255- // others, but at least on FF 3.0.11) will then not notice any
1256- // following sibling nodes, like the spinner image.
1257- var link_node = Y.Node.create([
1258- '<div id="temp-username"> ',
1259- ' <img alt="" width="14" height="14" />',
1260- ' <span>Other Display Name</span>',
1261- ' <img id="temp-name-spinner" src="/@@/spinner" alt="" ',
1262- ' style="position:absolute;right:8px" /></div>'].join(''));
1263- link_node.one('img').set('src', img_src);
1264- link_node.replaceChild(
1265- document.createTextNode(display_name),
1266- link_node.one('span'));
1267-
1268- var subscribers = Y.one('#subscribers-links');
1269- var next = get_next_subscriber_node(subscription);
1270- if (next) {
1271- subscribers.insertBefore(link_node, next);
1272- } else {
1273- // Handle the case of no subscribers.
1274- var none_subscribers = Y.one('#none-subscribers');
1275- if (none_subscribers) {
1276- var none_parent = none_subscribers.get('parentNode');
1277- none_parent.removeChild(none_subscribers);
1278- }
1279- subscribers.appendChild(link_node);
1280- }
1281-
1282- // Fire a custom event to know it's safe to begin
1283- // any actual subscribing work.
1284- namespace.portlet.fire('bugs:nameloaded', subscription);
1285-}
1286-
1287-}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
1288- "json-parse", "substitute", "widget-position-ext",
1289- "lazr.formoverlay", "lazr.anim", "lazr.base",
1290- "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
1291- "lp.client",
1292- "lp.client.plugins", "lp.bugs.subscriber",
1293- "lp.bugs.subscribers_list",
1294- "lp.bugs.bug_notification_level", "lp.app.errors"]});
1295
1296=== removed file 'lib/lp/bugs/javascript/subscriber.js'
1297--- lib/lp/bugs/javascript/subscriber.js 2011-05-18 21:36:46 +0000
1298+++ lib/lp/bugs/javascript/subscriber.js 1970-01-01 00:00:00 +0000
1299@@ -1,390 +0,0 @@
1300-/** Copyright (c) 2009, Canonical Ltd. All rights reserved.
1301- *
1302- * Objects for subscription handling.
1303- *
1304- * @module bugs
1305- * @submodule subscriber
1306- */
1307-
1308-YUI.add('lp.bugs.subscriber', function(Y) {
1309-
1310-var namespace = Y.namespace('lp.bugs.subscriber');
1311-namespace.subscription_labels = {
1312- 'EDIT': 'Edit subscription',
1313- 'SUBSCRIBE': 'Subscribe',
1314- 'UNSUBSCRIBE': 'Unsubscribe'
1315-};
1316-
1317-
1318-/**
1319- * A Subscription object which represents the subscription
1320- * being attempted.
1321- *
1322- * @class Subscription
1323- * @namespace lp
1324- */
1325-function Subscription(config) {
1326- Subscription.superclass.constructor.apply(this, arguments);
1327-}
1328-
1329-Subscription.ATTRS = {
1330- 'link': {
1331- value: null
1332- },
1333-
1334- 'can_be_unsubscribed': {
1335- value: false
1336- },
1337-
1338- 'is_direct': {
1339- value: true
1340- },
1341-
1342- 'has_dupes': {
1343- value: false
1344- },
1345-
1346- 'person': {
1347- value: null
1348- },
1349-
1350- 'is_team': {
1351- value: false
1352- },
1353-
1354- 'subscriber': {
1355- value: null
1356- },
1357-
1358- 'spinner': {
1359- value: null
1360- },
1361-
1362- 'bug_notification_level': {
1363- value: 'Discussion'
1364- }
1365-};
1366-
1367-Y.extend(Subscription, Y.Base, {
1368-
1369- /**
1370- * Is the current subscription link a node?
1371- * Useful in checking that the link is defined.
1372- *
1373- * @method is_node
1374- * @return {Boolean}
1375- */
1376- 'is_node': function() {
1377- return this.get('link') instanceof Y.Node;
1378- },
1379-
1380- /**
1381- * Is the person being subscribed already subscribed?
1382- *
1383- * @method is_already_subscribed
1384- * @return {Boolean}
1385- */
1386- 'is_already_subscribed': function() {
1387- var display_name = this.get('person').get('full_display_name');
1388- var already_subscribed = false;
1389-
1390- Y.all('#subscribers-links div').each(function(link) {
1391- var name = link.one('a').getAttribute('name');
1392- if (name === display_name) {
1393- already_subscribed = true;
1394- }
1395- });
1396-
1397- return already_subscribed;
1398- },
1399-
1400- /**
1401- * Can this subscriber being unsubscribed by the current
1402- * user?
1403- *
1404- * @method can_be_unsubscribed_by_user
1405- * @return {Boolean}
1406- */
1407- 'can_be_unsubscribed_by_user': function() {
1408- return this.get('can_be_unsubscribed');
1409- },
1410-
1411- /**
1412- * Is this the current user subscribing him/herself?
1413- *
1414- * @method is_current_user_subscribing
1415- * @return {Boolean}
1416- */
1417- 'is_current_user_subscribing': function() {
1418- return (
1419- this.get('subscriber').get('name') ===
1420- this.get('person').get('name')
1421- );
1422- },
1423-
1424- /**
1425- * Is the current subscription a direct subscription?
1426- *
1427- * @method is_direct_subscription
1428- * @return {Boolean}
1429- */
1430- 'is_direct_subscription': function() {
1431- return this.get('is_direct');
1432- },
1433-
1434- /**
1435- * Does this subscription have dupes?
1436- *
1437- * @method has_duplicate_subscriptions
1438- * @return {Boolean}
1439- */
1440- 'has_duplicate_subscriptions': function() {
1441- return this.get('has_dupes');
1442- },
1443-
1444- /**
1445- * Is this subscriber a team?
1446- *
1447- * @method is_team
1448- * @return {Boolean}
1449- */
1450- 'is_team': function() {
1451- return this.get('is_team');
1452- },
1453-
1454- /**
1455- * Turn on the progess spinner.
1456- *
1457- * @method enable_spinner
1458- */
1459- 'enable_spinner': function(text) {
1460- if (Y.Lang.isValue(text)) {
1461- this.get('spinner').set('innerHTML', text);
1462- }
1463- this.get('link').setStyle('display', 'none');
1464- this.get('spinner').setStyle('display', 'block');
1465- },
1466-
1467- /**
1468- * Turn off the progress spinner.
1469- *
1470- * @method disable_spinner
1471- */
1472- 'disable_spinner': function(text) {
1473- if (Y.Lang.isValue(text)) {
1474- var link = this.get('link');
1475- link.set('innerHTML', text);
1476- if (text === namespace.subscription_labels.SUBSCRIBE) {
1477- link.removeClass('modify remove');
1478- link.removeClass('modify edit');
1479- link.addClass('add');
1480- } else if (text === namespace.subscription_labels.EDIT) {
1481- link.removeClass('add');
1482- link.addClass('modify edit');
1483- } else {
1484- link.removeClass('add');
1485- link.addClass('modify remove');
1486- }
1487- }
1488- this.get('spinner').setStyle('display', 'none');
1489- this.get('link').setStyle('display', 'inline');
1490- }
1491-});
1492-
1493-namespace.Subscription = Subscription;
1494-
1495-/** A Subscriber object which can represent the subscribing person or
1496- * the person being subscribed.
1497- *
1498- * @class Subscriber
1499- * @namespace lp
1500- */
1501-function Subscriber(config) {
1502- Subscriber.superclass.constructor.apply(this, arguments);
1503-}
1504-
1505-Subscriber.NAME = 'Subscriber';
1506-Subscriber.ATTRS = {
1507- uri: {
1508- value: ''
1509- },
1510-
1511- name: {
1512- value: ''
1513- },
1514-
1515- css_name: {
1516- value: ''
1517- },
1518-
1519- escaped_uri: {
1520- value: ''
1521- },
1522-
1523- user_node: {
1524- value: null
1525- },
1526-
1527- display_name: {
1528- value: ''
1529- },
1530-
1531- full_display_name: {
1532- value: ''
1533- },
1534-
1535- subscriber_ids: {
1536- value: null
1537- }
1538-};
1539-
1540-Y.extend(Subscriber, Y.Base, {
1541-
1542- /**
1543- * Subscriber can take as little as a Person uri and work
1544- * out most of the person's name attributes from that.
1545- *
1546- * The display_name is the tricky part and has to be worked
1547- * out either from the DOM or via the LP API. The object can
1548- * be passed a DOM node in the config, but the object tries
1549- * to work out the DOM on the fly if not and falls back to
1550- * the API if LP is available. (See the included display_name
1551- * methods for more.)
1552- *
1553- * @method initializer
1554- */
1555- initializer: function(config) {
1556- if (this.get('uri') !== '') {
1557- this.set('name', this.get('uri').substring(2));
1558- var name = this.get('name');
1559-
1560- // If we have a subscriber_ids object and that object
1561- // has an entry for this Subscriber, then set css_name.
1562- // Otherwise, create a css_name with a guid and update
1563- // subscriber_ids to include it.
1564- var subscriber_ids = this.get('subscriber_ids');
1565- if (Y.Lang.isValue(subscriber_ids)) {
1566- var css_name = this.get('subscriber_ids')[name];
1567- if (Y.Lang.isValue(css_name)) {
1568- this.set('css_name', css_name);
1569- } else {
1570- css_name = 'subscriber-' + Y.guid();
1571- subscriber_ids[name] = css_name;
1572- this.set('subscriber_ids', subscriber_ids);
1573- this.set('css_name', css_name);
1574- }
1575- }
1576-
1577- // Handle the case of plus signs in user names.
1578- var escaped_uri;
1579- if (name.indexOf('+') > 0) {
1580- escaped_uri = name.replace('+', '%2B');
1581- } else {
1582- escaped_uri = name;
1583- }
1584- this.set('escaped_uri', '/~' + escaped_uri);
1585- }
1586-
1587- this.set_display_name();
1588- this.set_truncated_display_name();
1589- },
1590-
1591- /**
1592- * Finds the display name using the LP API.
1593- *
1594- * @method get_display_name_from_api
1595- * @param client {Object} An LP API client.
1596- * @param on_done {Object} A function to call when the display name has
1597- * been loaded.
1598- */
1599- get_display_name_from_api: function(client, on_done) {
1600- var self = this;
1601- var cfg = {
1602- on: {
1603- success: function(person) {
1604- var display_name = person.lookup_value('display_name');
1605- self.set('display_name', display_name);
1606- self.set_truncated_display_name();
1607- if (Y.Lang.isFunction(on_done)) {
1608- on_done();
1609- }
1610- }
1611- }
1612- };
1613- client.get(this.get('escaped_uri'), cfg);
1614- },
1615-
1616- /** Finds the display name in a DOM node.
1617- *
1618- * This method can use a DOM node supplied in the config but
1619- * will also try the standard class name for a subscriber's
1620- * node.
1621- *
1622- * @method get_display_name_from_node
1623- */
1624- get_display_name_from_node: function() {
1625- var user_node;
1626- if (Y.Lang.isValue(this.get('user_node'))) {
1627- user_node = this.get('user_node');
1628- } else {
1629- user_node = Y.one('.subscriber-' + this.get('name'));
1630- }
1631-
1632- if (Y.Lang.isValue(user_node)) {
1633- this.set('user_node', user_node);
1634- var anchor = this.get('user_node').one('a');
1635- var display_name = anchor.get('name');
1636- return display_name;
1637- } else {
1638- return '';
1639- }
1640- },
1641-
1642- /**
1643- * A wrapper around the other getDisplayNameXXX functions to
1644- * work out if setting the display_name is possible. Calling
1645- * this is the safest way to ensure display_name is set
1646- * correctly.
1647- *
1648- * @method set_display_name
1649- * @param on_done {Object} A function to call when the display name has
1650- * been loaded.
1651- */
1652- set_display_name: function(on_done) {
1653- var display_name = this.get_display_name_from_node();
1654- if (display_name !== '') {
1655- this.set('display_name', display_name);
1656- this.set_truncated_display_name();
1657- } else {
1658- if (window.LP !== undefined &&
1659- window.LP.links.me !== undefined) {
1660- var client = new Y.lp.client.Launchpad();
1661- this.get_display_name_from_api(client, on_done);
1662- }
1663- }
1664- },
1665-
1666- /**
1667- * Sets the truncated version of the display_name.
1668- *
1669- * @method set_truncated_display_name
1670- */
1671- set_truncated_display_name: function() {
1672- var display_name = this.get('display_name');
1673- if (display_name !== '') {
1674- var truncated_name;
1675- if (display_name.length > 20) {
1676- truncated_name = display_name.substring(0, 17) + '...';
1677- } else {
1678- truncated_name = display_name;
1679- }
1680- this.set('display_name', truncated_name);
1681- this.set('full_display_name', display_name);
1682- }
1683- }
1684-
1685-});
1686-
1687-namespace.Subscriber = Subscriber;
1688-
1689- }, "0.1", {"requires": ["base", "node", "lp.client"]});
1690
1691=== modified file 'lib/lp/bugs/javascript/subscribers_list.js'
1692--- lib/lp/bugs/javascript/subscribers_list.js 2011-06-15 11:09:59 +0000
1693+++ lib/lp/bugs/javascript/subscribers_list.js 2011-06-15 11:10:20 +0000
1694@@ -27,72 +27,6 @@
1695
1696 var namespace = Y.namespace('lp.bugs.subscribers_list');
1697
1698-/**
1699- * Reset the subscribers list if needed.
1700- *
1701- * Adds the "None" div to the subscribers list if
1702- * there aren't any subscribers left, and clears up
1703- * the duplicate subscribers list if empty.
1704- *
1705- * @method reset
1706- */
1707-function reset() {
1708- var subscriber_list = Y.one('#subscribers-links');
1709- // Assume if subscriber_list has no child divs
1710- // then the list of subscribers is empty.
1711- if (!Y.Lang.isValue(subscriber_list.one('div')) &&
1712- !Y.Lang.isValue(Y.one('#none-subscribers'))) {
1713- var none_div = Y.Node.create(
1714- '<div id="none-subscribers">No subscribers.</div>');
1715- var subscribers = subscriber_list.get('parentNode');
1716- subscribers.appendChild(none_div);
1717- }
1718-
1719- // Clear the empty duplicate subscribers list if it exists.
1720- var dup_list = Y.one('#subscribers-from-duplicates');
1721- if (Y.Lang.isValue(dup_list) &&
1722- !Y.Lang.isValue(dup_list.one('div'))) {
1723- dup_list.remove();
1724- }
1725-}
1726-namespace._reset = reset;
1727-
1728-/**
1729- * Remove the user's name from the subscribers list.
1730- * It uses the green-flash animation to indicate successful removal.
1731- *
1732- * @method remove_user_link
1733- * @param subscriber {Subscriber} Subscriber that you want to remove.
1734- * @param is_dupe {Boolean} Uses subscription link from the duplicates
1735- * instead.
1736- */
1737-function remove_user_link(subscriber, is_dupe) {
1738- var user_node_id;
1739- var user_name = subscriber.get('css_name');
1740- if (is_dupe === true) {
1741- user_node_id = '#dupe-' + user_name;
1742- } else {
1743- user_node_id = '#direct-' + user_name;
1744- }
1745- var user_node = Y.one(user_node_id);
1746- if (Y.Lang.isValue(user_node)) {
1747- // If there's an icon, we remove it prior to animation
1748- // so animation looks better.
1749- var unsub_icon = user_node.one('#unsubscribe-icon-' + user_name);
1750- if (Y.Lang.isValue(unsub_icon)) {
1751- unsub_icon.remove();
1752- }
1753- var anim = Y.lazr.anim.green_flash({ node: user_node });
1754- anim.on('end', function() {
1755- user_node.remove();
1756- reset();
1757- });
1758- anim.run();
1759- }
1760-}
1761-namespace.remove_user_link = remove_user_link;
1762-
1763-
1764 var CSS_CLASSES = {
1765 section : 'subscribers-section',
1766 list: 'subscribers-list',
1767
1768=== removed file 'lib/lp/bugs/javascript/tests/test_subscriber.html'
1769--- lib/lp/bugs/javascript/tests/test_subscriber.html 2011-02-28 00:54:30 +0000
1770+++ lib/lp/bugs/javascript/tests/test_subscriber.html 1970-01-01 00:00:00 +0000
1771@@ -1,104 +0,0 @@
1772-<html>
1773- <head>
1774- <title>Launchpad subscriber</title>
1775-
1776- <!-- YUI 3.0 Setup -->
1777- <script type="text/javascript"
1778- src="../../../../canonical/launchpad/icing/yui/yui/yui.js"></script>
1779- <script type="text/javascript"
1780- src="../../../../canonical/launchpad/icing/lazr/build/lazr.js"></script>
1781- <link rel="stylesheet"
1782- href="../../../../canonical/launchpad/icing/yui/cssreset/reset.css"/>
1783- <link rel="stylesheet"
1784- href="../../../../canonical/launchpad/icing/yui/cssfonts/fonts.css"/>
1785- <link rel="stylesheet"
1786- href="../../../../canonical/launchpad/icing/yui/cssbase/base.css"/>
1787- <link rel="stylesheet"
1788- href="../../../../canonical/launchpad/javascript/test.css" />
1789-
1790- <script type="text/javascript"
1791- src="../../../app/javascript/client.js"></script>
1792-
1793- <!-- The module under test -->
1794- <script type="text/javascript"
1795- src="../subscriber.js"></script>
1796-
1797- <!-- The test suite -->
1798- <script type="text/javascript"
1799- src="test_subscriber.js"></script>
1800-
1801- <!-- Pretty up the sample html -->
1802- <style type="text/css">
1803- div#sample {margin:15px; width:200px; border:1px solid #999; padding:10px;}
1804- </style>
1805- </head>
1806- <body class="yui3-skin-sam">
1807- <!-- Example markup required by test suite -->
1808- <div id="sample">
1809- <div class="section">
1810- <div class="subscribed-true dup-subscribed-false">
1811- <a href="+subscribe"
1812- class="menu-link-subscription sprite remove js-action">Unsubscribe
1813- </a>
1814- </div>
1815- <div>
1816- <a href="+addsubscriber"
1817- class="menu-link-addsubscriber sprite add js-action">Subscribe
1818- someone else
1819- </a>
1820- </div>
1821- </div>
1822-
1823- <div class="section" id="subscribers-direct">
1824- <h2>Subscribers</h2>
1825- <div id="subscribers-links">
1826- <div class="subscriber-tester">
1827- <a href="/~tester" name="JS Test User"
1828- title="Subscribed by Launchpad Janitor">
1829- <span class="sprite person"></span>
1830- JS Test User
1831- </a>
1832-
1833- <a href="+subscribe"
1834- class="subscribed-true dup-subscribed-false"
1835- id="unsubscribe-tester" title="Unsubscribe JS Test User">
1836- <img class="unsub-icon"
1837- src="../../../../canonical/launchpad/images/remove.png"
1838- id="unsubscribe-icon-tester">
1839- </a>
1840- </div>
1841-
1842- <div class="subscriber-some-team">
1843- <a href="/~some-team" name="Some Team"
1844- title="Subscribed by Launchpad Janitor">
1845- <span class="sprite team"></span>
1846- Some Team
1847- </a>
1848-
1849- <a href="+subscribe"
1850- class="subscribed-true dup-subscribed-false"
1851- id="unsubscribe-some-team" title="Unsubscribe Some Team">
1852- <img class="unsub-icon"
1853- src="../../../../canonical/launchpad/images/remove.png"
1854- id="unsubscribe-icon-some-team">
1855- </a>
1856- </div>
1857- </div>
1858- </div>
1859-
1860- <div id="subscribers-from-duplicates" class="section">
1861- <h2>From duplicates</h2>
1862- <div class="subscriber-duper" id="dupe-subscriber-duper">
1863- <a href="/~duper" name="Duper Dude"
1864- title="Subscribed by JS Test User">
1865- <span class="sprite person"></span>
1866- Duper Dude
1867- </a>
1868- </div>
1869- </div>
1870- </div>
1871-
1872- <!-- The test output -->
1873- <div id="log"></div>
1874- </body>
1875-</html>
1876
1877=== removed file 'lib/lp/bugs/javascript/tests/test_subscriber.js'
1878--- lib/lp/bugs/javascript/tests/test_subscriber.js 2011-06-07 16:42:11 +0000
1879+++ lib/lp/bugs/javascript/tests/test_subscriber.js 1970-01-01 00:00:00 +0000
1880@@ -1,288 +0,0 @@
1881-YUI({
1882- base: '../../../../canonical/launchpad/icing/yui/',
1883- filter: 'raw', combine: false, fetchCSS: false
1884- }).use('test', 'console', 'lp.bugs.subscriber', function(Y) {
1885-
1886-var suite = new Y.Test.Suite("lp.bugs.subscriber Tests");
1887-
1888-/*
1889- * Test that all the parts of the user name
1890- * are set when given just a URI.
1891- */
1892-suite.add(new Y.Test.Case({
1893- name: 'Subscriber From Simple Config',
1894-
1895- setUp: function() {
1896- this.config = {
1897- uri: '/~deryck'
1898- };
1899- this.subscriber = new Y.lp.bugs.subscriber.Subscriber(this.config);
1900- },
1901-
1902- tearDown: function() {
1903- delete this.config;
1904- delete this.subscriber;
1905- },
1906-
1907- test_uri_config: function() {
1908- Y.Assert.areEqual(
1909- '/~deryck',
1910- this.subscriber.get('uri'),
1911- 'User URI should be /~deryck');
1912- Y.Assert.areEqual(
1913- 'deryck',
1914- this.subscriber.get('name'),
1915- 'User name should be deryck');
1916- Y.Assert.areEqual(
1917- this.subscriber.get('uri'),
1918- this.subscriber.get('escaped_uri'),
1919- 'The escaped user uri should be the same as the unescaped uri.');
1920- Y.Assert.isNull(
1921- this.subscriber.get('user_node'),
1922- 'User node should not be known and be null at this point.');
1923- Y.Assert.areSame(
1924- '',
1925- this.subscriber.get('css_name'),
1926- 'Without subscriber_ids object, css_name should not be set yet.');
1927- Y.Assert.areSame(
1928- '',
1929- this.subscriber.get('display_name'),
1930- 'Without user node or client, the display name should be empty.');
1931- }
1932-}));
1933-
1934-/*
1935- * Test that all the parts of the user name
1936- * are set correctly when a name needs escaping.
1937- */
1938-suite.add(new Y.Test.Case({
1939- name: 'Escaping Subscriber From Simple Config',
1940-
1941- setUp: function() {
1942- this.config = {
1943- uri: '/~foo+bar',
1944- subscriber_ids: {'foo+bar': 'subscriber-16'}
1945- };
1946- this.subscriber = new Y.lp.bugs.subscriber.Subscriber(this.config);
1947- },
1948-
1949- tearDown: function() {
1950- delete this.config;
1951- delete this.subscriber;
1952- },
1953-
1954- test_escaping_uri_config: function() {
1955- Y.Assert.areEqual(
1956- '/~foo+bar',
1957- this.subscriber.get('uri'),
1958- 'User URI should be /~foo+bar');
1959- Y.Assert.areEqual(
1960- 'foo+bar',
1961- this.subscriber.get('name'),
1962- 'User name should be foo+bar');
1963- Y.Assert.areEqual(
1964- '/~foo%2Bbar',
1965- this.subscriber.get('escaped_uri'),
1966- 'Escaped user URI should be /~foo%2Bbar');
1967- Y.Assert.areEqual(
1968- 'subscriber-16',
1969- this.subscriber.get('css_name'),
1970- 'css_name for user should be subscriber-16');
1971- }
1972-}));
1973-
1974-/*
1975- * Test that the display_name is correctly worked out
1976- * when passed a Node.
1977- */
1978-suite.add(new Y.Test.Case({
1979- name: 'Subscriber Name When Passed Node',
1980-
1981- setUp: function() {
1982- var node = Y.one('.subscriber-tester');
1983- this.config = {
1984- uri: '/~tester',
1985- user_node: node
1986- };
1987- this.subscriber = new Y.lp.bugs.subscriber.Subscriber(this.config);
1988- },
1989-
1990- tearDown: function() {
1991- delete this.config;
1992- delete this.subscriber;
1993- },
1994-
1995- test_display_name: function() {
1996- Y.Assert.areEqual(
1997- 'JS Test User',
1998- this.subscriber.get('display_name'),
1999- 'The user name should be JS Test User.');
2000- }
2001-}));
2002-
2003-/*
2004- * Test that display_name is correctly worked out from
2005- * the DOM when not passed a Node.
2006- */
2007-suite.add(new Y.Test.Case({
2008- name: 'Subscriber Name When Not Passed Node',
2009-
2010- setUp: function() {
2011- this.config = {
2012- uri: '/~tester'
2013- };
2014- this.subscriber = new Y.lp.bugs.subscriber.Subscriber(this.config);
2015- },
2016-
2017- tearDown: function() {
2018- delete this.config;
2019- delete this.subscriber;
2020- },
2021-
2022- test_display_name_from_dom: function() {
2023- Y.Assert.areEqual(
2024- 'JS Test User',
2025- this.subscriber.get('display_name'),
2026- 'The user name should be JS Test User.');
2027- }
2028-}));
2029-
2030-/*
2031- * Subscriber class that stubs out API calls.
2032- */
2033-function APIStubSubscriber(config) {
2034- APIStubSubscriber.superclass.constructor.apply(this, arguments);
2035-}
2036-Y.extend(APIStubSubscriber, Y.lp.bugs.subscriber.Subscriber, {
2037- get_display_name_from_api: function(client) {
2038- this.set('display_name', 'From API');
2039- this.set_truncated_display_name();
2040- }
2041-});
2042-
2043-/*
2044- * Test that the API is consulted when the display_name cannot be
2045- * worked out from a given Node or the DOM.
2046- */
2047-suite.add(new Y.Test.Case({
2048- name: 'Subscriber Name From API',
2049-
2050- setUp: function() {
2051- // LP is global.
2052- window.LP = {
2053- cache: {},
2054- links: {}
2055- };
2056- Y.lp.client.Launchpad = function() {};
2057- },
2058-
2059- tearDown: function() {
2060- delete window.LP;
2061- },
2062-
2063- test_display_name_from_api: function() {
2064- // The API should be consulted when the user is logged in. Set
2065- // the link to "me" to something other than undefined to
2066- // indicate that there is a logged-in user.
2067- LP.links.me = 'not-undefined';
2068- var subscriber = new APIStubSubscriber({});
2069- Y.Assert.areEqual(
2070- 'From API', subscriber.get('display_name'),
2071- 'The display name should be "From API".');
2072- },
2073-
2074- test_display_name_when_not_logged_in: function() {
2075- // The API should not be consulted when no user is logged in.
2076- var subscriber = new APIStubSubscriber({});
2077- Y.Assert.areEqual(
2078- '', subscriber.get('display_name'),
2079- 'The display name should be the empty string.');
2080- }
2081-}));
2082-
2083-/*
2084- * Test that a Subscription is properly initialized from
2085- * a simple config and that the basic methods work.
2086- */
2087-suite.add(new Y.Test.Case({
2088- name: 'Subscription Test',
2089-
2090- setUp: function() {
2091- this.config = {
2092- can_be_unsubscribed: false,
2093- is_direct: true,
2094- is_team: true
2095- };
2096- this.subscription = new Y.lp.bugs.subscriber.Subscription(
2097- this.config);
2098- },
2099-
2100- tearDown: function() {
2101- delete this.config;
2102- delete this.subscription;
2103- },
2104-
2105- test_subscription_config: function() {
2106- Y.Assert.isFalse(
2107- this.subscription.can_be_unsubscribed_by_user(),
2108- 'The user should not be able to unsubscribed this subscription.');
2109- Y.Assert.isTrue(
2110- this.subscription.is_team(),
2111- 'This subscription should be for a team.');
2112- Y.Assert.isTrue(
2113- this.subscription.is_direct_subscription(),
2114- 'This should be a direct subscription.');
2115- // Also check that the defaults were set.
2116- Y.Assert.isNull(
2117- this.subscription.get('person'),
2118- 'The subscription should not be setup for a person.');
2119- Y.Assert.isNull(
2120- this.subscription.get('subscriber'),
2121- 'The subscription should not be setup for a subscriber.');
2122- },
2123-
2124- test_subscription_is_node: function() {
2125- Y.Assert.isFalse(
2126- this.subscription.is_node(),
2127- 'Initially, no node should be supplied to the config.');
2128- var link = Y.one('.menu-link-subscription');
2129- this.subscription.set('link', link);
2130- Y.Assert.isTrue(
2131- this.subscription.is_node(),
2132- 'This subscription should have a node for subscription link.');
2133- },
2134-
2135- test_already_subscribed: function() {
2136- var person = new Y.lp.bugs.subscriber.Subscriber({uri: '/~tester'});
2137- this.subscription.set('person', person);
2138- Y.Assert.isTrue(
2139- this.subscription.is_already_subscribed(),
2140- 'The JS Test User should be already subscribed.');
2141- },
2142-
2143- test_is_current_user_subscribing: function() {
2144- var person = new Y.lp.bugs.subscriber.Subscriber({uri: '/~tester'});
2145- this.subscription.set('person', person);
2146- var subscriber = this.subscription.get('person');
2147- this.subscription.set('subscriber', subscriber);
2148- Y.Assert.isTrue(
2149- this.subscription.is_current_user_subscribing(),
2150- 'Current user should be the same person being subscribed.');
2151- }
2152-}));
2153-
2154-
2155-var handle_complete = function(data) {
2156- window.status = '::::' + JSON.stringify(data);
2157- };
2158-Y.Test.Runner.on('complete', handle_complete);
2159-Y.Test.Runner.add(suite);
2160-
2161-var console = new Y.Console({newestOnTop: false});
2162-console.render('#log');
2163-
2164-Y.on('domready', function() {
2165- Y.Test.Runner.run();
2166-});
2167-});
2168-
2169
2170=== modified file 'lib/lp/bugs/javascript/tests/test_subscribers_list.js'
2171--- lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-06-15 11:09:59 +0000
2172+++ lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-06-15 11:10:20 +0000
2173@@ -12,324 +12,6 @@
2174 /**
2175 * Set-up all the nodes required for subscribers list testing.
2176 */
2177-function setUpOldSubscribersList(root_node, with_dupes) {
2178- // Set-up subscribers list.
2179- var direct_links = Y.Node.create('<div></div>')
2180- .set('id', 'subscribers-links');
2181- var direct_list = Y.Node.create('<div></div>')
2182- .set('id', 'subscribers-direct');
2183- direct_list.appendChild(direct_links);
2184- root_node.appendChild(direct_list);
2185-
2186- if (with_dupes === true) {
2187- var dupe_links = Y.Node.create('<div></div>')
2188- .set('id', 'subscribers-from-duplicates');
2189- var dupe_list = Y.Node.create('<div></div>')
2190- .set('id', 'subscribers-from-duplicates-container');
2191- dupe_list.appendChild(dupe_links);
2192- root_node.appendChild(dupe_list);
2193- }
2194- return direct_list;
2195-}
2196-
2197-/**
2198- * Test resetting of the subscribers list.
2199- */
2200-suite.add(new Y.Test.Case({
2201- name: 'Resetting subscribers list',
2202-
2203- setUp: function() {
2204- this.root = Y.Node.create('<div></div>');
2205- Y.one('body').appendChild(this.root);
2206- },
2207-
2208- tearDown: function() {
2209- this.root.remove();
2210- },
2211-
2212- test_no_subscribers: function() {
2213- // There are no subscribers left in the subscribers_list
2214- // (iow, subscribers_links is empty).
2215- var subscribers_list = setUpOldSubscribersList(this.root);
2216-
2217- // Resetting the list adds a 'None' div to the
2218- // subscribers_list (and not to the subscriber_links).
2219- module._reset();
2220- var none_node = subscribers_list.one('#none-subscribers');
2221- Y.Assert.isNotNull(none_node);
2222- Y.Assert.areEqual('No subscribers.', none_node.get('innerHTML'));
2223- Y.Assert.areEqual(subscribers_list,
2224- none_node.get('parentNode'));
2225-
2226- },
2227-
2228- test_subscribers: function() {
2229- // When there is at least one subscriber, nothing
2230- // happens when reset() is called.
2231- var subscribers_list = setUpOldSubscribersList(this.root);
2232- var subscribers_links = subscribers_list.one('#subscribers-links');
2233- subscribers_links.appendChild(
2234- Y.Node.create('<div>Subscriber</div>'));
2235-
2236- // Resetting the list is a no-op.
2237- module._reset();
2238- var none_node = subscribers_list.one('#none-subscribers');
2239- Y.Assert.isNull(none_node);
2240- },
2241-
2242-
2243- test_empty_duplicates: function() {
2244- // There are no subscribers among the duplicate subscribers.
2245- var subscribers_list = setUpOldSubscribersList(this.root, true);
2246- var dupe_subscribers = this.root.one('#subscribers-from-duplicates');
2247-
2248- // Resetting the list removes the entire duplicate subscribers node.
2249- module._reset();
2250- Y.Assert.isNull(Y.one('#subscribers-from-duplicates'));
2251-
2252- },
2253-
2254- test_duplicates: function() {
2255- // There are subscribers among the duplicate subscribers,
2256- // and nothing changes.
2257- var subscribers_list = setUpOldSubscribersList(this.root, true);
2258- var dupe_subscribers = this.root.one('#subscribers-from-duplicates');
2259- dupe_subscribers.appendChild(Y.Node.create('<div>Subscriber</div>'));
2260-
2261- // Resetting the list does nothing.
2262- module._reset();
2263-
2264- // The list is still there.
2265- var dupes_node = this.root.one('#subscribers-from-duplicates');
2266- Y.Assert.isNotNull(dupes_node);
2267- Y.Assert.areEqual(1, dupes_node.all('div').size());
2268- }
2269-}));
2270-
2271-
2272-/**
2273- * Test removal of a single person link from the subscribers list.
2274- */
2275-suite.add(new Y.Test.Case({
2276- name: 'Removal of a subscriber link',
2277-
2278- addSubscriber: function(root_node, subscriber, through_dupe) {
2279- var css_class = subscriber.get('css_name');
2280- var id;
2281- if (through_dupe === true) {
2282- links = root_node.one('#subscribers-from-duplicates');
2283- id = 'dupe-' + css_class;
2284- } else {
2285- links = root_node.one('#subscribers-links');
2286- id = 'direct-' + css_class;
2287- }
2288- return links.appendChild(
2289- Y.Node.create('<div></div>')
2290- .addClass(css_class)
2291- .set('id', id)
2292- .set('text', subscriber.get('uri')));
2293- },
2294-
2295- setUp: function() {
2296- this.root = Y.Node.create('<div></div>');
2297- Y.one('body').appendChild(this.root);
2298- this.subscriber_ids = {};
2299- },
2300-
2301- tearDown: function() {
2302- this.root.remove();
2303- delete this.subscriber_ids;
2304- },
2305-
2306- test_no_matching_subscriber: function() {
2307- // If there is no matching subscriber, removal silently passes.
2308-
2309- // Set-up subscribers list.
2310- setUpOldSubscribersList(this.root);
2311-
2312- var person = new Y.lp.bugs.subscriber.Subscriber({
2313- uri: 'myself',
2314- subscriber_ids: this.subscriber_ids
2315- });
2316- var other_person = new Y.lp.bugs.subscriber.Subscriber({
2317- uri: 'someone',
2318- subscriber_ids: this.subscriber_ids
2319- });
2320- this.addSubscriber(this.root, other_person);
2321-
2322- module.remove_user_link(person);
2323-
2324- // `other_person` is not removed.
2325- Y.Assert.isNotNull(
2326- this.root.one('.' + other_person.get('css_name')));
2327- },
2328-
2329- test_unsubscribe_icon_removal: function() {
2330- // If there is an unsubscribe icon, it gets removed
2331- // before animation starts.
2332-
2333- // Set-up subscribers list.
2334- setUpOldSubscribersList(this.root);
2335-
2336- var person = new Y.lp.bugs.subscriber.Subscriber({
2337- uri: 'myself',
2338- subscriber_ids: this.subscriber_ids
2339- });
2340- this.addSubscriber(this.root, person);
2341- var css_name = person.get('css_name');
2342- this.root.one('.' + css_name)
2343- .appendChild('<div></div>')
2344- .appendChild('<img></img>')
2345- .set('id', 'unsubscribe-icon-' + css_name);
2346-
2347- module.remove_user_link(person);
2348-
2349- // Unsubscribe icon is removed immediatelly.
2350- Y.Assert.isNull(this.root.one('#unsubscribe-icon-' + css_name));
2351- },
2352-
2353- test_direct_subscriber: function() {
2354- // If there is a direct subscriber, removal works fine.
2355-
2356- // Set-up subscribers list.
2357- setUpOldSubscribersList(this.root);
2358-
2359- var person = new Y.lp.bugs.subscriber.Subscriber({
2360- uri: 'myself',
2361- subscriber_ids: this.subscriber_ids
2362- });
2363- this.addSubscriber(this.root, person);
2364-
2365- module.remove_user_link(person);
2366-
2367- this.wait(function() {
2368- // There is no subscriber link anymore.
2369- Y.Assert.isNull(this.root.one('.' + person.get('css_name')));
2370- // And the reset() call adds the "No subscribers" node.
2371- Y.Assert.isNotNull(this.root.one('#none-subscribers'));
2372- }, 1100);
2373- },
2374-
2375- test_direct_subscriber_remove_dupe: function() {
2376- // If there is only a direct subscriber, attempting removal of
2377- // a duplicate subscription link does nothing.
2378-
2379- // Set-up subscribers list.
2380- setUpOldSubscribersList(this.root);
2381-
2382- var person = new Y.lp.bugs.subscriber.Subscriber({
2383- uri: 'myself',
2384- subscriber_ids: this.subscriber_ids
2385- });
2386- this.addSubscriber(this.root, person);
2387-
2388- module.remove_user_link(person, true);
2389-
2390- this.wait(function() {
2391- // There is no subscriber link anymore.
2392- Y.Assert.isNotNull(this.root.one('.' + person.get('css_name')));
2393- }, 1100);
2394- },
2395-
2396- test_dupe_subscriber: function() {
2397- // If there is a duplicate subscriber, removal works fine.
2398-
2399- // Set-up subscribers list.
2400- setUpOldSubscribersList(this.root, true);
2401-
2402- var person = new Y.lp.bugs.subscriber.Subscriber({
2403- uri: 'myself',
2404- subscriber_ids: this.subscriber_ids
2405- });
2406- this.addSubscriber(this.root, person, true);
2407-
2408- module.remove_user_link(person, true);
2409-
2410- this.wait(function() {
2411- // There is no subscriber link anymore.
2412- Y.Assert.isNull(this.root.one('.' + person.get('css_name')));
2413- // And the reset() call cleans up the entire duplicate section.
2414- Y.Assert.isNull(this.root.one('#subscribers-from-duplicates'));
2415- }, 1100);
2416- },
2417-
2418- test_dupe_subscriber_remove_direct: function() {
2419- // If there is a duplicate subscriber, trying to remove the
2420- // direct subscription user link doesn't do anything.
2421-
2422- // Set-up subscribers list.
2423- setUpOldSubscribersList(this.root, true);
2424-
2425- var person = new Y.lp.bugs.subscriber.Subscriber({
2426- uri: 'myself',
2427- subscriber_ids: this.subscriber_ids
2428- });
2429- this.addSubscriber(this.root, person, true);
2430-
2431- module.remove_user_link(person);
2432-
2433- this.wait(function() {
2434- // There is no subscriber link anymore.
2435- Y.Assert.isNotNull(this.root.one('.' + person.get('css_name')));
2436- }, 1100);
2437- },
2438-
2439- test_direct_and_dupe_subscriber_remove_dupe: function() {
2440- // If there a subscriber is both directly subscribed and
2441- // subscribed through duplicate, removal removes only one link.
2442-
2443- // Set-up subscribers list.
2444- setUpOldSubscribersList(this.root, true);
2445-
2446- var person = new Y.lp.bugs.subscriber.Subscriber({
2447- uri: 'myself',
2448- subscriber_ids: this.subscriber_ids
2449- });
2450- this.addSubscriber(this.root, person);
2451- this.addSubscriber(this.root, person, true);
2452-
2453- // Remove the duplicate subscription link.
2454- module.remove_user_link(person, true);
2455-
2456- this.wait(function() {
2457- // Remaining entry is the direct subscription one.
2458- var nodes = this.root.all('.' + person.get('css_name'));
2459- Y.Assert.areEqual(1, nodes.size());
2460- Y.Assert.areEqual('direct-' + person.get('css_name'),
2461- nodes.pop().get('id'));
2462- }, 1100);
2463- },
2464-
2465- test_direct_and_dupe_subscriber_remove_direct: function() {
2466- // If there a subscriber is both directly subscribed and
2467- // subscribed through duplicate, removal removes only one link.
2468-
2469- // Set-up subscribers list.
2470- setUpOldSubscribersList(this.root, true);
2471-
2472- var person = new Y.lp.bugs.subscriber.Subscriber({
2473- uri: 'myself',
2474- subscriber_ids: this.subscriber_ids
2475- });
2476- this.addSubscriber(this.root, person);
2477- this.addSubscriber(this.root, person, true);
2478-
2479- // Remove the direct subscription link.
2480- module.remove_user_link(person);
2481-
2482- this.wait(function() {
2483- // Remaining entry is the duplicate subscription one.
2484- var nodes = this.root.all('.' + person.get('css_name'));
2485- Y.Assert.areEqual(1, nodes.size());
2486- Y.Assert.areEqual('dupe-' + person.get('css_name'),
2487- nodes.pop().get('id'));
2488- }, 1100);
2489- }
2490-}));
2491-
2492-/**
2493- * Set-up all the nodes required for subscribers list testing.
2494- */
2495 function setUpSubscribersList(root_node) {
2496 // Set-up subscribers list.
2497 var node = Y.Node.create('<div />')
2498@@ -341,6 +23,7 @@
2499 return new module.SubscribersList(config);
2500 }
2501
2502+
2503 /**
2504 * Test resetting of the no subscribers indication.
2505 */
2506
2507=== modified file 'lib/lp/bugs/stories/bug-privacy/05-set-bug-private-as-admin.txt'
2508--- lib/lp/bugs/stories/bug-privacy/05-set-bug-private-as-admin.txt 2010-12-24 10:37:15 +0000
2509+++ lib/lp/bugs/stories/bug-privacy/05-set-bug-private-as-admin.txt 2011-06-15 11:10:20 +0000
2510@@ -9,17 +9,13 @@
2511 explicit. There are two implicit subscribers.
2512
2513 >>> from lp.bugs.tests.bug import (
2514- ... print_also_notified, print_direct_subscribers,
2515- ... print_subscribers_from_duplicates)
2516+ ... print_also_notified, print_direct_subscribers)
2517
2518 >>> browser.open(
2519- ... "http://launchpad.dev/bugs/2/+bug-portlet-subscribers-content")
2520+ ... "http://launchpad.dev/bugs/2/+bug-portlet-subscribers-details")
2521
2522 >>> print_direct_subscribers(browser.contents)
2523- Steve Alexander (Subscribed by Launchpad Janitor)
2524-
2525- >>> print_subscribers_from_duplicates(browser.contents)
2526- From duplicates:
2527+ Steve Alexander
2528 >>> print_also_notified(browser.contents)
2529 Also notified:
2530 Sample Person
2531@@ -40,14 +36,12 @@
2532 All the implicit subscribers have been made explicit.
2533
2534 >>> browser.open(
2535- ... "http://launchpad.dev/bugs/2/+bug-portlet-subscribers-content")
2536+ ... "http://launchpad.dev/bugs/2/+bug-portlet-subscribers-details")
2537 >>> print_direct_subscribers(browser.contents)
2538- Ubuntu Team (Subscribed by Foo Bar) (Unsubscribe Ubuntu Team)
2539- Sample Person (Subscribed by Foo Bar)
2540- Steve Alexander (Subscribed by Launchpad Janitor)
2541+ Sample Person
2542+ Steve Alexander
2543+ Ubuntu Team (Unsubscribe)
2544
2545- >>> print_subscribers_from_duplicates(browser.contents)
2546- From duplicates:
2547 >>> print_also_notified(browser.contents)
2548 Also notified:
2549
2550
2551=== modified file 'lib/lp/bugs/stories/bugs/bug-add-subscriber.txt'
2552--- lib/lp/bugs/stories/bugs/bug-add-subscriber.txt 2010-04-29 17:49:19 +0000
2553+++ lib/lp/bugs/stories/bugs/bug-add-subscriber.txt 2011-06-15 11:10:20 +0000
2554@@ -32,17 +32,14 @@
2555 currently subscribed to the bug:
2556
2557 >>> from lp.bugs.tests.bug import (
2558- ... print_also_notified, print_direct_subscribers,
2559- ... print_subscribers_from_duplicates)
2560+ ... print_also_notified, print_direct_subscribers)
2561
2562 >>> user_browser.open(
2563 ... 'http://bugs.launchpad.dev/bugs/1/'
2564- ... '+bug-portlet-subscribers-content')
2565+ ... '+bug-portlet-subscribers-details')
2566 >>> print_direct_subscribers(user_browser.contents)
2567- Sample Person (Subscribed by Launchpad Janitor)
2568- Steve Alexander (Subscribed by Launchpad Janitor)
2569- >>> print_subscribers_from_duplicates(user_browser.contents)
2570- From duplicates:
2571+ Sample Person
2572+ Steve Alexander
2573 >>> print_also_notified(user_browser.contents)
2574 Also notified:
2575 Foo Bar
2576@@ -68,11 +65,11 @@
2577
2578 >>> user_browser.open(
2579 ... 'http://bugs.launchpad.dev/bugs/1/'
2580- ... '+bug-portlet-subscribers-content')
2581+ ... '+bug-portlet-subscribers-details')
2582 >>> print_direct_subscribers(user_browser.contents)
2583- David Allouche (Subscribed by No Privileges Person)
2584- Sample Person (Subscribed by Launchpad Janitor)
2585- Steve Alexander (Subscribed by Launchpad Janitor)
2586+ David Allouche
2587+ Sample Person
2588+ Steve Alexander
2589
2590 David got a notification, saying that he was subscribed to the bug.
2591
2592@@ -111,12 +108,12 @@
2593
2594 >>> user_browser.open(
2595 ... 'http://bugs.launchpad.dev/bugs/1/'
2596- ... '+bug-portlet-subscribers-content')
2597+ ... '+bug-portlet-subscribers-details')
2598 >>> print_direct_subscribers(user_browser.contents)
2599- David Allouche (Subscribed by No Privileges Person)
2600- Landscape Developers (Subscribed by No Privileges Person)
2601- Sample Person (Subscribed by Launchpad Janitor)
2602- Steve Alexander (Subscribed by Launchpad Janitor)
2603+ David Allouche
2604+ Landscape Developers
2605+ Sample Person
2606+ Steve Alexander
2607
2608
2609 Subscription of private teams
2610@@ -145,37 +142,37 @@
2611 'http://bugs.launchpad.dev/firefox/+bug/1'
2612 >>> foobar_browser.open(
2613 ... 'http://bugs.launchpad.dev/bugs/1/'
2614- ... '+bug-portlet-subscribers-content')
2615+ ... '+bug-portlet-subscribers-details')
2616 >>> print_direct_subscribers(foobar_browser.contents)
2617- Private Team (Subscribed by Foo Bar) (Unsubscribe Private Team)
2618- David Allouche (Subscribed by No Privileges Person)
2619- Landscape Developers (Subscribed by No Privileges Person)
2620- Sample Person (Subscribed by Launchpad Janitor)
2621- Steve Alexander (Subscribed by Launchpad Janitor)
2622+ David Allouche
2623+ Landscape Developers
2624+ Private Team (Unsubscribe)
2625+ Sample Person
2626+ Steve Alexander
2627
2628 Someone not in the team will not see the private team in the
2629 subscribers list.
2630
2631 >>> user_browser.open(
2632 ... 'http://bugs.launchpad.dev/bugs/1/'
2633- ... '+bug-portlet-subscribers-content')
2634+ ... '+bug-portlet-subscribers-details')
2635 >>> print_direct_subscribers(user_browser.contents)
2636- David Allouche (Subscribed by No Privileges Person)
2637- Landscape Developers (Subscribed by No Privileges Person)
2638- Sample Person (Subscribed by Launchpad Janitor)
2639- Steve Alexander (Subscribed by Launchpad Janitor)
2640+ David Allouche
2641+ Landscape Developers
2642+ Sample Person
2643+ Steve Alexander
2644
2645 An anonymous user will not be shown the private team in the subscribers
2646 list.
2647
2648 >>> anon_browser.open(
2649 ... 'http://bugs.launchpad.dev/bugs/1/'
2650- ... '+bug-portlet-subscribers-content')
2651+ ... '+bug-portlet-subscribers-details')
2652 >>> print_direct_subscribers(anon_browser.contents)
2653- David Allouche (Subscribed by No Privileges Person)
2654- Landscape Developers (Subscribed by No Privileges Person)
2655- Sample Person (Subscribed by Launchpad Janitor)
2656- Steve Alexander (Subscribed by Launchpad Janitor)
2657+ David Allouche
2658+ Landscape Developers
2659+ Sample Person
2660+ Steve Alexander
2661
2662 The activity log also does not show subscribed private teams. If we
2663 look at the activity log for the bug used above, we'll see that David Allouche
2664
2665=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions.txt'
2666--- lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions.txt 2011-06-15 11:09:59 +0000
2667+++ lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions.txt 2011-06-15 11:10:20 +0000
2668@@ -5,11 +5,11 @@
2669 in the actions portlet.
2670
2671 >>> from lp.bugs.tests.bug import (
2672- ... print_direct_subscribers, print_also_notified,
2673- ... print_subscribers_from_duplicates)
2674+ ... print_direct_subscribers, print_also_notified)
2675
2676 >>> browser = setupBrowser(auth='Basic foo.bar@canonical.com:test')
2677- >>> browser.open('http://bugs.launchpad.dev/firefox/+bug/1')
2678+ >>> bug_1 = 'http://bugs.launchpad.dev/bugs/1/'
2679+ >>> browser.open(bug_1)
2680 >>> browser.url
2681 'http://bugs.launchpad.dev/firefox/+bug/1'
2682
2683@@ -37,21 +37,7 @@
2684 ... print tag.renderContents()
2685 You have subscribed to this bug report.
2686
2687-There's also now a link to unsubscribe the user next to the name. It's a
2688-relative URL to +subscribe, so it will only work when the portlet is
2689-in the context of the bug page.
2690-
2691- >>> bug_1 = 'http://bugs.launchpad.dev/bugs/1/'
2692- >>> browser.open(bug_1 + '+bug-portlet-subscribers-content')
2693- >>> print_direct_subscribers(browser.contents)
2694- Foo Bar (Self-subscribed) (Unsubscribe Foo Bar)
2695- Sample Person (Subscribed by Launchpad Janitor)
2696- Steve Alexander (Subscribed by Launchpad Janitor)
2697-
2698- >>> browser.open(bug_1 + '+bug-portlet-subscribers-content')
2699- >>> link = browser.getLink(id='unsubscribe-subscriber-16')
2700- >>> print link.mech_link.url
2701- +subscribe
2702+Reloading the page shows the link for editing one's subscription.
2703
2704 >>> browser.open(bug_1)
2705 >>> browser.getLink(
2706@@ -92,21 +78,15 @@
2707
2708 There's an unsubscribe link next to the team name.
2709
2710- >>> browser.open(bug_1 + '+bug-portlet-subscribers-content')
2711+ >>> browser.open(bug_1 + '+bug-portlet-subscribers-details')
2712 >>> print_direct_subscribers(browser.contents)
2713- Launchpad Developers (Subscribed by Foo Bar) (Unsubscribe Launchpad
2714- Developers)
2715- Sample Person (Subscribed by Launchpad Janitor)
2716- Steve Alexander (Subscribed by Launchpad Janitor)
2717+ Launchpad Developers (Unsubscribe)
2718+ Sample Person
2719+ Steve Alexander
2720
2721-Clicking either the subscribe link (for subscribing the user to the bug)
2722-or the unsubscribe link for the team gives us the option of both
2723+Clicking either the subscribe link gives us the option of both
2724 subscribing Foo Bar, and unsubscribing the Launchpad team.
2725
2726- >>> browser.open(bug_1 + '+bug-portlet-subscribers-content')
2727- >>> browser.getLink(id='unsubscribe-subscriber-57').mech_link.url
2728- '+subscribe'
2729-
2730 >>> browser.open(bug_1)
2731 >>> browser.getLink(
2732 ... "not directly subscribed to this bug's notifications.").click()
2733@@ -130,10 +110,10 @@
2734 ... print tag.renderContents()
2735 Launchpad Developers has been unsubscribed from bug 1.
2736
2737- >>> browser.open(bug_1 + '+bug-portlet-subscribers-content')
2738+ >>> browser.open(bug_1 + '+bug-portlet-subscribers-details')
2739 >>> print_direct_subscribers(browser.contents)
2740- Sample Person (Subscribed by Launchpad Janitor)
2741- Steve Alexander (Subscribed by Launchpad Janitor)
2742+ Sample Person
2743+ Steve Alexander
2744
2745 On the subscribe page there's a Cancel link as well, that will return
2746 the browser to the bug page.
2747@@ -155,25 +135,19 @@
2748
2749 >>> len(find_tags_by_class(browser.contents, 'informational message'))
2750 0
2751- >>> browser.open(bug_1 + '+bug-portlet-subscribers-content')
2752- >>> print_direct_subscribers(browser.contents)
2753- Sample Person (Subscribed by Launchpad Janitor)
2754- Steve Alexander (Subscribed by Launchpad Janitor)
2755
2756-Subscribers which the current user may unsubscribe (the current user and teams
2757-they are a member of) display first in the list, before all other
2758-subscriptions.
2759+Subscribers which the current user may unsubscribe have an indication
2760+that shows the unsubscribe link in the list of subscribers.
2761
2762 >>> browser.open(
2763 ... 'http://bugs.launchpad.dev/firefox/+bug/1/+addsubscriber')
2764 >>> browser.getControl('Person').value = 'testing-spanish-team'
2765 >>> browser.getControl('Subscribe user').click()
2766- >>> browser.open(bug_1 + '+bug-portlet-subscribers-content')
2767+ >>> browser.open(bug_1 + '+bug-portlet-subscribers-details')
2768 >>> print_direct_subscribers(browser.contents)
2769- testing Spanish team (Subscribed by Foo Bar)
2770- (Unsubscribe testing Spanish team)
2771- Sample Person (Subscribed by Launchpad Janitor)
2772- Steve Alexander (Subscribed by Launchpad Janitor)
2773+ Sample Person
2774+ Steve Alexander
2775+ testing Spanish team (Unsubscribe)
2776
2777 Subscriptions and Duplicate Bugs
2778 --------------------------------
2779@@ -188,17 +162,12 @@
2780 >>> stevea_browser = setupBrowser(
2781 ... auth="Basic steve.alexander@ubuntulinux.com:test")
2782 >>> bug_3 = 'http://launchpad.dev/bugs/3/'
2783- >>> stevea_browser.open(bug_3 + "+bug-portlet-dupe-subscribers-content")
2784- >>> print_subscribers_from_duplicates(stevea_browser.contents)
2785- From duplicates:
2786-
2787- >>> stevea_browser.open(bug_3 + "+bug-portlet-subscribers-content")
2788+ >>> stevea_browser.open(bug_3 + "+bug-portlet-subscribers-details")
2789 >>> print_also_notified(stevea_browser.contents)
2790 Also notified:
2791
2792 But if bug #2, a bug that Steve is directly subscribed to, is marked as
2793-a dupe of bug #3, then Steve gets indirectly subscribed to bug #3, and
2794-is presented with the Unsubscribe link instead.
2795+a dupe of bug #3, then Steve gets indirectly subscribed to bug #3.
2796
2797 >>> bug_2 = 'http://launchpad.dev/tomcat/+bug/2/'
2798 >>> stevea_browser.open(bug_2 + "+duplicate")
2799@@ -206,14 +175,18 @@
2800 >>> stevea_browser.getControl("Duplicate Of").value = "3"
2801 >>> stevea_browser.getControl("Change").click()
2802
2803- >>> stevea_browser.open(bug_3 + "+bug-portlet-dupe-subscribers-content")
2804- >>> print_subscribers_from_duplicates(stevea_browser.contents)
2805- From duplicates:
2806- Steve Alexander (Subscribed to bug 2 by Launchpad Janitor)
2807- (Unsubscribe Steve Alexander)
2808-
2809- >>> stevea_browser.getLink(id='unsubscribe-subscriber-11').mech_link.url
2810- '+subscribe'
2811+A logged-in user doesn't show up in the subscribers list.
2812+
2813+ >>> stevea_browser.open(bug_3 + "+bug-portlet-subscribers-details")
2814+ >>> print_also_notified(stevea_browser.contents)
2815+ Also notified:
2816+
2817+However, an anonymous user will see them listed.
2818+
2819+ >>> anon_browser.open(bug_3 + "+bug-portlet-subscribers-details")
2820+ >>> print_also_notified(anon_browser.contents)
2821+ Also notified:
2822+ Steve Alexander
2823
2824 When he chooses to unsubscribe, he will be unsubscribed from bug #2, the
2825 dupe of bug #3, so he'll no longer get mail from bug #3.
2826@@ -223,24 +196,16 @@
2827 ... "not directly subscribed to this bug's notifications.").click()
2828 >>> stevea_browser.getControl("Continue").click()
2829
2830-# XXX: Brad Bollenbach 2006-09-27 bug=62634: Printing the tag here,
2831-# instead of tag.string.
2832-
2833 >>> for tag in find_tags_by_class(
2834 ... stevea_browser.contents, 'informational message'):
2835 ... print tag.renderContents()
2836 You have been unsubscribed from bug 3 and 1 duplicate (<a...#2</a>)...
2837
2838-(Except for Mark, who has a structural subscription to the target,
2839-there are no longer any indirect subscribers, because Steve was
2840+There are no longer any indirect subscribers, because Steve was
2841 unsubscribed from the dupes and thus is no longer indirectly subscribed
2842-to bug #3.)
2843-
2844- >>> stevea_browser.open(bug_3 + "+bug-portlet-dupe-subscribers-content")
2845- >>> print_subscribers_from_duplicates(stevea_browser.contents)
2846- From duplicates:
2847-
2848- >>> stevea_browser.open(bug_3 + "+bug-portlet-subscribers-content")
2849+to bug #3.
2850+
2851+ >>> stevea_browser.open(bug_3 + "+bug-portlet-subscribers-details")
2852 >>> print_also_notified(stevea_browser.contents)
2853 Also notified:
2854
2855@@ -260,16 +225,11 @@
2856 >>> stevea_browser.getControl('Person').value = 'stevea'
2857 >>> stevea_browser.getControl('Subscribe user').click()
2858
2859- >>> stevea_browser.open(bug_3 + "+bug-portlet-dupe-subscribers-content")
2860- >>> print_subscribers_from_duplicates(stevea_browser.contents)
2861- From duplicates:
2862- Sample Person (Subscribed ...)
2863- Steve Alexander (Subscribed ...) (Unsubscribe Steve Alexander)
2864- testing Spanish team (Subscribed to bug 1 by Foo Bar)
2865-
2866- >>> stevea_browser.open(bug_3 + "+bug-portlet-subscribers-content")
2867+ >>> stevea_browser.open(bug_3 + "+bug-portlet-subscribers-details")
2868 >>> print_also_notified(stevea_browser.contents)
2869 Also notified:
2870+ Sample Person
2871+ testing Spanish team
2872
2873 >>> stevea_browser.open("http://launchpad.dev/bugs/3")
2874 >>> stevea_browser.getLink(
2875@@ -289,11 +249,9 @@
2876 >>> stevea_browser.getControl("Duplicate Of").value = ""
2877 >>> stevea_browser.getControl("Change").click()
2878
2879-This unsubscribe behaviour is team-aware too, so you can unsubscribe
2880-your teams from a bug, even when the team's subscription comes from a
2881-duplicate. For example, let's login as Foo Bar, subscribe ubuntu-team to
2882-bug #2, and notice how bug #3's indirect subscriptions are update to
2883-include that team.
2884+This unsubscribe behaviour is team-aware: if we login as Foo Bar,
2885+subscribe ubuntu-team to bug #2, bug #3's indirect subscriptions
2886+are updated to include that team.
2887
2888 >>> foobar_browser = setupBrowser(auth="Basic foo.bar@canonical.com:test")
2889 >>> foobar_browser.open("http://launchpad.dev/bugs/2")
2890@@ -301,17 +259,10 @@
2891 >>> foobar_browser.getControl("Person").value = "ubuntu-team"
2892 >>> foobar_browser.getControl("Subscribe user").click()
2893
2894- >>> foobar_browser.open(bug_3 + "+bug-portlet-dupe-subscribers-content")
2895-
2896- >>> print_subscribers_from_duplicates(foobar_browser.contents)
2897- From duplicates:
2898- Ubuntu Team (Subscribed to bug 2 by Foo Bar) (Unsubscribe Ubuntu Team)
2899-
2900- >>> foobar_browser.open(
2901- ... "http://launchpad.dev/bugs/3/+bug-portlet-subscribers-content")
2902-
2903+ >>> foobar_browser.open(bug_3 + "+bug-portlet-subscribers-details")
2904 >>> print_also_notified(foobar_browser.contents)
2905 Also notified:
2906+ Ubuntu Team
2907
2908 The subscribe link for Foo Bar still says "Subscribe", because
2909 Foo Bar can subscribe himself directly to this bug. For unsubscribing
2910@@ -337,38 +288,7 @@
2911
2912 (ubuntu-team is no longer an indirect subscriber.)
2913
2914- >>> foobar_browser.open(bug_3 + "+bug-portlet-dupe-subscribers-content")
2915- >>> print_subscribers_from_duplicates(foobar_browser.contents)
2916- From duplicates:
2917-
2918 >>> foobar_browser.open(
2919- ... "http://launchpad.dev/bugs/3/+bug-portlet-subscribers-content")
2920+ ... "http://launchpad.dev/bugs/3/+bug-portlet-subscribers-details")
2921 >>> print_also_notified(foobar_browser.contents)
2922 Also notified:
2923-
2924-
2925-Displaying subscribers
2926-----------------------
2927-
2928-The display names of subscribers are escaped in the subscribers list, they are
2929-also trimmed to 20 characters, so that they fit alongside the unsubscribe
2930-icon.
2931-
2932- >>> login(ANONYMOUS)
2933- >>> abuser = factory.makePerson(
2934- ... name='abuser',
2935- ... displayname='<script>javascript:alert("YO")</script>')
2936- >>> logout()
2937- >>> browser.open(
2938- ... 'http://bugs.launchpad.dev/firefox/+bug/1/+addsubscriber')
2939- >>> browser.getControl('Person').value = 'abuser'
2940- >>> browser.getControl('Subscribe user').click()
2941- >>> bug_1 = 'http://bugs.launchpad.dev/bugs/1/'
2942- >>> browser.open(bug_1 + '+bug-portlet-subscribers-content')
2943- >>> subscriber_list = find_tag_by_id(
2944- ... browser.contents, 'subscribers-direct')
2945- >>> for subscriber in subscriber_list.findAll('div'):
2946- ... if '~abuser' in subscriber.a['href']:
2947- ... print subscriber.a.contents[2].strip()
2948- &lt;script&gt;javascrip...
2949-
2950
2951=== removed file 'lib/lp/bugs/templates/bug-portlet-dupe-subscribers-content.pt'
2952--- lib/lp/bugs/templates/bug-portlet-dupe-subscribers-content.pt 2011-02-03 05:14:54 +0000
2953+++ lib/lp/bugs/templates/bug-portlet-dupe-subscribers-content.pt 1970-01-01 00:00:00 +0000
2954@@ -1,49 +0,0 @@
2955-<div
2956- tal:omit-tag=""
2957- xmlns:tal="http://xml.zope.org/namespaces/tal"
2958- xmlns:metal="http://xml.zope.org/namespaces/metal"
2959- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
2960- tal:define="
2961- bug context/bug|context;
2962- from_dupes_subscriptions view/sorted_subscriptions_from_dupes;
2963- ">
2964- <div
2965- tal:condition="from_dupes_subscriptions"
2966- id="subscribers-from-duplicates"
2967- class="section"
2968- >
2969- <h2>From duplicates</h2>
2970- <div
2971- tal:repeat="subscription from_dupes_subscriptions"
2972- tal:attributes="
2973- class subscription/css_name;
2974- id string:dupe-${subscription/css_name};
2975- "
2976- >
2977-
2978- <a
2979- tal:condition="subscription/person/name|nothing"
2980- tal:attributes="
2981- href subscription/person/fmt:url;
2982- title subscription/display_duplicate_subscribed_by;
2983- name subscription/person/fmt:displayname
2984- "
2985- >
2986- <tal:block replace="structure subscription/person/fmt:icon" />
2987- <tal:block replace="subscription/person/fmt:displayname/fmt:shorten/20" />
2988- </a>
2989-
2990- <a tal:condition="python: subscription.canBeUnsubscribedByUser(view.user)"
2991- href="+subscribe"
2992- tal:attributes="
2993- title string:Unsubscribe ${subscription/person/fmt:displayname};
2994- id string:unsubscribe-${subscription/css_name};
2995- class python: view.getSubscriptionClassForUser(subscription.person)
2996- "
2997- >
2998- <img class="unsub-icon" src="/@@/remove" alt="Remove"
2999- tal:attributes="id string:unsubscribe-icon-${subscription/css_name}" />
3000- </a>
3001- </div>
3002- </div>
3003-</div>
3004
3005=== removed file 'lib/lp/bugs/templates/bug-portlet-subscribers-content.pt'
3006--- lib/lp/bugs/templates/bug-portlet-subscribers-content.pt 2011-05-20 21:09:40 +0000
3007+++ lib/lp/bugs/templates/bug-portlet-subscribers-content.pt 1970-01-01 00:00:00 +0000
3008@@ -1,74 +0,0 @@
3009-<div
3010- tal:omit-tag=""
3011- xmlns:tal="http://xml.zope.org/namespaces/tal"
3012- xmlns:metal="http://xml.zope.org/namespaces/metal"
3013- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
3014- tal:define="
3015- bug context/bug|context;
3016- direct_subscriptions view/sorted_direct_subscriptions;
3017- ">
3018- <div class="section" id="subscribers-direct">
3019- <h3>Directly subscribed</h3>
3020- <div id="subscribers-links">
3021- <div
3022- tal:condition="direct_subscriptions"
3023- tal:repeat="subscription direct_subscriptions"
3024- tal:attributes="
3025- class subscription/css_name;
3026- id string:direct-${subscription/css_name}">
3027- <metal:subscriber metal:define-macro="subscriber-row">
3028-
3029- <a
3030- tal:condition="subscription/person/name|nothing"
3031- tal:attributes="
3032- href subscription/person/fmt:url;
3033- title subscription/display_subscribed_by;
3034- name subscription/person/fmt:displayname
3035- "
3036- >
3037- <tal:block replace="structure subscription/person/fmt:icon" />
3038- <tal:block replace="subscription/person/fmt:displayname/fmt:shorten/20" />
3039- </a>
3040-
3041- <a tal:condition="python: subscription.canBeUnsubscribedByUser(view.user)"
3042- href="+subscribe"
3043- tal:attributes="
3044- title string:Unsubscribe ${subscription/person/fmt:displayname};
3045- id string:unsubscribe-${subscription/css_name};
3046- class python: view.getSubscriptionClassForUser(subscription.person)
3047- "
3048- >
3049- <img class="unsub-icon" src="/@@/remove" alt="Remove"
3050- tal:attributes="id string:unsubscribe-icon-${subscription/css_name}" />
3051- </a>
3052-
3053- </metal:subscriber>
3054- </div>
3055- </div>
3056- <div id="none-subscribers"
3057- tal:condition="not:direct_subscriptions">
3058- No subscribers.</div>
3059- </div>
3060- <div id="subscribers-from-duplicates-container">
3061- <a id="subscribers-from-dupes-content-link"
3062- tal:attributes="href bug/fmt:url/+bug-portlet-dupe-subscribers-content"></a>
3063- <div id="subscribers-portlet-dupe-spinner"
3064- style="text-align: center; display: none">
3065- <img src="/@@/spinner" />
3066- </div>
3067- </div>
3068- <tal:also_notified condition="not: request/features/malone.advanced-subscriptions.enabled">
3069- <div
3070- tal:define="also_notified_subscribers bug/getAlsoNotifiedSubscribers"
3071- tal:condition="also_notified_subscribers"
3072- id="subscribers-indirect"
3073- class="Section"
3074- >
3075- <h3>Also notified</h3>
3076- <div
3077- tal:repeat="subscriber also_notified_subscribers"
3078- tal:content="structure subscriber/fmt:link"
3079- />
3080- </div>
3081- </tal:also_notified>
3082-</div>
3083
3084=== modified file 'lib/lp/bugs/templates/bug-portlet-subscribers.pt'
3085--- lib/lp/bugs/templates/bug-portlet-subscribers.pt 2011-06-15 11:09:59 +0000
3086+++ lib/lp/bugs/templates/bug-portlet-subscribers.pt 2011-06-15 11:10:20 +0000
3087@@ -11,14 +11,4 @@
3088 <div tal:content="structure context_menu/addsubscriber/render" />
3089 </div>
3090 <div id="other-bug-subscribers"></div>
3091- <a id="subscribers-ids-link"
3092- tal:define="bug context/bug|context"
3093- tal:attributes="href bug/fmt:url/+bug-portlet-subscribers-ids"></a>
3094- <a id="subscribers-content-link"
3095- tal:define="bug context/bug|context"
3096- tal:attributes="href bug/fmt:url/+bug-portlet-subscribers-content"></a>
3097- <div id="subscribers-portlet-spinner"
3098- style="text-align: center; display: none">
3099- <img src="/@@/spinner" />
3100- </div>
3101 </div>
3102
3103=== modified file 'lib/lp/bugs/templates/bug-portlet-subscription.pt'
3104--- lib/lp/bugs/templates/bug-portlet-subscription.pt 2011-06-15 11:09:59 +0000
3105+++ lib/lp/bugs/templates/bug-portlet-subscription.pt 2011-06-15 11:10:20 +0000
3106@@ -51,7 +51,6 @@
3107 </div>
3108 <script type="text/javascript">
3109 LPS.use('io-base', 'node',
3110- 'lp.bugs.bugtask_index.portlets',
3111 'lp.bugs.bugtask_index.portlets.subscription', function(Y) {
3112 // Must be done inline here to ensure the load event fires.
3113 // This is a work around for a YUI3 issue with event handling.
3114@@ -64,8 +63,6 @@
3115
3116 Y.on('domready', function() {
3117 Y.lp.bugs.bugtask_index.portlets.subscription.initialize();
3118- Y.lp.bugs.bugtask_index.portlets.load_subscribers_portlet(
3119- subscription_link, subscription_link_handler);
3120 });
3121 });
3122 </script>
3123
3124=== modified file 'lib/lp/bugs/templates/bugtask-index.pt'
3125--- lib/lp/bugs/templates/bugtask-index.pt 2011-06-15 11:09:59 +0000
3126+++ lib/lp/bugs/templates/bugtask-index.pt 2011-06-15 11:10:20 +0000
3127@@ -10,17 +10,6 @@
3128 <script type='text/javascript' tal:content="string:var yui_base='${yui}';" />
3129 <script type='text/javascript' id='available-official-tags-js'
3130 tal:content="view/available_official_tags_js" />
3131- <tal:subscriptions-js
3132- define="enabled features/malone.advanced-subscriptions.enabled">
3133- <script type="text/javascript" id="use-advanced-subscriptions"
3134- tal:condition="enabled">
3135- use_advanced_subscriptions = true;
3136- </script>
3137- <script type="text/javascript" id="use-advanced-subscriptions"
3138- tal:condition="not:enabled">
3139- use_advanced_subscriptions = false;
3140- </script>
3141- </tal:subscriptions-js>
3142 <tal:if condition="context/bug/private">
3143 <script type="text/javascript">
3144 var bug_private = true;
3145@@ -43,11 +32,9 @@
3146 </tal:if>
3147 <script type="text/javascript">
3148 LPS.use('base', 'node', 'oop', 'event', 'lp.bugs.bugtask_index',
3149- 'lp.bugs.bugtask_index.portlets', 'lp.bugs.subscribers_list',
3150+ 'lp.bugs.subscribers_list',
3151 'lp.code.branchmergeproposal.diff', 'lp.comments.hide',
3152 function(Y) {
3153- Y.lp.bugs.bugtask_index.portlets.use_advanced_subscriptions =
3154- use_advanced_subscriptions;
3155 Y.lp.bugs.bugtask_index.setup_bugtask_index();
3156 Y.on('load', function(e) {
3157 Y.lp.code.branchmergeproposal.diff.connect_diff_links();
3158
3159=== modified file 'lib/lp/bugs/tests/bug.py'
3160--- lib/lp/bugs/tests/bug.py 2011-05-27 21:12:25 +0000
3161+++ lib/lp/bugs/tests/bug.py 2011-06-15 11:10:20 +0000
3162@@ -43,35 +43,38 @@
3163
3164 def print_direct_subscribers(bug_page):
3165 """Print the direct subscribers listed in a portlet."""
3166- print_subscribers(bug_page, 'subscribers-direct')
3167+ print_subscribers(bug_page, 'Maybe', reverse=True)
3168
3169
3170 def print_also_notified(bug_page):
3171- """Print the structural subscribers listed in a portlet."""
3172+ """Print the structural and duplicate subscribers listed in a portlet."""
3173 print 'Also notified:'
3174- print_subscribers(bug_page, 'subscribers-indirect')
3175-
3176-
3177-def print_subscribers_from_duplicates(bug_page):
3178- """Print the subscribers from duplicates listed in a portlet."""
3179- print 'From duplicates:'
3180- print_subscribers(bug_page, 'subscribers-from-duplicates')
3181-
3182-
3183-def print_subscribers(bug_page, subscriber_list_id):
3184- """Print the subscribers listed in the subscriber portlet."""
3185- subscriber_list = find_tag_by_id(bug_page, subscriber_list_id)
3186- if subscriber_list is None:
3187- # No list with this ID (as can happen if there are
3188- # no indirect subscribers), so just print an empty string.
3189+ print_subscribers(bug_page, 'Maybe')
3190+
3191+
3192+def print_subscribers(bug_page, subscription_level=None, reverse=False):
3193+ """Print the subscribers listed in the subscribers JSON portlet."""
3194+ from simplejson import loads
3195+ details = loads(bug_page)
3196+
3197+ if details is None:
3198+ # No subscribers at all.
3199 print ""
3200 else:
3201- anchors = subscriber_list.findAll('a')
3202- for anchor in anchors:
3203- sub_display = extract_text(anchor)
3204- if anchor.has_key('title'):
3205- sub_display += (' (%s)' % anchor['title'])
3206- print sub_display
3207+ lines = []
3208+ for subscription in details:
3209+ level_matches = (
3210+ (not reverse and
3211+ subscription['subscription_level'] == subscription_level) or
3212+ (reverse and
3213+ subscription['subscription_level'] != subscription_level))
3214+ if subscription_level is None or level_matches:
3215+ subscriber = subscription['subscriber']
3216+ line = subscriber['display_name']
3217+ if subscriber['can_edit']:
3218+ line += " (Unsubscribe)"
3219+ lines.append(line)
3220+ print "\n".join(sorted(lines))
3221
3222
3223 def print_bug_affects_table(content, highlighted_only=False):