← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Данило Шеган has proposed merging lp:~danilo/launchpad/bug-772754-other-subscribers-remove-cruft into lp:launchpad with lp:~danilo/launchpad/bug-772754-other-subscribers-actions as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #772754 in Launchpad itself: "After better-bug-notification changes, list of bug subscribers is confusing"
  https://bugs.launchpad.net/launchpad/+bug/772754

For more details, see:
https://code.launchpad.net/~danilo/launchpad/bug-772754-other-subscribers-remove-cruft/+merge/64188

= Bug 772754: Other subscribers list, part 7 =

NOT READY FOR REVIEW YET, though not much review will be needed (this is just removal of all the unneeded bits and pieces).  Final step.

This is 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

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/browser/bug.py
  lib/lp/bugs/browser/bugsubscription.py
  lib/lp/bugs/browser/configure.zcml
  lib/lp/bugs/browser/tests/test_bugsubscription_views.py
  lib/lp/bugs/javascript/bugtask_index.js
  lib/lp/bugs/javascript/subscribers_list.js
  lib/lp/bugs/javascript/tests/test_subscribers_list.js
  lib/lp/bugs/templates/bug-portlet-subscribers.pt
  lib/lp/bugs/templates/bug-portlet-subscription.pt
  lib/lp/bugs/templates/bugtask-index.pt

./lib/lp/bugs/browser/bug.py
     592: E302 expected 2 blank lines, found 1
./lib/lp/bugs/browser/bugsubscription.py
      46: 'BugViewMixin' imported but unused
./lib/lp/bugs/javascript/subscribers_list.js
     286: Expected ';' and instead saw 'BugSubscribersLoader'.
     319: Expected ';' and instead saw 'BugSubscribersLoader'.
     332: Function statements should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.
     356: Expected ';' and instead saw 'function'.
make: *** [lint] Error 6
-- 
https://code.launchpad.net/~danilo/launchpad/bug-772754-other-subscribers-remove-cruft/+merge/64188
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~danilo/launchpad/bug-772754-other-subscribers-remove-cruft into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bug.py'
--- lib/lp/bugs/browser/bug.py	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/browser/bug.py	2011-06-10 14:01:53 +0000
@@ -477,24 +477,6 @@
         """
         return self.subscription_info.duplicate_subscriptions.subscribers
 
-    @cachedproperty
-    def subscriber_ids(self):
-        """Return a dictionary mapping a css_name to user name."""
-        subscribers = set().union(
-            self.direct_subscribers,
-            self.duplicate_subscribers)
-
-        # The current user has to be in subscribers_id so
-        # in case the id is needed for a new subscription.
-        user = getUtility(ILaunchBag).user
-        if user is not None:
-            subscribers.add(user)
-
-        ids = {}
-        for sub in subscribers:
-            ids[sub.name] = 'subscriber-%s' % sub.id
-        return ids
-
     def getSubscriptionClassForUser(self, subscribed_person):
         """Return a set of CSS class names based on subscription status.
 

=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
--- lib/lp/bugs/browser/bugsubscription.py	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/browser/bugsubscription.py	2011-06-10 14:01:53 +0000
@@ -7,8 +7,7 @@
 __all__ = [
     'AdvancedSubscriptionMixin',
     'BugMuteSelfView',
-    'BugPortletDuplicateSubcribersContents',
-    'BugPortletSubcribersContents',
+    'BugPortletSubscribersWithDetails',
     'BugSubscriptionAddView',
     'BugSubscriptionListView',
     ]
@@ -546,46 +545,6 @@
                 'dupe_links_string': dupe_links_string})
 
 
-class BugPortletSubcribersContents(LaunchpadView, BugViewMixin):
-    """View for the contents for the subscribers portlet."""
-
-    @property
-    def sorted_direct_subscriptions(self):
-        """Get the list of direct subscriptions to the bug.
-
-        The list is sorted such that subscriptions you can unsubscribe appear
-        before all other subscriptions.
-        """
-        direct_subscriptions = [
-            SubscriptionAttrDecorator(subscription)
-            for subscription in self.context.getDirectSubscriptions().sorted]
-        can_unsubscribe = []
-        cannot_unsubscribe = []
-        for subscription in direct_subscriptions:
-            if not check_permission('launchpad.View', subscription.person):
-                continue
-            if subscription.person == self.user:
-                can_unsubscribe = [subscription] + can_unsubscribe
-            elif subscription.canBeUnsubscribedByUser(self.user):
-                can_unsubscribe.append(subscription)
-            else:
-                cannot_unsubscribe.append(subscription)
-        return can_unsubscribe + cannot_unsubscribe
-
-
-class BugPortletDuplicateSubcribersContents(LaunchpadView, BugViewMixin):
-    """View for the contents for the subscribers-from-dupes portlet block."""
-
-    @property
-    def sorted_subscriptions_from_dupes(self):
-        """Get the list of subscriptions to duplicates of this bug."""
-        return [
-            SubscriptionAttrDecorator(subscription)
-            for subscription in sorted(
-                self.context.getSubscriptionsFromDuplicates(),
-                key=(lambda subscription: subscription.person.displayname))]
-
-
 class BugPortletSubscribersWithDetails(LaunchpadView):
     """A view that returns a JSON dump of the subscriber details for a bug."""
 
@@ -641,20 +600,6 @@
         return self.subscriber_data_js
 
 
-class BugPortletSubcribersIds(LaunchpadView, BugViewMixin):
-    """A view that returns a JSON dump of the subscriber IDs for a bug."""
-
-    @property
-    def subscriber_ids_js(self):
-        """Return subscriber_ids in a form suitable for JavaScript use."""
-        return dumps(self.subscriber_ids)
-
-    def render(self):
-        """Override the default render() to return only JSON."""
-        self.request.response.setHeader('content-type', 'application/json')
-        return self.subscriber_ids_js
-
-
 class SubscriptionAttrDecorator:
     """A BugSubscription with added attributes for HTML/JS."""
     delegates(IBugSubscription, 'subscription')

=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/browser/configure.zcml	2011-06-10 14:01:53 +0000
@@ -1085,23 +1085,6 @@
         </browser:pages>
         <browser:page
             for="lp.bugs.interfaces.bug.IBug"
-            name="+bug-portlet-subscribers-content"
-            class="lp.bugs.browser.bugsubscription.BugPortletSubcribersContents"
-            template="../templates/bug-portlet-subscribers-content.pt"
-            permission="zope.Public"/>
-        <browser:page
-            for="lp.bugs.interfaces.bug.IBug"
-            name="+bug-portlet-dupe-subscribers-content"
-            class="lp.bugs.browser.bugsubscription.BugPortletDuplicateSubcribersContents"
-            template="../templates/bug-portlet-dupe-subscribers-content.pt"
-            permission="zope.Public"/>
-        <browser:page
-            for="lp.bugs.interfaces.bug.IBug"
-            name="+bug-portlet-subscribers-ids"
-            class="lp.bugs.browser.bugsubscription.BugPortletSubcribersIds"
-            permission="zope.Public"/>
-        <browser:page
-            for="lp.bugs.interfaces.bug.IBug"
             name="+bug-portlet-subscribers-details"
             class="
               lp.bugs.browser.bugsubscription.BugPortletSubscribersWithDetails"

=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py	2011-06-10 14:01:53 +0000
@@ -12,7 +12,6 @@
 from canonical.testing.layers import LaunchpadFunctionalLayer
 
 from lp.bugs.browser.bugsubscription import (
-    BugPortletSubcribersIds,
     BugPortletSubscribersWithDetails,
     BugSubscriptionListView,
     BugSubscriptionSubscribeSelfView,
@@ -375,24 +374,6 @@
         self.assertTrue('Also notified' in contents)
 
 
-class BugPortletSubcribersIdsTests(TestCaseWithFactory):
-
-    layer = LaunchpadFunctionalLayer
-
-    def test_content_type(self):
-        bug = self.factory.makeBug()
-
-        person = self.factory.makePerson()
-        with person_logged_in(person):
-            harness = LaunchpadFormHarness(
-                bug.default_bugtask, BugPortletSubcribersIds)
-            harness.view.render()
-
-        self.assertEqual(
-            harness.request.response.getHeader('content-type'),
-            'application/json')
-
-
 class BugSubscriptionsListViewTestCase(TestCaseWithFactory):
     """Tests for the BugSubscriptionsListView."""
 

=== modified file 'lib/lp/bugs/javascript/bugtask_index.js'
--- lib/lp/bugs/javascript/bugtask_index.js	2011-05-12 02:09:42 +0000
+++ lib/lp/bugs/javascript/bugtask_index.js	2011-06-10 14:01:53 +0000
@@ -40,8 +40,6 @@
 var link_branch_link;
 
 namespace.setup_bugtask_index = function() {
-    Y.lp.bugs.bugtask_index.portlets.setup_portlet_handlers();
-
     /*
      * Display the privacy notification if the bug is private
      */
@@ -1115,5 +1113,5 @@
                         "lazr.formoverlay", "lazr.anim", "lazr.base",
                         "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
                         "lp.client", "escape",
-                        "lp.client.plugins", "lp.bugs.bugtask_index.portlets",
-                        "lp.bugs.subscriber", "lp.app.errors"]});
+                        "lp.client.plugins", "lp.bugs.subscriber",
+                        "lp.app.errors"]});

