← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wallyworld/launchpad/subscription-portlet-update-1017818 into lp:launchpad

 

Ian Booth has proposed merging lp:~wallyworld/launchpad/subscription-portlet-update-1017818 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1017818 in Launchpad itself: "subscriber list does not update after changing information type"
  https://bugs.launchpad.net/launchpad/+bug/1017818

For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/subscription-portlet-update-1017818/+merge/113162

== Implementation ==

This branch reinstalls the behaviour whereby changing a bug's privacy settings (now information type) using the ui results in the subscribers list also being updated since subscribers may have changed as a result.

The core change was to have the ui invoke a POST operation against the bug secrecy view as opposed to making a direct API call to transitionToInformationType on the bug object. The view action does the transition and then gathers the data to return to the XHR caller.

A change was required to the data added to the json request cache. The info type vocab objects were being inserted with title eg 'User Data' as the enum value, instead of the token eg USERDATA. This is because lazr-restful uses the title for marshalling over the API, but everything else uses token. So some extra javascript was added to handle the required data transformation into/outof the request cache.

Essentially, for the view itself, some code deleted when info type was implemented was reinserted. On the javascript side, some older code was reinstated and moved to the new module and some old code that should have been deleted with info type was introduced was finally removed.

== Tests ==

Add test for BugSecrecyView
Add extra yui tests for the information_type_choice module.

I also enhanced an existing information_type_choice yui test to make it more complete.


== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/app/browser/informationtype.py
  lib/lp/bugs/browser/bug.py
  lib/lp/bugs/browser/tests/test_bug_views.py
  lib/lp/bugs/browser/tests/test_bugview.py
  lib/lp/bugs/javascript/bugtask_index.js
  lib/lp/bugs/javascript/information_type_choice.js
  lib/lp/bugs/javascript/tests/test_information_type_choice.html
  lib/lp/bugs/javascript/tests/test_information_type_choice.js
-- 
https://code.launchpad.net/~wallyworld/launchpad/subscription-portlet-update-1017818/+merge/113162
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/subscription-portlet-update-1017818 into lp:launchpad.
=== modified file 'lib/lp/app/browser/informationtype.py'
--- lib/lp/app/browser/informationtype.py	2012-06-20 05:25:44 +0000
+++ lib/lp/app/browser/informationtype.py	2012-07-04 01:25:24 +0000
@@ -20,13 +20,13 @@
 
     def initialize(self):
         cache = IJSONRequestCache(self.request)
-        cache.objects['information_types'] = [
-            {'value': term.value, 'description': term.description,
+        cache.objects['information_type_data'] = [
+            {'value': term.name, 'description': term.description,
             'name': term.title,
             'description_css_class': 'choice-description'}
-            for term in InformationTypeVocabulary()]
+            for term in InformationTypeVocabulary(self.context)]
         cache.objects['private_types'] = [
-            type.title for type in PRIVATE_INFORMATION_TYPES]
+            type.name for type in PRIVATE_INFORMATION_TYPES]
         cache.objects['show_userdata_as_private'] = (
             self.show_userdata_as_private)
 

=== modified file 'lib/lp/bugs/browser/bug.py'
--- lib/lp/bugs/browser/bug.py	2012-06-26 05:59:47 +0000
+++ lib/lp/bugs/browser/bug.py	2012-07-04 01:25:24 +0000
@@ -35,8 +35,13 @@
     )
 from lazr.lifecycle.event import ObjectModifiedEvent
 from lazr.lifecycle.snapshot import Snapshot
+from lazr.restful import (
+    EntryResource,
+    ResourceJSONEncoder,
+    )
 from lazr.restful.interface import copy_field
 from lazr.restful.interfaces import IJSONRequestCache
+from simplejson import dumps
 from zope import formlib
 from zope.app.form.browser import TextWidget
 from zope.component import getUtility
@@ -60,6 +65,7 @@
 from lp.app.errors import NotFoundError
 from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
 from lp.app.widgets.project import ProjectScopeWidget
