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 |
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) | Approve | ||
Review via email: mp+122140@code.launchpad.net |
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.