=== removed file 'lib/lp/bugs/javascript/bugtask_index_portlets.js'
--- lib/lp/bugs/javascript/bugtask_index_portlets.js	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/javascript/bugtask_index_portlets.js	1970-01-01 00:00:00 +0000
@@ -1,897 +0,0 @@
-/* Copyright 2011 Canonical Ltd.  This software is licensed under the
- * GNU Affero General Public License version 3 (see the file LICENSE).
- *
- * Form overlay widgets and subscriber handling for bug pages.
- *
- * @module bugs
- * @submodule bugtask_index.portlets
- */
-
-YUI.add('lp.bugs.bugtask_index.portlets', function(Y) {
-
-var namespace = Y.namespace('lp.bugs.bugtask_index.portlets');
-
-// The launchpad js client used.
-var lp_client;
-
-// The launchpad client entry for the current bug.
-var lp_bug_entry;
-
-// The bug itself, taken from cache.
-var bug_repr;
-
-// A boolean telling us whether advanced subscription features are to be
-// used or not.
-var use_advanced_subscriptions = false;
-
-var subscription_labels = Y.lp.bugs.subscriber.subscription_labels;
-
-submit_button_html =
-    '<button type="submit" name="field.actions.change" ' +
-    'value="Change" class="lazr-pos lazr-btn" >OK</button>';
-cancel_button_html =
-    '<button type="button" name="field.actions.cancel" ' +
-    'class="lazr-neg lazr-btn" >Cancel</button>';
-
-// The set of subscriber CSS IDs as a JSON struct.
-var subscriber_ids;
-
-/*
- * An object representing the bugtask subscribers portlet.
- *
- * Since the portlet loads via XHR and inline subscribing
- * depends on that portlet being loaded, setup a custom
- * event object, to provide a hook for initializing subscription
- * link callbacks after custom events.
- */
-var PortletTarget = function() {};
-Y.augment(PortletTarget, Y.Event.Target);
-namespace.portlet = new PortletTarget();
-
-/*
- * Create the lp client and bug entry if we haven't done so already.
- *
- * @method setup_client_and_bug
- */
-function setup_client_and_bug() {
-    lp_client = new Y.lp.client.Launchpad();
-
-    if (bug_repr === undefined) {
-        bug_repr = LP.cache.bug;
-        lp_bug_entry = new Y.lp.client.Entry(
-            lp_client, bug_repr, bug_repr.self_link);
-    }
-}
-
-namespace.load_subscribers_portlet = function(
-        subscription_link, subscription_link_handler) {
-    if (Y.UA.ie) {
-        return null;
-    }
-
-    Y.one('#subscribers-portlet-spinner').setStyle('display', 'block');
-
-    function hide_spinner() {
-        Y.one('#subscribers-portlet-spinner').setStyle('display', 'none');
-            // Fire a custom event to notify that the initial click
-            // handler on subscription_link set above should be
-            // cleared.
-            if (namespace) {
-                namespace.portlet.fire(
-                  'bugs:portletloadfailed', subscription_link_handler);
-        }
-    }
-
-    function setup_portlet(transactionid, response, args) {
-        hide_spinner();
-        Y.one('#portlet-subscribers')
-            .appendChild(Y.Node.create(response.responseText));
-
-        // Fire a custom portlet loaded event to notify when
-        // it's safe to setup subscriber link callbacks.
-        namespace.portlet.fire('bugs:portletloaded');
-    }
-
-    var config = {on: {success: setup_portlet,
-                       failure: hide_spinner}};
-    var url = Y.one(
-        '#subscribers-content-link').getAttribute('href').replace(
-            'bugs.', '');
-    Y.io(url, config);
-};
-
-
-namespace.setup_portlet_handlers = function() {
-    namespace.portlet.subscribe('bugs:portletloaded', function() {
-        load_subscriber_ids();
-    });
-    /*
-     * If the subscribers portlet fails to load, clear any
-     * click handlers, so the normal subscribe page can be reached.
-     */
-    namespace.portlet.subscribe('bugs:portletloadfailed', function(handler) {
-        handler.detach();
-    });
-    namespace.portlet.subscribe('bugs:dupeportletloaded', function() {
-        setup_subscription_link_handlers();
-        setup_unsubscribe_icon_handlers();
-    });
-    /* If the dupe subscribers portlet fails to load,
-     * be sure to try to handle any unsub icons that may
-     * exist for others.
-     */
-    namespace.portlet.subscribe(
-        'bugs:dupeportletloadfailed',
-        function(handlers) {
-            setup_subscription_link_handlers();
-            setup_unsubscribe_icon_handlers();
-        });
-
-    /* If loading the subscriber IDs JSON has succeeded, set up the
-     * subscription link handlers and load the subscribers from dupes.
-     */
-    namespace.portlet.subscribe(
-        'bugs:portletsubscriberidsloaded',
-        function() {
-            load_subscribers_from_duplicates();
-        });
-
-    /* If loading the subscriber IDs JSON fails we still need to load the
-     * subscribers from duplicates but we don't set up the subscription link
-     * handlers.
-     */
-    namespace.portlet.subscribe(
-        'bugs:portletsubscriberidsfailed',
-        function() {
-            load_subscribers_from_duplicates();
-        });
-
-    /*
-     * Subscribing someone else requires loading a grayed out
-     * username into the DOM until the subscribe action completes.
-     * There are a couple XHR requests in check_can_be_unsubscribed
-     * before the subscribe work can be done, so fire a custom event
-     * bugs:nameloaded and do the work here when the event fires.
-     */
-    namespace.portlet.subscribe('bugs:nameloaded', function(subscription) {
-        var error_handler = new Y.lp.client.ErrorHandler();
-        error_handler.clearProgressUI = function() {
-            var temp_link = Y.one('#temp-username');
-            if (temp_link) {
-                var temp_parent = temp_link.get('parentNode');
-                temp_parent.removeChild(temp_link);
-            }
-        };
-        error_handler.showError = function(error_msg) {
-            Y.lp.app.errors.display_error(
-                Y.one('.menu-link-addsubscriber'), error_msg);
-        };
-
-        var config = {
-            on: {
-                success: function() {
-                    var temp_link = Y.one('#temp-username');
-                    var temp_spinner = Y.one('#temp-name-spinner');
-                    temp_link.removeChild(temp_spinner);
-                    var anim = Y.lazr.anim.green_flash({ node: temp_link });
-                    anim.on('end', function() {
-                        add_user_name_link(subscription);
-                        var temp_parent = temp_link.get('parentNode');
-                        temp_parent.removeChild(temp_link);
-                    });
-                    anim.run();
-                },
-                failure: error_handler.getFailureHandler()
-            },
-            parameters: {
-                person: Y.lp.client.get_absolute_uri(
-                    subscription.get('person').get('escaped_uri')),
-                suppress_notify: false
-            }
-        };
-        lp_client.named_post(bug_repr.self_link, 'subscribe', config);
-    });
-};
-
-function load_subscriber_ids() {
-    function on_success(transactionid, response, args) {
-        try {
-            subscriber_ids = Y.JSON.parse(response.responseText);
-
-            // Fire a custom event to trigger the setting-up of the
-            // subscription handlers.
-            namespace.portlet.fire('bugs:portletsubscriberidsloaded');
-        } catch (e) {
-            // Fire an event to signal failure. This ensures that the
-            // subscribers-from-dupes still get loaded into the portlet.
-            namespace.portlet.fire('bugs:portletsubscriberidsfailed');
-        }
-    }
-
-    function on_failure() {
-        // Fire an event to signal failure. This ensures that the
-        // subscribers-from-dupes still get loaded into the portlet.
-        namespace.portlet.fire('bugs:portletsubscriberidsfailed');
-    }
-
-    var config = {on: {success: on_success,
-                       failure: on_failure}};
-    var url = Y.one(
-        '#subscribers-ids-link').getAttribute('href');
-    Y.io(url, config);
-}
-
-/*
- * Set click handlers for unsubscribe remove icons.
- *
- * @method setup_unsubscribe_icon_handlers
- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
- */
-function setup_unsubscribe_icon_handlers() {
-    var subscription = new Y.lp.bugs.subscriber.Subscription({
-        link: Y.one('.menu-link-subscription'),
-        spinner: Y.one('#sub-unsub-spinner'),
-        subscriber: new Y.lp.bugs.subscriber.Subscriber({
-            uri: LP.links.me,
-            subscriber_ids: subscriber_ids
-        })
-    });
-
-    Y.on('click', function(e) {
-        e.halt();
-        unsubscribe_user_via_icon(e.target, subscription);
-    }, '.unsub-icon');
-}
-
-/*
- * Set up and return a Subscription object for the direct subscription
- * link.
- */
-function get_subscribe_self_subscription() {
-    setup_client_and_bug();
-    var subscription = new Y.lp.bugs.subscriber.Subscription({
-        link: Y.one('.menu-link-subscription'),
-        spinner: Y.one('#sub-unsub-spinner'),
-        subscriber: new Y.lp.bugs.subscriber.Subscriber({
-            uri: LP.links.me,
-            subscriber_ids: subscriber_ids
-        })
-    });
-
-    subscription.set('can_be_unsubscribed', true);
-    subscription.set('person', subscription.get('subscriber'));
-    subscription.set('is_team', false);
-    var css_name = subscription.get('person').get('css_name');
-    var direct_css_name = '#direct-' + css_name;
-    var direct_node = Y.one(direct_css_name);
-    var is_direct = direct_node !== null;
-    subscription.set('is_direct', is_direct);
-    var dupe_css_name = '#dupe-' + css_name;
-    var dupe_node = Y.one(dupe_css_name);
-    var has_dupes = dupe_node !== null;
-    subscription.set('has_dupes', has_dupes);
-    return subscription;
-}
-
-
-/*
- * Set up and return a Subscription object for the team subscription
- * link.
- */
-function get_team_subscription(team_uri) {
-    setup_client_and_bug();
-    var subscription = new Y.lp.bugs.subscriber.Subscription({
-        link: Y.one('.menu-link-subscription'),
-        spinner: Y.one('#sub-unsub-spinner'),
-        subscriber: new Y.lp.bugs.subscriber.Subscriber({
-            uri: team_uri,
-            subscriber_ids: subscriber_ids
-        })
-    });
-
-    subscription.set('is_direct', true);
-    subscription.set('has_dupes', false);
-    subscription.set('can_be_unsubscribed', true);
-    subscription.set('person', subscription.get('subscriber'));
-    subscription.set('is_team', true);
-    return subscription;
-}
-
-/*
- * Initialize callbacks for subscribe/unsubscribe links.
- *
- * @method setup_subscription_link_handlers
- */
-function setup_subscription_link_handlers() {
-    if (LP.links.me === undefined) {
-        return;
-    }
-
-    var subscription = get_subscribe_self_subscription();
-
-}
-
-function load_subscribers_from_duplicates() {
-    if (Y.UA.ie) {
-        return null;
-    }
-
-    Y.one('#subscribers-portlet-dupe-spinner').setStyle(
-        'display', 'block');
-
-    function hide_spinner() {
-        Y.one('#subscribers-portlet-dupe-spinner').setStyle(
-            'display', 'none');
-    }
-
-    function on_failure(transactionid, response, args) {
-        hide_spinner();
-        // Fire a custom event to signal failure, so that
-        // any remaining unsub icons can be hooked up.
-        namespace.portlet.fire('bugs:dupeportletloadfailed');
-    }
-
-    function on_success(transactionid, response, args) {
-        hide_spinner();
-
-        var dupe_subscribers_container = Y.one(
-            '#subscribers-from-duplicates-container');
-        dupe_subscribers_container.set(
-            'innerHTML',
-            dupe_subscribers_container.get('innerHTML') +
-            response.responseText);
-
-        // Fire a custom portlet loaded event to notify when
-        // it's safe to setup dupe subscriber link callbacks.
-        namespace.portlet.fire('bugs:dupeportletloaded');
-    }
-
-    var config = {on: {success: on_success,
-                       failure: on_failure}};
-    var url = Y.one(
-        '#subscribers-from-dupes-content-link').getAttribute(
-            'href').replace('bugs.', '');
-    Y.io(url, config);
-}
-
-/*
- * Add the user name to the subscriber's list.
- *
- * @method add_user_name_link
- */
-function add_user_name_link(subscription) {
-    // Be paranoid about display_name, since timeouts or other errors
-    // could mean display_name wasn't set on initialization.
-    subscription.get('person').set_display_name(function () {
-        _add_user_name_link(subscription);
-    });
-}
-
-function _add_user_name_link(subscription) {
-    var person = subscription.get('person');
-    var link_node = build_user_link_html(subscription);
-    var subscribers = Y.one('#subscribers-links');
-    if (subscription.is_current_user_subscribing()) {
-        // If this is the current user, then top post the name and be done.
-        subscribers.insertBefore(link_node, subscribers.get('firstChild'));
-    } else {
-        var next = get_next_subscriber_node(subscription);
-        if (next) {
-            subscribers.insertBefore(link_node, next);
-        } else {
-            subscribers.appendChild(link_node);
-        }
-    }
-    // Handle the case of no previous subscribers.
-    var none_subscribers = Y.one('#none-subscribers');
-    if (none_subscribers) {
-        var none_parent = none_subscribers.get('parentNode');
-        none_parent.removeChild(none_subscribers);
-    }
-    // Highlight the new addition with a green flash.
-    Y.lazr.anim.green_flash({ node: link_node }).run();
-    // Set the click handler if adding a remove icon.
-    if (subscription.can_be_unsubscribed_by_user()) {
-        var remove_icon =
-          Y.one('#unsubscribe-icon-' + person.get('css_name'));
-        remove_icon.on('click', function(e) {
-            e.halt();
-            unsubscribe_user_via_icon(e.target, subscription);
-        });
-    }
-}
-
-/*
- * Unsubscribe a user from this bugtask when a remove icon is clicked.
- *
- * @method unsubscribe_user_via_icon
- * @param icon {Node} The remove icon that was clicked.
- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
-*/
-function unsubscribe_user_via_icon(icon, subscription) {
-    icon.set('src', '/@@/spinner');
-    var icon_parent = icon.get('parentNode');
-
-    var user_uri = get_user_uri_from_icon(icon);
-    var person = new Y.lp.bugs.subscriber.Subscriber({
-        uri: user_uri,
-        subscriber_ids: subscriber_ids
-    });
-    subscription.set('person', person);
-
-    // Determine if this is a dupe.
-    var is_dupe;
-    var icon_parent_div = icon_parent.get('parentNode');
-    var dupe_id = 'dupe-' + person.get('css_name');
-    if (icon_parent_div.get('id') === dupe_id) {
-        is_dupe = true;
-    } else {
-        is_dupe = false;
-    }
-
-    var error_handler = new Y.lp.client.ErrorHandler();
-    error_handler.clearProgressUI = function () {
-        icon.set('src', '/@@/remove');
-        // Grab the icon again to reset to click handler.
-        var unsubscribe_icon = Y.one(
-            '#unsubscribe-icon-' + person.get('css_name'));
-        unsubscribe_icon.on('click', function(e) {
-            e.halt();
-            unsubscribe_user_via_icon(e.target, subscription);
-        });
-
-    };
-    error_handler.showError = function (error_msg) {
-        var flash_node = Y.one('.' + person.get('css_name'));
-        Y.lp.app.errors.display_error(flash_node, error_msg);
-
-    };
-
-    var subscription_link = subscription.get('link');
-    var config = {
-        on: {
-            success: function(client) {
-                var num_person_links = Y.all(
-                    '.' + person.get('css_name')).size();
-                Y.lp.bugs.subscribers_list.remove_user_link(person, is_dupe);
-                var has_direct, has_dupes;
-                if (num_person_links === 1 &&
-                    subscription.is_current_user_subscribing()) {
-                    // Current user has been completely unsubscribed.
-                    subscription.disable_spinner(
-                        subscription_labels.SUBSCRIBE);
-                    has_direct = false;
-                    has_dupes = false;
-                } else {
-                    // If we removed the duplicate subscription,
-                    // we are left with the direct one, and vice versa.
-                    has_direct = is_dupe;
-                    has_dupes = !is_dupe;
-                }
-                subscription.set('is_direct', has_direct);
-                subscription.set('has_dupes', has_dupes);
-                set_subscription_link_parent_class(
-                    subscription_link, has_direct, has_dupes);
-            },
-
-            failure: error_handler.getFailureHandler()
-        }
-    };
-
-    if (!subscription.is_current_user_subscribing()) {
-        config.parameters = {
-            person: Y.lp.client.get_absolute_uri(user_uri)
-        };
-    }
-
-    if (is_dupe) {
-        lp_client.named_post(
-            bug_repr.self_link, 'unsubscribeFromDupes', config);
-    } else {
-        lp_client.named_post(bug_repr.self_link, 'unsubscribe', config);
-    }
-}
-
-/*
- * Initialize click handler for the subscribe someone else link.
- *
- * @method setup_subscribe_someone_else_handler
- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
- */
-function setup_subscribe_someone_else_handler(subscription) {
-    var config = {
-        header: 'Subscribe someone else',
-        step_title: 'Search',
-        picker_activator: '.menu-link-addsubscriber'
-    };
-
-    config.save = function(result) {
-        subscribe_someone_else(result, subscription);
-    };
-    var picker = Y.lp.app.picker.create('ValidPersonOrTeam', config);
-}
-
-/*
- * Build the HTML for a user link for the subscribers list.
- *
- * @method build_user_link_html
- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
- * @return html {String} The HTML used for creating a subscriber link.
- */
-function build_user_link_html(subscription) {
-    var name = subscription.get('person').get('name');
-    var css_name = subscription.get('person').get('css_name');
-    var full_name = subscription.get('person').get('full_display_name');
-    var display_name = subscription.get('person').get('display_name');
-    var terms = {
-        name: name,
-        css_name: css_name,
-        display_name: display_name,
-        full_name: full_name
-    };
-
-    if (subscription.is_current_user_subscribing()) {
-        terms.subscribed_by = 'themselves';
-    } else {
-        terms.subscribed_by = 'by ' + full_name;
-    }
-
-    var html = Y.Node.create('<div><a></a></div>');
-    html.addClass(terms.css_name);
-
-    if (subscription.has_duplicate_subscriptions()) {
-        html.set('id', 'dupe-' + terms.css_name);
-    } else {
-        html.set('id', 'direct-' + terms.css_name);
-    }
-
-    html.one('a')
-        .set('href', '/~' + terms.name)
-        .set('name', terms.full_name)
-        .set('title', 'Subscribed ' + terms.subscribed_by);
-
-    var span;
-    if (subscription.is_team()) {
-        span = '<span class="sprite team"></span>';
-    } else {
-        span = '<span class="sprite person"></span>';
-    }
-
-    html.one('a')
-        .appendChild(Y.Node.create(span))
-        .appendChild(document.createTextNode(terms.display_name));
-
-    // Add remove icon if the current user can unsubscribe the subscriber.
-    if (subscription.can_be_unsubscribed_by_user()) {
-        var icon_html = Y.Node.create(
-            '<a href="+subscribe">' +
-            '<img class="unsub-icon" src="/@@/remove" alt="Remove" /></a>');
-        icon_html
-            .set('id', 'unsubscribe-' + terms.css_name)
-            .set('title', 'Unsubscribe ' + terms.full_name);
-        icon_html.one('img')
-            .set('id', 'unsubscribe-icon-' + terms.css_name);
-        html.appendChild(icon_html);
-    }
-
-    return html;
-}
-
-/*
- * Returns the next node in alphabetical order after the subscriber
- * node now being added.  No node is returned to append to end of list.
- *
- * The name can appear in one of two different lists. 1) The list of
- * subscribers that can be unsubscribed by the current user, and
- * 2) the list of subscribers that cannot be unsubscribed.
- *
- * @method get_next_subscriber_node
- * @param subscription_link {Node} The sub/unsub link.
- * @return {Node} The node appearing next in the subscriber list or
- *          undefined if no node is next.
- */
-function get_next_subscriber_node(subscription) {
-    var full_name = subscription.get('person').get('full_display_name');
-    var can_be_unsubscribed = subscription.can_be_unsubscribed_by_user();
-    var nodes_by_name = {};
-    var unsubscribables = [];
-    var not_unsubscribables = [];
-
-    // Use the list of subscribers pulled from the DOM to have sortable
-    // lists of unsubscribable vs. not unsubscribable person links.
-    var all_subscribers = Y.all('#subscribers-links div');
-    if (all_subscribers.size() > 0) {
-        all_subscribers.each(function(sub_link) {
-            if (sub_link.getAttribute('id') !== 'temp-username') {
-                // User's displayname is found via the link's "name"
-                // attribute.
-                var sub_link_name = sub_link.one('a').getAttribute('name');
-                nodes_by_name[sub_link_name] = sub_link;
-                if (sub_link.one('img.unsub-icon')) {
-                    unsubscribables.push(sub_link_name);
-                } else {
-                    not_unsubscribables.push(sub_link_name);
-                }
-            }
-        });
-
-        // Add the current subscription.
-        if (can_be_unsubscribed) {
-            unsubscribables.push(full_name);
-        } else {
-            not_unsubscribables.push(full_name);
-        }
-        unsubscribables.sort();
-        not_unsubscribables.sort();
-    } else {
-        // If there is no all_subscribers, then we're dealing with
-        // the printed None, so return.
-        return;
-    }
-
-    var i;
-    if ((!unsubscribables && !not_unsubscribables) ||
-        // If A) neither list exists, B) the user belongs in the second
-        // list but the second list doesn't exist, or C) user belongs in the
-        // first list and the second doesn't exist, return no node to append.
-        (!can_be_unsubscribed && !not_unsubscribables) ||
-        (can_be_unsubscribed && unsubscribables && !not_unsubscribables)) {
-        return;
-    } else if (
-        // If the user belongs in the first list, and the first list
-        // doesn't exist, but the second one does, return the first node
-        // in the second list.
-        can_be_unsubscribed && !unsubscribables && not_unsubscribables) {
-        return nodes_by_name[not_unsubscribables[0]];
-    } else if (can_be_unsubscribed) {
-        // If the user belongs in the first list, loop the list for position.
-        for (i=0; i<unsubscribables.length; i++) {
-            if (unsubscribables[i] === full_name) {
-                if (i+1 < unsubscribables.length) {
-                    return nodes_by_name[unsubscribables[i+1]];
-                // If the current link should go at the end of the first
-                // list and we're at the end of that list, return the
-                // first node of the second list.  Due to earlier checks
-                // we can be sure this list exists.
-                } else if (i+1 >= unsubscribables.length) {
-                    return nodes_by_name[not_unsubscribables[0]];
-                }
-            }
-        }
-    } else if (!can_be_unsubscribed) {
-        // If user belongs in the second list, loop the list for position.
-        for (i=0; i<not_unsubscribables.length; i++) {
-            if (not_unsubscribables[i] === full_name) {
-                if (i+1 < not_unsubscribables.length) {
-                    return nodes_by_name[not_unsubscribables[i+1]];
-                } else {
-                    return;
-                }
-            }
-        }
-    }
-}
-
-/*
- * Traverse the DOM of a given remove icon to find
- * the user's link.  Returns a URI of the form "/~username".
- *
- * @method get_user_uri_from_icon
- * @param icon {Node} The node representing a remove icon.
- * @return user_uri {String} The user's uri, without the hostname.
- */
-function get_user_uri_from_icon(icon) {
-    var parent_div = icon.get('parentNode').get('parentNode');
-    // This should be parent_div.firstChild, but because of #text
-    // and cross-browser issues, using the YUI query syntax is
-    // safer here.
-    var user_uri = parent_div.one('a').getAttribute('href');
-
-    // Strip the domain off. We just want a path.
-    var host_start = user_uri.indexOf('//');
-    if (host_start !== -1) {
-        var host_end = user_uri.indexOf('/', host_start+2);
-        return user_uri.substring(host_end, user_uri.length);
-    }
-
-    return user_uri;
-}
-
-/*
- * Set the class on subscription link's parentNode.
- *
- * This is used to reset the class used by the
- * click handler to know which link was clicked.
- *
- * @method set_subscription_link_parent_class
- * @param subscription_link {Node} The sub/unsub link.
- * @param subscribed {Boolean} The sub/unsub'ed flag for the class.
- * @param dupe_subscribed {Boolean} The sub/unsub'ed flag for dupes
- *                                  on the class.
- */
-function set_subscription_link_parent_class(
-    user_link, subscribed, dupe_subscribed) {
-
-    var parent = user_link.get('parentNode');
-    if (subscribed) {
-        parent.removeClass('subscribed-false');
-        parent.addClass('subscribed-true');
-    } else {
-        parent.removeClass('subscribed-true');
-        parent.addClass('subscribed-false');
-    }
-
-    if (dupe_subscribed) {
-        parent.removeClass('dup-subscribed-false');
-        parent.addClass('dup-subscribed-true');
-    } else {
-        parent.removeClass('dup-subscribed-true');
-        parent.addClass('dup-subscribed-false');
-    }
-}
-
-/*
- * Subscribe a person or team other than the current user.
- * This is a callback for the subscribe someone else picker.
- *
- * @method subscribe_someone_else
- * @result {Object} The object representing a person returned by the API.
- */
-function subscribe_someone_else(result, subscription) {
-    var person = new Y.lp.bugs.subscriber.Subscriber({
-        uri: result.api_uri,
-        display_name: result.title,
-        subscriber_ids: subscriber_ids
-    });
-    subscription.set('person', person);
-
-    var error_handler = new Y.lp.client.ErrorHandler();
-    error_handler.showError = function(error_msg) {
-        Y.lp.app.errors.display_error(
-           Y.one('.menu-link-addsubscriber'), error_msg);
-    };
-
-    if (subscription.is_already_subscribed()) {
-        error_handler.showError(
-             subscription.get('person').get('full_display_name') +
-             ' has already been subscribed');
-    } else {
-        check_can_be_unsubscribed(subscription);
-    }
-}
-
-/*
- * Check if the current user can unsubscribe the person
- * being subscribed.
- *
- * This must be done in JavaScript, since the subscription
- * hasn't completed yet, and so, can_be_unsubscribed_by_user
- * cannot be used.
- *
- * @method check_can_be_unsubscribed
- * @param subscription {Object} A Y.lp.bugs.subscriber.Subscription object.
- */
-function check_can_be_unsubscribed(subscription) {
-    var error_handler = new Y.lp.client.ErrorHandler();
-    error_handler.showError = function (error_msg) {
-        Y.lp.app.errors.display_error(
-           Y.one('.menu-link-addsubscriber'), error_msg);
-    };
-
-    var config = {
-        on: {
-            success: function(result) {
-                var is_team = result.get('is_team');
-                subscription.set('is_team', is_team);
-                var final_config = {
-                    on: {
-                        success: function(result) {
-                            var team_member = false;
-                            var i;
-                            for (i=0; i<result.entries.length; i++) {
-                                if (result.entries[i].get('member_link') ===
-                                    Y.lp.client.get_absolute_uri(
-                                        subscription.get(
-                                            'subscriber').get('uri'))) {
-                                    team_member = true;
-                                }
-                            }
-
-                            if (team_member) {
-                                subscription.set('can_be_unsubscribed', true);
-                                add_temp_user_name(subscription);
-                            } else {
-                                subscription.set(
-                                   'can_be_unsubscribed', false);
-                                add_temp_user_name(subscription);
-                            }
-                        },
-
-                        failure: error_handler.getFailureHandler()
-                    }
-                };
-
-                if (is_team) {
-                    // Get a list of members to see if current user
-                    // is a team member.
-                    var members = result.get(
-                       'members_details_collection_link');
-                    lp_client.get(members, final_config);
-                } else {
-                    subscription.set('can_be_unsubscribed', false);
-                    add_temp_user_name(subscription);
-                }
-            },
-
-            failure: error_handler.getFailureHandler()
-        }
-    };
-    lp_client.get(Y.lp.client.get_absolute_uri(
-        subscription.get('person').get('escaped_uri')), config);
-}
-
-/*
- * Add a grayed out, temporary user name when subscribing
- * someone else.
- *
- * @method add_temp_user_name
- * @param subscription_link {Node} The sub/unsub link.
- */
-function add_temp_user_name(subscription) {
-    // Be paranoid about display_name, since timeouts or other errors
-    // could mean display_name wasn't set on initialization.
-    subscription.get('person').set_display_name(function () {
-        _add_temp_user_name(subscription);
-    });
-}
-
-function _add_temp_user_name(subscription) {
-    var display_name = subscription.get('person').get('display_name');
-    var img_src;
-    if (subscription.is_team()) {
-        img_src = '/@@/teamgray';
-    } else {
-        img_src = '/@@/persongray';
-    }
-
-    // The <span>...</span> below must *not* be <span/>. On FF (maybe
-    // others, but at least on FF 3.0.11) will then not notice any
-    // following sibling nodes, like the spinner image.
-    var link_node = Y.Node.create([
-        '<div id="temp-username"> ',
-        '  <img alt="" width="14" height="14" />',
-        '  <span>Other Display Name</span>',
-        '  <img id="temp-name-spinner" src="/@@/spinner" alt="" ',
-        '       style="position:absolute;right:8px" /></div>'].join(''));
-    link_node.one('img').set('src', img_src);
-    link_node.replaceChild(
-        document.createTextNode(display_name),
-        link_node.one('span'));
-
-    var subscribers = Y.one('#subscribers-links');
-    var next = get_next_subscriber_node(subscription);
-    if (next) {
-        subscribers.insertBefore(link_node, next);
-    } else {
-        // Handle the case of no subscribers.
-        var none_subscribers = Y.one('#none-subscribers');
-        if (none_subscribers) {
-            var none_parent = none_subscribers.get('parentNode');
-            none_parent.removeChild(none_subscribers);
-        }
-        subscribers.appendChild(link_node);
-    }
-
-    // Fire a custom event to know it's safe to begin
-    // any actual subscribing work.
-    namespace.portlet.fire('bugs:nameloaded', subscription);
-}
-
-}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
-                        "json-parse", "substitute", "widget-position-ext",
-                        "lazr.formoverlay", "lazr.anim", "lazr.base",
-                        "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
-                        "lp.client",
-                        "lp.client.plugins", "lp.bugs.subscriber",
-                        "lp.bugs.subscribers_list",
-                        "lp.bugs.bug_notification_level", "lp.app.errors"]});