+from lp.bugs.browser.bugsubscription import BugPortletSubscribersWithDetails
 from lp.bugs.browser.widgets.bug import BugTagsWidget
 from lp.bugs.enums import BugNotificationLevel
 from lp.bugs.interfaces.bug import (
@@ -74,6 +80,7 @@
 from lp.bugs.interfaces.bugtask import (
     BugTaskSearchParams,
     BugTaskStatus,
+    IBugTask,
     IFrontPageBugTaskSearch,
     )
 from lp.bugs.interfaces.bugwatch import IBugWatchSet
@@ -825,7 +832,9 @@
     @property
     def next_url(self):
         """Return the next URL to call when this call completes."""
-        return canonical_url(self.context)
+        if not self.request.is_ajax:
+            return canonical_url(self.context)
+        return None
 
     cancel_url = next_url
 
@@ -834,7 +843,8 @@
         """See `LaunchpadFormView.`"""
         return {'information_type': self.context.bug.information_type}
 
-    @action('Change', name='change')
+    @action('Change', name='change',
+        failure=LaunchpadFormView.ajax_failure_handler)
     def change_action(self, action, data):
         """Update the bug."""
         data = dict(data)
@@ -853,6 +863,33 @@
                 ObjectModifiedEvent(
                     bug, bug_before_modification, changed_fields,
                     user=self.user))
