Merge lp:~stevenk/launchpad/bugs-information_type-ui-portlet into lp:launchpad

Proposed by Steve Kowalik on 2012-04-24
Status: Merged
Approved by: Steve Kowalik on 2012-04-24
Approved revision: no longer in the source branch.
Merged at revision: 15141
Proposed branch: lp:~stevenk/launchpad/bugs-information_type-ui-portlet
Merge into: lp:launchpad
Diff against target: 419 lines (+312/-22)
7 files modified
lib/lp/bugs/browser/bug.py (+9/-2)
lib/lp/bugs/browser/tests/test_bugview.py (+17/-0)
lib/lp/bugs/javascript/bugtask_index.js (+6/-2)
lib/lp/bugs/javascript/information_type_choice.js (+69/-0)
lib/lp/bugs/javascript/tests/test_information_type_choice.html (+80/-0)
lib/lp/bugs/javascript/tests/test_information_type_choice.js (+106/-0)
lib/lp/bugs/templates/bug-portlet-privacy.pt (+25/-18)
To merge this branch: bzr merge lp:~stevenk/launchpad/bugs-information_type-ui-portlet
Reviewer Review Type Date Requested Status
Ian Booth (community) 2012-04-24 Approve on 2012-04-24
Review via email: mp+103193@code.launchpad.net

Commit Message

Show information_type in the privacy portlet and allow it to be edited via JS if a feature flag is set.

Description of the Change

Building on the changes introduced in https://code.launchpad.net/~stevenk/launchpad/bugs-information_type-ui-filebug/+merge/102635, and the march forward, show the information_type in the privacy portlet on Bug:+index.

This branch sort of closes bug 249112, but I'd rather not do so until the feature flag is enabled on production.

As before, I have left the existing tests and added new ones. I have done a drive-by that corrects a capitalization failure introduced by a previous branch.

To post a comment you must log in.
Ian Booth (wallyworld) wrote :

Looks good but I would prefer the code in the callbacks (info type click and xhr success) to be broken out as separate functions. This achieves the correct separation of concerns, allows for much better, cleaner unit tests (eg code to make xhr call tested standalone without having to invoke ui code, code to test ui update after success tested without having to make xhr call etc), and allows for subsequent refactoring to a more mvc style approach. One of the reasons bugtask_index is untested is because it didn't take this more modular approach and it would be a shame to continue old habits from the past. The core logic is all there, it just needs some minor changes and 2 extra tests. Strictly speaking there should also be a test for the setup function. Also, the new test_information_type_choice.js file doesn't follow the test template eg it's missing the test_library_exists test.

eg

namespace.setup_information_type_choice = function() {
  information_type_edit.on("save", function(e) {
      namespace.perform_info_type_save(
        information_type_edit.get('value'));
  });
}

namespace.perform_info_type_save = function(value) {
  var config = {
    on: {
     success: function(id, response) {
         namespace.info_type_save_success();
     }
    }
    ......
  };
  lp_client.named_post(xxxxx);
};

namespace.info_type_save_success = function() {
    subscription_ns.update_subscription_status();
    if (private_type) {
      body.replaceClass('public', 'private');
      privacy_ns.display_privacy_notification();
    } else {
      body.replaceClass('private', 'public');
      privacy_ns.hide_privacy_notification();
    }
}

Unit tests would monkey patch the relevant functions to ensure they are called as expected. This is a common pattern in our current yui tests.

review: Needs Fixing
Ian Booth (wallyworld) wrote :