=== removed file 'lib/lp/bugs/javascript/subscriber.js'
--- lib/lp/bugs/javascript/subscriber.js	2011-05-18 21:36:46 +0000
+++ lib/lp/bugs/javascript/subscriber.js	1970-01-01 00:00:00 +0000
@@ -1,390 +0,0 @@
-/** Copyright (c) 2009, Canonical Ltd. All rights reserved.
- *
- * Objects for subscription handling.
- *
- * @module bugs
- * @submodule subscriber
- */
-
-YUI.add('lp.bugs.subscriber', function(Y) {
-
-var namespace = Y.namespace('lp.bugs.subscriber');
-namespace.subscription_labels = {
-    'EDIT': 'Edit subscription',
-    'SUBSCRIBE': 'Subscribe',
-    'UNSUBSCRIBE': 'Unsubscribe'
-};
-
-
-/**
- * A Subscription object which represents the subscription
- * being attempted.
- *
- * @class Subscription
- * @namespace lp
- */
-function Subscription(config) {
-    Subscription.superclass.constructor.apply(this, arguments);
-}
-
-Subscription.ATTRS = {
-    'link': {
-        value: null
-    },
-
-    'can_be_unsubscribed': {
-        value: false
-    },
-
-    'is_direct': {
-        value: true
-    },
-
-    'has_dupes': {
-        value: false
-    },
-
-    'person': {
-        value: null
-    },
-
-    'is_team': {
-        value: false
-    },
-
-    'subscriber': {
-        value: null
-    },
-
-    'spinner': {
-        value: null
-    },
-
-    'bug_notification_level': {
-        value: 'Discussion'
-    }
-};
-
-Y.extend(Subscription, Y.Base, {
-
-    /**
-     * Is the current subscription link a node?
-     * Useful in checking that the link is defined.
-     *
-     * @method is_node
-     * @return {Boolean}
-     */
-    'is_node': function() {
-        return this.get('link') instanceof Y.Node;
-    },
-
-    /**
-     * Is the person being subscribed already subscribed?
-     *
-     * @method is_already_subscribed
-     * @return {Boolean}
-     */
-    'is_already_subscribed': function() {
-        var display_name = this.get('person').get('full_display_name');
-        var already_subscribed = false;
-
-        Y.all('#subscribers-links div').each(function(link) {
-            var name = link.one('a').getAttribute('name');
-            if (name === display_name) {
-                already_subscribed = true;
-            }
-        });
-
-        return already_subscribed;
-    },
-
-    /**
-     * Can this subscriber being unsubscribed by the current
-     * user?
-     *
-     * @method can_be_unsubscribed_by_user
-     * @return {Boolean}
-     */
-    'can_be_unsubscribed_by_user': function() {
-        return this.get('can_be_unsubscribed');
-    },
-
-    /**
-     * Is this the current user subscribing him/herself?
-     *
-     * @method is_current_user_subscribing
-     * @return {Boolean}
-     */
-    'is_current_user_subscribing': function() {
-        return (
-            this.get('subscriber').get('name') ===
-            this.get('person').get('name')
-        );
-    },
-
-    /**
-     * Is the current subscription a direct subscription?
-     *
-     * @method is_direct_subscription
-     * @return {Boolean}
-     */
-    'is_direct_subscription': function() {
-        return this.get('is_direct');
-    },
-
-    /**
-     * Does this subscription have dupes?
-     *
-     * @method has_duplicate_subscriptions
-     * @return {Boolean}
-     */
-    'has_duplicate_subscriptions': function() {
-        return this.get('has_dupes');
-    },
-
-    /**
-     * Is this subscriber a team?
-     *
-     * @method is_team
-     * @return {Boolean}
-     */
-    'is_team': function() {
-        return this.get('is_team');
-    },
-
-    /**
-     * Turn on the progess spinner.
-     *
-     * @method enable_spinner
-     */
-    'enable_spinner': function(text) {
-        if (Y.Lang.isValue(text)) {
-            this.get('spinner').set('innerHTML', text);
-        }
-        this.get('link').setStyle('display', 'none');
-        this.get('spinner').setStyle('display', 'block');
-    },
-
-    /**
-     * Turn off the progress spinner.
-     *
-     * @method disable_spinner
-     */
-    'disable_spinner': function(text) {
-        if (Y.Lang.isValue(text)) {
-            var link = this.get('link');
-            link.set('innerHTML', text);
-            if (text === namespace.subscription_labels.SUBSCRIBE) {
-                link.removeClass('modify remove');
-                link.removeClass('modify edit');
-                link.addClass('add');
-            } else if (text === namespace.subscription_labels.EDIT) {
-                link.removeClass('add');
-                link.addClass('modify edit');
-            } else {
-                link.removeClass('add');
-                link.addClass('modify remove');
-            }
-        }
-        this.get('spinner').setStyle('display', 'none');
-        this.get('link').setStyle('display', 'inline');
-    }
-});
-
-namespace.Subscription = Subscription;
-
-/** A Subscriber object which can represent the subscribing person or
- * the person being subscribed.
- *
- * @class Subscriber
- * @namespace lp
- */
-function Subscriber(config) {
-    Subscriber.superclass.constructor.apply(this, arguments);
-}
-
-Subscriber.NAME = 'Subscriber';
-Subscriber.ATTRS = {
-    uri: {
-        value: ''
-    },
-
-    name: {
-        value: ''
-    },
-
-    css_name: {
-        value: ''
-    },
-
-    escaped_uri: {
-        value: ''
-    },
-
-    user_node: {
-        value: null
-    },
-
-    display_name: {
-        value: ''
-    },
-
-    full_display_name: {
-        value: ''
-    },
-
-    subscriber_ids: {
-        value: null
-    }
-};
-
-Y.extend(Subscriber, Y.Base, {
-
-    /**
-     * Subscriber can take as little as a Person uri and work
-     * out most of the person's name attributes from that.
-     *
-     * The display_name is the tricky part and has to be worked
-     * out either from the DOM or via the LP API.  The object can
-     * be passed a DOM node in the config, but the object tries
-     * to work out the DOM on the fly if not and falls back to
-     * the API if LP is available. (See the included display_name
-     * methods for more.)
-     *
-     * @method initializer
-     */
-    initializer: function(config) {
-        if (this.get('uri') !== '') {
-            this.set('name', this.get('uri').substring(2));
-            var name = this.get('name');
-
-            // If we have a subscriber_ids object and that object
-            // has an entry for this Subscriber, then set css_name.
-            // Otherwise, create a css_name with a guid and update
-            // subscriber_ids to include it.
-            var subscriber_ids = this.get('subscriber_ids');
-            if (Y.Lang.isValue(subscriber_ids)) {
-                var css_name = this.get('subscriber_ids')[name];
-                if (Y.Lang.isValue(css_name)) {
-                    this.set('css_name', css_name);
-                } else {
-                    css_name = 'subscriber-' + Y.guid();
-                    subscriber_ids[name] = css_name;
-                    this.set('subscriber_ids', subscriber_ids);
-                    this.set('css_name', css_name);
-                }
-            }
-
-            // Handle the case of plus signs in user names.
-            var escaped_uri;
-            if (name.indexOf('+') > 0) {
-                escaped_uri = name.replace('+', '%2B');
-            } else {
-                escaped_uri = name;
-            }
-            this.set('escaped_uri', '/~' + escaped_uri);
-        }
-
-        this.set_display_name();
-        this.set_truncated_display_name();
-    },
-
-    /**
-     * Finds the display name using the LP API.
-     *
-     * @method get_display_name_from_api
-     * @param client {Object} An LP API client.
-     * @param on_done {Object} A function to call when the display name has
-     * been loaded.
-     */
-    get_display_name_from_api: function(client, on_done) {
-        var self = this;
-        var cfg = {
-            on: {
-                success: function(person) {
-                    var display_name = person.lookup_value('display_name');
-                    self.set('display_name', display_name);
-                    self.set_truncated_display_name();
-                    if (Y.Lang.isFunction(on_done)) {
-                        on_done();
-                    }
-                }
-            }
-        };
-        client.get(this.get('escaped_uri'), cfg);
-    },
-
-    /** Finds the display name in a DOM node.
-     *
-     * This method can use a DOM node supplied in the config but
-     * will also try the standard class name for a subscriber's
-     * node.
-     *
-     * @method get_display_name_from_node
-     */
-    get_display_name_from_node: function() {
-        var user_node;
-        if (Y.Lang.isValue(this.get('user_node'))) {
-            user_node = this.get('user_node');
-        } else {
-            user_node = Y.one('.subscriber-' + this.get('name'));
-        }
-
-        if (Y.Lang.isValue(user_node)) {
-            this.set('user_node', user_node);
-            var anchor = this.get('user_node').one('a');
-            var display_name = anchor.get('name');
-            return display_name;
-        } else {
-            return '';
-        }
-    },
-
-    /**
-     * A wrapper around the other getDisplayNameXXX functions to
-     * work out if setting the display_name is possible.  Calling
-     * this is the safest way to ensure display_name is set
-     * correctly.
-     *
-     * @method set_display_name
-     * @param on_done {Object} A function to call when the display name has
-     * been loaded.
-     */
-    set_display_name: function(on_done) {
-        var display_name = this.get_display_name_from_node();
-        if (display_name !== '') {
-            this.set('display_name', display_name);
-            this.set_truncated_display_name();
-        } else {
-            if (window.LP !== undefined &&
-                window.LP.links.me !== undefined) {
-                var client = new Y.lp.client.Launchpad();
-                this.get_display_name_from_api(client, on_done);
-            }
-        }
-    },
-
-    /**
-     * Sets the truncated version of the display_name.
-     *
-     * @method set_truncated_display_name
-     */
-    set_truncated_display_name: function() {
-        var display_name = this.get('display_name');
-        if (display_name !== '') {
-            var truncated_name;
-            if (display_name.length > 20) {
-                truncated_name = display_name.substring(0, 17) + '...';
-            } else {
-                truncated_name = display_name;
-            }
-            this.set('display_name', truncated_name);
-            this.set('full_display_name', display_name);
-        }
-    }
-
-});
-
-namespace.Subscriber = Subscriber;
-
-  }, "0.1", {"requires": ["base", "node", "lp.client"]});