+        if self.request.is_ajax:
+            if changed:
+                return self._getSubscriptionDetails()
+            else:
+                return ''
+
+    def _getSubscriptionDetails(self):
+        cache = dict()
+        # The subscription details for the current user.
+        self.extractBugSubscriptionDetails(self.user, self.context.bug, cache)
+
+        # The subscription details for other users to populate the subscribers
+        # list in the portlet.
+        if IBugTask.providedBy(self.context):
+            bug = self.context.bug
+        else:
+            bug = self.context
+        subscribers_portlet = BugPortletSubscribersWithDetails(
+            bug, self.request)
+        subscription_data = subscribers_portlet.subscriber_data
+        result_data = dict(
+            cache_data=cache,
+            subscription_data=subscription_data)
+        self.request.response.setHeader('content-type', 'application/json')
+        return dumps(
+            result_data, cls=ResourceJSONEncoder,
+            media_type=EntryResource.JSON_TYPE)
 
     def _handlePrivacyChanged(self, user_will_be_subscribed):
         """Handle the case where the privacy of the bug has been changed.

=== modified file 'lib/lp/bugs/browser/tests/test_bug_views.py'
--- lib/lp/bugs/browser/tests/test_bug_views.py	2012-06-22 05:52:17 +0000
+++ lib/lp/bugs/browser/tests/test_bug_views.py	2012-07-04 01:25:24 +0000
@@ -6,6 +6,7 @@
 __metaclass__ = type
 
 from BeautifulSoup import BeautifulSoup
+import simplejson
 from soupmatchers import (
     HTMLContains,
     Tag,
@@ -296,7 +297,8 @@
 
     layer = DatabaseFunctionalLayer
 
-    def createInitializedSecrecyView(self, person=None, bug=None):
+    def createInitializedSecrecyView(self, person=None, bug=None,
+                                     request=None):
         """Create and return an initialized BugSecrecyView."""
         if person is None:
             person = self.factory.makePerson()
@@ -307,7 +309,7 @@
                 bug.default_bugtask, name='+secrecy', form={
                     'field.information_type': 'USERDATA',
                     'field.actions.change': 'Change',
-                    })
+                    }, request=request)
             return view
 
     def test_notification_shown_if_marking_private_and_not_subscribed(self):
@@ -346,6 +348,44 @@
         view = self.createInitializedSecrecyView(person, bug)
         self.assertContentEqual([], view.request.response.notifications)
 
+    def test_secrecy_view_ajax_render(self):
+        # When the bug secrecy view is called from an ajax request, it should
+        # provide a json encoded dict when rendered. The dict contains bug
+        # subscription information resulting from the update to the bug
+        # privacy as well as information used to populate the updated
+        # subscribers list.
+        person = self.factory.makePerson()
+        bug = self.factory.makeBug(owner=person)
+        with person_logged_in(person):
+            bug.subscribe(person, person)
+
+        extra = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+        request = LaunchpadTestRequest(
+            method='POST', form={
+                'field.actions.change': 'Change',
+                'field.information_type': 'USERDATA'},
+            **extra)
+        view = self.createInitializedSecrecyView(person, bug, request)
+        result_data = simplejson.loads(view.render())
+
+        cache_data = result_data['cache_data']
+        self.assertFalse(cache_data['other_subscription_notifications'])
+        subscription_data = cache_data['subscription']
+        self.assertEqual(
+            'http://launchpad.dev/api/devel/bugs/%s' % bug.id,
+            subscription_data['bug_link'])
+        self.assertEqual(
+            'http://launchpad.dev/api/devel/~%s' % person.name,
+            subscription_data['person_link'])
+        self.assertEqual(
+            'Discussion', subscription_data['bug_notification_level'])
+
+        [subscriber_data] = result_data['subscription_data']
+        subscriber = removeSecurityProxy(bug).default_bugtask.pillar.owner
+        self.assertEqual(
+            subscriber.name, subscriber_data['subscriber']['name'])
+        self.assertEqual('Discussion', subscriber_data['subscription_level'])
+
     def test_set_information_type(self):
         # Test that the bug's information_type can be updated using the
         # view with the feature flag on.

=== modified file 'lib/lp/bugs/browser/tests/test_bugview.py'
--- lib/lp/bugs/browser/tests/test_bugview.py	2012-06-07 15:22:52 +0000
+++ lib/lp/bugs/browser/tests/test_bugview.py	2012-07-04 01:25:24 +0000
@@ -100,8 +100,10 @@
             view.initialize()
             cache = IJSONRequestCache(view.request)
             expected = [
-                InformationType.PUBLIC, InformationType.UNEMBARGOEDSECURITY,
-                InformationType.EMBARGOEDSECURITY, InformationType.USERDATA]
+                InformationType.PUBLIC.name,
+                InformationType.UNEMBARGOEDSECURITY.name,
+                InformationType.EMBARGOEDSECURITY.name,
+                InformationType.USERDATA.name]
             self.assertContentEqual(expected, [
                 type['value']
-                for type in cache.objects['information_types']])
+                for type in cache.objects['information_type_data']])

=== modified file 'lib/lp/bugs/javascript/bugtask_index.js'
--- lib/lp/bugs/javascript/bugtask_index.js	2012-06-20 05:25:44 +0000
+++ lib/lp/bugs/javascript/bugtask_index.js	2012-07-04 01:25:24 +0000
@@ -278,147 +278,6 @@
     }
 };
 
-
-var update_privacy_settings = function(data) {
-    // XXX noodles 2009-03-17 bug=336866 It seems the etag
-    // returned by lp_save() is incorrect. Remove it for now
-    // so that the second save does not result in a '412
-    // precondition failed' error.
-    //
-    // XXX deryck 2009-04-29 bug=369293 Also, this has to
-    // happen before *any* call to lp_save now that bug
-    // subscribing can be done inline.  Named operations
-    // don't return new objects, making the cached bug's
-    // etag invalid as well.
-    lp_bug_entry.removeAttr('http_etag');
-
-    privacy_form_overlay.hide();
-
-    var privacy_text = Y.one('#privacy-text');
-    var privacy_div = Y.one('#privacy');
-    privacy_link.setStyle('display', 'none');
-    privacy_spinner.setStyle('display', 'inline');
-
-    if (lp_client === undefined) {
-        lp_client = new Y.lp.client.Launchpad();
-    }
-
-    if (lp_bug_entry === undefined) {
-        var bug_repr = LP.cache.bug;
-        lp_bug_entry = new Y.lp.client.Entry(
-            lp_client, bug_repr, bug_repr.self_link);
-    }
-
-    var private_flag = data['field.private'] !== undefined;
-    var security_related =
-        data['field.security_related'] !== undefined;
-
-    lp_bug_entry.set('private', private_flag);
-    lp_bug_entry.set('security_related', security_related);
-    var error_handler = new Y.lp.client.ErrorHandler();
-    error_handler.clearProgressUI = function () {
-        privacy_spinner.setStyle('display', 'none');
-        privacy_link.setStyle('display', 'inline');
-    };
-    error_handler.showError = function (error_msg) {
-        Y.lp.anim.red_flash({
-            node: privacy_div,
-            duration: namespace.ANIM_DURATION
-            }).run();
-        privacy_form_overlay.showError(error_msg);
-        privacy_form_overlay.show();
-    };
-
-    var base_url = LP.cache.context.web_link;
-    var submit_url = base_url+"/+secrecy";
-    var qs = Y.lp.client.append_qs('', 'field.actions.change', 'Change');
-    qs = Y.lp.client.append_qs(qs, 'field.private', private_flag?'on':'off');
-    qs = Y.lp.client.append_qs(
-        qs, 'field.security_related', security_related?'on':'off');
-    var sub_list_node = Y.one('#other-bug-subscribers');
-    var subscribers_list = sub_list_node.getData('subscribers_loader');
-    var config = {
-        method: "POST",
-        headers: {'Accept': 'application/xhtml'},
-        data: qs,
-        on: {
-            start: function () {
-                subscribers_list.subscribers_list.startActivity(
-                    'Updating subscribers...');
-            },
-            end: function () {
-                subscribers_list.subscribers_list.stopActivity();
-            },
-            success: function (id, response) {
-                if (response.responseText !== '') {
-                    var result_data = Y.JSON.parse(response.responseText);
-                    var subscribers = result_data.subscription_data;
-                    subscribers_list._loadSubscribersFromList(subscribers);
-                    var cache_data = result_data.cache_data;
-                    var item;
-                    for (item in cache_data) {
-                        if (cache_data.hasOwnProperty(item)) {
-                            LP.cache[item] = cache_data[item];
-                        }
-                    }
-                    var ns = Y.lp.bugs.bugtask_index.portlets.subscription;
-                    ns.update_subscription_status();
-                }
-                privacy_spinner.setStyle('display', 'none');
-                privacy_link.setStyle('display', 'inline');
-
-                var banner = Y.lp.app.banner.privacy.getPrivacyBanner();
-                if (private_flag) {
-                    Y.one('body').replaceClass('public', 'private');
-                    privacy_div.replaceClass('public', 'private');
-                    privacy_text.set(
-                        'innerHTML',
-                        'This report is <strong>private</strong> ');
-                    banner.show();
-                } else {
-                    Y.one('body').replaceClass('private', 'public');
-                    privacy_div.replaceClass('private', 'public');
-                    privacy_text.set(
-                        'innerHTML', 'This report is public ');
-                    banner.hide();
-                }
-                privacy_text.appendChild(privacy_link);
-                privacy_text.appendChild(privacy_spinner);
-
-                var security_message = Y.one('#security-message');
-                if (security_related) {
-                    if (security_message === null) {
-                        var security_message_html = [
-                            '<div style="',
-                            '    margin-top: 0.5em;',
-                            '    padding-right: 18px;',
-                            '    center right no-repeat;"',
-                            '    class="sprite security"',
-                            '    id="security-message"',
-                            '>Security vulnerability</div>'
-                        ].join('');
-                        security_message = Y.Node.create(
-                           security_message_html);
-                        privacy_div.appendChild(security_message);
-                    }
-                } else {
-                    if (security_message !== null) {
-                        privacy_div.removeChild(security_message);
-                    }
-                }
-                Y.lp.client.display_notifications(
-                    response.getResponseHeader('X-Lazr-Notifications'));
-                Y.lp.anim.green_flash({
-                    node: privacy_div,
-                    duration: namespace.ANIM_DURATION
-                    }).run();
-            },
-            failure: error_handler.getFailureHandler()
-        }
-    };
-    lp_client.io_provider.io(submit_url, config);
-};
-
 /**
  * Do a preemptive search for branches that contain the current bug's ID.
  */

=== modified file 'lib/lp/bugs/javascript/information_type_choice.js'
--- lib/lp/bugs/javascript/information_type_choice.js	2012-06-20 05:25:44 +0000
+++ lib/lp/bugs/javascript/information_type_choice.js	2012-07-04 01:25:24 +0000
@@ -7,43 +7,82 @@
 YUI.add('lp.bugs.information_type_choice', function(Y) {
 
 var namespace = Y.namespace('lp.bugs.information_type_choice');
-var information_type_descriptions = {};
 
 // For testing.
 var skip_animation = false;
 
+/**
+ * Lookup the information_type property, keyed on the named value.
+ * @param key the key to lookup
+ * @param key_name the key property name
+ * @param value_name the value property_name
+ * @return {*}
+ */
+var information_type_value_from_key = function(key, key_name,
+                                                value_name) {
+    var data = null;
+    Y.Array.some(LP.cache.information_type_data, function(info_type) {
+        if (info_type[key_name] === key) {
+            data = info_type[value_name];
+            return true;
+        }
+    });
+    return data;
+};
+
 namespace.save_information_type = function(widget, value, lp_client) {
-    var error_handler = new Y.lp.client.ErrorHandler();
+    var error_handler = new Y.lp.client.FormErrorHandler();
     error_handler.showError = function(error_msg) {
         Y.lp.app.errors.display_error(
             Y.one('#information-type'), error_msg);
     };
     error_handler.handleError = function(ioId, response) {
-        var orig_value = LP.cache.bug.information_type;
+        var orig_value = information_type_value_from_key(
+            LP.cache.bug.information_type, 'name', 'value');
         widget.set('value', orig_value);
         widget._showFailed();
         update_privacy_portlet(orig_value);
         return false;
     };
+    var base_url = LP.cache.context.web_link;
+    var submit_url = base_url+"/+secrecy";
+    var qs = Y.lp.client.append_qs('', 'field.actions.change', 'Change');
+    qs = Y.lp.client.append_qs(qs, 'field.information_type', value);
+    var sub_list_node = Y.one('#other-bug-subscribers');
+    var subscribers_list = sub_list_node.getData('subscribers_loader');
     var config = {
+        method: "POST",
+        headers: {'Accept': 'application/xhtml;application/json'},
+        data: qs,
         on: {
-            start: Y.bind(widget._uiSetWaiting),
-            end: Y.bind(widget._uiClearWaiting),
-            success: function(id, response) {
-                namespace.information_type_save_success(widget, value);
+            start: function () {
+                widget._uiSetWaiting();
+                subscribers_list.subscribers_list.startActivity(
+                    'Updating subscribers...');
+            },
+            end: function () {
+                widget._uiClearWaiting();
+                subscribers_list.subscribers_list.stopActivity();
+            },
+            success: function (id, response) {
+                var result_data = null;
+                if (response.responseText !== '') {
+                    result_data = Y.JSON.parse(response.responseText);
+                }
+                namespace.information_type_save_success(
+                    widget, value, subscribers_list, result_data);
+                Y.lp.client.display_notifications(
+                    response.getResponseHeader('X-Lazr-Notifications'));
             },
             failure: error_handler.getFailureHandler()
-        },
-        parameters: {
-            information_type: value
         }
     };
-    lp_client.named_post(
-        LP.cache.bug.self_link, 'transitionToInformationType', config);
+    lp_client.io_provider.io(submit_url, config);
 };
 
 var update_privacy_portlet = function(value) {
-    var description = information_type_descriptions[value];
+    var description = information_type_value_from_key(
+        value, 'value', 'description');
     var desc_node = Y.one('#information-type-description');
     if (Y.Lang.isValue(desc_node)) {
         desc_node.set('text', description);
@@ -74,35 +113,48 @@
 
 namespace.get_information_type_banner_text = function(value) {
     var text_template = "This page contains {info_type} information.";
-
-    if (value === "User Data" && LP.cache.show_userdata_as_private) {
-            value = "Private";
+    var info_type = information_type_value_from_key(value, 'value', 'name');
+    if (info_type === "User Data" && LP.cache.show_userdata_as_private) {
+            info_type = "Private";
     }
-    return Y.Lang.substitute(text_template, {'info_type': value});
+    return Y.Lang.substitute(text_template, {'info_type': info_type});
 };
 
-namespace.information_type_save_success = function(widget, value) {
-    LP.cache.bug.information_type = value;
+namespace.information_type_save_success = function(widget, value,
+                                                   subscribers_list,
+                                                   subscribers_data) {
+    LP.cache.bug.information_type =
+        information_type_value_from_key(value, 'value', 'name');
     update_privacy_banner(value);
     widget._showSucceeded();
-    var subscription_ns = Y.lp.bugs.bugtask_index.portlets.subscription;
-    subscription_ns.update_subscription_status(skip_animation);
+    if (Y.Lang.isObject(subscribers_data)) {
+        var subscribers = subscribers_data.subscription_data;
+        subscribers_list._loadSubscribersFromList(subscribers);
+        var cache_data = subscribers_data.cache_data;
+        var item;
+        for (item in cache_data) {
+            if (cache_data.hasOwnProperty(item)) {
+                LP.cache[item] = cache_data[item];
+            }
+        }
+    }
+    var ns = Y.lp.bugs.bugtask_index.portlets.subscription;
+    ns.update_subscription_status(skip_animation);
 };
 
 namespace.setup_information_type_choice = function(privacy_link, lp_client,
                                                    skip_anim) {
     skip_animation = skip_anim;
-    Y.Array.each(LP.cache.information_types, function(info_type) {
-        information_type_descriptions[info_type.value] = info_type.description;
-    });
+    var initial_value = information_type_value_from_key(
+        LP.cache.bug.information_type, 'name', 'value');
     var information_type = Y.one('#information-type');
     var information_type_edit = new Y.ChoiceSource({
         editicon: privacy_link,
         contentBox: Y.one('#privacy'),
         value_location: information_type,
-        value: LP.cache.bug.information_type,
+        value: initial_value,
         title: "Change information type",
-        items: LP.cache.information_types,
+        items: LP.cache.information_type_data,
         backgroundColor: '#FFFF99',
         flashEnabled: false
     });

=== modified file 'lib/lp/bugs/javascript/tests/test_information_type_choice.html'
--- lib/lp/bugs/javascript/tests/test_information_type_choice.html	2012-06-07 18:27:33 +0000
+++ lib/lp/bugs/javascript/tests/test_information_type_choice.html	2012-07-04 01:25:24 +0000
@@ -70,6 +70,10 @@
       <script type="text/javascript"
           src="../../../../../build/js/lp/bugs/bug_subscription_portlet.js"></script>
       <script type="text/javascript"
+          src="../../../../../build/js/lp/bugs/subscribers.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/subscribers/subscribers_list.js"></script>
+      <script type="text/javascript"
           src="../../../../../build/js/lp/bugs/bugtask_index.js"></script>
 
       <!-- The module under test. -->
@@ -104,6 +108,7 @@
                 <a href="#" text="" class="menu-link-mute_subscription unmute"></a>
                 <a href="#" text="" class="menu-link-mute_subscription mute"></a>
             </div>
+            <div id="other-bug-subscribers"></div>
         </script>
     </body>
 </html>

=== modified file 'lib/lp/bugs/javascript/tests/test_information_type_choice.js'
--- lib/lp/bugs/javascript/tests/test_information_type_choice.js	2012-06-27 14:05:07 +0000
+++ lib/lp/bugs/javascript/tests/test_information_type_choice.js	2012-07-04 01:25:24 +0000
@@ -22,13 +22,13 @@
                         information_type: 'Public',
                         self_link: '/bug/1'
                     },
-                    private_types: ['Private', 'User Data'],
-                    information_types: [
-                        {'value': 'Public', 'name': 'Public',
+                    private_types: ['PRIVATE', 'USERDATA'],
+                    information_type_data: [
+                        {'value': 'PUBLIC', 'name': 'Public',
                             'description': 'Public Description'},
-                        {'value': 'Private', 'name': 'Private',
+                        {'value': 'PRIVATE', 'name': 'Private',
                             'description': 'Private Description'},
-                        {'value': 'User Data', 'name': 'User Data',
+                        {'value': 'USERDATA', 'name': 'User Data',
                             'description': 'User Data Description'}
                     ]
                 }
@@ -42,6 +42,11 @@
             var privacy_link = Y.one('#privacy-link');
             this.widget = ns.setup_information_type_choice(
                 privacy_link, lp_client, true);
+            Y.lp.bugs.subscribers.createBugSubscribersLoader({
+                container_box: '#other-bug-subscribers',
+                subscribers_details_view:
+                    '/+bug-portlet-subscribers-details'});
+
         },
         tearDown: function () {
             if (this.fixture !== null) {
@@ -72,19 +77,39 @@
                 "Cannot locate the lp.bugs.information_type_choice module");
         },
 
+        // The save XHR call works as expected.
         test_save_information_type: function() {
             var mockio = new Y.lp.testing.mockio.MockIo();
             var lp_client = new Y.lp.client.Launchpad({
                 io_provider: mockio
             });
-            ns.save_information_type(this.widget, 'User Data', lp_client);
-            Y.Assert.areEqual('/api/devel/bug/1', mockio.last_request.url);
+            var orig_save_success = ns.information_type_save_success;
+            var save_success_called = false;
+            ns.information_type_save_success = function(widget, value,
+                                                   subscribers_list,
+                                                   subscribers_data) {
+                Y.Assert.areEqual('USERDATA', value);
+                Y.Assert.areEqual(
+                    'subscribers', subscribers_data.subscription_data);
+                Y.Assert.areEqual(
+                    'value', subscribers_data.cache_data.item);
+                save_success_called = true;
+            };
+            ns.save_information_type(this.widget, 'USERDATA', lp_client);
+            mockio.success({
+                responseText: '{"subscription_data": "subscribers",' +
+                    '"cache_data": {"item": "value"}}',
+                responseHeaders: {'Content-Type': 'application/json'}});
+            Y.Assert.areEqual('/+secrecy', mockio.last_request.url);
             Y.Assert.areEqual(
-                'ws.op=transitionToInformationType&' +
-                'information_type=User%20Data',
+                'field.actions.change=Change&' +
+                'field.information_type=USERDATA',
                 mockio.last_request.config.data);
+            Y.Assert.isTrue(save_success_called);
+            ns.information_type_save_success = orig_save_success;
         },
 
+        // Setting a private type shows the privacy banner.
         test_information_type_save_success_private: function() {
             var old_func = this._shim_privacy_banner();
             var hide_flag = false;
@@ -96,7 +121,7 @@
                 update_flag = true;
             });
 
-            ns.information_type_save_success(this.widget, 'Private');
+            ns.information_type_save_success(this.widget, 'PRIVATE');
             var body = Y.one('body');
             Y.Assert.isTrue(body.hasClass('private'));
             Y.Assert.isTrue(hide_flag);
@@ -105,6 +130,7 @@
             this._unshim_privacy_banner(old_func);
         },
 
+        // Setting a private type hides the privacy banner.
         test_information_type_save_success_public: function() {
             var old_func = this._shim_privacy_banner();
             var flag = false;
@@ -114,7 +140,7 @@
             var summary = Y.one('#information-type-summary');
             summary.replaceClass('public', 'private');
 
-            ns.information_type_save_success(this.widget, 'Public');
+            ns.information_type_save_success(this.widget, 'PUBLIC');
             var body = Y.one('body');
             Y.Assert.isTrue(body.hasClass('public'));
             Y.Assert.isTrue(flag);
@@ -122,16 +148,49 @@
             this._unshim_privacy_banner(old_func);
         },
 
+        // A successful save updates the subscribers portlet.
+        test_information_type_save_success_with_subscribers_data: function() {
+            var old_func = this._shim_privacy_banner();
+            var flag = false;
+            Y.on('test:banner:hide', function() {
+                flag = true;
+            });
+            var summary = Y.one('#information-type-summary');
+            summary.replaceClass('public', 'private');
+
+            var load_subscribers_called = false;
+            var subscribers_list = {
+                _loadSubscribersFromList: function(subscription_data) {
+                    Y.Assert.areEqual('subscriber', subscription_data);
+                    load_subscribers_called = true;
+                }
+            };
+            var subscribers_data = {
+                subscription_data: 'subscriber',
+                cache_data: {
+                    item1: 'value1',
+                    item2: 'value2'
+                }
+            };
+            ns.information_type_save_success(
+                this.widget, 'PUBLIC', subscribers_list, subscribers_data);
+            Y.Assert.isTrue(load_subscribers_called);
+            Y.Assert.areEqual('value1', window.LP.cache.item1);
+            Y.Assert.areEqual('value2', window.LP.cache.item2);
+            this._unshim_privacy_banner(old_func);
+        },
+
+        // Selecting a new information type calls save correctly.
         test_perform_update_information_type: function() {
             var privacy_link = Y.one('#privacy-link');
             privacy_link.simulate('click');
             var private_choice = Y.one(
-                '.yui3-ichoicelist-content a[href="#Private"]');
+                '.yui3-ichoicelist-content a[href="#USERDATA"]');
             var orig_save_information_type = ns.save_information_type;
             var function_called = false;
             ns.save_information_type = function(widget, value, lp_client) {
                 Y.Assert.areEqual(
-                    'User Data', value); function_called = true; };
+                    'USERDATA', value); function_called = true; };
             private_choice.simulate('click');
             var description_node = Y.one('#information-type-description');
             Y.Assert.areEqual(
@@ -142,20 +201,21 @@
             ns.save_information_type = orig_save_information_type;
         },
 
+        // Test error handling when a save fails.
         test_information_type_save_error: function() {
             var mockio = new Y.lp.testing.mockio.MockIo();
             var lp_client = new Y.lp.client.Launchpad({
                 io_provider: mockio
             });
-            this.widget.set('value', 'User Data');
-            ns.save_information_type(this.widget, 'User Data', lp_client);
+            this.widget.set('value', 'USERDATA');
+            ns.save_information_type(this.widget, 'USERDATA', lp_client);
             mockio.last_request.respond({
                 status: 500,
                 statusText: 'An error occurred'
             });
             // The original info type value from the cache should have been
             // set back into the widget.
-            Y.Assert.areEqual('Public', this.widget.get('value'));
+            Y.Assert.areEqual('PUBLIC', this.widget.get('value'));
             var description_node = Y.one('#information-type-description');
             Y.Assert.areEqual(
                 'Public Description', description_node.get('text'));
@@ -167,5 +227,5 @@
     }));
 
 }, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate',
-        'lp.testing.mockio', 'lp.client',
+        'lp.testing.mockio', 'lp.client','lp.bugs.subscribers',
         'lp.bugs.information_type_choice']});


Follow ups