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

Proposed by Ian Booth
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 15567
Proposed branch: lp:~wallyworld/launchpad/subscription-portlet-update-1017818
Merge into: lp:launchpad
Diff against target: 730 lines (+250/-195)
8 files modified
lib/lp/app/browser/informationtype.py (+4/-4)
lib/lp/bugs/browser/bug.py (+39/-2)
lib/lp/bugs/browser/tests/test_bug_views.py (+42/-2)
lib/lp/bugs/browser/tests/test_bugview.py (+5/-3)
lib/lp/bugs/javascript/bugtask_index.js (+0/-141)
lib/lp/bugs/javascript/information_type_choice.js (+78/-26)
lib/lp/bugs/javascript/tests/test_information_type_choice.html (+5/-0)
lib/lp/bugs/javascript/tests/test_information_type_choice.js (+77/-17)
To merge this branch: bzr merge lp:~wallyworld/launchpad/subscription-portlet-update-1017818
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+113162@code.launchpad.net

Commit message

Ensure subscribers portlet is updated after changing bug information type.

Description of the change

== 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

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

Looks good, Ian. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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 @@
2020
21 def initialize(self):21 def initialize(self):
22 cache = IJSONRequestCache(self.request)22 cache = IJSONRequestCache(self.request)
23 cache.objects['information_types'] = [23 cache.objects['information_type_data'] = [
24 {'value': term.value, 'description': term.description,24 {'value': term.name, 'description': term.description,
25 'name': term.title,25 'name': term.title,
26 'description_css_class': 'choice-description'}26 'description_css_class': 'choice-description'}
27 for term in InformationTypeVocabulary()]27 for term in InformationTypeVocabulary(self.context)]
28 cache.objects['private_types'] = [28 cache.objects['private_types'] = [
29 type.title for type in PRIVATE_INFORMATION_TYPES]29 type.name for type in PRIVATE_INFORMATION_TYPES]
30 cache.objects['show_userdata_as_private'] = (30 cache.objects['show_userdata_as_private'] = (
31 self.show_userdata_as_private)31 self.show_userdata_as_private)
3232
3333
=== 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 @@
35 )35 )
36from lazr.lifecycle.event import ObjectModifiedEvent36from lazr.lifecycle.event import ObjectModifiedEvent
37from lazr.lifecycle.snapshot import Snapshot37from lazr.lifecycle.snapshot import Snapshot
38from lazr.restful import (
39 EntryResource,
40 ResourceJSONEncoder,
41 )
38from lazr.restful.interface import copy_field42from lazr.restful.interface import copy_field
39from lazr.restful.interfaces import IJSONRequestCache43from lazr.restful.interfaces import IJSONRequestCache
44from simplejson import dumps
40from zope import formlib45from zope import formlib
41from zope.app.form.browser import TextWidget46from zope.app.form.browser import TextWidget
42from zope.component import getUtility47from zope.component import getUtility
@@ -60,6 +65,7 @@
60from lp.app.errors import NotFoundError65from lp.app.errors import NotFoundError
61from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription66from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
62from lp.app.widgets.project import ProjectScopeWidget67from lp.app.widgets.project import ProjectScopeWidget
68from lp.bugs.browser.bugsubscription import BugPortletSubscribersWithDetails
63from lp.bugs.browser.widgets.bug import BugTagsWidget69from lp.bugs.browser.widgets.bug import BugTagsWidget
64from lp.bugs.enums import BugNotificationLevel70from lp.bugs.enums import BugNotificationLevel
65from lp.bugs.interfaces.bug import (71from lp.bugs.interfaces.bug import (
@@ -74,6 +80,7 @@
74from lp.bugs.interfaces.bugtask import (80from lp.bugs.interfaces.bugtask import (
75 BugTaskSearchParams,81 BugTaskSearchParams,
76 BugTaskStatus,82 BugTaskStatus,
83 IBugTask,
77 IFrontPageBugTaskSearch,84 IFrontPageBugTaskSearch,
78 )85 )
79from lp.bugs.interfaces.bugwatch import IBugWatchSet86from lp.bugs.interfaces.bugwatch import IBugWatchSet
@@ -825,7 +832,9 @@
825 @property832 @property
826 def next_url(self):833 def next_url(self):
827 """Return the next URL to call when this call completes."""834 """Return the next URL to call when this call completes."""
828 return canonical_url(self.context)835 if not self.request.is_ajax:
836 return canonical_url(self.context)
837 return None
829838
830 cancel_url = next_url839 cancel_url = next_url
831840
@@ -834,7 +843,8 @@
834 """See `LaunchpadFormView.`"""843 """See `LaunchpadFormView.`"""
835 return {'information_type': self.context.bug.information_type}844 return {'information_type': self.context.bug.information_type}
836845
837 @action('Change', name='change')846 @action('Change', name='change',
847 failure=LaunchpadFormView.ajax_failure_handler)
838 def change_action(self, action, data):848 def change_action(self, action, data):
839 """Update the bug."""849 """Update the bug."""
840 data = dict(data)850 data = dict(data)
@@ -853,6 +863,33 @@
853 ObjectModifiedEvent(863 ObjectModifiedEvent(
854 bug, bug_before_modification, changed_fields,864 bug, bug_before_modification, changed_fields,
855 user=self.user))865 user=self.user))
866 if self.request.is_ajax:
867 if changed:
868 return self._getSubscriptionDetails()
869 else:
870 return ''
871
872 def _getSubscriptionDetails(self):
873 cache = dict()
874 # The subscription details for the current user.
875 self.extractBugSubscriptionDetails(self.user, self.context.bug, cache)
876
877 # The subscription details for other users to populate the subscribers
878 # list in the portlet.
879 if IBugTask.providedBy(self.context):
880 bug = self.context.bug
881 else:
882 bug = self.context
883 subscribers_portlet = BugPortletSubscribersWithDetails(
884 bug, self.request)
885 subscription_data = subscribers_portlet.subscriber_data
886 result_data = dict(
887 cache_data=cache,
888 subscription_data=subscription_data)
889 self.request.response.setHeader('content-type', 'application/json')
890 return dumps(
891 result_data, cls=ResourceJSONEncoder,
892 media_type=EntryResource.JSON_TYPE)
856893
857 def _handlePrivacyChanged(self, user_will_be_subscribed):894 def _handlePrivacyChanged(self, user_will_be_subscribed):
858 """Handle the case where the privacy of the bug has been changed.895 """Handle the case where the privacy of the bug has been changed.
859896
=== 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 @@
6__metaclass__ = type6__metaclass__ = type
77
8from BeautifulSoup import BeautifulSoup8from BeautifulSoup import BeautifulSoup
9import simplejson
9from soupmatchers import (10from soupmatchers import (
10 HTMLContains,11 HTMLContains,
11 Tag,12 Tag,
@@ -296,7 +297,8 @@
296297
297 layer = DatabaseFunctionalLayer298 layer = DatabaseFunctionalLayer
298299
299 def createInitializedSecrecyView(self, person=None, bug=None):300 def createInitializedSecrecyView(self, person=None, bug=None,
301 request=None):
300 """Create and return an initialized BugSecrecyView."""302 """Create and return an initialized BugSecrecyView."""
301 if person is None:303 if person is None:
302 person = self.factory.makePerson()304 person = self.factory.makePerson()
@@ -307,7 +309,7 @@
307 bug.default_bugtask, name='+secrecy', form={309 bug.default_bugtask, name='+secrecy', form={
308 'field.information_type': 'USERDATA',310 'field.information_type': 'USERDATA',
309 'field.actions.change': 'Change',311 'field.actions.change': 'Change',
310 })312 }, request=request)
311 return view313 return view
312314
313 def test_notification_shown_if_marking_private_and_not_subscribed(self):315 def test_notification_shown_if_marking_private_and_not_subscribed(self):
@@ -346,6 +348,44 @@
346 view = self.createInitializedSecrecyView(person, bug)348 view = self.createInitializedSecrecyView(person, bug)
347 self.assertContentEqual([], view.request.response.notifications)349 self.assertContentEqual([], view.request.response.notifications)
348350
351 def test_secrecy_view_ajax_render(self):
352 # When the bug secrecy view is called from an ajax request, it should
353 # provide a json encoded dict when rendered. The dict contains bug
354 # subscription information resulting from the update to the bug
355 # privacy as well as information used to populate the updated
356 # subscribers list.
357 person = self.factory.makePerson()
358 bug = self.factory.makeBug(owner=person)
359 with person_logged_in(person):
360 bug.subscribe(person, person)
361
362 extra = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
363 request = LaunchpadTestRequest(
364 method='POST', form={
365 'field.actions.change': 'Change',
366 'field.information_type': 'USERDATA'},
367 **extra)
368 view = self.createInitializedSecrecyView(person, bug, request)
369 result_data = simplejson.loads(view.render())
370
371 cache_data = result_data['cache_data']
372 self.assertFalse(cache_data['other_subscription_notifications'])
373 subscription_data = cache_data['subscription']
374 self.assertEqual(
375 'http://launchpad.dev/api/devel/bugs/%s' % bug.id,
376 subscription_data['bug_link'])
377 self.assertEqual(
378 'http://launchpad.dev/api/devel/~%s' % person.name,
379 subscription_data['person_link'])
380 self.assertEqual(
381 'Discussion', subscription_data['bug_notification_level'])
382
383 [subscriber_data] = result_data['subscription_data']
384 subscriber = removeSecurityProxy(bug).default_bugtask.pillar.owner
385 self.assertEqual(
386 subscriber.name, subscriber_data['subscriber']['name'])
387 self.assertEqual('Discussion', subscriber_data['subscription_level'])
388
349 def test_set_information_type(self):389 def test_set_information_type(self):
350 # Test that the bug's information_type can be updated using the390 # Test that the bug's information_type can be updated using the
351 # view with the feature flag on.391 # view with the feature flag on.
352392
=== 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 @@
100 view.initialize()100 view.initialize()
101 cache = IJSONRequestCache(view.request)101 cache = IJSONRequestCache(view.request)
102 expected = [102 expected = [
103 InformationType.PUBLIC, InformationType.UNEMBARGOEDSECURITY,103 InformationType.PUBLIC.name,
104 InformationType.EMBARGOEDSECURITY, InformationType.USERDATA]104 InformationType.UNEMBARGOEDSECURITY.name,
105 InformationType.EMBARGOEDSECURITY.name,
106 InformationType.USERDATA.name]
105 self.assertContentEqual(expected, [107 self.assertContentEqual(expected, [
106 type['value']108 type['value']
107 for type in cache.objects['information_types']])109 for type in cache.objects['information_type_data']])
108110
=== 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 @@
278 }278 }
279};279};
280280
281
282var update_privacy_settings = function(data) {
283 // XXX noodles 2009-03-17 bug=336866 It seems the etag
284 // returned by lp_save() is incorrect. Remove it for now
285 // so that the second save does not result in a '412
286 // precondition failed' error.
287 //
288 // XXX deryck 2009-04-29 bug=369293 Also, this has to
289 // happen before *any* call to lp_save now that bug
290 // subscribing can be done inline. Named operations
291 // don't return new objects, making the cached bug's
292 // etag invalid as well.
293 lp_bug_entry.removeAttr('http_etag');
294
295 privacy_form_overlay.hide();
296
297 var privacy_text = Y.one('#privacy-text');
298 var privacy_div = Y.one('#privacy');
299 privacy_link.setStyle('display', 'none');
300 privacy_spinner.setStyle('display', 'inline');
301
302 if (lp_client === undefined) {
303 lp_client = new Y.lp.client.Launchpad();
304 }
305
306 if (lp_bug_entry === undefined) {
307 var bug_repr = LP.cache.bug;
308 lp_bug_entry = new Y.lp.client.Entry(
309 lp_client, bug_repr, bug_repr.self_link);
310 }
311
312 var private_flag = data['field.private'] !== undefined;
313 var security_related =
314 data['field.security_related'] !== undefined;
315
316 lp_bug_entry.set('private', private_flag);
317 lp_bug_entry.set('security_related', security_related);
318 var error_handler = new Y.lp.client.ErrorHandler();
319 error_handler.clearProgressUI = function () {
320 privacy_spinner.setStyle('display', 'none');
321 privacy_link.setStyle('display', 'inline');
322 };
323 error_handler.showError = function (error_msg) {
324 Y.lp.anim.red_flash({
325 node: privacy_div,
326 duration: namespace.ANIM_DURATION
327 }).run();
328 privacy_form_overlay.showError(error_msg);
329 privacy_form_overlay.show();
330 };
331
332 var base_url = LP.cache.context.web_link;
333 var submit_url = base_url+"/+secrecy";
334 var qs = Y.lp.client.append_qs('', 'field.actions.change', 'Change');
335 qs = Y.lp.client.append_qs(qs, 'field.private', private_flag?'on':'off');
336 qs = Y.lp.client.append_qs(
337 qs, 'field.security_related', security_related?'on':'off');
338 var sub_list_node = Y.one('#other-bug-subscribers');
339 var subscribers_list = sub_list_node.getData('subscribers_loader');
340 var config = {
341 method: "POST",
342 headers: {'Accept': 'application/xhtml'},
343 data: qs,
344 on: {
345 start: function () {
346 subscribers_list.subscribers_list.startActivity(
347 'Updating subscribers...');
348 },
349 end: function () {
350 subscribers_list.subscribers_list.stopActivity();
351 },
352 success: function (id, response) {
353 if (response.responseText !== '') {
354 var result_data = Y.JSON.parse(response.responseText);
355 var subscribers = result_data.subscription_data;
356 subscribers_list._loadSubscribersFromList(subscribers);
357 var cache_data = result_data.cache_data;
358 var item;
359 for (item in cache_data) {
360 if (cache_data.hasOwnProperty(item)) {
361 LP.cache[item] = cache_data[item];
362 }
363 }
364 var ns = Y.lp.bugs.bugtask_index.portlets.subscription;
365 ns.update_subscription_status();
366 }
367 privacy_spinner.setStyle('display', 'none');
368 privacy_link.setStyle('display', 'inline');
369
370 var banner = Y.lp.app.banner.privacy.getPrivacyBanner();
371 if (private_flag) {
372 Y.one('body').replaceClass('public', 'private');
373 privacy_div.replaceClass('public', 'private');
374 privacy_text.set(
375 'innerHTML',
376 'This report is <strong>private</strong> ');
377 banner.show();
378 } else {
379 Y.one('body').replaceClass('private', 'public');
380 privacy_div.replaceClass('private', 'public');
381 privacy_text.set(
382 'innerHTML', 'This report is public ');
383 banner.hide();
384 }
385 privacy_text.appendChild(privacy_link);
386 privacy_text.appendChild(privacy_spinner);
387
388 var security_message = Y.one('#security-message');
389 if (security_related) {
390 if (security_message === null) {
391 var security_message_html = [
392 '<div style="',
393 ' margin-top: 0.5em;',
394 ' padding-right: 18px;',
395 ' center right no-repeat;"',
396 ' class="sprite security"',
397 ' id="security-message"',
398 '>Security vulnerability</div>'
399 ].join('');
400 security_message = Y.Node.create(
401 security_message_html);
402 privacy_div.appendChild(security_message);
403 }
404 } else {
405 if (security_message !== null) {
406 privacy_div.removeChild(security_message);
407 }
408 }
409 Y.lp.client.display_notifications(
410 response.getResponseHeader('X-Lazr-Notifications'));
411 Y.lp.anim.green_flash({
412 node: privacy_div,
413 duration: namespace.ANIM_DURATION
414 }).run();
415 },
416 failure: error_handler.getFailureHandler()
417 }
418 };
419 lp_client.io_provider.io(submit_url, config);
420};
421
422/**281/**
423 * Do a preemptive search for branches that contain the current bug's ID.282 * Do a preemptive search for branches that contain the current bug's ID.
424 */283 */
425284
=== 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 @@
7YUI.add('lp.bugs.information_type_choice', function(Y) {7YUI.add('lp.bugs.information_type_choice', function(Y) {
88
9var namespace = Y.namespace('lp.bugs.information_type_choice');9var namespace = Y.namespace('lp.bugs.information_type_choice');
10var information_type_descriptions = {};
1110
12// For testing.11// For testing.
13var skip_animation = false;12var skip_animation = false;
1413
14/**
15 * Lookup the information_type property, keyed on the named value.
16 * @param key the key to lookup
17 * @param key_name the key property name
18 * @param value_name the value property_name
19 * @return {*}
20 */
21var information_type_value_from_key = function(key, key_name,
22 value_name) {
23 var data = null;
24 Y.Array.some(LP.cache.information_type_data, function(info_type) {
25 if (info_type[key_name] === key) {
26 data = info_type[value_name];
27 return true;
28 }
29 });
30 return data;
31};
32
15namespace.save_information_type = function(widget, value, lp_client) {33namespace.save_information_type = function(widget, value, lp_client) {
16 var error_handler = new Y.lp.client.ErrorHandler();34 var error_handler = new Y.lp.client.FormErrorHandler();
17 error_handler.showError = function(error_msg) {35 error_handler.showError = function(error_msg) {
18 Y.lp.app.errors.display_error(36 Y.lp.app.errors.display_error(
19 Y.one('#information-type'), error_msg);37 Y.one('#information-type'), error_msg);
20 };38 };
21 error_handler.handleError = function(ioId, response) {39 error_handler.handleError = function(ioId, response) {
22 var orig_value = LP.cache.bug.information_type;40 var orig_value = information_type_value_from_key(
41 LP.cache.bug.information_type, 'name', 'value');
23 widget.set('value', orig_value);42 widget.set('value', orig_value);
24 widget._showFailed();43 widget._showFailed();
25 update_privacy_portlet(orig_value);44 update_privacy_portlet(orig_value);
26 return false;45 return false;
27 };46 };
47 var base_url = LP.cache.context.web_link;
48 var submit_url = base_url+"/+secrecy";
49 var qs = Y.lp.client.append_qs('', 'field.actions.change', 'Change');
50 qs = Y.lp.client.append_qs(qs, 'field.information_type', value);
51 var sub_list_node = Y.one('#other-bug-subscribers');
52 var subscribers_list = sub_list_node.getData('subscribers_loader');
28 var config = {53 var config = {
54 method: "POST",
55 headers: {'Accept': 'application/xhtml;application/json'},
56 data: qs,
29 on: {57 on: {
30 start: Y.bind(widget._uiSetWaiting),58 start: function () {
31 end: Y.bind(widget._uiClearWaiting),59 widget._uiSetWaiting();
32 success: function(id, response) {60 subscribers_list.subscribers_list.startActivity(
33 namespace.information_type_save_success(widget, value);61 'Updating subscribers...');
62 },
63 end: function () {
64 widget._uiClearWaiting();
65 subscribers_list.subscribers_list.stopActivity();
66 },
67 success: function (id, response) {
68 var result_data = null;
69 if (response.responseText !== '') {
70 result_data = Y.JSON.parse(response.responseText);
71 }
72 namespace.information_type_save_success(
73 widget, value, subscribers_list, result_data);
74 Y.lp.client.display_notifications(
75 response.getResponseHeader('X-Lazr-Notifications'));
34 },76 },
35 failure: error_handler.getFailureHandler()77 failure: error_handler.getFailureHandler()
36 },
37 parameters: {
38 information_type: value
39 }78 }
40 };79 };
41 lp_client.named_post(80 lp_client.io_provider.io(submit_url, config);
42 LP.cache.bug.self_link, 'transitionToInformationType', config);
43};81};
4482
45var update_privacy_portlet = function(value) {83var update_privacy_portlet = function(value) {
46 var description = information_type_descriptions[value];84 var description = information_type_value_from_key(
85 value, 'value', 'description');
47 var desc_node = Y.one('#information-type-description');86 var desc_node = Y.one('#information-type-description');
48 if (Y.Lang.isValue(desc_node)) {87 if (Y.Lang.isValue(desc_node)) {
49 desc_node.set('text', description);88 desc_node.set('text', description);
@@ -74,35 +113,48 @@
74113
75namespace.get_information_type_banner_text = function(value) {114namespace.get_information_type_banner_text = function(value) {
76 var text_template = "This page contains {info_type} information.";115 var text_template = "This page contains {info_type} information.";
77116 var info_type = information_type_value_from_key(value, 'value', 'name');
78 if (value === "User Data" && LP.cache.show_userdata_as_private) {117 if (info_type === "User Data" && LP.cache.show_userdata_as_private) {
79 value = "Private";118 info_type = "Private";
80 }119 }
81 return Y.Lang.substitute(text_template, {'info_type': value});120 return Y.Lang.substitute(text_template, {'info_type': info_type});
82};121};
83122
84namespace.information_type_save_success = function(widget, value) {123namespace.information_type_save_success = function(widget, value,
85 LP.cache.bug.information_type = value;124 subscribers_list,
125 subscribers_data) {
126 LP.cache.bug.information_type =
127 information_type_value_from_key(value, 'value', 'name');
86 update_privacy_banner(value);128 update_privacy_banner(value);
87 widget._showSucceeded();129 widget._showSucceeded();
88 var subscription_ns = Y.lp.bugs.bugtask_index.portlets.subscription;130 if (Y.Lang.isObject(subscribers_data)) {
89 subscription_ns.update_subscription_status(skip_animation);131 var subscribers = subscribers_data.subscription_data;
132 subscribers_list._loadSubscribersFromList(subscribers);
133 var cache_data = subscribers_data.cache_data;
134 var item;
135 for (item in cache_data) {
136 if (cache_data.hasOwnProperty(item)) {
137 LP.cache[item] = cache_data[item];
138 }
139 }
140 }
141 var ns = Y.lp.bugs.bugtask_index.portlets.subscription;
142 ns.update_subscription_status(skip_animation);
90};143};
91144
92namespace.setup_information_type_choice = function(privacy_link, lp_client,145namespace.setup_information_type_choice = function(privacy_link, lp_client,
93 skip_anim) {146 skip_anim) {
94 skip_animation = skip_anim;147 skip_animation = skip_anim;
95 Y.Array.each(LP.cache.information_types, function(info_type) {148 var initial_value = information_type_value_from_key(
96 information_type_descriptions[info_type.value] = info_type.description;149 LP.cache.bug.information_type, 'name', 'value');
97 });
98 var information_type = Y.one('#information-type');150 var information_type = Y.one('#information-type');
99 var information_type_edit = new Y.ChoiceSource({151 var information_type_edit = new Y.ChoiceSource({
100 editicon: privacy_link,152 editicon: privacy_link,
101 contentBox: Y.one('#privacy'),153 contentBox: Y.one('#privacy'),
102 value_location: information_type,154 value_location: information_type,
103 value: LP.cache.bug.information_type,155 value: initial_value,
104 title: "Change information type",156 title: "Change information type",
105 items: LP.cache.information_types,157 items: LP.cache.information_type_data,
106 backgroundColor: '#FFFF99',158 backgroundColor: '#FFFF99',
107 flashEnabled: false159 flashEnabled: false
108 });160 });
109161
=== 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 @@
70 <script type="text/javascript"70 <script type="text/javascript"
71 src="../../../../../build/js/lp/bugs/bug_subscription_portlet.js"></script>71 src="../../../../../build/js/lp/bugs/bug_subscription_portlet.js"></script>
72 <script type="text/javascript"72 <script type="text/javascript"
73 src="../../../../../build/js/lp/bugs/subscribers.js"></script>
74 <script type="text/javascript"
75 src="../../../../../build/js/lp/app/subscribers/subscribers_list.js"></script>
76 <script type="text/javascript"
73 src="../../../../../build/js/lp/bugs/bugtask_index.js"></script>77 src="../../../../../build/js/lp/bugs/bugtask_index.js"></script>
7478
75 <!-- The module under test. -->79 <!-- The module under test. -->
@@ -104,6 +108,7 @@
104 <a href="#" text="" class="menu-link-mute_subscription unmute"></a>108 <a href="#" text="" class="menu-link-mute_subscription unmute"></a>
105 <a href="#" text="" class="menu-link-mute_subscription mute"></a>109 <a href="#" text="" class="menu-link-mute_subscription mute"></a>
106 </div>110 </div>
111 <div id="other-bug-subscribers"></div>
107 </script>112 </script>
108 </body>113 </body>
109</html>114</html>
110115
=== 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 @@
22 information_type: 'Public',22 information_type: 'Public',
23 self_link: '/bug/1'23 self_link: '/bug/1'
24 },24 },
25 private_types: ['Private', 'User Data'],25 private_types: ['PRIVATE', 'USERDATA'],
26 information_types: [26 information_type_data: [
27 {'value': 'Public', 'name': 'Public',27 {'value': 'PUBLIC', 'name': 'Public',
28 'description': 'Public Description'},28 'description': 'Public Description'},
29 {'value': 'Private', 'name': 'Private',29 {'value': 'PRIVATE', 'name': 'Private',
30 'description': 'Private Description'},30 'description': 'Private Description'},
31 {'value': 'User Data', 'name': 'User Data',31 {'value': 'USERDATA', 'name': 'User Data',
32 'description': 'User Data Description'}32 'description': 'User Data Description'}
33 ]33 ]
34 }34 }
@@ -42,6 +42,11 @@
42 var privacy_link = Y.one('#privacy-link');42 var privacy_link = Y.one('#privacy-link');
43 this.widget = ns.setup_information_type_choice(43 this.widget = ns.setup_information_type_choice(
44 privacy_link, lp_client, true);44 privacy_link, lp_client, true);
45 Y.lp.bugs.subscribers.createBugSubscribersLoader({
46 container_box: '#other-bug-subscribers',
47 subscribers_details_view:
48 '/+bug-portlet-subscribers-details'});
49
45 },50 },
46 tearDown: function () {51 tearDown: function () {
47 if (this.fixture !== null) {52 if (this.fixture !== null) {
@@ -72,19 +77,39 @@
72 "Cannot locate the lp.bugs.information_type_choice module");77 "Cannot locate the lp.bugs.information_type_choice module");
73 },78 },
7479
80 // The save XHR call works as expected.
75 test_save_information_type: function() {81 test_save_information_type: function() {
76 var mockio = new Y.lp.testing.mockio.MockIo();82 var mockio = new Y.lp.testing.mockio.MockIo();
77 var lp_client = new Y.lp.client.Launchpad({83 var lp_client = new Y.lp.client.Launchpad({
78 io_provider: mockio84 io_provider: mockio
79 });85 });
80 ns.save_information_type(this.widget, 'User Data', lp_client);86 var orig_save_success = ns.information_type_save_success;
81 Y.Assert.areEqual('/api/devel/bug/1', mockio.last_request.url);87 var save_success_called = false;
88 ns.information_type_save_success = function(widget, value,
89 subscribers_list,
90 subscribers_data) {
91 Y.Assert.areEqual('USERDATA', value);
92 Y.Assert.areEqual(
93 'subscribers', subscribers_data.subscription_data);
94 Y.Assert.areEqual(
95 'value', subscribers_data.cache_data.item);
96 save_success_called = true;
97 };
98 ns.save_information_type(this.widget, 'USERDATA', lp_client);
99 mockio.success({
100 responseText: '{"subscription_data": "subscribers",' +
101 '"cache_data": {"item": "value"}}',
102 responseHeaders: {'Content-Type': 'application/json'}});
103 Y.Assert.areEqual('/+secrecy', mockio.last_request.url);
82 Y.Assert.areEqual(104 Y.Assert.areEqual(
83 'ws.op=transitionToInformationType&' +105 'field.actions.change=Change&' +
84 'information_type=User%20Data',106 'field.information_type=USERDATA',
85 mockio.last_request.config.data);107 mockio.last_request.config.data);
108 Y.Assert.isTrue(save_success_called);
109 ns.information_type_save_success = orig_save_success;
86 },110 },
87111
112 // Setting a private type shows the privacy banner.
88 test_information_type_save_success_private: function() {113 test_information_type_save_success_private: function() {
89 var old_func = this._shim_privacy_banner();114 var old_func = this._shim_privacy_banner();
90 var hide_flag = false;115 var hide_flag = false;
@@ -96,7 +121,7 @@
96 update_flag = true;121 update_flag = true;
97 });122 });
98123
99 ns.information_type_save_success(this.widget, 'Private');124 ns.information_type_save_success(this.widget, 'PRIVATE');
100 var body = Y.one('body');125 var body = Y.one('body');
101 Y.Assert.isTrue(body.hasClass('private'));126 Y.Assert.isTrue(body.hasClass('private'));
102 Y.Assert.isTrue(hide_flag);127 Y.Assert.isTrue(hide_flag);
@@ -105,6 +130,7 @@
105 this._unshim_privacy_banner(old_func);130 this._unshim_privacy_banner(old_func);
106 },131 },
107132
133 // Setting a private type hides the privacy banner.
108 test_information_type_save_success_public: function() {134 test_information_type_save_success_public: function() {
109 var old_func = this._shim_privacy_banner();135 var old_func = this._shim_privacy_banner();
110 var flag = false;136 var flag = false;
@@ -114,7 +140,7 @@
114 var summary = Y.one('#information-type-summary');140 var summary = Y.one('#information-type-summary');
115 summary.replaceClass('public', 'private');141 summary.replaceClass('public', 'private');
116142
117 ns.information_type_save_success(this.widget, 'Public');143 ns.information_type_save_success(this.widget, 'PUBLIC');
118 var body = Y.one('body');144 var body = Y.one('body');
119 Y.Assert.isTrue(body.hasClass('public'));145 Y.Assert.isTrue(body.hasClass('public'));
120 Y.Assert.isTrue(flag);146 Y.Assert.isTrue(flag);
@@ -122,16 +148,49 @@
122 this._unshim_privacy_banner(old_func);148 this._unshim_privacy_banner(old_func);
123 },149 },
124150
151 // A successful save updates the subscribers portlet.
152 test_information_type_save_success_with_subscribers_data: function() {
153 var old_func = this._shim_privacy_banner();
154 var flag = false;
155 Y.on('test:banner:hide', function() {
156 flag = true;
157 });
158 var summary = Y.one('#information-type-summary');
159 summary.replaceClass('public', 'private');
160
161 var load_subscribers_called = false;
162 var subscribers_list = {
163 _loadSubscribersFromList: function(subscription_data) {
164 Y.Assert.areEqual('subscriber', subscription_data);
165 load_subscribers_called = true;
166 }
167 };
168 var subscribers_data = {
169 subscription_data: 'subscriber',
170 cache_data: {
171 item1: 'value1',
172 item2: 'value2'
173 }
174 };
175 ns.information_type_save_success(
176 this.widget, 'PUBLIC', subscribers_list, subscribers_data);
177 Y.Assert.isTrue(load_subscribers_called);
178 Y.Assert.areEqual('value1', window.LP.cache.item1);
179 Y.Assert.areEqual('value2', window.LP.cache.item2);
180 this._unshim_privacy_banner(old_func);
181 },
182
183 // Selecting a new information type calls save correctly.
125 test_perform_update_information_type: function() {184 test_perform_update_information_type: function() {
126 var privacy_link = Y.one('#privacy-link');185 var privacy_link = Y.one('#privacy-link');
127 privacy_link.simulate('click');186 privacy_link.simulate('click');
128 var private_choice = Y.one(187 var private_choice = Y.one(
129 '.yui3-ichoicelist-content a[href="#Private"]');188 '.yui3-ichoicelist-content a[href="#USERDATA"]');
130 var orig_save_information_type = ns.save_information_type;189 var orig_save_information_type = ns.save_information_type;
131 var function_called = false;190 var function_called = false;
132 ns.save_information_type = function(widget, value, lp_client) {191 ns.save_information_type = function(widget, value, lp_client) {
133 Y.Assert.areEqual(192 Y.Assert.areEqual(
134 'User Data', value); function_called = true; };193 'USERDATA', value); function_called = true; };
135 private_choice.simulate('click');194 private_choice.simulate('click');
136 var description_node = Y.one('#information-type-description');195 var description_node = Y.one('#information-type-description');
137 Y.Assert.areEqual(196 Y.Assert.areEqual(
@@ -142,20 +201,21 @@
142 ns.save_information_type = orig_save_information_type;201 ns.save_information_type = orig_save_information_type;
143 },202 },
144203
204 // Test error handling when a save fails.
145 test_information_type_save_error: function() {205 test_information_type_save_error: function() {
146 var mockio = new Y.lp.testing.mockio.MockIo();206 var mockio = new Y.lp.testing.mockio.MockIo();
147 var lp_client = new Y.lp.client.Launchpad({207 var lp_client = new Y.lp.client.Launchpad({
148 io_provider: mockio208 io_provider: mockio
149 });209 });
150 this.widget.set('value', 'User Data');210 this.widget.set('value', 'USERDATA');
151 ns.save_information_type(this.widget, 'User Data', lp_client);211 ns.save_information_type(this.widget, 'USERDATA', lp_client);
152 mockio.last_request.respond({212 mockio.last_request.respond({
153 status: 500,213 status: 500,
154 statusText: 'An error occurred'214 statusText: 'An error occurred'
155 });215 });
156 // The original info type value from the cache should have been216 // The original info type value from the cache should have been
157 // set back into the widget.217 // set back into the widget.
158 Y.Assert.areEqual('Public', this.widget.get('value'));218 Y.Assert.areEqual('PUBLIC', this.widget.get('value'));
159 var description_node = Y.one('#information-type-description');219 var description_node = Y.one('#information-type-description');
160 Y.Assert.areEqual(220 Y.Assert.areEqual(
161 'Public Description', description_node.get('text'));221 'Public Description', description_node.get('text'));
@@ -167,5 +227,5 @@
167 }));227 }));
168228
169}, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate',229}, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate',
170 'lp.testing.mockio', 'lp.client',230 'lp.testing.mockio', 'lp.client','lp.bugs.subscribers',
171 'lp.bugs.information_type_choice']});231 'lp.bugs.information_type_choice']});