=== modified file 'lib/lp/bugs/javascript/subscribers_list.js'
--- lib/lp/bugs/javascript/subscribers_list.js	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/javascript/subscribers_list.js	2011-06-10 14:01:53 +0000
@@ -27,72 +27,6 @@
 
 var namespace = Y.namespace('lp.bugs.subscribers_list');
 
-/**
- * Reset the subscribers list if needed.
- *
- * Adds the "None" div to the subscribers list if
- * there aren't any subscribers left, and clears up
- * the duplicate subscribers list if empty.
- *
- * @method reset
- */
-function reset() {
-    var subscriber_list = Y.one('#subscribers-links');
-    // Assume if subscriber_list has no child divs
-    // then the list of subscribers is empty.
-    if (!Y.Lang.isValue(subscriber_list.one('div')) &&
-        !Y.Lang.isValue(Y.one('#none-subscribers'))) {
-        var none_div = Y.Node.create(
-            '<div id="none-subscribers">No subscribers.</div>');
-        var subscribers = subscriber_list.get('parentNode');
-        subscribers.appendChild(none_div);
-    }
-
-    // Clear the empty duplicate subscribers list if it exists.
-    var dup_list = Y.one('#subscribers-from-duplicates');
-    if (Y.Lang.isValue(dup_list) &&
-        !Y.Lang.isValue(dup_list.one('div'))) {
-        dup_list.remove();
-    }
-}
-namespace._reset = reset;
-
-/**
- * Remove the user's name from the subscribers list.
- * It uses the green-flash animation to indicate successful removal.
- *
- * @method remove_user_link
- * @param subscriber {Subscriber} Subscriber that you want to remove.
- * @param is_dupe {Boolean} Uses subscription link from the duplicates
- *     instead.
- */
-function remove_user_link(subscriber, is_dupe) {
-    var user_node_id;
-    var user_name = subscriber.get('css_name');
-    if (is_dupe === true) {
-        user_node_id = '#dupe-' + user_name;
-    } else {
-        user_node_id = '#direct-' + user_name;
-    }
-    var user_node = Y.one(user_node_id);
-    if (Y.Lang.isValue(user_node)) {
-        // If there's an icon, we remove it prior to animation
-        // so animation looks better.
-        var unsub_icon = user_node.one('#unsubscribe-icon-' + user_name);
-        if (Y.Lang.isValue(unsub_icon)) {
-            unsub_icon.remove();
-        }
-        var anim = Y.lazr.anim.green_flash({ node: user_node });
-        anim.on('end', function() {
-            user_node.remove();
-            reset();
-        });
-        anim.run();
-    }
-}
-namespace.remove_user_link = remove_user_link;
-
-
 var CSS_CLASSES = {
     section : 'subscribers-section',
     list: 'subscribers-list',

=== removed file 'lib/lp/bugs/javascript/tests/test_subscriber.html'
--- lib/lp/bugs/javascript/tests/test_subscriber.html	2011-02-28 00:54:30 +0000
+++ lib/lp/bugs/javascript/tests/test_subscriber.html	1970-01-01 00:00:00 +0000
@@ -1,104 +0,0 @@
-<html>
-  <head>
-    <title>Launchpad subscriber</title>
-
-     <!-- YUI 3.0 Setup -->
-    <script type="text/javascript"
-      src="../../../../canonical/launchpad/icing/yui/yui/yui.js"></script>
-    <script type="text/javascript"
-      src="../../../../canonical/launchpad/icing/lazr/build/lazr.js"></script>
-    <link rel="stylesheet"
-      href="../../../../canonical/launchpad/icing/yui/cssreset/reset.css"/>
-    <link rel="stylesheet"
-      href="../../../../canonical/launchpad/icing/yui/cssfonts/fonts.css"/>
-    <link rel="stylesheet"
-      href="../../../../canonical/launchpad/icing/yui/cssbase/base.css"/>
-    <link rel="stylesheet"
-      href="../../../../canonical/launchpad/javascript/test.css" />
-
-    <script type="text/javascript"
-      src="../../../app/javascript/client.js"></script>
-
-    <!-- The module under test -->
-    <script type="text/javascript"
-      src="../subscriber.js"></script>
-
-    <!-- The test suite -->
-    <script type="text/javascript"
-      src="test_subscriber.js"></script>
-
-    <!-- Pretty up the sample html -->
-    <style type="text/css">
-      div#sample {margin:15px; width:200px; border:1px solid #999; padding:10px;}
-    </style>
-  </head>
-  <body class="yui3-skin-sam">
-    <!-- Example markup required by test suite -->
-  <div id="sample">
-    <div class="section">
-      <div class="subscribed-true dup-subscribed-false">
-          <a href="+subscribe"
-              class="menu-link-subscription sprite remove js-action">Unsubscribe
-          </a>
-      </div>
-      <div>
-        <a href="+addsubscriber"
-          class="menu-link-addsubscriber sprite add js-action">Subscribe
-          someone else
-        </a>
-      </div>
-    </div>
-
-    <div class="section" id="subscribers-direct">
-      <h2>Subscribers</h2>
-      <div id="subscribers-links">
-        <div class="subscriber-tester">
-          <a href="/~tester" name="JS Test User"
-            title="Subscribed by Launchpad Janitor">
-            <span class="sprite person"></span>
-            JS Test User
-          </a>
-
-          <a href="+subscribe"
-            class="subscribed-true dup-subscribed-false"
-            id="unsubscribe-tester" title="Unsubscribe JS Test User">
-            <img class="unsub-icon"
-              src="../../../../canonical/launchpad/images/remove.png"
-              id="unsubscribe-icon-tester">
-          </a>
-        </div>
-
-        <div class="subscriber-some-team">
-          <a href="/~some-team" name="Some Team"
-            title="Subscribed by Launchpad Janitor">
-            <span class="sprite team"></span>
-            Some Team
-          </a>
-
-          <a href="+subscribe"
-            class="subscribed-true dup-subscribed-false"
-            id="unsubscribe-some-team" title="Unsubscribe Some Team">
-            <img class="unsub-icon"
-              src="../../../../canonical/launchpad/images/remove.png"
-              id="unsubscribe-icon-some-team">
-          </a>
-        </div>
-      </div>
-    </div>
-
-    <div id="subscribers-from-duplicates" class="section">
-      <h2>From duplicates</h2>
-      <div class="subscriber-duper" id="dupe-subscriber-duper">
-         <a href="/~duper" name="Duper Dude"
-            title="Subscribed by JS Test User">
-              <span class="sprite person"></span>
-              Duper Dude
-            </a>
-      </div>
-    </div>
-  </div>
-
-    <!-- The test output -->
-    <div id="log"></div>
-  </body>
-</html>

=== removed file 'lib/lp/bugs/javascript/tests/test_subscriber.js'
--- lib/lp/bugs/javascript/tests/test_subscriber.js	2011-04-22 16:59:16 +0000
+++ lib/lp/bugs/javascript/tests/test_subscriber.js	1970-01-01 00:00:00 +0000
@@ -1,290 +0,0 @@
-YUI({
-    base: '../../../../canonical/launchpad/icing/yui/',
-    filter: 'raw', combine: false, fetchCSS: false
-    }).use('test', 'console', 'lp.bugs.subscriber', function(Y) {
-
-var suite = new Y.Test.Suite("lp.bugs.subscriber Tests");
-
-/*
- * Test that all the parts of the user name
- * are set when given just a URI.
- */
-suite.add(new Y.Test.Case({
-    name: 'Subscriber From Simple Config',
-
-    setUp: function() {
-        this.config = {
-            uri: '/~deryck'
-        };
-        this.subscriber = new Y.lp.bugs.subscriber.Subscriber(this.config);
-    },
-
-    tearDown: function() {
-        delete this.config;
-        delete this.subscriber;
-    },
-
-    test_uri_config: function() {
-        Y.Assert.areEqual(
-            '/~deryck',
-            this.subscriber.get('uri'),
-            'User URI should be /~deryck');
-        Y.Assert.areEqual(
-            'deryck',
-            this.subscriber.get('name'),
-            'User name should be deryck');
-        Y.Assert.areEqual(
-            this.subscriber.get('uri'),
-            this.subscriber.get('escaped_uri'),
-            'The escaped user uri should be the same as the unescaped uri.');
-        Y.Assert.isNull(
-            this.subscriber.get('user_node'),
-            'User node should not be known and be null at this point.');
-        Y.Assert.areSame(
-            '',
-            this.subscriber.get('css_name'),
-            'Without subscriber_ids object, css_name should not be set yet.');
-        Y.Assert.areSame(
-            '',
-            this.subscriber.get('display_name'),
-            'Without user node or client, the display name should be empty.');
-    }
-}));
-
-/*
- * Test that all the parts of the user name
- * are set correctly when a name needs escaping.
- */
-suite.add(new Y.Test.Case({
-    name: 'Escaping Subscriber From Simple Config',
-
-    setUp: function() {
-        this.config = {
-            uri: '/~foo+bar',
-            subscriber_ids: {'foo+bar': 'subscriber-16'}
-        };
-        this.subscriber = new Y.lp.bugs.subscriber.Subscriber(this.config);
-    },
-
-    tearDown: function() {
-        delete this.config;
-        delete this.subscriber;
-    },
-
-    test_escaping_uri_config: function() {
-        Y.Assert.areEqual(
-            '/~foo+bar',
-            this.subscriber.get('uri'),
-            'User URI should be /~foo+bar');
-        Y.Assert.areEqual(
-            'foo+bar',
-            this.subscriber.get('name'),
-            'User name should be foo+bar');
-        Y.Assert.areEqual(
-            '/~foo%2Bbar',
-            this.subscriber.get('escaped_uri'),
-            'Escaped user URI should be /~foo%2Bbar');
-        Y.Assert.areEqual(
-            'subscriber-16',
-            this.subscriber.get('css_name'),
-            'css_name for user should be subscriber-16');
-    }
-}));
-
-/*
- * Test that the display_name is correctly worked out
- * when passed a Node.
- */
-suite.add(new Y.Test.Case({
-    name: 'Subscriber Name When Passed Node',
-
-    setUp: function() {
-        var node = Y.one('.subscriber-tester');
-        this.config = {
-            uri: '/~tester',
-            user_node: node
-        };
-        this.subscriber = new Y.lp.bugs.subscriber.Subscriber(this.config);
-    },
-
-    tearDown: function() {
-        delete this.config;
-        delete this.subscriber;
-    },
-
-    test_display_name: function() {
-        Y.Assert.areEqual(
-            'JS Test User',
-            this.subscriber.get('display_name'),
-            'The user name should be JS Test User.');
-    }
-}));
-
-/*
- * Test that display_name is correctly worked out from
- * the DOM when not passed a Node.
- */
-suite.add(new Y.Test.Case({
-    name: 'Subscriber Name When Not Passed Node',
-
-    setUp: function() {
-        this.config = {
-            uri: '/~tester'
-        };
-        this.subscriber = new Y.lp.bugs.subscriber.Subscriber(this.config);
-    },
-
-    tearDown: function() {
-        delete this.config;
-        delete this.subscriber;
-    },
-
-    test_display_name_from_dom: function() {
-        Y.Assert.areEqual(
-            'JS Test User',
-            this.subscriber.get('display_name'),
-            'The user name should be JS Test User.');
-    }
-}));
-
-/*
- * Subscriber class that stubs out API calls.
- */
-function APIStubSubscriber(config) {
-    APIStubSubscriber.superclass.constructor.apply(this, arguments);
-}
-Y.extend(APIStubSubscriber, Y.lp.bugs.subscriber.Subscriber, {
-    get_display_name_from_api: function(client) {
-        this.set('display_name', 'From API');
-        this.set_truncated_display_name();
-    }
-});
-
-/*
- * Test that the API is consulted when the display_name cannot be
- * worked out from a given Node or the DOM.
- */
-suite.add(new Y.Test.Case({
-    name: 'Subscriber Name From API',
-
-    setUp: function() {
-        // LP is global.
-        window.LP = {
-            cache: {},
-            links: {}
-        };
-        Y.lp.client.Launchpad = function() {};
-    },
-
-    tearDown: function() {
-        delete window.LP;
-    },
-
-    test_display_name_from_api: function() {
-        // The API should be consulted when the user is logged in. Set
-        // the link to "me" to something other than undefined to
-        // indicate that there is a logged-in user.
-        LP.links.me = 'not-undefined';
-        var subscriber = new APIStubSubscriber({});
-        Y.Assert.areEqual(
-            'From API', subscriber.get('display_name'),
-            'The display name should be "From API".');
-    },
-
-    test_display_name_when_not_logged_in: function() {
-        // The API should not be consulted when no user is logged in.
-        var subscriber = new APIStubSubscriber({});
-        Y.Assert.areEqual(
-            '', subscriber.get('display_name'),
-            'The display name should be the empty string.');
-    }
-}));
-
-/*
- * Test that a Subscription is properly initialized from
- * a simple config and that the basic methods work.
- */
-suite.add(new Y.Test.Case({
-    name: 'Subscription Test',
-
-    setUp: function() {
-        this.config = {
-            can_be_unsubscribed: false,
-            is_direct: true,
-            is_team: true
-        };
-        this.subscription = new Y.lp.bugs.subscriber.Subscription(
-            this.config);
-    },
-
-    tearDown: function() {
-        delete this.config;
-        delete this.subscription;
-    },
-
-    test_subscription_config: function() {
-        Y.Assert.isFalse(
-            this.subscription.can_be_unsubscribed_by_user(),
-            'The user should not be able to unsubscribed this subscription.');
-        Y.Assert.isTrue(
-            this.subscription.is_team(),
-            'This subscription should be for a team.');
-        Y.Assert.isTrue(
-            this.subscription.is_direct_subscription(),
-            'This should be a direct subscription.');
-        // Also check that the defaults were set.
-        Y.Assert.isNull(
-            this.subscription.get('person'),
-            'The subscription should not be setup for a person.');
-        Y.Assert.isNull(
-            this.subscription.get('subscriber'),
-            'The subscription should not be setup for a subscriber.');
-    },
-
-    test_subscription_is_node: function() {
-        Y.Assert.isFalse(
-            this.subscription.is_node(),
-            'Initially, no node should be supplied to the config.');
-        var link = Y.one('.menu-link-subscription');
-        this.subscription.set('link', link);
-        Y.Assert.isTrue(
-            this.subscription.is_node(),
-            'This subscription should have a node for subscription link.');
-    },
-
-    test_already_subscribed: function() {
-        var person = new Y.lp.bugs.subscriber.Subscriber({uri: '/~tester'});
-        this.subscription.set('person', person);
-        Y.Assert.isTrue(
-            this.subscription.is_already_subscribed(),
-            'The JS Test User should be already subscribed.');
-    },
-
-    test_is_current_user_subscribing: function() {
-        var person = new Y.lp.bugs.subscriber.Subscriber({uri: '/~tester'});
-        this.subscription.set('person', person);
-        var subscriber = this.subscription.get('person');
-        this.subscription.set('subscriber', subscriber);
-        Y.Assert.isTrue(
-            this.subscription.is_current_user_subscribing(),
-            'Current user should be the same person being subscribed.');
-    }
-}));
-
-
-var handle_complete = function(data) {
-    status_node = Y.Node.create(
-        '<p id="complete">Test status: complete</p>');
-    Y.one('body').appendChild(status_node);
-    };
-Y.Test.Runner.on('complete', handle_complete);
-Y.Test.Runner.add(suite);
-
-var console = new Y.Console({newestOnTop: false});
-console.render('#log');
-
-Y.on('domready', function() {
-    Y.Test.Runner.run();
-});
-});
-

=== modified file 'lib/lp/bugs/javascript/tests/test_subscribers_list.js'
--- lib/lp/bugs/javascript/tests/test_subscribers_list.js	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/javascript/tests/test_subscribers_list.js	2011-06-10 14:01:53 +0000
@@ -12,324 +12,6 @@
 /**
  * Set-up all the nodes required for subscribers list testing.
  */
-function setUpOldSubscribersList(root_node, with_dupes) {
-    // Set-up subscribers list.
-    var direct_links = Y.Node.create('<div></div>')
-        .set('id', 'subscribers-links');
-    var direct_list = Y.Node.create('<div></div>')
-        .set('id', 'subscribers-direct');
-    direct_list.appendChild(direct_links);
-    root_node.appendChild(direct_list);
-
-    if (with_dupes === true) {
-        var dupe_links = Y.Node.create('<div></div>')
-            .set('id', 'subscribers-from-duplicates');
-        var dupe_list = Y.Node.create('<div></div>')
-            .set('id', 'subscribers-from-duplicates-container');
-        dupe_list.appendChild(dupe_links);
-        root_node.appendChild(dupe_list);
-    }
-    return direct_list;
-}
-
-/**
- * Test resetting of the subscribers list.
- */
-suite.add(new Y.Test.Case({
-    name: 'Resetting subscribers list',
-
-    setUp: function() {
-        this.root = Y.Node.create('<div></div>');
-        Y.one('body').appendChild(this.root);
-    },
-
-    tearDown: function() {
-        this.root.remove();
-    },
-
-    test_no_subscribers: function() {
-        // There are no subscribers left in the subscribers_list
-        // (iow, subscribers_links is empty).
-        var subscribers_list = setUpOldSubscribersList(this.root);
-
-        // Resetting the list adds a 'None' div to the
-        // subscribers_list (and not to the subscriber_links).
-        module._reset();
-        var none_node = subscribers_list.one('#none-subscribers');
-        Y.Assert.isNotNull(none_node);
-        Y.Assert.areEqual('No subscribers.', none_node.get('innerHTML'));
-        Y.Assert.areEqual(subscribers_list,
-                          none_node.get('parentNode'));
-
-    },
-
-    test_subscribers: function() {
-        // When there is at least one subscriber, nothing
-        // happens when reset() is called.
-        var subscribers_list = setUpOldSubscribersList(this.root);
-        var subscribers_links = subscribers_list.one('#subscribers-links');
-        subscribers_links.appendChild(
-            Y.Node.create('<div>Subscriber</div>'));
-
-        // Resetting the list is a no-op.
-        module._reset();
-        var none_node = subscribers_list.one('#none-subscribers');
-        Y.Assert.isNull(none_node);
-    },
-
-
-    test_empty_duplicates: function() {
-        // There are no subscribers among the duplicate subscribers.
-        var subscribers_list = setUpOldSubscribersList(this.root, true);
-        var dupe_subscribers = this.root.one('#subscribers-from-duplicates');
-
-        // Resetting the list removes the entire duplicate subscribers node.
-        module._reset();
-        Y.Assert.isNull(Y.one('#subscribers-from-duplicates'));
-
-    },
-
-    test_duplicates: function() {
-        // There are subscribers among the duplicate subscribers,
-        // and nothing changes.
-        var subscribers_list = setUpOldSubscribersList(this.root, true);
-        var dupe_subscribers = this.root.one('#subscribers-from-duplicates');
-        dupe_subscribers.appendChild(Y.Node.create('<div>Subscriber</div>'));
-
-        // Resetting the list does nothing.
-        module._reset();
-
-        // The list is still there.
-        var dupes_node = this.root.one('#subscribers-from-duplicates');
-        Y.Assert.isNotNull(dupes_node);
-        Y.Assert.areEqual(1, dupes_node.all('div').size());
-    }
-}));
-
-
-/**
- * Test removal of a single person link from the subscribers list.
- */
-suite.add(new Y.Test.Case({
-    name: 'Removal of a subscriber link',
-
-    addSubscriber: function(root_node, subscriber, through_dupe) {
-        var css_class = subscriber.get('css_name');
-        var id;
-        if (through_dupe === true) {
-            links = root_node.one('#subscribers-from-duplicates');
-            id = 'dupe-' + css_class;
-        } else {
-            links = root_node.one('#subscribers-links');
-            id = 'direct-' + css_class;
-        }
-        return links.appendChild(
-            Y.Node.create('<div></div>')
-                .addClass(css_class)
-                .set('id', id)
-                .set('text', subscriber.get('uri')));
-    },
-
-    setUp: function() {
-        this.root = Y.Node.create('<div></div>');
-        Y.one('body').appendChild(this.root);
-        this.subscriber_ids = {};
-    },
-
-    tearDown: function() {
-        this.root.remove();
-        delete this.subscriber_ids;
-    },
-
-    test_no_matching_subscriber: function() {
-        // If there is no matching subscriber, removal silently passes.
-
-        // Set-up subscribers list.
-        setUpOldSubscribersList(this.root);
-
-        var person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'myself',
-            subscriber_ids: this.subscriber_ids
-        });
-        var other_person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'someone',
-            subscriber_ids: this.subscriber_ids
-        });
-        this.addSubscriber(this.root, other_person);
-
-        module.remove_user_link(person);
-
-        // `other_person` is not removed.
-        Y.Assert.isNotNull(
-            this.root.one('.' + other_person.get('css_name')));
-    },
-
-    test_unsubscribe_icon_removal: function() {
-        // If there is an unsubscribe icon, it gets removed
-        // before animation starts.
-
-        // Set-up subscribers list.
-        setUpOldSubscribersList(this.root);
-
-        var person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'myself',
-            subscriber_ids: this.subscriber_ids
-        });
-        this.addSubscriber(this.root, person);
-        var css_name = person.get('css_name');
-        this.root.one('.' + css_name)
-            .appendChild('<div></div>')
-            .appendChild('<img></img>')
-            .set('id', 'unsubscribe-icon-' + css_name);
-
-        module.remove_user_link(person);
-
-        // Unsubscribe icon is removed immediatelly.
-        Y.Assert.isNull(this.root.one('#unsubscribe-icon-' + css_name));
-    },
-
-    test_direct_subscriber: function() {
-        // If there is a direct subscriber, removal works fine.
-
-        // Set-up subscribers list.
-        setUpOldSubscribersList(this.root);
-
-        var person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'myself',
-            subscriber_ids: this.subscriber_ids
-        });
-        this.addSubscriber(this.root, person);
-
-        module.remove_user_link(person);
-
-        this.wait(function() {
-            // There is no subscriber link anymore.
-            Y.Assert.isNull(this.root.one('.' + person.get('css_name')));
-            // And the reset() call adds the "No subscribers" node.
-            Y.Assert.isNotNull(this.root.one('#none-subscribers'));
-        }, 1100);
-    },
-
-    test_direct_subscriber_remove_dupe: function() {
-        // If there is only a direct subscriber, attempting removal of
-        // a duplicate subscription link does nothing.
-
-        // Set-up subscribers list.
-        setUpOldSubscribersList(this.root);
-
-        var person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'myself',
-            subscriber_ids: this.subscriber_ids
-        });
-        this.addSubscriber(this.root, person);
-
-        module.remove_user_link(person, true);
-
-        this.wait(function() {
-            // There is no subscriber link anymore.
-            Y.Assert.isNotNull(this.root.one('.' + person.get('css_name')));
-        }, 1100);
-    },
-
-    test_dupe_subscriber: function() {
-        // If there is a duplicate subscriber, removal works fine.
-
-        // Set-up subscribers list.
-        setUpOldSubscribersList(this.root, true);
-
-        var person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'myself',
-            subscriber_ids: this.subscriber_ids
-        });
-        this.addSubscriber(this.root, person, true);
-
-        module.remove_user_link(person, true);
-
-        this.wait(function() {
-            // There is no subscriber link anymore.
-            Y.Assert.isNull(this.root.one('.' + person.get('css_name')));
-            // And the reset() call cleans up the entire duplicate section.
-            Y.Assert.isNull(this.root.one('#subscribers-from-duplicates'));
-        }, 1100);
-    },
-
-    test_dupe_subscriber_remove_direct: function() {
-        // If there is a duplicate subscriber, trying to remove the
-        // direct subscription user link doesn't do anything.
-
-        // Set-up subscribers list.
-        setUpOldSubscribersList(this.root, true);
-
-        var person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'myself',
-            subscriber_ids: this.subscriber_ids
-        });
-        this.addSubscriber(this.root, person, true);
-
-        module.remove_user_link(person);
-
-        this.wait(function() {
-            // There is no subscriber link anymore.
-            Y.Assert.isNotNull(this.root.one('.' + person.get('css_name')));
-        }, 1100);
-    },
-
-    test_direct_and_dupe_subscriber_remove_dupe: function() {
-        // If there a subscriber is both directly subscribed and
-        // subscribed through duplicate, removal removes only one link.
-
-        // Set-up subscribers list.
-        setUpOldSubscribersList(this.root, true);
-
-        var person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'myself',
-            subscriber_ids: this.subscriber_ids
-        });
-        this.addSubscriber(this.root, person);
-        this.addSubscriber(this.root, person, true);
-
-        // Remove the duplicate subscription link.
-        module.remove_user_link(person, true);
-
-        this.wait(function() {
-            // Remaining entry is the direct subscription one.
-            var nodes = this.root.all('.' + person.get('css_name'));
-            Y.Assert.areEqual(1, nodes.size());
-            Y.Assert.areEqual('direct-' + person.get('css_name'),
-                              nodes.pop().get('id'));
-        }, 1100);
-    },
-
-    test_direct_and_dupe_subscriber_remove_direct: function() {
-        // If there a subscriber is both directly subscribed and
-        // subscribed through duplicate, removal removes only one link.
-
-        // Set-up subscribers list.
-        setUpOldSubscribersList(this.root, true);
-
-        var person = new Y.lp.bugs.subscriber.Subscriber({
-            uri: 'myself',
-            subscriber_ids: this.subscriber_ids
-        });
-        this.addSubscriber(this.root, person);
-        this.addSubscriber(this.root, person, true);
-
-        // Remove the direct subscription link.
-        module.remove_user_link(person);
-
-        this.wait(function() {
-            // Remaining entry is the duplicate subscription one.
-            var nodes = this.root.all('.' + person.get('css_name'));
-            Y.Assert.areEqual(1, nodes.size());
-            Y.Assert.areEqual('dupe-' + person.get('css_name'),
-                              nodes.pop().get('id'));
-        }, 1100);
-    }
-}));
-
-/**
- * Set-up all the nodes required for subscribers list testing.
- */
 function setUpSubscribersList(root_node) {
     // Set-up subscribers list.
     var node = Y.Node.create('<div></div>')
@@ -341,6 +23,7 @@
     return new module.SubscribersList(config);
 }
 