Thanks very much for making the suggested fixes.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bug.py'
2--- lib/lp/bugs/browser/bug.py 2012-04-19 05:08:15 +0000
3+++ lib/lp/bugs/browser/bug.py 2012-04-24 07:48:19 +0000
4@@ -553,6 +553,11 @@
5 all the pages off IBugTask instead of IBug.
6 """
7
8+ @property
9+ def show_information_type_in_ui(self):
10+ return bool(getFeatureFlag(
11+ 'disclosure.show_information_type_in_ui.enabled'))
12+
13 def initialize(self):
14 super(BugView, self).initialize()
15 cache = IJSONRequestCache(self.request)
16@@ -560,7 +565,9 @@
17 {'value': term.value, 'description': term.description,
18 'name': term.title} for term in InformationTypeVocabulary()]
19 cache.objects['private_types'] = [
20- type.name for type in PRIVATE_INFORMATION_TYPES]
21+ type.title for type in PRIVATE_INFORMATION_TYPES]
22+ cache.objects['show_information_type_in_ui'] = (
23+ self.show_information_type_in_ui)
24
25 @cachedproperty
26 def page_description(self):
27@@ -865,7 +872,7 @@
28 label = 'Bug #%i - Set ' % self.context.bug.id
29 if bool(getFeatureFlag(
30 'disclosure.show_information_type_in_ui.enabled')):
31- label += 'Information type'
32+ label += 'information type'
33 else:
34 label += 'visibility and security'
35 return label
36
37=== modified file 'lib/lp/bugs/browser/tests/test_bugview.py'
38--- lib/lp/bugs/browser/tests/test_bugview.py 2012-04-11 06:01:04 +0000
39+++ lib/lp/bugs/browser/tests/test_bugview.py 2012-04-24 07:48:19 +0000
40@@ -3,6 +3,7 @@
41
42 __metaclass__ = type
43
44+from lazr.restful.interfaces import IJSONRequestCache
45 from zope.security.proxy import removeSecurityProxy
46
47 from lp.bugs.browser.bug import BugView
48@@ -73,3 +74,19 @@
49 with FeatureFixture(feature_flag):
50 view = BugView(self.bug, LaunchpadTestRequest())
51 self.assertEqual('Private', view.information_type)
52+
53+ def test_proprietary_hidden(self):
54+ # When the proprietary_information_type.disabled feature flag is
55+ # enabled, it isn't in the JSON request cache.
56+ feature_flag = {
57+ 'disclosure.proprietary_information_type.disabled': 'on'}
58+ with FeatureFixture(feature_flag):
59+ view = BugView(self.bug, LaunchpadTestRequest())
60+ view.initialize()
61+ cache = IJSONRequestCache(view.request)
62+ expected = [
63+ InformationType.PUBLIC, InformationType.UNEMBARGOEDSECURITY,
64+ InformationType.EMBARGOEDSECURITY, InformationType.USERDATA]
65+ self.assertContentEqual(expected, [
66+ type['value']
67+ for type in cache.objects['information_types']])
68
69=== modified file 'lib/lp/bugs/javascript/bugtask_index.js'
70--- lib/lp/bugs/javascript/bugtask_index.js 2012-04-06 17:28:25 +0000
71+++ lib/lp/bugs/javascript/bugtask_index.js 2012-04-24 07:48:19 +0000
72@@ -1,4 +1,4 @@
73-/* Copyright 2009-2011 Canonical Ltd. This software is licensed under the
74+/* Copyright 2009-2012 Canonical Ltd. This software is licensed under the
75 * GNU Affero General Public License version 3 (see the file LICENSE).
76 *
77 * Form overlay widgets and subscriber handling for bug pages.
78@@ -117,7 +117,10 @@
79
80 privacy_link = Y.one('#privacy-link');
81
82- if (privacy_link) {
83+ if (privacy_link && LP.cache.show_information_type_in_ui) {
84+ Y.lp.bugs.information_type_choice.setup_information_type_choice(
85+ privacy_link, lp_client);
86+ } else if (privacy_link) {
87 var privacy_link_url = privacy_link.getAttribute('href') +
88 '/++form++';
89 var privacy_div = Y.one('#privacy-text');
90@@ -1521,6 +1524,7 @@
91 "lazr.formoverlay", "lp.anim", "lazr.base",
92 "lazr.overlay", "lazr.choiceedit", "lp.app.picker",
93 "lp.bugs.bugtask_index.portlets.subscription",
94+ "lp.bugs.information_type_choice",
95 "lp.app.widgets.expander", "lp.client", "escape",
96 "lp.client.plugins", "lp.app.errors",
97 "lp.app.privacy", "lp.app.confirmationoverlay"]});
98
99=== added file 'lib/lp/bugs/javascript/information_type_choice.js'
100--- lib/lp/bugs/javascript/information_type_choice.js 1970-01-01 00:00:00 +0000
101+++ lib/lp/bugs/javascript/information_type_choice.js 2012-04-24 07:48:19 +0000
102@@ -0,0 +1,69 @@
103+/* Copyright 2012 Canonical Ltd. This software is licensed under the
104+ * GNU Affero General Public License version 3 (see the file LICENSE).
105+ *
106+ * Information Type choice widget for bug pages.
107+ */
108+
109+YUI.add('lp.bugs.information_type_choice', function(Y) {
110+
111+var namespace = Y.namespace('lp.bugs.information_type_choice');
112+
113+namespace.save_information_type = function(value, lp_client) {
114+ var error_handler = new Y.lp.client.ErrorHandler();
115+ var config = {
116+ on: {
117+ success: function(id, response) {
118+ namespace.information_type_save_success(value);
119+ },
120+ failure: error_handler.getFailureHandler()
121+ },
122+ parameters: {
123+ information_type: value
124+ }
125+ };
126+ lp_client.named_post(
127+ LP.cache.bug.self_link, 'transitionToInformationType', config);
128+};
129+
130+namespace.information_type_save_success = function(value) {
131+ var body = Y.one('body');
132+ var private_type = (Y.Array.indexOf(LP.cache.private_types, value) >= 0);
133+ var subscription_ns = Y.lp.bugs.bugtask_index.portlets.subscription;
134+ var privacy_ns = Y.lp.app.privacy;
135+ subscription_ns.update_subscription_status();
136+ if (private_type) {
137+ body.replaceClass('public', 'private');
138+ privacy_ns.display_privacy_notification();
139+ } else {
140+ body.replaceClass('private', 'public');
141+ privacy_ns.hide_privacy_notification();
142+ }
143+};
144+
145+namespace.setup_information_type_choice = function(privacy_link, lp_client) {
146+ var information_type = Y.one('#information-type');
147+ var information_type_edit = new Y.ChoiceSource({
148+ editicon: privacy_link,
149+ contentBox: Y.one('#privacy'),
150+ value_location: information_type,
151+ value: LP.cache.bug.information_type,
152+ title: "Change information type",
153+ items: LP.cache.information_types,
154+ backgroundColor: '#FFFF99'
155+ });
156+ information_type_edit.render();
157+ information_type_edit.on("save", function(e) {
158+ var value = information_type_edit.get('value');
159+ // This is required due to the display_userdata_as_private feature
160+ // flag.
161+ if (value === 'Private') {
162+ value = 'User Data';
163+ }
164+ namespace.save_information_type(value, lp_client);
165+ });
166+ privacy_link.addClass('js-action');
167+};
168+
169+}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
170+ "lazr.choiceedit", "lp.app.privacy",
171+ "lp.bugs.bugtask_index"]});
172
173=== added file 'lib/lp/bugs/javascript/tests/test_information_type_choice.html'
174--- lib/lp/bugs/javascript/tests/test_information_type_choice.html 1970-01-01 00:00:00 +0000
175+++ lib/lp/bugs/javascript/tests/test_information_type_choice.html 2012-04-24 07:48:19 +0000
176@@ -0,0 +1,80 @@
177+<!DOCTYPE html>
178+<!--
179+Copyright 2012 Canonical Ltd. This software is licensed under the
180+GNU Affero General Public License version 3 (see the file LICENSE).
181+-->
182+
183+<html>
184+ <head>
185+ <title>lp.bugs.information_type_choice Tests</title>
186+
187+ <!-- YUI and test setup -->
188+ <script type="text/javascript"
189+ src="../../../../../build/js/yui/yui/yui.js">
190+ </script>
191+ <link rel="stylesheet"
192+ href="../../../../../build/js/yui/console/assets/console-core.css" />
193+ <link rel="stylesheet"
194+ href="../../../../../build/js/yui/console/assets/skins/sam/console.css" />
195+ <link rel="stylesheet"
196+ href="../../../../../build/js/yui/test/assets/skins/sam/test.css" />
197+
198+ <script type="text/javascript"
199+ src="../../../../../build/js/lp/app/testing/testrunner.js"></script>
200+
201+ <link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
202+
203+ <!-- Dependencies -->
204+ <script type="text/javascript"
205+ src="../../../../../build/js/lp/app/testing/mockio.js"></script>
206+ <script type="text/javascript"
207+ src="../../../../../build/js/lp/app/client.js"></script>
208+ <script type="text/javascript"
209+ src="../../../../../build/js/lp/app/extras/extras.js"></script>
210+ <script type="text/javascript"
211+ src="../../../../../build/js/lp/app/anim/anim.js"></script>
212+ <script type="text/javascript"
213+ src="../../../../../build/js/lp/app/effects/effects.js"></script>
214+ <script type="text/javascript"
215+ src="../../../../../build/js/lp/app/lazr/lazr.js"></script>
216+ <script type="text/javascript"
217+ src="../../../../../build/js/lp/app/choiceedit/choiceedit.js"></script>
218+ <script type="text/javascript"
219+ src="../../../../../build/js/lp/app/privacy.js"></script>
220+ <script type="text/javascript"
221+ src="../../../../../build/js/lp/app/overlay/overlay.js"></script>
222+ <script type="text/javascript"
223+ src="../../../../../build/js/lp/app/expander.js"></script>
224+ <script type="text/javascript"
225+ src="../../../../../build/js/lp/app/errors.js"></script>
226+ <script type="text/javascript"
227+ src="../../../../../build/js/lp/bugs/bug_subscription_portlet.js"></script>
228+ <script type="text/javascript"
229+ src="../../../../../build/js/lp/bugs/bugtask_index.js"></script>
230+
231+ <!-- The module under test. -->
232+ <script type="text/javascript" src="../information_type_choice.js"></script>
233+
234+ <!-- Placeholder for any css asset for this module. -->
235+ <!-- <link rel="stylesheet" href="../assets/bugs.information_type_choice-core.css" /> -->
236+
237+ <!-- The test suite -->
238+ <script type="text/javascript" src="test_information_type_choice.js"></script>
239+
240+ </head>
241+ <body class="yui3-skin-sam">
242+ <ul id="suites">
243+ <li>lp.bugs.information_type_choice.test</li>
244+ </ul>
245+ <div id="privacy">
246+ <div id="privacy-text">
247+ This report contains information that is <strong id="information-type">Public</strong><a class="sprite edit" id="privacy-link" href="#">edit</a>
248+ </div>
249+ </div>
250+ <div id="current_user_subscription">
251+ <span></span>
252+ <a href="#" text="" class="menu-link-mute_subscription unmute"></a>
253+ <a href="#" text="" class="menu-link-mute_subscription mute"></a>
254+ </div>
255+ </body>
256+</html>
257
258=== added file 'lib/lp/bugs/javascript/tests/test_information_type_choice.js'
259--- lib/lp/bugs/javascript/tests/test_information_type_choice.js 1970-01-01 00:00:00 +0000
260+++ lib/lp/bugs/javascript/tests/test_information_type_choice.js 2012-04-24 07:48:19 +0000
261@@ -0,0 +1,106 @@
262+/* Copyright (c) 2012, Canonical Ltd. All rights reserved. */
263+
264+YUI.add('lp.bugs.information_type_choice.test', function (Y) {
265+
266+ var tests = Y.namespace('lp.bugs.information_type_choice.test');
267+ var ns = Y.lp.bugs.information_type_choice;
268+ tests.suite = new Y.Test.Suite('lp.bugs.information_type_choice Tests');
269+
270+ tests.suite.add(new Y.Test.Case({
271+ name: 'lp.bugs.information_type_choice_tests',
272+
273+ setUp: function() {
274+ window.LP = {
275+ cache: {
276+ context: {
277+ web_link: ''
278+ },
279+ notifications_text: {
280+ muted: ''
281+ },
282+ bug: {
283+ information_type: 'Public',
284+ self_link: '/bug/1'
285+ },
286+ show_information_type_in_ui: true,
287+ private_types: ['Private'],
288+ information_types: [
289+ {'value': 'Public', 'description': 'Public',
290+ 'name': 'Public'},
291+ {'value': 'Private', 'description': 'Private',
292+ 'name': 'Private'}
293+ ]
294+ }
295+ };
296+ },
297+ tearDown: function () {},
298+
299+ test_library_exists: function () {
300+ Y.Assert.isObject(Y.lp.bugs.information_type_choice,
301+ "Cannot locate the lp.bugs.information_type_choice module");
302+ },
303+
304+ test_save_information_type: function() {
305+ var mockio = new Y.lp.testing.mockio.MockIo();
306+ var lp_client = new Y.lp.client.Launchpad({
307+ io_provider: mockio
308+ });
309+ ns.save_information_type('User Data', lp_client);
310+ Y.Assert.areEqual('/api/devel/bug/1', mockio.last_request.url);
311+ Y.Assert.areEqual(
312+ 'ws.op=transitionToInformationType&' +
313+ 'information_type=User%20Data',
314+ mockio.last_request.config.data);
315+ },
316+
317+ test_information_type_save_success_private: function() {
318+ var privacy_ns = Y.lp.app.privacy;
319+ var orig_function = privacy_ns.display_privacy_notification;
320+ var function_called = false;
321+ privacy_ns.display_privacy_notification = function() {
322+ function_called = true;
323+ };
324+ ns.information_type_save_success('Private');
325+ var body = Y.one('body');
326+ Y.Assert.isTrue(body.hasClass('private'));
327+ Y.Assert.isTrue(function_called);
328+ privacy_ns.display_privacy_notification = orig_function;
329+ },
330+
331+ test_information_type_save_success_public: function() {
332+ var privacy_ns = Y.lp.app.privacy;
333+ var orig_function = privacy_ns.hide_privacy_notification;
334+ var function_called = false;
335+ privacy_ns.hide_privacy_notification = function() {
336+ function_called = true;
337+ };
338+ ns.information_type_save_success('Public');
339+ var body = Y.one('body');
340+ Y.Assert.isTrue(body.hasClass('public'));
341+ Y.Assert.isTrue(function_called);
342+ privacy_ns.hide_privacy_notification = orig_function;
343+ },
344+
345+ test_perform_update_information_type: function() {
346+ var lp_client = new Y.lp.client.Launchpad();
347+ var privacy_link = Y.one('#privacy-link');
348+ var information_type = Y.one('#information-type');
349+ ns.setup_information_type_choice(privacy_link, lp_client);
350+ privacy_link.simulate('click');
351+ var private_choice = Y.one(
352+ '.yui3-ichoicelist-content a[href=#Private]');
353+ var orig_save_information_type = ns.save_information_type;
354+ var function_called = false;
355+ ns.save_information_type = function(value, lp_client) {
356+ Y.Assert.areEqual('User Data', value);
357+ function_called = true;
358+ };
359+ private_choice.simulate('click');
360+ Y.Assert.isTrue(function_called);
361+ ns.save_information_type = orig_save_information_type;
362+ }
363+ }));
364+
365+}, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate',
366+ 'lp.testing.mockio', 'lp.client', 'lp.bugs.information_type_choice',
367+ 'lp.app.privacy']});
368
369=== modified file 'lib/lp/bugs/templates/bug-portlet-privacy.pt'
370--- lib/lp/bugs/templates/bug-portlet-privacy.pt 2009-09-18 09:11:17 +0000
371+++ lib/lp/bugs/templates/bug-portlet-privacy.pt 2012-04-24 07:48:19 +0000
372@@ -8,22 +8,29 @@
373 "
374 tal:define="link context/menu:context/visibility"
375 >
376- <div tal:condition="not:context/private" id="privacy-text">
377- <a class="sprite edit" id="privacy-link"
378- tal:attributes="href link/path; title link/text"
379- tal:condition="link/enabled">This report is public</a>
380- <tal:unchangeable condition="not:link/enabled">
381- This report is public
382- </tal:unchangeable>
383- </div>
384- <div tal:condition="context/private" id="privacy-text">
385- <a class="sprite edit" id="privacy-link"
386- tal:attributes="href link/path; title link/text"
387- tal:condition="link/enabled">This report is <strong>private</strong></a>
388- <tal:unchangeable condition="not:link/enabled">
389- This report is private
390- </tal:unchangeable>
391- </div>
392- <div class="sprite security" id="security-message"
393- tal:condition="context/security_related">Security vulnerability</div>
394+ <div id="privacy-text">
395+ <tal:information_type tal:condition="view/show_information_type_in_ui">
396+ This report contains information that is <strong id="information-type" tal:content="view/information_type"></strong>&nbsp;<a class="sprite edit" id="privacy-link" tal:attributes="href link/path" tal:condition="link/enabled"></a>
397+ </tal:information_type>
398+ <tal:privacy tal:condition="not:view/show_information_type_in_ui">
399+ <div tal:condition="not:context/private" id="privacy-text">
400+ <a class="sprite edit" id="privacy-link"
401+ tal:attributes="href link/path; title link/text"
402+ tal:condition="link/enabled">This report is public</a>
403+ <tal:unchangeable condition="not:link/enabled">
404+ This report is public
405+ </tal:unchangeable>
406+ </div>
407+ <div tal:condition="context/private" id="privacy-text">
408+ <a class="sprite edit" id="privacy-link"
409+ tal:attributes="href link/path; title link/text"
410+ tal:condition="link/enabled">This report is <strong>private</strong></a>
411+ <tal:unchangeable condition="not:link/enabled">
412+ This report is private
413+ </tal:unchangeable>
414+ </div>
415+ <div class="sprite security" id="security-message"
416+ tal:condition="context/security_related">Security vulnerability</div>
417+ </tal:privacy>
418+ </div>
419 </div>