launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #11479
[Merge] lp:~abentley/launchpad/blueprint-info-type-ui into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/blueprint-info-type-ui into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~abentley/launchpad/blueprint-info-type-ui/+merge/122140
= Summary =
Partial implementation of Specification.information_type UI
== 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=YUITestLayer
== 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.information_type.enabled' feature flag to "on".
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/blueprints/templates/specification-index.pt
lib/lp/app/javascript/information_type.js
lib/lp/blueprints/templates/blueprint-portlet-privacy.pt
lib/lp/testing/factory.py
lib/lp/app/javascript/tests/test_information_type.js
lib/lp/blueprints/model/specification.py
lib/lp/bugs/javascript/bugtask_index.js
lib/lp/services/features/flags.py
lib/lp/blueprints/browser/configure.zcml
lib/lp/blueprints/browser/specification.py
lib/lp/blueprints/browser/tests/test_specification.py
lib/lp/app/javascript/tests/test_information_type.html
lib/lp/blueprints/interfaces/specification.py
--
https://code.launchpad.net/~abentley/launchpad/blueprint-info-type-ui/+merge/122140
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/blueprint-info-type-ui into lp:launchpad.
=== modified file 'lib/lp/app/javascript/information_type.js'
--- lib/lp/app/javascript/information_type.js 2012-08-28 23:53:32 +0000
+++ lib/lp/app/javascript/information_type.js 2012-08-30 20:24:20 +0000
@@ -8,6 +8,87 @@
var namespace = Y.namespace('lp.app.information_type');
+// For testing.
+var skip_animation = false;
+
+/**
+ * Save the new information type. If validate_change is true, then a check
+ * will be done to ensure the bug will not become invisible. If the bug will
+ * become invisible, a confirmation popup is used to confirm the user's
+ * intention. Then this method is called again with validate_change set to
+ * false to allow the change to proceed.
+ *
+ * @param widget
+ * @param initial_value
+ * @param value
+ * @param lp_client
+ * @param validate_change
+ */
+namespace.save_information_type = function(widget, initial_value, value,
+ lp_client, context,
+ subscribers_list, validate_change) {
+ var error_handler = new Y.lp.client.FormErrorHandler();
+ error_handler.showError = function(error_msg) {
+ Y.lp.app.errors.display_error(
+ Y.one('#information-type'), error_msg);
+ };
+ error_handler.handleError = function(ioId, response) {
+ if( response.status === 400
+ && response.statusText === 'Bug Visibility') {
+ namespace._confirm_information_type_change(
+ widget, initial_value, lp_client, context,
+ subscribers_list);
+ return true;
+ }
+ var orig_value = namespace.information_type_value_from_key(
+ context.information_type, 'name', 'value');
+ widget.set('value', orig_value);
+ widget._showFailed();
+ namespace.update_privacy_portlet(orig_value);
+ return false;
+ };
+ var submit_url = document.URL + "/+secrecy";
+ var qs = Y.lp.client.append_qs('', 'field.actions.change', 'Change');
+ qs = Y.lp.client.append_qs(qs, 'field.information_type', value);
+ qs = Y.lp.client.append_qs(
+ qs, 'field.validate_change', validate_change?'on':'off');
+ var config = {
+ method: "POST",
+ headers: {'Accept': 'application/xhtml;application/json'},
+ data: qs,
+ on: {
+ start: function () {
+ widget._uiSetWaiting();
+ if (Y.Lang.isValue(subscribers_list)){
+ subscribers_list.subscribers_list.startActivity(
+ 'Updating subscribers...');
+ }
+ },
+ end: function () {
+ widget._uiClearWaiting();
+ if (Y.Lang.isValue(subscribers_list)){
+ subscribers_list.subscribers_list.stopActivity();
+ }
+ },
+ success: function (id, response) {
+ var result_data = null;
+ if (response.responseText !== '' &&
+ response.getResponseHeader('Content-Type') ===
+ 'application/json')
+ {
+ result_data = Y.JSON.parse(response.responseText);
+ }
+ namespace.information_type_save_success(
+ widget, context, value, subscribers_list, result_data);
+ Y.lp.client.display_notifications(
+ response.getResponseHeader('X-Lazr-Notifications'));
+ },
+ failure: error_handler.getFailureHandler()
+ }
+ };
+ lp_client.io_provider.io(submit_url, config);
+};
+
var get_information_type_banner_text = function(value) {
var text_template = "This page contains {info_type} information.";
var info_type = namespace.information_type_value_from_key(
@@ -15,6 +96,112 @@
return Y.Lang.sub(text_template, {'info_type': info_type});
};
+namespace.information_type_save_success = function(widget, context, value,
+ subscribers_list,
+ subscribers_data) {
+ context.information_type =
+ namespace.information_type_value_from_key(
+ value, 'value', 'name');
+ namespace.update_privacy_banner(value);
+ widget._showSucceeded();
+ if (Y.Lang.isObject(subscribers_data)) {
+ var subscribers = subscribers_data.subscription_data;
+ subscribers_list._loadSubscribersFromList(subscribers);
+ var cache_data = subscribers_data.cache_data;
+ var item;
+ for (item in cache_data) {
+ if (cache_data.hasOwnProperty(item)) {
+ LP.cache[item] = cache_data[item];
+ }
+ }
+ }
+ if (Y.Lang.isValue(subscribers_list)){
+ var ns = Y.lp.bugs.bugtask_index.portlets.subscription;
+ ns.update_subscription_status(skip_animation);
+ }
+};
+
+/**
+ * Possibly prompt the user to confirm the change of information type.
+ * If the old value is public, and the new value is private, we want to
+ * confirm that the user really wants to make the change.
+ *
+ * @param widget
+ * @param initial_value
+ * @param lp_client
+ * @private
+ */
+namespace._confirm_information_type_change = function(widget, initial_value,
+ lp_client, context,
+ subscribers_list) {
+ var value = widget.get('value');
+ var do_save = function() {
+ namespace.update_privacy_portlet(value);
+ namespace.save_information_type(
+ widget, initial_value, value, lp_client, context, subscribers_list,
+ false);
+ };
+ // Reset the widget back to it's original value so the user doesn't see it
+ // change while the confirmation dialog is showing.
+ var new_value = widget.get('value');
+ widget.set('value', initial_value);
+ namespace.update_privacy_portlet(initial_value);
+ var confirm_text_template = [
+ '<p class="block-sprite large-warning">',
+ ' You are about to mark this bug as ',
+ ' <strong>{information_type}</strong>.<br>',
+ ' The bug will become invisible because there is no-one with',
+ ' permissions to see {information_type} bugs.',
+ '</p><p>',
+ ' <strong>Please confirm you really want to do this.</strong>',
+ '</p>'
+ ].join('');
+ var title = namespace.information_type_value_from_key(
+ value, 'value', 'name');
+ var confirm_text = Y.Lang.sub(confirm_text_template,
+ {information_type: title});
+ var co = new Y.lp.app.confirmationoverlay.ConfirmationOverlay({
+ submit_fn: function() {
+ widget.set('value', new_value);
+ namespace.update_privacy_portlet(new_value);
+ do_save();
+ },
+ form_content: confirm_text,
+ headerContent: '<h2>Confirm information type change</h2>'
+ });
+ co.show();
+};
+
+namespace.setup_information_type_choice = function(privacy_link, lp_client,
+ context, subscribers_list,
+ skip_anim) {
+ skip_animation = skip_anim;
+ var initial_value = namespace.information_type_value_from_key(
+ context.information_type, 'name', 'value');
+ var information_type_value = Y.one('#information-type');
+ var information_type_edit = new Y.ChoiceSource({
+ editicon: privacy_link,
+ contentBox: Y.one('#privacy'),
+ value_location: information_type_value,
+ value: initial_value,
+ title: "Change information type",
+ items: LP.cache.information_type_data,
+ backgroundColor: '#FFFF99',
+ flashEnabled: false
+ });
+ Y.lp.app.choice.hook_up_choicesource_spinner(information_type_edit);
+ information_type_edit.render();
+ information_type_edit.on("save", function(e) {
+ var value = information_type_edit.get('value');
+ namespace.update_privacy_portlet(value);
+ namespace.save_information_type(
+ information_type_edit, initial_value, value, lp_client, context,
+ subscribers_list, true);
+ });
+ privacy_link.addClass('js-action');
+ return information_type_edit;
+};
+
/**
* Lookup the information_type property, keyed on the named value.
*
@@ -80,4 +267,6 @@
}
};
-}, "0.1", {"requires": ["base", "oop", "node", "lp.app.banner.privacy"]});
+}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
+ "lazr.choiceedit", "lp.bugs.bugtask_index",
+ "lp.app.banner.privacy", "lp.app.choice"]});
=== renamed file 'lib/lp/bugs/javascript/tests/test_information_type_choice.html' => 'lib/lp/app/javascript/tests/test_information_type.html'
--- lib/lp/bugs/javascript/tests/test_information_type_choice.html 2012-08-29 14:02:57 +0000
+++ lib/lp/app/javascript/tests/test_information_type.html 2012-08-30 20:24:20 +0000
@@ -6,7 +6,7 @@
<html>
<head>
- <title>lp.bugs.information_type_choice Tests</title>
+ <title>lp.app.information_type Tests</title>
<!-- YUI and test setup -->
<script type="text/javascript"
@@ -30,8 +30,6 @@
<script type="text/javascript"
src="../../../../../build/js/lp/app/choice.js"></script>
<script type="text/javascript"
- src="../../../../../build/js/lp/app/information_type.js"></script>
- <script type="text/javascript"
src="../../../../../build/js/lp/app/testing/mockio.js"></script>
<script type="text/javascript"
src="../../../../../build/js/lp/app/client.js"></script>
@@ -79,18 +77,18 @@
src="../../../../../build/js/lp/bugs/bugtask_index.js"></script>
<!-- The module under test. -->
- <script type="text/javascript" src="../information_type_choice.js"></script>
+ <script type="text/javascript" src="../information_type.js"></script>
<!-- Placeholder for any css asset for this module. -->
<!-- <link rel="stylesheet" href="../assets/bugs.information_type_choice-core.css" /> -->
<!-- The test suite -->
- <script type="text/javascript" src="test_information_type_choice.js"></script>
+ <script type="text/javascript" src="test_information_type.js"></script>
</head>
<body class="yui3-skin-sam">
<ul id="suites">
- <li>lp.bugs.information_type_choice.test</li>
+ <li>lp.app.information_type.test</li>
</ul>
<div id="fixture"></div>
<script type="text/x-template" id="portlet-template">
=== renamed file 'lib/lp/bugs/javascript/tests/test_information_type_choice.js' => 'lib/lp/app/javascript/tests/test_information_type.js'
--- lib/lp/bugs/javascript/tests/test_information_type_choice.js 2012-08-28 01:52:34 +0000
+++ lib/lp/app/javascript/tests/test_information_type.js 2012-08-30 20:24:20 +0000
@@ -1,13 +1,13 @@
/* Copyright (c) 2012, Canonical Ltd. All rights reserved. */
-YUI.add('lp.bugs.information_type_choice.test', function (Y) {
+YUI.add('lp.app.information_type.test', function (Y) {
- var tests = Y.namespace('lp.bugs.information_type_choice.test');
- var ns = Y.lp.bugs.information_type_choice;
- tests.suite = new Y.Test.Suite('lp.bugs.information_type_choice Tests');
+ var tests = Y.namespace('lp.app.information_type.test');
+ var ns = Y.lp.app.information_type;
+ tests.suite = new Y.Test.Suite('lp.app.information_type Tests');
tests.suite.add(new Y.Test.Case({
- name: 'lp.bugs.information_type_choice_tests',
+ name: 'lp.app.information_type_tests',
setUp: function() {
window.LP = {
@@ -64,7 +64,7 @@
makeWidget: function() {
var privacy_link = Y.one('#privacy-link');
this.widget = ns.setup_information_type_choice(
- privacy_link, this.lp_client, true);
+ privacy_link, this.lp_client, LP.cache.bug, null, true);
},
_shim_privacy_banner: function () {
@@ -84,8 +84,8 @@
},
test_library_exists: function () {
- Y.Assert.isObject(Y.lp.bugs.information_type_choice,
- "Cannot locate the lp.bugs.information_type_choice module");
+ Y.Assert.isObject(Y.lp.app.information_type,
+ "Cannot locate the lp.app.information_type module");
},
// The save XHR call works as expected.
@@ -93,7 +93,7 @@
this.makeWidget();
var orig_save_success = ns.information_type_save_success;
var save_success_called = false;
- ns.information_type_save_success = function(widget, value,
+ ns.information_type_save_success = function(widget, context, value,
subscribers_list,
subscribers_data) {
Y.Assert.areEqual('USERDATA', value);
@@ -104,7 +104,8 @@
save_success_called = true;
};
ns.save_information_type(
- this.widget, 'PUBLIC', 'USERDATA', this.lp_client, true);
+ this.widget, 'PUBLIC', 'USERDATA', this.lp_client,
+ LP.cache.bug, null, true);
this.mockio.success({
responseText: '{"subscription_data": "subscribers",' +
'"cache_data": {"item": "value"}}',
@@ -133,7 +134,8 @@
update_flag = true;
});
- ns.information_type_save_success(this.widget, 'PROPRIETARY');
+ ns.information_type_save_success(this.widget, LP.cache.bug,
+ 'PROPRIETARY');
var body = Y.one('body');
Y.Assert.isTrue(body.hasClass('private'));
Y.Assert.isTrue(hide_flag);
@@ -188,7 +190,8 @@
}
};
ns.information_type_save_success(
- this.widget, 'PUBLIC', subscribers_list, subscribers_data);
+ this.widget, LP.cache.bug, 'PUBLIC', subscribers_list,
+ subscribers_data);
Y.Assert.isTrue(load_subscribers_called);
Y.Assert.areEqual('value1', window.LP.cache.item1);
Y.Assert.areEqual('value2', window.LP.cache.item2);
@@ -227,7 +230,7 @@
var function_called = false;
ns.save_information_type =
function(widget, initial_value, value, lp_client,
- validate_change) {
+ context, subscribers_list, validate_change) {
// We only care if the function is called with
// validate_change = false
Y.Assert.areEqual('PUBLIC', initial_value);
@@ -259,7 +262,7 @@
var function_called = false;
ns.save_information_type =
function(widget, initial_value, value, lp_client,
- validate_change) {
+ context, subscribers_list, validate_change) {
// We only care if the function is called with
// validate_change = false
function_called = !validate_change;
@@ -285,7 +288,8 @@
this.makeWidget();
this.widget.set('value', 'USERDATA');
ns.save_information_type(
- this.widget, 'PUBLIC', 'USERDATA', this.lp_client);
+ this.widget, 'PUBLIC', 'USERDATA', this.lp_client,
+ LP.cache.bug);
this.mockio.last_request.respond({
status: 500,
statusText: 'An error occurred'
@@ -304,5 +308,5 @@
}));
}, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate',
- 'lp.testing.mockio', 'lp.client', 'lp.app.informatin_type',
- 'lp.bugs.information_type_choice', 'lp.bugs.subscribers']});
+ 'lp.testing.mockio', 'lp.client', 'lp.app.information_type',
+ 'lp.bugs.subscribers']});
=== modified file 'lib/lp/blueprints/browser/configure.zcml'
--- lib/lp/blueprints/browser/configure.zcml 2012-08-01 19:02:49 +0000
+++ lib/lp/blueprints/browser/configure.zcml 2012-08-30 20:24:20 +0000
@@ -311,6 +311,9 @@
<browser:page
name="+listing-detailed"
template="../templates/specification-listing-detailed.pt"/>
+ <browser:page
+ name="+portlet-privacy"
+ template="../templates/blueprint-portlet-privacy.pt"/>
</browser:pages>
<browser:page
for="lp.blueprints.interfaces.specification.ISpecification"
=== modified file 'lib/lp/blueprints/browser/specification.py'
--- lib/lp/blueprints/browser/specification.py 2012-08-20 19:14:22 +0000
+++ lib/lp/blueprints/browser/specification.py 2012-08-30 20:24:20 +0000
@@ -78,6 +78,7 @@
)
from lp import _
+from lp.app.browser.informationtype import InformationTypePortletMixin
from lp.app.browser.launchpad import AppFrontPageSearchView
from lp.app.browser.launchpadform import (
action,
@@ -111,6 +112,7 @@
from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
+from lp.registry.enums import PRIVATE_INFORMATION_TYPES
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.product import IProduct
from lp.services.config import config
@@ -423,7 +425,7 @@
'linkbug', 'unlinkbug', 'linkbranch',
'adddependency', 'removedependency',
'dependencytree', 'linksprint', 'supersede',
- 'retarget']
+ 'retarget', 'information_type']
@enabled_with_permission('launchpad.Edit')
def milestone(self):
@@ -528,8 +530,13 @@
text = 'Link a related branch'
return Link('+linkbranch', text, icon='add')
-
-class SpecificationSimpleView(LaunchpadView):
+ def information_type(self):
+ """Return the 'Set privacy/security' Link."""
+ text = 'Change privacy/security'
+ return Link('#', text)
+
+
+class SpecificationSimpleView(InformationTypePortletMixin, LaunchpadView):
"""Used to render portlets and listing items that need browser code."""
@cachedproperty
@@ -545,6 +552,17 @@
def bug_links(self):
return self.context.getLinkedBugTasks(self.user)
+ @cachedproperty
+ def privacy_portlet_css(self):
+ if self.private:
+ return 'portlet private'
+ else:
+ return 'portlet public'
+
+ @cachedproperty
+ def private(self):
+ return self.context.information_type in PRIVATE_INFORMATION_TYPES
+
class SpecificationView(SpecificationSimpleView):
"""Used to render the main view of a specification."""
@@ -562,6 +580,7 @@
return self.context.summary
def initialize(self):
+ super(SpecificationView, self).initialize()
# The review that the user requested on this spec, if any.
self.notices = []
=== modified file 'lib/lp/blueprints/browser/tests/test_specification.py'
--- lib/lp/blueprints/browser/tests/test_specification.py 2012-01-01 02:58:52 +0000
+++ lib/lp/blueprints/browser/tests/test_specification.py 2012-08-30 20:24:20 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -8,10 +8,14 @@
from BeautifulSoup import BeautifulSoup
import pytz
-from testtools.matchers import Equals
+from testtools.matchers import (
+ Equals,
+ Not,
+ )
from zope.component import getUtility
from zope.publisher.interfaces import NotFound
from zope.security.proxy import removeSecurityProxy
+import soupmatchers
from lp.app.browser.tales import format_link
from lp.blueprints.browser import specification
@@ -20,7 +24,9 @@
ISpecification,
ISpecificationSet,
)
+from lp.registry.enums import InformationType
from lp.registry.interfaces.person import PersonVisibility
+from lp.services.features.testing import FeatureFixture
from lp.services.webapp.interfaces import BrowserNotificationLevel
from lp.services.webapp.publisher import canonical_url
from lp.testing import (
@@ -165,6 +171,41 @@
"... Registered by Some Person ... ago ..."))
+class TestSpecificationInformationType(BrowserTestCase):
+
+ layer = DatabaseFunctionalLayer
+
+ portlet_tag = soupmatchers.Tag('info-type-portlet', True,
+ attrs=dict(id='information-type-summary'))
+
+ def setUp(self):
+ super(TestSpecificationInformationType, self).setUp()
+ self.useFixture(FeatureFixture({'blueprints.information_type.enabled':
+ 'true'}))
+
+ def assertBrowserMatches(self, matcher):
+ browser = self.getViewBrowser(self.factory.makeSpecification())
+ self.assertThat(browser.contents, matcher)
+
+ def test_has_privacy_portlet(self):
+ self.assertBrowserMatches(soupmatchers.HTMLContains(self.portlet_tag))
+
+ def test_privacy_portlet_requires_flag(self):
+ self.useFixture(FeatureFixture({'blueprints.information_type.enabled':
+ ''}))
+ self.assertBrowserMatches(
+ Not(soupmatchers.HTMLContains(self.portlet_tag)))
+
+ def test_has_privacy_banner(self):
+ spec = self.factory.makeSpecification(
+ information_type=InformationType.PROPRIETARY)
+ browser = self.getViewBrowser(spec)
+ privacy_banner = soupmatchers.Tag('privacy-banner', True,
+ attrs={'class': 'banner-text'})
+ self.assertThat(browser.contents,
+ soupmatchers.HTMLContains(privacy_banner))
+
+
class TestSpecificationViewPrivateArtifacts(BrowserTestCase):
""" Tests that specifications with private team artifacts can be viewed.
=== modified file 'lib/lp/blueprints/interfaces/specification.py'
--- lib/lp/blueprints/interfaces/specification.py 2012-08-22 15:41:05 +0000
+++ lib/lp/blueprints/interfaces/specification.py 2012-08-30 20:24:20 +0000
@@ -565,6 +565,12 @@
:param user: The user doing the search.
"""
+ def getAllowedInformationTypes(who):
+ """Get a list of acceptable `InformationType`s for this spec.
+
+ The intersection of the affected pillars' allowed types is permitted.
+ """
+
class ISpecificationEditRestricted(Interface):
"""Specification's attributes and methods protected with launchpad.Edit.
=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py 2012-08-22 15:42:58 +0000
+++ lib/lp/blueprints/model/specification.py 2012-08-30 20:24:20 +0000
@@ -815,6 +815,9 @@
return '<Specification %s %r for %r>' % (
self.id, self.name, self.target.name)
+ def getAllowedInformationTypes(self, who):
+ return set(InformationType.items)
+
@property
def private(self):
return self.information_type in PRIVATE_INFORMATION_TYPES
@@ -1080,10 +1083,12 @@
def new(self, name, title, specurl, summary, definition_status,
owner, approver=None, product=None, distribution=None, assignee=None,
drafter=None, whiteboard=None, workitems_text=None,
- priority=SpecificationPriority.UNDEFINED):
+ priority=SpecificationPriority.UNDEFINED, information_type=None):
"""See ISpecificationSet."""
# Adapt the NewSpecificationDefinitionStatus item to a
# SpecificationDefinitionStatus item.
+ if information_type is None:
+ information_type = InformationType.PUBLIC
status_name = definition_status.name
status_names = NewSpecificationDefinitionStatus.items.mapping.keys()
if status_name not in status_names:
@@ -1095,7 +1100,8 @@
summary=summary, priority=priority,
definition_status=definition_status, owner=owner,
approver=approver, product=product, distribution=distribution,
- assignee=assignee, drafter=drafter, whiteboard=whiteboard)
+ assignee=assignee, drafter=drafter, whiteboard=whiteboard,
+ information_type=information_type)
def getDependencyDict(self, specifications):
"""See `ISpecificationSet`."""
=== added file 'lib/lp/blueprints/templates/blueprint-portlet-privacy.pt'
--- lib/lp/blueprints/templates/blueprint-portlet-privacy.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/templates/blueprint-portlet-privacy.pt 2012-08-30 20:24:20 +0000
@@ -0,0 +1,16 @@
+<div
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ id="privacy"
+ tal:attributes="class view/privacy_portlet_css"
+ tal:define="link context/menu:context/information_type"
+>
+ <span id="information-type-summary"
+ tal:attributes="class view/information_type_css;">This blueprint
+ contains <strong id="information-type" tal:content="view/information_type"></strong> information</span> <a class="sprite edit action-icon"
+ id="privacy-link" tal:attributes="href link/path"
+ tal:condition="link/enabled" style="display: none" >Edit</a>
+
+ <div id="information-type-description" style="padding-top: 5px" tal:content="view/information_type_description"></div>
+</div>
=== modified file 'lib/lp/blueprints/templates/specification-index.pt'
--- lib/lp/blueprints/templates/specification-index.pt 2012-08-23 13:55:00 +0000
+++ lib/lp/blueprints/templates/specification-index.pt 2012-08-30 20:24:20 +0000
@@ -310,7 +310,15 @@
</div>
<script type="text/javascript">
- LPJS.use('lp.anim', 'lp.deprecated.ui', 'node', 'widget', function(Y) {
+ LPJS.use('lp.anim', 'lp.client', 'lp.deprecated.ui',
+ 'lp.app.information_type', 'node', 'widget', function(Y) {
+ Y.on('domready', function(){
+ var privacy_link = Y.one('#privacy-link');
+ Y.lp.app.information_type.setup_information_type_choice(
+ privacy_link, new Y.lp.client.Launchpad(), LP.cache.context,
+ null);
+ privacy_link.setStyle('display', 'inline');
+ });
Y.on('lp:context:implementation_status:changed', function(e) {
var icon = Y.one('#informational-icon');
@@ -345,7 +353,8 @@
});
Y.on('lp:context:title:changed', function(e) {
// change the window title and breadcrumb.
- Y.lp.deprecated.ui.update_field('ol.breadcrumbs li:last-child', e.new_value);
+ Y.lp.deprecated.ui.update_field('ol.breadcrumbs li:last-child',
+ e.new_value);
var title = window.document.title;
title = e.new_value + title.substring(e.old_value.length);
window.document.title = title;
@@ -377,7 +386,7 @@
<tal:side metal:fill-slot="side">
<tal:menu replace="structure context/@@+global-actions" />
-
+ <tal:privacy replace="structure context/@@+portlet-privacy" condition="features/blueprints.information_type.enabled" />
<div tal:replace="structure context/@@+portlet-subscribers" />
</tal:side>
</body>
=== modified file 'lib/lp/bugs/javascript/bugtask_index.js'
--- lib/lp/bugs/javascript/bugtask_index.js 2012-08-23 18:58:50 +0000
+++ lib/lp/bugs/javascript/bugtask_index.js 2012-08-30 20:24:20 +0000
@@ -49,8 +49,11 @@
privacy_link = Y.one('#privacy-link');
if (privacy_link) {
- Y.lp.bugs.information_type_choice.setup_information_type_choice(
- privacy_link, lp_client);
+ var sub_list_node = Y.one('#other-bug-subscribers');
+ var subscribers_list = sub_list_node.getData(
+ 'subscribers_loader');
+ Y.lp.app.information_type.setup_information_type_choice(
+ privacy_link, lp_client, LP.cache.bug, subscribers_list);
}
setup_add_attachment();
setup_link_branch_picker();
@@ -1125,7 +1128,7 @@
"lazr.formoverlay", "lp.anim", "lazr.overlay",
"lazr.choiceedit", "lp.app.picker",
"lp.bugs.bugtask_index.portlets.subscription",
- "lp.bugs.information_type_choice",
+ "lp.app.information_type",
"lp.app.widgets.expander", "lp.client", "escape",
"lp.client.plugins", "lp.app.errors",
"lp.app.banner.privacy",
=== removed file 'lib/lp/bugs/javascript/information_type_choice.js'
--- lib/lp/bugs/javascript/information_type_choice.js 2012-08-28 01:52:34 +0000
+++ lib/lp/bugs/javascript/information_type_choice.js 1970-01-01 00:00:00 +0000
@@ -1,189 +0,0 @@
-/* Copyright 2012 Canonical Ltd. This software is licensed under the
- * GNU Affero General Public License version 3 (see the file LICENSE).
- *
- * Information Type choice widget for bug pages.
- */
-
-YUI.add('lp.bugs.information_type_choice', function(Y) {
-
-var namespace = Y.namespace('lp.bugs.information_type_choice');
-var information_type = Y.namespace('lp.app.information_type');
-
-// For testing.
-var skip_animation = false;
-
-/**
- * Save the new information type. If validate_change is true, then a check
- * will be done to ensure the bug will not become invisible. If the bug will
- * become invisible, a confirmation popup is used to confirm the user's
- * intention. Then this method is called again with validate_change set to
- * false to allow the change to proceed.
- *
- * @param widget
- * @param initial_value
- * @param value
- * @param lp_client
- * @param validate_change
- */
-namespace.save_information_type = function(widget, initial_value, value,
- lp_client, validate_change) {
- var error_handler = new Y.lp.client.FormErrorHandler();
- error_handler.showError = function(error_msg) {
- Y.lp.app.errors.display_error(
- Y.one('#information-type'), error_msg);
- };
- error_handler.handleError = function(ioId, response) {
- if( response.status === 400
- && response.statusText === 'Bug Visibility') {
- namespace._confirm_information_type_change(
- widget, initial_value, lp_client);
- return true;
- }
- var orig_value = information_type.information_type_value_from_key(
- LP.cache.bug.information_type, 'name', 'value');
- widget.set('value', orig_value);
- widget._showFailed();
- information_type.update_privacy_portlet(orig_value);
- return false;
- };
- var submit_url = document.URL + "/+secrecy";
- var qs = Y.lp.client.append_qs('', 'field.actions.change', 'Change');
- qs = Y.lp.client.append_qs(qs, 'field.information_type', value);
- qs = Y.lp.client.append_qs(
- qs, 'field.validate_change', validate_change?'on':'off');
- var sub_list_node = Y.one('#other-bug-subscribers');
- var subscribers_list = sub_list_node.getData('subscribers_loader');
- var config = {
- method: "POST",
- headers: {'Accept': 'application/xhtml;application/json'},
- data: qs,
- on: {
- start: function () {
- widget._uiSetWaiting();
- subscribers_list.subscribers_list.startActivity(
- 'Updating subscribers...');
- },
- end: function () {
- widget._uiClearWaiting();
- subscribers_list.subscribers_list.stopActivity();
- },
- success: function (id, response) {
- var result_data = null;
- if (response.responseText !== '') {
- result_data = Y.JSON.parse(response.responseText);
- }
- namespace.information_type_save_success(
- widget, value, subscribers_list, result_data);
- Y.lp.client.display_notifications(
- response.getResponseHeader('X-Lazr-Notifications'));
- },
- failure: error_handler.getFailureHandler()
- }
- };
- lp_client.io_provider.io(submit_url, config);
-};
-
-namespace.information_type_save_success = function(widget, value,
- subscribers_list,
- subscribers_data) {
- LP.cache.bug.information_type =
- information_type.information_type_value_from_key(
- value, 'value', 'name');
- information_type.update_privacy_banner(value);
- widget._showSucceeded();
- if (Y.Lang.isObject(subscribers_data)) {
- var subscribers = subscribers_data.subscription_data;
- subscribers_list._loadSubscribersFromList(subscribers);
- var cache_data = subscribers_data.cache_data;
- var item;
- for (item in cache_data) {
- if (cache_data.hasOwnProperty(item)) {
- LP.cache[item] = cache_data[item];
- }
- }
- }
- var ns = Y.lp.bugs.bugtask_index.portlets.subscription;
- ns.update_subscription_status(skip_animation);
-};
-
-/**
- * Possibly prompt the user to confirm the change of information type.
- * If the old value is public, and the new value is private, we want to
- * confirm that the user really wants to make the change.
- *
- * @param widget
- * @param initial_value
- * @param lp_client
- * @private
- */
-namespace._confirm_information_type_change = function(widget, initial_value,
- lp_client) {
- var value = widget.get('value');
- var do_save = function() {
- information_type.update_privacy_portlet(value);
- namespace.save_information_type(
- widget, initial_value, value, lp_client, false);
- };
- // Reset the widget back to it's original value so the user doesn't see it
- // change while the confirmation dialog is showing.
- var new_value = widget.get('value');
- widget.set('value', initial_value);
- information_type.update_privacy_portlet(initial_value);
- var confirm_text_template = [
- '<p class="block-sprite large-warning">',
- ' You are about to mark this bug as ',
- ' <strong>{information_type}</strong>.<br>',
- ' The bug will become invisible because there is no-one with',
- ' permissions to see {information_type} bugs.',
- '</p><p>',
- ' <strong>Please confirm you really want to do this.</strong>',
- '</p>'
- ].join('');
- var title = information_type.information_type_value_from_key(
- value, 'value', 'name');
- var confirm_text = Y.Lang.sub(confirm_text_template,
- {information_type: title});
- var co = new Y.lp.app.confirmationoverlay.ConfirmationOverlay({
- submit_fn: function() {
- widget.set('value', new_value);
- information_type.update_privacy_portlet(new_value);
- do_save();
- },
- form_content: confirm_text,
- headerContent: '<h2>Confirm information type change</h2>'
- });
- co.show();
-};
-
-namespace.setup_information_type_choice = function(privacy_link, lp_client,
- skip_anim) {
- skip_animation = skip_anim;
- var initial_value = information_type.information_type_value_from_key(
- LP.cache.bug.information_type, 'name', 'value');
- var information_type_value = Y.one('#information-type');
- var information_type_edit = new Y.ChoiceSource({
- editicon: privacy_link,
- contentBox: Y.one('#privacy'),
- value_location: information_type_value,
- value: initial_value,
- title: "Change information type",
- items: LP.cache.information_type_data,
- backgroundColor: '#FFFF99',
- flashEnabled: false
- });
- Y.lp.app.choice.hook_up_choicesource_spinner(information_type_edit);
- information_type_edit.render();
- information_type_edit.on("save", function(e) {
- var value = information_type_edit.get('value');
- information_type.update_privacy_portlet(value);
- namespace.save_information_type(
- information_type_edit, initial_value, value, lp_client, true);
-
- });
- privacy_link.addClass('js-action');
- return information_type_edit;
-};
-}, "0.1", {"requires": ["base", "oop", "node", "event", "io-base",
- "lazr.choiceedit", "lp.bugs.bugtask_index",
- "lp.app.banner.privacy", "lp.app.choice",
- "lp.app.information_type"]});
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2012-08-30 11:52:28 +0000
+++ lib/lp/services/features/flags.py 2012-08-30 20:24:20 +0000
@@ -57,6 +57,12 @@
'',
'',
''),
+ ('blueprints.information_type.enabled',
+ 'boolean',
+ 'Enable UI for information_type on Blueprints.',
+ 'Disable UI',
+ 'Blueprint information_type UI',
+ 'https://dev.launchpad.net/LEP/PrivateProjects'),
('bugs.affected_count_includes_dupes.disabled',
'boolean',
("Disable adding up affected users across all duplicate bugs."),
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2012-08-29 03:19:38 +0000
+++ lib/lp/testing/factory.py 2012-08-30 20:24:20 +0000
@@ -2065,7 +2065,8 @@
status=NewSpecificationDefinitionStatus.NEW,
implementation_status=None, goal=None, specurl=None,
assignee=None, drafter=None, approver=None,
- priority=None, whiteboard=None, milestone=None):
+ priority=None, whiteboard=None, milestone=None,
+ information_type=None):
"""Create and return a new, arbitrary Blueprint.
:param product: The product to make the blueprint on. If one is
@@ -2102,7 +2103,8 @@
approver=approver,
product=product,
distribution=distribution,
- priority=priority)
+ priority=priority,
+ information_type=information_type)
naked_spec = removeSecurityProxy(spec)
if status.name not in status_names:
# Set the closed status after the status has a sane initial state.
Follow ups