+
 /**
  * Test resetting of the no subscribers indication.
  */

=== removed file 'lib/lp/bugs/templates/bug-portlet-dupe-subscribers-content.pt'
--- lib/lp/bugs/templates/bug-portlet-dupe-subscribers-content.pt	2011-02-03 05:14:54 +0000
+++ lib/lp/bugs/templates/bug-portlet-dupe-subscribers-content.pt	1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
-<div
-  tal:omit-tag=""
-  xmlns:tal="http://xml.zope.org/namespaces/tal";
-  xmlns:metal="http://xml.zope.org/namespaces/metal";
-  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
-  tal:define="
-    bug context/bug|context;
-    from_dupes_subscriptions view/sorted_subscriptions_from_dupes;
-  ">
-  <div
-    tal:condition="from_dupes_subscriptions"
-    id="subscribers-from-duplicates"
-    class="section"
-  >
-    <h2>From duplicates</h2>
-    <div
-      tal:repeat="subscription from_dupes_subscriptions"
-      tal:attributes="
-        class subscription/css_name;
-        id string:dupe-${subscription/css_name};
-      "
-    >
-
-            <a
-               tal:condition="subscription/person/name|nothing"
-               tal:attributes="
-                 href subscription/person/fmt:url;
-                 title subscription/display_duplicate_subscribed_by;
-                 name subscription/person/fmt:displayname
-               "
-            >
-              <tal:block replace="structure subscription/person/fmt:icon" />
-              <tal:block replace="subscription/person/fmt:displayname/fmt:shorten/20" />
-            </a>
-
-            <a tal:condition="python: subscription.canBeUnsubscribedByUser(view.user)"
-               href="+subscribe"
-               tal:attributes="
-                 title string:Unsubscribe ${subscription/person/fmt:displayname};
-                 id string:unsubscribe-${subscription/css_name};
-                 class python: view.getSubscriptionClassForUser(subscription.person)
-               "
-            >
-              <img class="unsub-icon" src="/@@/remove" alt="Remove"
-                tal:attributes="id string:unsubscribe-icon-${subscription/css_name}" />
-            </a>
-    </div>
-  </div>
-</div>

