Merge lp:~abentley/launchpad/blueprint-info-type-ui into lp:launchpad
- blueprint-info-type-ui
- Merge into devel
| Status: | Merged |
|---|---|
| Approved by: | j.c.sackett on 2012-08-30 |
| Approved revision: | no longer in the source branch. |
| Merged at revision: | 15911 |
| Proposed branch: | lp:~abentley/launchpad/blueprint-info-type-ui |
| Merge into: | lp:launchpad |
| Diff against target: |
976 lines (+358/-240) 16 files modified
lib/lp/app/javascript/information_type.js (+191/-1) lib/lp/app/javascript/tests/test_information_type.html (+4/-6) lib/lp/app/javascript/tests/test_information_type.js (+21/-17) lib/lp/blueprints/browser/configure.zcml (+3/-0) lib/lp/blueprints/browser/specification.py (+22/-3) lib/lp/blueprints/browser/tests/test_specification.py (+44/-2) lib/lp/blueprints/interfaces/specification.py (+6/-0) lib/lp/blueprints/model/specification.py (+8/-2) lib/lp/blueprints/templates/blueprint-portlet-privacy.pt (+16/-0) lib/lp/blueprints/templates/specification-index.pt (+12/-3) lib/lp/blueprints/tests/test_specification.py (+9/-7) lib/lp/bugs/javascript/bugtask_index.js (+6/-3) lib/lp/bugs/javascript/information_type_choice.js (+0/-190) lib/lp/services/features/flags.py (+6/-0) lib/lp/testing/__init__.py (+6/-4) lib/lp/testing/factory.py (+4/-2) |
| To merge this branch: | bzr merge lp:~abentley/launchpad/blueprint-info-type-ui |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| j.c.sackett (community) | 2012-08-30 | Approve on 2012-08-30 | |
|
Review via email:
|
|||
Commit Message
Initial UI for Specification.
Description of the Change
= Summary =
Partial implementation of Specification.
== Pre-implementation notes ==
None
== LOC Rationale ==
Part of Private Projects
== Implementation details ==
Generalize the implementation of the Bugs information_type UI.
== Tests ==
xvfb-run bin/test --layer=
== Demo and Q/A ==
Change the the type of a bug to private. It should change, and you should be subscribed to the bug. Reload to ensure that the change stuck.
Go to a blueprint. You should see no information_type UI.
Set the 'blueprints.
Reload the blueprint. You should see the information_type chooser.
Attempt to change the information type. It should inform you you do not have the correct permission.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
| j.c.sackett (jcsackett) wrote : | # |
Actually, I had a concern that this wasn't getting all the call sites for the bugs.informatio
Preview Diff
| 1 | === modified file 'lib/lp/app/javascript/information_type.js' |
| 2 | --- lib/lp/app/javascript/information_type.js 2012-08-28 23:53:32 +0000 |
| 3 | +++ lib/lp/app/javascript/information_type.js 2012-09-04 14:00:36 +0000 |
| 4 | @@ -8,6 +8,87 @@ |
| 5 | |
| 6 | var namespace = Y.namespace('lp.app.information_type'); |
| 7 | |
| 8 | +// For testing. |
| 9 | +var skip_animation = false; |
| 10 | + |
| 11 | +/** |
| 12 | + * Save the new information type. If validate_change is true, then a check |
| 13 | + * will be done to ensure the bug will not become invisible. If the bug will |
| 14 | + * become invisible, a confirmation popup is used to confirm the user's |
| 15 | + * intention. Then this method is called again with validate_change set to |
| 16 | + * false to allow the change to proceed. |
| 17 | + * |
| 18 | + * @param widget |
| 19 | + * @param initial_value |
| 20 | + * @param value |
| 21 | + * @param lp_client |
| 22 | + * @param validate_change |
| 23 | + */ |
| 24 | +namespace.save_information_type = function(widget, initial_value, value, |
| 25 | + lp_client, context, |
| 26 | + subscribers_list, validate_change) { |
| 27 | + var error_handler = new Y.lp.client.FormErrorHandler(); |
| 28 | + error_handler.showError = function(error_msg) { |
| 29 | + Y.lp.app.errors.display_error( |
| 30 | + Y.one('#information-type'), error_msg); |
| 31 | + }; |
| 32 | + error_handler.handleError = function(ioId, response) { |
| 33 | + if( response.status === 400 |
| 34 | + && response.statusText === 'Bug Visibility') { |
| 35 | + namespace._confirm_information_type_change( |
| 36 | + widget, initial_value, lp_client, context, |
| 37 | + subscribers_list); |
| 38 | + return true; |
| 39 | + } |
| 40 | + var orig_value = namespace.information_type_value_from_key( |
| 41 | + context.information_type, 'name', 'value'); |
| 42 | + widget.set('value', orig_value); |
| 43 | + widget._showFailed(); |
| 44 | + namespace.update_privacy_portlet(orig_value); |
| 45 | + return false; |
| 46 | + }; |
| 47 | + var submit_url = document.URL + "/+secrecy"; |
| 48 | + var qs = Y.lp.client.append_qs('', 'field.actions.change', 'Change'); |
| 49 | + qs = Y.lp.client.append_qs(qs, 'field.information_type', value); |
| 50 | + qs = Y.lp.client.append_qs( |
| 51 | + qs, 'field.validate_change', validate_change?'on':'off'); |
| 52 | + var config = { |
| 53 | + method: "POST", |
| 54 | + headers: {'Accept': 'application/xhtml;application/json'}, |
| 55 | + data: qs, |
| 56 | + on: { |
| 57 | + start: function () { |
| 58 | + widget._uiSetWaiting(); |
| 59 | + if (Y.Lang.isValue(subscribers_list)){ |
| 60 | + subscribers_list.subscribers_list.startActivity( |
| 61 | + 'Updating subscribers...'); |
| 62 | + } |
| 63 | + }, |
| 64 | + end: function () { |
| 65 | + widget._uiClearWaiting(); |
| 66 | + if (Y.Lang.isValue(subscribers_list)){ |
| 67 | + subscribers_list.subscribers_list.stopActivity(); |
| 68 | + } |
| 69 | + }, |
| 70 | + success: function (id, response) { |
| 71 | + var result_data = null; |
| 72 | + if (response.responseText !== '' && |
| 73 | + response.getResponseHeader('Content-Type') === |
| 74 | + 'application/json') |
| 75 | + { |
| 76 | + result_data = Y.JSON.parse(response.responseText); |
| 77 | + } |
| 78 | + namespace.information_type_save_success( |
| 79 | + widget, context, value, subscribers_list, result_data); |
| 80 | + Y.lp.client.display_notifications( |
| 81 | + response.getResponseHeader('X-Lazr-Notifications')); |
| 82 | + }, |
| 83 | + failure: error_handler.getFailureHandler() |
| 84 | + } |
| 85 | + }; |
| 86 | + lp_client.io_provider.io(submit_url, config); |
| 87 | +}; |
| 88 | + |
| 89 | var get_information_type_banner_text = function(value) { |
| 90 | var text_template = "This page contains {info_type} information."; |
| 91 | var info_type = namespace.information_type_value_from_key( |
| 92 | @@ -15,6 +96,113 @@ |
| 93 | return Y.Lang.sub(text_template, {'info_type': info_type}); |
| 94 | }; |
| 95 | |
| 96 | +namespace.information_type_save_success = function(widget, context, value, |
| 97 | + subscribers_list, |
| 98 | + subscribers_data) { |
| 99 | + context.information_type = |
| 100 | + namespace.information_type_value_from_key( |
| 101 | + value, 'value', 'name'); |
| 102 | + namespace.update_privacy_banner(value); |
| 103 | + widget._showSucceeded(); |
| 104 | + if (Y.Lang.isObject(subscribers_data)) { |
| 105 | + var subscribers = subscribers_data.subscription_data; |
| 106 | + subscribers_list._loadSubscribersFromList(subscribers); |
| 107 | + var cache_data = subscribers_data.cache_data; |
| 108 | + var item; |
| 109 | + for (item in cache_data) { |
| 110 | + if (cache_data.hasOwnProperty(item)) { |
| 111 | + LP.cache[item] = cache_data[item]; |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + if (Y.Lang.isValue(subscribers_list)){ |
| 116 | + var ns = Y.lp.bugs.bugtask_index.portlets.subscription; |
| 117 | + ns.update_subscription_status(skip_animation); |
| 118 | + } |
| 119 | +}; |
| 120 | + |
| 121 | +/** |
| 122 | + * Possibly prompt the user to confirm the change of information type. |
| 123 | + * If the old value is public, and the new value is private, we want to |
| 124 | + * confirm that the user really wants to make the change. |
| 125 | + * |
| 126 | + * @param widget |
| 127 | + * @param initial_value |
| 128 | + * @param lp_client |
| 129 | + * @private |
| 130 | + */ |
| 131 | +namespace._confirm_information_type_change = function(widget, initial_value, |
| 132 | + lp_client, context, |
| 133 | + subscribers_list) { |
| 134 | + var value = widget.get('value'); |
| 135 | + var do_save = function() { |
| 136 | + namespace.update_privacy_portlet(value); |
| 137 | + namespace.save_information_type( |
| 138 | + widget, initial_value, value, lp_client, context, subscribers_list, |
| 139 | + false); |
| 140 | + }; |
| 141 | + // Reset the widget back to it's original value so the user doesn't see it |
| 142 | + // change while the confirmation dialog is showing. |
| 143 | + var new_value = widget.get('value'); |
| 144 | + widget.set('value', initial_value); |
| 145 | + namespace.update_privacy_portlet(initial_value); |
| 146 | + var confirm_text_template = [ |
| 147 | + '<p class="block-sprite large-warning">', |
| 148 | + ' You are about to mark this bug as ', |
| 149 | + ' <strong>{information_type}</strong>.<br>', |
| 150 | + ' The bug will become invisible because there is no-one with', |
| 151 | + ' permissions to see {information_type} bugs.', |
| 152 | + '</p><p>', |
| 153 | + ' <strong>Please confirm you really want to do this.</strong>', |
| 154 | + '</p>' |
| 155 | + ].join(''); |
| 156 | + var title = namespace.information_type_value_from_key( |
| 157 | + value, 'value', 'name'); |
| 158 | + var confirm_text = Y.Lang.sub(confirm_text_template, |
| 159 | + {information_type: title}); |
| 160 | + var co = new Y.lp.app.confirmationoverlay.ConfirmationOverlay({ |
| 161 | + submit_fn: function() { |
| 162 | + widget.set('value', new_value); |
| 163 | + namespace.update_privacy_portlet(new_value); |
| 164 | + do_save(); |
| 165 | + }, |
| 166 | + form_content: confirm_text, |
| 167 | + headerContent: '<h2>Confirm information type change</h2>', |
| 168 | + submit_text: 'Confirm' |
| 169 | + }); |
| 170 | + co.show(); |
| 171 | +}; |
| 172 | + |
| 173 | +namespace.setup_information_type_choice = function(privacy_link, lp_client, |
| 174 | + context, subscribers_list, |
| 175 | + skip_anim) { |
| 176 | + skip_animation = skip_anim; |
| 177 | + var initial_value = namespace.information_type_value_from_key( |
| 178 | + context.information_type, 'name', 'value'); |
| 179 | + var information_type_value = Y.one('#information-type'); |
| 180 | + var information_type_edit = new Y.ChoiceSource({ |
| 181 | + editicon: privacy_link, |
| 182 | + contentBox: Y.one('#privacy'), |
| 183 | + value_location: information_type_value, |
| 184 | + value: initial_value, |
| 185 | + title: "Change information type", |
| 186 | + items: LP.cache.information_type_data, |
| 187 | + backgroundColor: '#FFFF99', |
| 188 | + flashEnabled: false |
| 189 | + }); |
| 190 | + Y.lp.app.choice.hook_up_choicesource_spinner(information_type_edit); |
| 191 | + information_type_edit.render(); |
| 192 | + information_type_edit.on("save", function(e) { |
| 193 | + var value = information_type_edit.get('value'); |
| 194 | + namespace.update_privacy_portlet(value); |
| 195 | + namespace.save_information_type( |
| 196 | + information_type_edit, initial_value, value, lp_client, context, |
| 197 | + subscribers_list, true); |
| 198 | + }); |
| 199 | + privacy_link.addClass('js-action'); |
| 200 | + return information_type_edit; |
| 201 | +}; |
| 202 | + |
| 203 | /** |
| 204 | * Lookup the information_type property, keyed on the named value. |
| 205 | * |
| 206 | @@ -80,4 +268,6 @@ |
| 207 | } |
| 208 | }; |
| 209 | |
| 210 | -}, "0.1", {"requires": ["base", "oop", "node", "lp.app.banner.privacy"]}); |
| 211 | +}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base", |
| 212 | + "lazr.choiceedit", "lp.bugs.bugtask_index", |
| 213 | + "lp.app.banner.privacy", "lp.app.choice"]}); |
| 214 | |
| 215 | === renamed file 'lib/lp/bugs/javascript/tests/test_information_type_choice.html' => 'lib/lp/app/javascript/tests/test_information_type.html' |
| 216 | --- lib/lp/bugs/javascript/tests/test_information_type_choice.html 2012-08-29 14:02:57 +0000 |
| 217 | +++ lib/lp/app/javascript/tests/test_information_type.html 2012-09-04 14:00:36 +0000 |
| 218 | @@ -6,7 +6,7 @@ |
| 219 | |
| 220 | <html> |
| 221 | <head> |
| 222 | - <title>lp.bugs.information_type_choice Tests</title> |
| 223 | + <title>lp.app.information_type Tests</title> |
| 224 | |
| 225 | <!-- YUI and test setup --> |
| 226 | <script type="text/javascript" |
| 227 | @@ -30,8 +30,6 @@ |
| 228 | <script type="text/javascript" |
| 229 | src="../../../../../build/js/lp/app/choice.js"></script> |
| 230 | <script type="text/javascript" |
| 231 | - src="../../../../../build/js/lp/app/information_type.js"></script> |
| 232 | - <script type="text/javascript" |
| 233 | src="../../../../../build/js/lp/app/testing/mockio.js"></script> |
| 234 | <script type="text/javascript" |
| 235 | src="../../../../../build/js/lp/app/client.js"></script> |
| 236 | @@ -79,18 +77,18 @@ |
| 237 | src="../../../../../build/js/lp/bugs/bugtask_index.js"></script> |
| 238 | |
| 239 | <!-- The module under test. --> |
| 240 | - <script type="text/javascript" src="../information_type_choice.js"></script> |
| 241 | + <script type="text/javascript" src="../information_type.js"></script> |
| 242 | |
| 243 | <!-- Placeholder for any css asset for this module. --> |
| 244 | <!-- <link rel="stylesheet" href="../assets/bugs.information_type_choice-core.css" /> --> |
| 245 | |
| 246 | <!-- The test suite --> |
| 247 | - <script type="text/javascript" src="test_information_type_choice.js"></script> |
| 248 | + <script type="text/javascript" src="test_information_type.js"></script> |
| 249 | |
| 250 | </head> |
| 251 | <body class="yui3-skin-sam"> |
| 252 | <ul id="suites"> |
| 253 | - <li>lp.bugs.information_type_choice.test</li> |
| 254 | + <li>lp.app.information_type.test</li> |
| 255 | </ul> |
| 256 | <div id="fixture"></div> |
| 257 | <script type="text/x-template" id="portlet-template"> |
| 258 | |
| 259 | === renamed file 'lib/lp/bugs/javascript/tests/test_information_type_choice.js' => 'lib/lp/app/javascript/tests/test_information_type.js' |
| 260 | --- lib/lp/bugs/javascript/tests/test_information_type_choice.js 2012-08-28 01:52:34 +0000 |
| 261 | +++ lib/lp/app/javascript/tests/test_information_type.js 2012-09-04 14:00:36 +0000 |
| 262 | @@ -1,13 +1,13 @@ |
| 263 | /* Copyright (c) 2012, Canonical Ltd. All rights reserved. */ |
| 264 | |
| 265 | -YUI.add('lp.bugs.information_type_choice.test', function (Y) { |
| 266 | +YUI.add('lp.app.information_type.test', function (Y) { |
| 267 | |
| 268 | - var tests = Y.namespace('lp.bugs.information_type_choice.test'); |
| 269 | - var ns = Y.lp.bugs.information_type_choice; |
| 270 | - tests.suite = new Y.Test.Suite('lp.bugs.information_type_choice Tests'); |
| 271 | + var tests = Y.namespace('lp.app.information_type.test'); |
| 272 | + var ns = Y.lp.app.information_type; |
| 273 | + tests.suite = new Y.Test.Suite('lp.app.information_type Tests'); |
| 274 | |
| 275 | tests.suite.add(new Y.Test.Case({ |
| 276 | - name: 'lp.bugs.information_type_choice_tests', |
| 277 | + name: 'lp.app.information_type_tests', |
| 278 | |
| 279 | setUp: function() { |
| 280 | window.LP = { |
| 281 | @@ -64,7 +64,7 @@ |
| 282 | makeWidget: function() { |
| 283 | var privacy_link = Y.one('#privacy-link'); |
| 284 | this.widget = ns.setup_information_type_choice( |
| 285 | - privacy_link, this.lp_client, true); |
| 286 | + privacy_link, this.lp_client, LP.cache.bug, null, true); |
| 287 | }, |
| 288 | |
| 289 | _shim_privacy_banner: function () { |
| 290 | @@ -84,8 +84,8 @@ |
| 291 | }, |
| 292 | |
| 293 | test_library_exists: function () { |
| 294 | - Y.Assert.isObject(Y.lp.bugs.information_type_choice, |
| 295 | - "Cannot locate the lp.bugs.information_type_choice module"); |
| 296 | + Y.Assert.isObject(Y.lp.app.information_type, |
| 297 | + "Cannot locate the lp.app.information_type module"); |
| 298 | }, |
| 299 | |
| 300 | // The save XHR call works as expected. |
| 301 | @@ -93,7 +93,7 @@ |
| 302 | this.makeWidget(); |
| 303 | var orig_save_success = ns.information_type_save_success; |
| 304 | var save_success_called = false; |
| 305 | - ns.information_type_save_success = function(widget, value, |
| 306 | + ns.information_type_save_success = function(widget, context, value, |
| 307 | subscribers_list, |
| 308 | subscribers_data) { |
| 309 | Y.Assert.areEqual('USERDATA', value); |
| 310 | @@ -104,7 +104,8 @@ |
| 311 | save_success_called = true; |
| 312 | }; |
| 313 | ns.save_information_type( |
| 314 | - this.widget, 'PUBLIC', 'USERDATA', this.lp_client, true); |
| 315 | + this.widget, 'PUBLIC', 'USERDATA', this.lp_client, |
| 316 | + LP.cache.bug, null, true); |
| 317 | this.mockio.success({ |
| 318 | responseText: '{"subscription_data": "subscribers",' + |
| 319 | '"cache_data": {"item": "value"}}', |
| 320 | @@ -133,7 +134,8 @@ |
| 321 | update_flag = true; |
| 322 | }); |
| 323 | |
| 324 | - ns.information_type_save_success(this.widget, 'PROPRIETARY'); |
| 325 | + ns.information_type_save_success(this.widget, LP.cache.bug, |
| 326 | + 'PROPRIETARY'); |
| 327 | var body = Y.one('body'); |
| 328 | Y.Assert.isTrue(body.hasClass('private')); |
| 329 | Y.Assert.isTrue(hide_flag); |
| 330 | @@ -188,7 +190,8 @@ |
| 331 | } |
| 332 | }; |
| 333 | ns.information_type_save_success( |
| 334 | - this.widget, 'PUBLIC', subscribers_list, subscribers_data); |
| 335 | + this.widget, LP.cache.bug, 'PUBLIC', subscribers_list, |
| 336 | + subscribers_data); |
| 337 | Y.Assert.isTrue(load_subscribers_called); |
| 338 | Y.Assert.areEqual('value1', window.LP.cache.item1); |
| 339 | Y.Assert.areEqual('value2', window.LP.cache.item2); |
| 340 | @@ -227,7 +230,7 @@ |
| 341 | var function_called = false; |
| 342 | ns.save_information_type = |
| 343 | function(widget, initial_value, value, lp_client, |
| 344 | - validate_change) { |
| 345 | + context, subscribers_list, validate_change) { |
| 346 | // We only care if the function is called with |
| 347 | // validate_change = false |
| 348 | Y.Assert.areEqual('PUBLIC', initial_value); |
| 349 | @@ -259,7 +262,7 @@ |
| 350 | var function_called = false; |
| 351 | ns.save_information_type = |
| 352 | function(widget, initial_value, value, lp_client, |
| 353 | - validate_change) { |
| 354 | + context, subscribers_list, validate_change) { |
| 355 | // We only care if the function is called with |
| 356 | // validate_change = false |
| 357 | function_called = !validate_change; |
| 358 | @@ -285,7 +288,8 @@ |
| 359 | this.makeWidget(); |
| 360 | this.widget.set('value', 'USERDATA'); |
| 361 | ns.save_information_type( |
| 362 | - this.widget, 'PUBLIC', 'USERDATA', this.lp_client); |
| 363 | + this.widget, 'PUBLIC', 'USERDATA', this.lp_client, |
| 364 | + LP.cache.bug); |
| 365 | this.mockio.last_request.respond({ |
| 366 | status: 500, |
| 367 | statusText: 'An error occurred' |
| 368 | @@ -304,5 +308,5 @@ |
| 369 | })); |
| 370 | |
| 371 | }, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate', |
| 372 | - 'lp.testing.mockio', 'lp.client', 'lp.app.informatin_type', |
| 373 | - 'lp.bugs.information_type_choice', 'lp.bugs.subscribers']}); |
| 374 | + 'lp.testing.mockio', 'lp.client', 'lp.app.information_type', |
| 375 | + 'lp.bugs.subscribers']}); |
| 376 | |
| 377 | === modified file 'lib/lp/blueprints/browser/configure.zcml' |
| 378 | --- lib/lp/blueprints/browser/configure.zcml 2012-08-01 19:02:49 +0000 |
| 379 | +++ lib/lp/blueprints/browser/configure.zcml 2012-09-04 14:00:36 +0000 |
| 380 | @@ -311,6 +311,9 @@ |
| 381 | <browser:page |
| 382 | name="+listing-detailed" |
| 383 | template="../templates/specification-listing-detailed.pt"/> |
| 384 | + <browser:page |
| 385 | + name="+portlet-privacy" |
| 386 | + template="../templates/blueprint-portlet-privacy.pt"/> |
| 387 | </browser:pages> |
| 388 | <browser:page |
| 389 | for="lp.blueprints.interfaces.specification.ISpecification" |
| 390 | |
| 391 | === modified file 'lib/lp/blueprints/browser/specification.py' |
| 392 | --- lib/lp/blueprints/browser/specification.py 2012-08-20 19:14:22 +0000 |
| 393 | +++ lib/lp/blueprints/browser/specification.py 2012-09-04 14:00:36 +0000 |
| 394 | @@ -78,6 +78,7 @@ |
| 395 | ) |
| 396 | |
| 397 | from lp import _ |
| 398 | +from lp.app.browser.informationtype import InformationTypePortletMixin |
| 399 | from lp.app.browser.launchpad import AppFrontPageSearchView |
| 400 | from lp.app.browser.launchpadform import ( |
| 401 | action, |
| 402 | @@ -111,6 +112,7 @@ |
| 403 | from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch |
| 404 | from lp.blueprints.interfaces.sprintspecification import ISprintSpecification |
| 405 | from lp.code.interfaces.branchnamespace import IBranchNamespaceSet |
| 406 | +from lp.registry.enums import PRIVATE_INFORMATION_TYPES |
| 407 | from lp.registry.interfaces.distribution import IDistribution |
| 408 | from lp.registry.interfaces.product import IProduct |
| 409 | from lp.services.config import config |
| 410 | @@ -423,7 +425,7 @@ |
| 411 | 'linkbug', 'unlinkbug', 'linkbranch', |
| 412 | 'adddependency', 'removedependency', |
| 413 | 'dependencytree', 'linksprint', 'supersede', |
| 414 | - 'retarget'] |
| 415 | + 'retarget', 'information_type'] |
| 416 | |
| 417 | @enabled_with_permission('launchpad.Edit') |
| 418 | def milestone(self): |
| 419 | @@ -528,8 +530,13 @@ |
| 420 | text = 'Link a related branch' |
| 421 | return Link('+linkbranch', text, icon='add') |
| 422 | |
| 423 | - |
| 424 | -class SpecificationSimpleView(LaunchpadView): |
| 425 | + def information_type(self): |
| 426 | + """Return the 'Set privacy/security' Link.""" |
| 427 | + text = 'Change privacy/security' |
| 428 | + return Link('#', text) |
| 429 | + |
| 430 | + |
| 431 | +class SpecificationSimpleView(InformationTypePortletMixin, LaunchpadView): |
| 432 | """Used to render portlets and listing items that need browser code.""" |
| 433 | |
| 434 | @cachedproperty |
| 435 | @@ -545,6 +552,17 @@ |
| 436 | def bug_links(self): |
| 437 | return self.context.getLinkedBugTasks(self.user) |
| 438 | |
| 439 | + @cachedproperty |
| 440 | + def privacy_portlet_css(self): |
| 441 | + if self.private: |
| 442 | + return 'portlet private' |
| 443 | + else: |
| 444 | + return 'portlet public' |
| 445 | + |
| 446 | + @cachedproperty |
| 447 | + def private(self): |
| 448 | + return self.context.information_type in PRIVATE_INFORMATION_TYPES |
| 449 | + |
| 450 | |
| 451 | class SpecificationView(SpecificationSimpleView): |
| 452 | """Used to render the main view of a specification.""" |
| 453 | @@ -562,6 +580,7 @@ |
| 454 | return self.context.summary |
| 455 | |
| 456 | def initialize(self): |
| 457 | + super(SpecificationView, self).initialize() |
| 458 | # The review that the user requested on this spec, if any. |
| 459 | self.notices = [] |
| 460 | |
| 461 | |
| 462 | === modified file 'lib/lp/blueprints/browser/tests/test_specification.py' |
| 463 | --- lib/lp/blueprints/browser/tests/test_specification.py 2012-01-01 02:58:52 +0000 |
| 464 | +++ lib/lp/blueprints/browser/tests/test_specification.py 2012-09-04 14:00:36 +0000 |
| 465 | @@ -1,4 +1,4 @@ |
| 466 | -# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
| 467 | +# Copyright 2009-2012 Canonical Ltd. This software is licensed under the |
| 468 | # GNU Affero General Public License version 3 (see the file LICENSE). |
| 469 | |
| 470 | __metaclass__ = type |
| 471 | @@ -8,10 +8,14 @@ |
| 472 | |
| 473 | from BeautifulSoup import BeautifulSoup |
| 474 | import pytz |
| 475 | -from testtools.matchers import Equals |
| 476 | +from testtools.matchers import ( |
| 477 | + Equals, |
| 478 | + Not, |
| 479 | + ) |
| 480 | from zope.component import getUtility |
| 481 | from zope.publisher.interfaces import NotFound |
| 482 | from zope.security.proxy import removeSecurityProxy |
| 483 | +import soupmatchers |
| 484 | |
| 485 | from lp.app.browser.tales import format_link |
| 486 | from lp.blueprints.browser import specification |
| 487 | @@ -20,7 +24,9 @@ |
| 488 | ISpecification, |
| 489 | ISpecificationSet, |
| 490 | ) |
| 491 | +from lp.registry.enums import InformationType |
| 492 | from lp.registry.interfaces.person import PersonVisibility |
| 493 | +from lp.services.features.testing import FeatureFixture |
| 494 | from lp.services.webapp.interfaces import BrowserNotificationLevel |
| 495 | from lp.services.webapp.publisher import canonical_url |
| 496 | from lp.testing import ( |
| 497 | @@ -165,6 +171,42 @@ |
| 498 | "... Registered by Some Person ... ago ...")) |
| 499 | |
| 500 | |
| 501 | +class TestSpecificationInformationType(BrowserTestCase): |
| 502 | + |
| 503 | + layer = DatabaseFunctionalLayer |
| 504 | + |
| 505 | + portlet_tag = soupmatchers.Tag('info-type-portlet', True, |
| 506 | + attrs=dict(id='information-type-summary')) |
| 507 | + |
| 508 | + def setUp(self): |
| 509 | + super(TestSpecificationInformationType, self).setUp() |
| 510 | + self.useFixture(FeatureFixture({'blueprints.information_type.enabled': |
| 511 | + 'true'})) |
| 512 | + |
| 513 | + def assertBrowserMatches(self, matcher): |
| 514 | + browser = self.getViewBrowser(self.factory.makeSpecification()) |
| 515 | + self.assertThat(browser.contents, matcher) |
| 516 | + |
| 517 | + def test_has_privacy_portlet(self): |
| 518 | + self.assertBrowserMatches(soupmatchers.HTMLContains(self.portlet_tag)) |
| 519 | + |
| 520 | + def test_privacy_portlet_requires_flag(self): |
| 521 | + self.useFixture(FeatureFixture({'blueprints.information_type.enabled': |
| 522 | + ''})) |
| 523 | + self.assertBrowserMatches( |
| 524 | + Not(soupmatchers.HTMLContains(self.portlet_tag))) |
| 525 | + |
| 526 | + def test_has_privacy_banner(self): |
| 527 | + owner = self.factory.makePerson() |
| 528 | + spec = self.factory.makeSpecification( |
| 529 | + information_type=InformationType.PROPRIETARY, owner=owner) |
| 530 | + browser = self.getViewBrowser(spec, user=owner) |
| 531 | + privacy_banner = soupmatchers.Tag('privacy-banner', True, |
| 532 | + attrs={'class': 'banner-text'}) |
| 533 | + self.assertThat(browser.contents, |
| 534 | + soupmatchers.HTMLContains(privacy_banner)) |
| 535 | + |
| 536 | + |
| 537 | class TestSpecificationViewPrivateArtifacts(BrowserTestCase): |
| 538 | """ Tests that specifications with private team artifacts can be viewed. |
| 539 | |
| 540 | |
| 541 | === modified file 'lib/lp/blueprints/interfaces/specification.py' |
| 542 | --- lib/lp/blueprints/interfaces/specification.py 2012-08-22 15:41:05 +0000 |
| 543 | +++ lib/lp/blueprints/interfaces/specification.py 2012-09-04 14:00:36 +0000 |
| 544 | @@ -565,6 +565,12 @@ |
| 545 | :param user: The user doing the search. |
| 546 | """ |
| 547 | |
| 548 | + def getAllowedInformationTypes(who): |
| 549 | + """Get a list of acceptable `InformationType`s for this spec. |
| 550 | + |
| 551 | + The intersection of the affected pillars' allowed types is permitted. |
| 552 | + """ |
| 553 | + |
| 554 | |
| 555 | class ISpecificationEditRestricted(Interface): |
| 556 | """Specification's attributes and methods protected with launchpad.Edit. |
| 557 | |
| 558 | === modified file 'lib/lp/blueprints/model/specification.py' |
| 559 | --- lib/lp/blueprints/model/specification.py 2012-08-22 15:42:58 +0000 |
| 560 | +++ lib/lp/blueprints/model/specification.py 2012-09-04 14:00:36 +0000 |
| 561 | @@ -815,6 +815,9 @@ |
| 562 | return '<Specification %s %r for %r>' % ( |
| 563 | self.id, self.name, self.target.name) |
| 564 | |
| 565 | + def getAllowedInformationTypes(self, who): |
| 566 | + return set(InformationType.items) |
| 567 | + |
| 568 | @property |
| 569 | def private(self): |
| 570 | return self.information_type in PRIVATE_INFORMATION_TYPES |
| 571 | @@ -1080,10 +1083,12 @@ |
| 572 | def new(self, name, title, specurl, summary, definition_status, |
| 573 | owner, approver=None, product=None, distribution=None, assignee=None, |
| 574 | drafter=None, whiteboard=None, workitems_text=None, |
| 575 | - priority=SpecificationPriority.UNDEFINED): |
| 576 | + priority=SpecificationPriority.UNDEFINED, information_type=None): |
| 577 | """See ISpecificationSet.""" |
| 578 | # Adapt the NewSpecificationDefinitionStatus item to a |
| 579 | # SpecificationDefinitionStatus item. |
| 580 | + if information_type is None: |
| 581 | + information_type = InformationType.PUBLIC |
| 582 | status_name = definition_status.name |
| 583 | status_names = NewSpecificationDefinitionStatus.items.mapping.keys() |
| 584 | if status_name not in status_names: |
| 585 | @@ -1095,7 +1100,8 @@ |
| 586 | summary=summary, priority=priority, |
| 587 | definition_status=definition_status, owner=owner, |
| 588 | approver=approver, product=product, distribution=distribution, |
| 589 | - assignee=assignee, drafter=drafter, whiteboard=whiteboard) |
| 590 | + assignee=assignee, drafter=drafter, whiteboard=whiteboard, |
| 591 | + information_type=information_type) |
| 592 | |
| 593 | def getDependencyDict(self, specifications): |
| 594 | """See `ISpecificationSet`.""" |
| 595 | |
| 596 | === added file 'lib/lp/blueprints/templates/blueprint-portlet-privacy.pt' |
| 597 | --- lib/lp/blueprints/templates/blueprint-portlet-privacy.pt 1970-01-01 00:00:00 +0000 |
| 598 | +++ lib/lp/blueprints/templates/blueprint-portlet-privacy.pt 2012-09-04 14:00:36 +0000 |
| 599 | @@ -0,0 +1,16 @@ |
| 600 | +<div |
| 601 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
| 602 | + xmlns:metal="http://xml.zope.org/namespaces/metal" |
| 603 | + xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
| 604 | + id="privacy" |
| 605 | + tal:attributes="class view/privacy_portlet_css" |
| 606 | + tal:define="link context/menu:context/information_type" |
| 607 | +> |
| 608 | + <span id="information-type-summary" |
| 609 | + tal:attributes="class view/information_type_css;">This blueprint |
| 610 | + contains <strong id="information-type" tal:content="view/information_type"></strong> information</span> <a class="sprite edit action-icon" |
| 611 | + id="privacy-link" tal:attributes="href link/path" |
| 612 | + tal:condition="link/enabled" style="display: none" >Edit</a> |
| 613 | + |
| 614 | + <div id="information-type-description" style="padding-top: 5px" tal:content="view/information_type_description"></div> |
| 615 | +</div> |
| 616 | |
| 617 | === modified file 'lib/lp/blueprints/templates/specification-index.pt' |
| 618 | --- lib/lp/blueprints/templates/specification-index.pt 2012-08-23 13:55:00 +0000 |
| 619 | +++ lib/lp/blueprints/templates/specification-index.pt 2012-09-04 14:00:36 +0000 |
| 620 | @@ -310,7 +310,15 @@ |
| 621 | </div> |
| 622 | |
| 623 | <script type="text/javascript"> |
| 624 | - LPJS.use('lp.anim', 'lp.deprecated.ui', 'node', 'widget', function(Y) { |
| 625 | + LPJS.use('lp.anim', 'lp.client', 'lp.deprecated.ui', |
| 626 | + 'lp.app.information_type', 'node', 'widget', function(Y) { |
| 627 | + Y.on('domready', function(){ |
| 628 | + var privacy_link = Y.one('#privacy-link'); |
| 629 | + Y.lp.app.information_type.setup_information_type_choice( |
| 630 | + privacy_link, new Y.lp.client.Launchpad(), LP.cache.context, |
| 631 | + null); |
| 632 | + privacy_link.setStyle('display', 'inline'); |
| 633 | + }); |
| 634 | |
| 635 | Y.on('lp:context:implementation_status:changed', function(e) { |
| 636 | var icon = Y.one('#informational-icon'); |
| 637 | @@ -345,7 +353,8 @@ |
| 638 | }); |
| 639 | Y.on('lp:context:title:changed', function(e) { |
| 640 | // change the window title and breadcrumb. |
| 641 | - Y.lp.deprecated.ui.update_field('ol.breadcrumbs li:last-child', e.new_value); |
| 642 | + Y.lp.deprecated.ui.update_field('ol.breadcrumbs li:last-child', |
| 643 | + e.new_value); |
| 644 | var title = window.document.title; |
| 645 | title = e.new_value + title.substring(e.old_value.length); |
| 646 | window.document.title = title; |
| 647 | @@ -377,7 +386,7 @@ |
| 648 | |
| 649 | <tal:side metal:fill-slot="side"> |
| 650 | <tal:menu replace="structure context/@@+global-actions" /> |
| 651 | - |
| 652 | + <tal:privacy replace="structure context/@@+portlet-privacy" condition="features/blueprints.information_type.enabled" /> |
| 653 | <div tal:replace="structure context/@@+portlet-subscribers" /> |
| 654 | </tal:side> |
| 655 | </body> |
| 656 | |
| 657 | === modified file 'lib/lp/blueprints/tests/test_specification.py' |
| 658 | --- lib/lp/blueprints/tests/test_specification.py 2012-08-20 16:38:10 +0000 |
| 659 | +++ lib/lp/blueprints/tests/test_specification.py 2012-09-04 14:00:36 +0000 |
| 660 | @@ -159,12 +159,13 @@ |
| 661 | 'date_started', 'datecreated', 'declineBy', |
| 662 | 'definition_status', 'dependencies', 'direction_approved', |
| 663 | 'distribution', 'distroseries', 'drafter', 'drafterID', |
| 664 | - 'getBranchLink', 'getDelta', 'getLinkedBugTasks', |
| 665 | - 'getSprintSpecification', 'getSubscriptionByName', 'goal', |
| 666 | - 'goal_decider', 'goal_proposer', 'goalstatus', |
| 667 | - 'has_accepted_goal', 'implementation_status', 'informational', |
| 668 | - 'isSubscribed', 'is_blocked', 'is_complete', 'is_incomplete', |
| 669 | - 'is_started', 'lifecycle_status', 'linkBranch', 'linkSprint', |
| 670 | + 'getBranchLink', 'getDelta', 'getAllowedInformationTypes', |
| 671 | + 'getLinkedBugTasks', 'getSprintSpecification', |
| 672 | + 'getSubscriptionByName', 'goal', 'goal_decider', |
| 673 | + 'goal_proposer', 'goalstatus', 'has_accepted_goal', |
| 674 | + 'implementation_status', 'informational', 'isSubscribed', |
| 675 | + 'is_blocked', 'is_complete', 'is_incomplete', 'is_started', |
| 676 | + 'lifecycle_status', 'linkBranch', 'linkSprint', |
| 677 | 'linked_branches', 'man_days', 'milestone', 'name', |
| 678 | 'notificationRecipientAddresses', 'owner', 'priority', |
| 679 | 'product', 'productseries', 'proposeGoal', 'removeDependency', |
| 680 | @@ -172,7 +173,8 @@ |
| 681 | 'subscribers', 'subscription', 'subscriptions', 'summary', |
| 682 | 'superseded_by', 'target', 'title', 'unlinkBranch', |
| 683 | 'unlinkSprint', 'unsubscribe', 'updateLifecycleStatus', |
| 684 | - 'validateMove', 'whiteboard', 'work_items', 'workitems_text')), |
| 685 | + 'validateMove', 'whiteboard', 'work_items', |
| 686 | + 'workitems_text')), |
| 687 | 'launchpad.Edit': set(( |
| 688 | 'newWorkItem', 'retarget', 'setDefinitionStatus', |
| 689 | 'setImplementationStatus', 'setTarget', 'updateWorkItems')), |
| 690 | |
| 691 | === modified file 'lib/lp/bugs/javascript/bugtask_index.js' |
| 692 | --- lib/lp/bugs/javascript/bugtask_index.js 2012-08-30 02:52:33 +0000 |
| 693 | +++ lib/lp/bugs/javascript/bugtask_index.js 2012-09-04 14:00:36 +0000 |
| 694 | @@ -49,8 +49,11 @@ |
| 695 | |
| 696 | privacy_link = Y.one('#privacy-link'); |
| 697 | if (privacy_link) { |
| 698 | - Y.lp.bugs.information_type_choice.setup_information_type_choice( |
| 699 | - privacy_link, lp_client); |
| 700 | + var sub_list_node = Y.one('#other-bug-subscribers'); |
| 701 | + var subscribers_list = sub_list_node.getData( |
| 702 | + 'subscribers_loader'); |
| 703 | + Y.lp.app.information_type.setup_information_type_choice( |
| 704 | + privacy_link, lp_client, LP.cache.bug, subscribers_list); |
| 705 | } |
| 706 | setup_add_attachment(); |
| 707 | setup_link_branch_picker(); |
| 708 | @@ -1126,7 +1129,7 @@ |
| 709 | "lazr.formoverlay", "lp.anim", "lazr.overlay", |
| 710 | "lazr.choiceedit", "lp.app.picker", |
| 711 | "lp.bugs.bugtask_index.portlets.subscription", |
| 712 | - "lp.bugs.information_type_choice", |
| 713 | + "lp.app.information_type", |
| 714 | "lp.app.widgets.expander", "lp.client", "escape", |
| 715 | "lp.client.plugins", "lp.app.errors", |
| 716 | "lp.app.banner.privacy", |
| 717 | |
| 718 | === removed file 'lib/lp/bugs/javascript/information_type_choice.js' |
| 719 | --- lib/lp/bugs/javascript/information_type_choice.js 2012-08-30 02:40:04 +0000 |
| 720 | +++ lib/lp/bugs/javascript/information_type_choice.js 1970-01-01 00:00:00 +0000 |
| 721 | @@ -1,190 +0,0 @@ |
| 722 | -/* Copyright 2012 Canonical Ltd. This software is licensed under the |
| 723 | - * GNU Affero General Public License version 3 (see the file LICENSE). |
| 724 | - * |
| 725 | - * Information Type choice widget for bug pages. |
| 726 | - */ |
| 727 | - |
| 728 | -YUI.add('lp.bugs.information_type_choice', function(Y) { |
| 729 | - |
| 730 | -var namespace = Y.namespace('lp.bugs.information_type_choice'); |
| 731 | -var information_type = Y.namespace('lp.app.information_type'); |
| 732 | - |
| 733 | -// For testing. |
| 734 | -var skip_animation = false; |
| 735 | - |
| 736 | -/** |
| 737 | - * Save the new information type. If validate_change is true, then a check |
| 738 | - * will be done to ensure the bug will not become invisible. If the bug will |
| 739 | - * become invisible, a confirmation popup is used to confirm the user's |
| 740 | - * intention. Then this method is called again with validate_change set to |
| 741 | - * false to allow the change to proceed. |
| 742 | - * |
| 743 | - * @param widget |
| 744 | - * @param initial_value |
| 745 | - * @param value |
| 746 | - * @param lp_client |
| 747 | - * @param validate_change |
| 748 | - */ |
| 749 | -namespace.save_information_type = function(widget, initial_value, value, |
| 750 | - lp_client, validate_change) { |
| 751 | - var error_handler = new Y.lp.client.FormErrorHandler(); |
| 752 | - error_handler.showError = function(error_msg) { |
| 753 | - Y.lp.app.errors.display_error( |
| 754 | - Y.one('#information-type'), error_msg); |
| 755 | - }; |
| 756 | - error_handler.handleError = function(ioId, response) { |
| 757 | - if( response.status === 400 |
| 758 | - && response.statusText === 'Bug Visibility') { |
| 759 | - namespace._confirm_information_type_change( |
| 760 | - widget, initial_value, lp_client); |
| 761 | - return true; |
| 762 | - } |
| 763 | - var orig_value = information_type.information_type_value_from_key( |
| 764 | - LP.cache.bug.information_type, 'name', 'value'); |
| 765 | - widget.set('value', orig_value); |
| 766 | - widget._showFailed(); |
| 767 | - information_type.update_privacy_portlet(orig_value); |
| 768 | - return false; |
| 769 | - }; |
| 770 | - var submit_url = document.URL + "/+secrecy"; |
| 771 | - var qs = Y.lp.client.append_qs('', 'field.actions.change', 'Change'); |
| 772 | - qs = Y.lp.client.append_qs(qs, 'field.information_type', value); |
| 773 | - qs = Y.lp.client.append_qs( |
| 774 | - qs, 'field.validate_change', validate_change?'on':'off'); |
| 775 | - var sub_list_node = Y.one('#other-bug-subscribers'); |
| 776 | - var subscribers_list = sub_list_node.getData('subscribers_loader'); |
| 777 | - var config = { |
| 778 | - method: "POST", |
| 779 | - headers: {'Accept': 'application/xhtml;application/json'}, |
| 780 | - data: qs, |
| 781 | - on: { |
| 782 | - start: function () { |
| 783 | - widget._uiSetWaiting(); |
| 784 | - subscribers_list.subscribers_list.startActivity( |
| 785 | - 'Updating subscribers...'); |
| 786 | - }, |
| 787 | - end: function () { |
| 788 | - widget._uiClearWaiting(); |
| 789 | - subscribers_list.subscribers_list.stopActivity(); |
| 790 | - }, |
| 791 | - success: function (id, response) { |
| 792 | - var result_data = null; |
| 793 | - if (response.responseText !== '') { |
| 794 | - result_data = Y.JSON.parse(response.responseText); |
| 795 | - } |
| 796 | - namespace.information_type_save_success( |
| 797 | - widget, value, subscribers_list, result_data); |
| 798 | - Y.lp.client.display_notifications( |
| 799 | - response.getResponseHeader('X-Lazr-Notifications')); |
| 800 | - }, |
| 801 | - failure: error_handler.getFailureHandler() |
| 802 | - } |
| 803 | - }; |
| 804 | - lp_client.io_provider.io(submit_url, config); |
| 805 | -}; |
| 806 | - |
| 807 | -namespace.information_type_save_success = function(widget, value, |
| 808 | - subscribers_list, |
| 809 | - subscribers_data) { |
| 810 | - LP.cache.bug.information_type = |
| 811 | - information_type.information_type_value_from_key( |
| 812 | - value, 'value', 'name'); |
| 813 | - information_type.update_privacy_banner(value); |
| 814 | - widget._showSucceeded(); |
| 815 | - if (Y.Lang.isObject(subscribers_data)) { |
| 816 | - var subscribers = subscribers_data.subscription_data; |
| 817 | - subscribers_list._loadSubscribersFromList(subscribers); |
| 818 | - var cache_data = subscribers_data.cache_data; |
| 819 | - var item; |
| 820 | - for (item in cache_data) { |
| 821 | - if (cache_data.hasOwnProperty(item)) { |
| 822 | - LP.cache[item] = cache_data[item]; |
| 823 | - } |
| 824 | - } |
| 825 | - } |
| 826 | - var ns = Y.lp.bugs.bugtask_index.portlets.subscription; |
| 827 | - ns.update_subscription_status(skip_animation); |
| 828 | -}; |
| 829 | - |
| 830 | -/** |
| 831 | - * Possibly prompt the user to confirm the change of information type. |
| 832 | - * If the old value is public, and the new value is private, we want to |
| 833 | - * confirm that the user really wants to make the change. |
| 834 | - * |
| 835 | - * @param widget |
| 836 | - * @param initial_value |
| 837 | - * @param lp_client |
| 838 | - * @private |
| 839 | - */ |
| 840 | -namespace._confirm_information_type_change = function(widget, initial_value, |
| 841 | - lp_client) { |
| 842 | - var value = widget.get('value'); |
| 843 | - var do_save = function() { |
| 844 | - information_type.update_privacy_portlet(value); |
| 845 | - namespace.save_information_type( |
| 846 | - widget, initial_value, value, lp_client, false); |
| 847 | - }; |
| 848 | - // Reset the widget back to it's original value so the user doesn't see it |
| 849 | - // change while the confirmation dialog is showing. |
| 850 | - var new_value = widget.get('value'); |
| 851 | - widget.set('value', initial_value); |
| 852 | - information_type.update_privacy_portlet(initial_value); |
| 853 | - var confirm_text_template = [ |
| 854 | - '<p class="block-sprite large-warning">', |
| 855 | - ' You are about to mark this bug as ', |
| 856 | - ' <strong>{information_type}</strong>.<br>', |
| 857 | - ' The bug will become invisible because there is no-one with', |
| 858 | - ' permissions to see {information_type} bugs.', |
| 859 | - '</p><p>', |
| 860 | - ' <strong>Please confirm you really want to do this.</strong>', |
| 861 | - '</p>' |
| 862 | - ].join(''); |
| 863 | - var title = information_type.information_type_value_from_key( |
| 864 | - value, 'value', 'name'); |
| 865 | - var confirm_text = Y.Lang.sub(confirm_text_template, |
| 866 | - {information_type: title}); |
| 867 | - var co = new Y.lp.app.confirmationoverlay.ConfirmationOverlay({ |
| 868 | - submit_fn: function() { |
| 869 | - widget.set('value', new_value); |
| 870 | - information_type.update_privacy_portlet(new_value); |
| 871 | - do_save(); |
| 872 | - }, |
| 873 | - form_content: confirm_text, |
| 874 | - headerContent: '<h2>Confirm information type change</h2>', |
| 875 | - submit_text: 'Confirm' |
| 876 | - }); |
| 877 | - co.show(); |
| 878 | -}; |
| 879 | - |
| 880 | -namespace.setup_information_type_choice = function(privacy_link, lp_client, |
| 881 | - skip_anim) { |
| 882 | - skip_animation = skip_anim; |
| 883 | - var initial_value = information_type.information_type_value_from_key( |
| 884 | - LP.cache.bug.information_type, 'name', 'value'); |
| 885 | - var information_type_value = Y.one('#information-type'); |
| 886 | - var information_type_edit = new Y.ChoiceSource({ |
| 887 | - editicon: privacy_link, |
| 888 | - contentBox: Y.one('#privacy'), |
| 889 | - value_location: information_type_value, |
| 890 | - value: initial_value, |
| 891 | - title: "Change information type", |
| 892 | - items: LP.cache.information_type_data, |
| 893 | - backgroundColor: '#FFFF99', |
| 894 | - flashEnabled: false |
| 895 | - }); |
| 896 | - Y.lp.app.choice.hook_up_choicesource_spinner(information_type_edit); |
| 897 | - information_type_edit.render(); |
| 898 | - information_type_edit.on("save", function(e) { |
| 899 | - var value = information_type_edit.get('value'); |
| 900 | - information_type.update_privacy_portlet(value); |
| 901 | - namespace.save_information_type( |
| 902 | - information_type_edit, initial_value, value, lp_client, true); |
| 903 | - |
| 904 | - }); |
| 905 | - privacy_link.addClass('js-action'); |
| 906 | - return information_type_edit; |
| 907 | -}; |
| 908 | -}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base", |
| 909 | - "lazr.choiceedit", "lp.bugs.bugtask_index", |
| 910 | - "lp.app.banner.privacy", "lp.app.choice", |
| 911 | - "lp.app.information_type"]}); |
| 912 | |
| 913 | === modified file 'lib/lp/services/features/flags.py' |
| 914 | --- lib/lp/services/features/flags.py 2012-08-30 11:52:28 +0000 |
| 915 | +++ lib/lp/services/features/flags.py 2012-09-04 14:00:36 +0000 |
| 916 | @@ -57,6 +57,12 @@ |
| 917 | '', |
| 918 | '', |
| 919 | ''), |
| 920 | + ('blueprints.information_type.enabled', |
| 921 | + 'boolean', |
| 922 | + 'Enable UI for information_type on Blueprints.', |
| 923 | + 'Disable UI', |
| 924 | + 'Blueprint information_type UI', |
| 925 | + 'https://dev.launchpad.net/LEP/PrivateProjects'), |
| 926 | ('bugs.affected_count_includes_dupes.disabled', |
| 927 | 'boolean', |
| 928 | ("Disable adding up affected users across all duplicate bugs."), |
| 929 | |
| 930 | === modified file 'lib/lp/testing/__init__.py' |
| 931 | --- lib/lp/testing/__init__.py 2012-08-17 09:31:57 +0000 |
| 932 | +++ lib/lp/testing/__init__.py 2012-09-04 14:00:36 +0000 |
| 933 | @@ -850,13 +850,15 @@ |
| 934 | |
| 935 | def getViewBrowser(self, context, view_name=None, no_login=False, |
| 936 | rootsite=None, user=None): |
| 937 | - if user is None: |
| 938 | + if no_login: |
| 939 | + user = ANONYMOUS |
| 940 | + elif user is None: |
| 941 | user = self.user |
| 942 | # Make sure that there is a user interaction in order to generate the |
| 943 | # canonical url for the context object. |
| 944 | - login(ANONYMOUS) |
| 945 | - url = canonical_url(context, view_name=view_name, rootsite=rootsite) |
| 946 | - logout() |
| 947 | + with person_logged_in(user): |
| 948 | + url = canonical_url(context, view_name=view_name, |
| 949 | + rootsite=rootsite) |
| 950 | if no_login: |
| 951 | from lp.testing.pages import setupBrowser |
| 952 | browser = setupBrowser() |
| 953 | |
| 954 | === modified file 'lib/lp/testing/factory.py' |
| 955 | --- lib/lp/testing/factory.py 2012-08-30 23:50:35 +0000 |
| 956 | +++ lib/lp/testing/factory.py 2012-09-04 14:00:36 +0000 |
| 957 | @@ -2069,7 +2069,8 @@ |
| 958 | status=NewSpecificationDefinitionStatus.NEW, |
| 959 | implementation_status=None, goal=None, specurl=None, |
| 960 | assignee=None, drafter=None, approver=None, |
| 961 | - priority=None, whiteboard=None, milestone=None): |
| 962 | + priority=None, whiteboard=None, milestone=None, |
| 963 | + information_type=None): |
| 964 | """Create and return a new, arbitrary Blueprint. |
| 965 | |
| 966 | :param product: The product to make the blueprint on. If one is |
| 967 | @@ -2106,7 +2107,8 @@ |
| 968 | approver=approver, |
| 969 | product=product, |
| 970 | distribution=distribution, |
| 971 | - priority=priority) |
| 972 | + priority=priority, |
| 973 | + information_type=information_type) |
| 974 | naked_spec = removeSecurityProxy(spec) |
| 975 | if status.name not in status_names: |
| 976 | # Set the closed status after the status has a sane initial state. |

This looks good to me. I would add two points to your QA:
1) You'll want to test transition back from private to non-private as well
2) You'll want to check the behavior on +filebug when you declare the bug private/security.