=== removed file 'lib/lp/bugs/templates/bug-portlet-subscribers-content.pt'
--- lib/lp/bugs/templates/bug-portlet-subscribers-content.pt	2011-05-20 21:09:40 +0000
+++ lib/lp/bugs/templates/bug-portlet-subscribers-content.pt	1970-01-01 00:00:00 +0000
@@ -1,74 +0,0 @@
-<div
-  tal:omit-tag=""
-  xmlns:tal="http://xml.zope.org/namespaces/tal";
-  xmlns:metal="http://xml.zope.org/namespaces/metal";
-  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
-  tal:define="
-    bug context/bug|context;
-    direct_subscriptions view/sorted_direct_subscriptions;
-  ">
-  <div class="section" id="subscribers-direct">
-    <h3>Directly subscribed</h3>
-      <div id="subscribers-links">
-      <div
-        tal:condition="direct_subscriptions"
-        tal:repeat="subscription direct_subscriptions"
-        tal:attributes="
-            class subscription/css_name;
-            id string:direct-${subscription/css_name}">
-        <metal:subscriber metal:define-macro="subscriber-row">
-
-            <a
-               tal:condition="subscription/person/name|nothing"
-               tal:attributes="
-                 href subscription/person/fmt:url;
-                 title subscription/display_subscribed_by;
-                 name subscription/person/fmt:displayname
-               "
-            >
-              <tal:block replace="structure subscription/person/fmt:icon" />
-              <tal:block replace="subscription/person/fmt:displayname/fmt:shorten/20" />
-            </a>
-
-            <a tal:condition="python: subscription.canBeUnsubscribedByUser(view.user)"
-               href="+subscribe"
-               tal:attributes="
-                 title string:Unsubscribe ${subscription/person/fmt:displayname};
-                 id string:unsubscribe-${subscription/css_name};
-                 class python: view.getSubscriptionClassForUser(subscription.person)
-               "
-            >
-              <img class="unsub-icon" src="/@@/remove" alt="Remove"
-                tal:attributes="id string:unsubscribe-icon-${subscription/css_name}" />
-            </a>
-
-        </metal:subscriber>
-      </div>
-      </div>
-      <div id="none-subscribers"
-        tal:condition="not:direct_subscriptions">
-        No subscribers.</div>
-  </div>
-  <div id="subscribers-from-duplicates-container">
-    <a id="subscribers-from-dupes-content-link"
-       tal:attributes="href bug/fmt:url/+bug-portlet-dupe-subscribers-content"></a>
-    <div id="subscribers-portlet-dupe-spinner"
-         style="text-align: center; display: none">
-      <img src="/@@/spinner" />
-    </div>
-  </div>
-  <tal:also_notified condition="not: request/features/malone.advanced-subscriptions.enabled">
-    <div
-      tal:define="also_notified_subscribers bug/getAlsoNotifiedSubscribers"
-      tal:condition="also_notified_subscribers"
-      id="subscribers-indirect"
-      class="Section"
-    >
-      <h3>Also notified</h3>
-      <div
-        tal:repeat="subscriber also_notified_subscribers"
-        tal:content="structure subscriber/fmt:link"
-      />
-    </div>
-  </tal:also_notified>
-</div>

=== modified file 'lib/lp/bugs/templates/bug-portlet-subscribers.pt'
--- lib/lp/bugs/templates/bug-portlet-subscribers.pt	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/templates/bug-portlet-subscribers.pt	2011-06-10 14:01:53 +0000
@@ -11,14 +11,4 @@
     <div tal:content="structure context_menu/addsubscriber/render" />
   </div>
   <div id="other-bug-subscribers"></div>
-  <a id="subscribers-ids-link"
-     tal:define="bug context/bug|context"
-     tal:attributes="href bug/fmt:url/+bug-portlet-subscribers-ids"></a>
-  <a id="subscribers-content-link"
-     tal:define="bug context/bug|context"
-     tal:attributes="href bug/fmt:url/+bug-portlet-subscribers-content"></a>
-  <div id="subscribers-portlet-spinner"
-       style="text-align: center; display: none">
-    <img src="/@@/spinner" />
-  </div>
 </div>

=== modified file 'lib/lp/bugs/templates/bug-portlet-subscription.pt'
--- lib/lp/bugs/templates/bug-portlet-subscription.pt	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/templates/bug-portlet-subscription.pt	2011-06-10 14:01:53 +0000
@@ -53,7 +53,6 @@
   </div>
   <script type="text/javascript">
     LPS.use('io-base', 'node',
-            'lp.bugs.bugtask_index.portlets',
             'lp.bugs.bugtask_index.portlets.subscription', function(Y) {
         // Must be done inline here to ensure the load event fires.
         // This is a work around for a YUI3 issue with event handling.
@@ -66,8 +65,6 @@
 
         Y.on('domready', function() {
             Y.lp.bugs.bugtask_index.portlets.subscription.initialize();
-            Y.lp.bugs.bugtask_index.portlets.load_subscribers_portlet(
-                subscription_link, subscription_link_handler);
         });
     });
   </script>

=== modified file 'lib/lp/bugs/templates/bugtask-index.pt'
--- lib/lp/bugs/templates/bugtask-index.pt	2011-06-10 14:01:51 +0000
+++ lib/lp/bugs/templates/bugtask-index.pt	2011-06-10 14:01:53 +0000
@@ -10,17 +10,6 @@
       <script type='text/javascript' tal:content="string:var yui_base='${yui}';" />
       <script type='text/javascript' id='available-official-tags-js'
               tal:content="view/available_official_tags_js" />
-      <tal:subscriptions-js
-          define="enabled features/malone.advanced-subscriptions.enabled">
-        <script type="text/javascript" id="use-advanced-subscriptions"
-            tal:condition="enabled">
-          use_advanced_subscriptions = true;
-        </script>
-        <script type="text/javascript" id="use-advanced-subscriptions"
-            tal:condition="not:enabled">
-          use_advanced_subscriptions = false;
-        </script>
-      </tal:subscriptions-js>
       <tal:if condition="context/bug/private">
         <script type="text/javascript">
           var bug_private = true;
@@ -43,11 +32,9 @@
       </tal:if>
       <script type="text/javascript">
         LPS.use('base', 'node', 'oop', 'event', 'lp.bugs.bugtask_index',
-                  'lp.bugs.bugtask_index.portlets', 'lp.bugs.subscribers_list',
+                  'lp.bugs.subscribers_list',
                   'lp.code.branchmergeproposal.diff', 'lp.comments.hide',
                   function(Y) {
-            Y.lp.bugs.bugtask_index.portlets.use_advanced_subscriptions =
-                use_advanced_subscriptions;
             Y.lp.bugs.bugtask_index.setup_bugtask_index();
             Y.on('load', function(e) {
                 Y.lp.code.branchmergeproposal.diff.connect_diff_links();


Follow ups