← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wallyworld/launchpad/edit-sharing-policies-1036437 into lp:launchpad

 

Ian Booth has proposed merging lp:~wallyworld/launchpad/edit-sharing-policies-1036437 into lp:launchpad.

Requested reviews:
  Curtis Hovey (sinzui)
Related bugs:
  Bug #1036437 in Launchpad itself: "UI for maintaining bug/branch sharing policies"
  https://bugs.launchpad.net/launchpad/+bug/1036437

For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/edit-sharing-policies-1036437/+merge/119466

== Implementation ==

This branch renders a project's bug and branch subscription policies on the +sharing page so they can be edited. A choice source popup is used to do the editing, but the saving part is not wired up yet - this will be done in the next branch. If the sharing page is read only, then the policy is rendered but no edit link is provided.

All current projects are not yet configured to use the new sharing policies, so their values for bug/branch_sharing_policy will be None. Such projects will continue to use the legacy private checkbox and/or BVP until they have been reconfigured. To handle this situation, the UI uses a 'legacy' policy choice to inform the user that the project is still using the old methodology. Once a new policy has been chosen, the legacy policy choice is no longer available. The wording used for the legacy policy choice is:

Title: 'Legacy policy'
Description:
'Legacy project sharing policy will continue to be used until a new policy is configured.'

This wording could use some improvement, suggestions welcome. The same wording is currently used for bit bug and branch sharing policies so it's been kept generic. Perhaps we want separate wording for each type.

For non-commercial projects, there is only one choice - Public (assuming the project has been configured to the use the new policies). For these cases when there's only one choice, the choice source popup edit links have been disabled. So everything renders as expected but it doesn't make sense to allow editing.

Side portlets have been used to render the policies. Perhaps only one portlet is required? See screenshot.

Distros currently don't support these policies but the code has been kept quite generic so it will pretty much just work as expected when and if they do.

== Demo ==

http://people.canonical.com/~ianb/sharing-policy-edit.png

== Tests ==

I added tests for the sharing service, pillar sharing view on the server side, and yui tests for the pillar sharing view on the client side.

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/browser/pillar.py
  lib/lp/registry/browser/tests/test_pillar_sharing.py
  lib/lp/registry/interfaces/sharingservice.py
  lib/lp/registry/javascript/sharing/pillarsharingview.js
  lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.html
  lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js
  lib/lp/registry/services/sharingservice.py
  lib/lp/registry/services/tests/test_sharingservice.py
  lib/lp/registry/templates/pillar-sharing.pt
  lib/lp/testing/factory.py
-- 
https://code.launchpad.net/~wallyworld/launchpad/edit-sharing-policies-1036437/+merge/119466
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
=== modified file 'lib/lp/registry/browser/pillar.py'
--- lib/lp/registry/browser/pillar.py	2012-07-21 03:04:06 +0000
+++ lib/lp/registry/browser/pillar.py	2012-08-14 04:59:25 +0000
@@ -297,6 +297,14 @@
         return self._getSharingService().getInformationTypes(self.context)
 
     @property
+    def bug_sharing_policies(self):
+        return self._getSharingService().getBugSharingPolicies(self.context)
+
+    @property
+    def branch_sharing_policies(self):
+        return self._getSharingService().getBranchSharingPolicies(self.context)
+
+    @property
     def sharing_permissions(self):
         return self._getSharingService().getSharingPermissions()
 
@@ -356,6 +364,9 @@
             and check_permission('launchpad.Edit', self.context))
         cache.objects['information_types'] = self.information_types
         cache.objects['sharing_permissions'] = self.sharing_permissions
+        cache.objects['bug_sharing_policies'] = self.bug_sharing_policies
+        cache.objects['branch_sharing_policies'] = (
+            self.branch_sharing_policies)
 
         view_names = set(reg.name for reg
             in iter_view_registrations(self.__class__))

=== modified file 'lib/lp/registry/browser/tests/test_pillar_sharing.py'
--- lib/lp/registry/browser/tests/test_pillar_sharing.py	2012-08-08 11:48:29 +0000
+++ lib/lp/registry/browser/tests/test_pillar_sharing.py	2012-08-14 04:59:25 +0000
@@ -21,7 +21,11 @@
 from zope.traversing.browser.absoluteurl import absoluteURL
 
 from lp.app.interfaces.services import IService
-from lp.registry.enums import InformationType
+from lp.registry.enums import (
+    BranchSharingPolicy,
+    BugSharingPolicy,
+    InformationType,
+    )
 from lp.registry.interfaces.accesspolicy import IAccessPolicyGrantFlatSource
 from lp.registry.model.pillar import PillarPerson
 from lp.services.config import config
@@ -68,7 +72,9 @@
                 owner=self.owner, driver=self.driver)
         elif self.pillar_type == 'product':
             self.pillar = self.factory.makeProduct(
-                owner=self.owner, driver=self.driver)
+                owner=self.owner, driver=self.driver,
+                bug_sharing_policy=BugSharingPolicy.PUBLIC,
+                branch_sharing_policy=BranchSharingPolicy.PUBLIC)
         self.access_policy = self.factory.makeAccessPolicy(
             pillar=self.pillar, type=InformationType.PROPRIETARY)
         self.grantees = []
@@ -333,6 +339,9 @@
             view = create_initialized_view(self.pillar, name='+sharing')
             cache = IJSONRequestCache(view.request)
             self.assertIsNotNone(cache.objects.get('information_types'))
+            self.assertIsNotNone(
+                cache.objects.get('branch_sharing_policies'))
+            self.assertIsNotNone(cache.objects.get('bug_sharing_policies'))
             self.assertIsNotNone(cache.objects.get('sharing_permissions'))
             batch_size = config.launchpad.default_batch_size
             apgfs = getUtility(IAccessPolicyGrantFlatSource)

=== modified file 'lib/lp/registry/interfaces/sharingservice.py'
--- lib/lp/registry/interfaces/sharingservice.py	2012-07-21 03:04:06 +0000
+++ lib/lp/registry/interfaces/sharingservice.py	2012-08-14 04:59:25 +0000
@@ -114,6 +114,12 @@
     def getInformationTypes(pillar):
         """Return the allowed information types for the given pillar."""
 
+    def getBugSharingPolicies(pillar):
+        """Return the allowed bug sharing policies for the given pillar."""
+
+    def getBranchSharingPolicies(pillar):
+        """Return the allowed branch sharing policies for the given pillar."""
+
     def getSharingPermissions():
         """Return the information sharing permissions."""
 

=== modified file 'lib/lp/registry/javascript/sharing/pillarsharingview.js'
--- lib/lp/registry/javascript/sharing/pillarsharingview.js	2012-07-21 03:04:06 +0000
+++ lib/lp/registry/javascript/sharing/pillarsharingview.js	2012-08-14 04:59:25 +0000
@@ -124,9 +124,80 @@
         });
         this.set('grantee_table', grantee_table);
         grantee_table.render();
-        if (!this.get('write_enabled')) {
-            Y.one('#add-grantee-link').addClass('hidden');
-        }
+        if (this.get('write_enabled')) {
+            Y.one('#add-grantee-link').removeClass('hidden');
+        }
+        this.bug_sharing_policy_widget
+                = this._render_sharing_policy('bug', 'Bug');
+        this.branch_sharing_policy_widget
+                = this._render_sharing_policy('branch', 'Branch');
+    },
+
+    // Render the sharing policy choice popup.
+    _render_sharing_policy: function(artifact_type, artifact_title) {
+        var sharing_policy_portlet = Y.one(
+                '#' + artifact_type + '-sharing-policy-portlet');
+        if (!Y.Lang.isValue(sharing_policy_portlet)) {
+            return null;
+        }
+        sharing_policy_portlet.removeClass('hidden');
+        var desc_node = Y.one(
+                '#' + artifact_type + '-sharing-policy-description');
+        var current_value
+                = LP.cache.context[artifact_type + '_sharing_policy'];
+        var legacy_description = "Legacy project sharing policy will " +
+            "continue to be used until a new policy is configured.";
+
+        var desc_text = legacy_description;
+        if (current_value !== null) {
+            Y.Array.some(LP.cache[artifact_type + '_sharing_policies'],
+                    function(policy_info) {
+                if (policy_info.title === current_value) {
+                    desc_text = policy_info.description;
+                    return true;
+                }
+                return false;
+            });
+        }
+        desc_node.set('text', desc_text);
+        var contentBox = sharing_policy_portlet.one(
+                '#' + artifact_type + '-sharing-policy');
+        var value_location = contentBox.one('.value');
+        var editicon = contentBox.one('a.editicon');
+        var choice_items = [];
+        if (!Y.Lang.isValue(current_value)) {
+            choice_items.push({
+                value: 'LEGACY',
+                name: 'Legacy policy',
+                description: legacy_description
+            });
+        }
+        Y.each(LP.cache[artifact_type + '_sharing_policies'],
+            function(policy) {
+                choice_items.push({
+                    value: policy.title,
+                    name: policy.title,
+                    description: policy.description
+                });
+        });
+        var editable = LP.cache.sharing_write_enabled
+                && choice_items.length > 1;
+        var policy_edit = new Y.ChoiceSource({
+            clickable_content: editable,
+            contentBox: contentBox,
+            value_location: value_location,
+            editicon: editicon,
+            value: current_value || 'LEGACY',
+            title: artifact_title + " sharing policy",
+            items: choice_items,
+            elementToFlash: contentBox,
+            backgroundColor: '#FFFF99'
+        });
+        policy_edit.render();
+        if (!editable && Y.Lang.isValue(editicon)) {
+            editicon.addClass('hidden');
+        }
+        return policy_edit;
     },
 
     bindUI: function() {
@@ -157,13 +228,24 @@
                 permissions[e.details[1]] = e.details[2];
                 self.save_sharing_selection(e.details[0], permissions);
         });
+        // Hook up the sharing policy popups.
+        if (this.bug_sharing_policy_widget !== null) {
+            this.bug_sharing_policy_widget.on('save', function(e) {
+                var policy = self.bug_sharing_policy_widget.get('value');
+                Y.log(policy);
+            });
+        }
+        if (this.branch_sharing_policy_widget !== null) {
+            this.branch_sharing_policy_widget.on('save', function(e) {
+                var policy = self.branch_sharing_policy_widget.get('value');
+                Y.log(policy);
+            });
+        }
     },
 
     syncUI: function() {
         var grantee_table = this.get('grantee_table');
         grantee_table.syncUI();
-        var grantee_table = this.get('grantee_table');
-        grantee_table.syncUI();
         var invisible_info_types = LP.cache.invisible_information_types;
         var exiting_warning = Y.one('#sharing-warning');
         if (Y.Lang.isObject(exiting_warning)) {

=== modified file 'lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.html'
--- lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.html	2012-07-21 03:04:06 +0000
+++ lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.html	2012-08-14 04:59:25 +0000
@@ -79,7 +79,24 @@
       <script id="test-fixture" type="text/x-template">
           <div id="sharing-header"></div>
           <table id='grantee-table'></table>
-          <a id='add-grantee-link' class='sprite add js-action' href="#">Share</a>
+          <a id='add-grantee-link'
+             class='sprite add js-action hidden' href="#">Share</a>
+          <div id="branch-sharing-policy-portlet" class="hidden portlet">
+               <span id="branch-sharing-policy">
+                 Sharing policy for this project's branches:&nbsp;
+                 <strong><span class="value"></span></strong>
+                 <a class="editicon sprite edit action-icon">Edit</a>
+               </span>
+              <div id="branch-sharing-policy-description"></div>
+          </div>
+          <div id="bug-sharing-policy-portlet" class="hidden portlet">
+               <span id="bug-sharing-policy">
+                 Sharing policy for this project's bugs:&nbsp;
+                 <strong><span class="value"></span></strong>
+                 <a class="editicon sprite edit action-icon">Edit</a>
+               </span>
+              <div id="bug-sharing-policy-description"></div>
+          </div>
       </script>
     </head>
     <body class="yui3-skin-sam">

=== modified file 'lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js'
--- lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js	2012-07-21 03:08:45 +0000
+++ lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js	2012-08-14 04:59:25 +0000
@@ -6,17 +6,16 @@
     tests.suite = new Y.Test.Suite(
         'lp.registry.sharing.sharing Tests');
 
-    tests.suite.add(new Y.Test.Case({
-        name: 'lp.registry.sharing.sharing_tests',
-
-        setUp: function () {
+    var common_test_methods = {
+            setUp: function () {
             Y.one('#fixture').appendChild(
                 Y.Node.create(Y.one('#test-fixture').getContent()));
             window.LP = {
                 links: {},
                 cache: {
                     context: {
-                        self_link: "~pillar", display_name: 'Pillar'},
+                        self_link: '~pillar', display_name: 'Pillar',
+                        bug_sharing_policy: 'Bug Policy 1'},
                     grantee_data: [
                     {'name': 'fred', 'display_name': 'Fred Bloggs',
                      'role': '(Maintainer)', web_link: '~fred',
@@ -45,6 +44,17 @@
                         {index: '2', value: 'P3', title: 'Policy 3',
                          description: 'Policy 3 description'}
                     ],
+                    bug_sharing_policies: [
+                        {index: '0', value: 'BSP1', title: 'Bug Policy 1',
+                         description: 'Bug Policy 1 description'},
+                        {index: '1', value: 'BSP2', title: 'Bug Policy 2',
+                         description: 'Bug Policy 2 description'}
+                    ],
+                    branch_sharing_policies: [
+                        {index: '0', value: 'BRSP1',
+                         title: 'Branch Policy 1',
+                         description: 'Branch Policy 1 description'}
+                    ],
                     sharing_write_enabled: true
                 }
             };
@@ -63,7 +73,11 @@
             });
             var ns = Y.lp.registry.sharing.pillarsharingview;
             return new ns.PillarSharingView(config);
-        },
+        }
+    };
+
+    tests.suite.add(new Y.Test.Case(Y.merge(common_test_methods, {
+        name: 'lp.registry.sharing.sharing_tests',
 
         test_library_exists: function () {
             Y.Assert.isObject(Y.lp.registry.sharing.pillarsharingview,
@@ -513,7 +527,72 @@
             this.view.render();
             Y.Assert.isNull(Y.one('.large-warning ul.bulleted'));
         }
-    }));
+    })));
+
+    tests.suite.add(new Y.Test.Case(Y.merge(common_test_methods, {
+        name: 'lp.registry.sharing.sharing_policy_tests',
+
+        // A pillar's sharing policy is correctly rendered.
+        _assert_sharing_policies_editable: function(editable) {
+            var bug_edit_link = Y.one('#bug-sharing-policy .edit');
+            Y.Assert.areEqual(editable, !bug_edit_link.hasClass('hidden'));
+            var branch_edit_link = Y.one('#branch-sharing-policy .edit');
+            Y.Assert.areEqual(editable, !branch_edit_link.hasClass('hidden'));
+        },
+
+        // When there is no sharing policy defined for a pillar, the default
+        // policy becomes the legacy policy.
+        test_sharing_policy_render_no_model_value: function() {
+            this.view = this._create_Widget();
+            this.view.render();
+            this._assert_sharing_policies_editable(true);
+            var portlet = Y.one('#branch-sharing-policy-portlet');
+            Y.Assert.isFalse(portlet.hasClass('hidden'));
+            var desc_node = Y.one('#branch-sharing-policy-description');
+            Y.Assert.areEqual(
+                'Legacy project sharing policy will continue to be ' +
+                'used until a new policy is configured.',
+                desc_node.get('text').trim());
+            var value_node = Y.one('#branch-sharing-policy .value');
+            Y.Assert.areEqual(
+                    'Legacy policy', value_node.get('text').trim());
+        },
+
+        // A pillar's sharing policy is correctly rendered.
+        test_sharing_policy_render: function() {
+            this.view = this._create_Widget();
+            this.view.render();
+            this._assert_sharing_policies_editable(true);
+            var portlet = Y.one('#bug-sharing-policy-portlet');
+            Y.Assert.isFalse(portlet.hasClass('hidden'));
+            var desc_node = Y.one('#bug-sharing-policy-description');
+            Y.Assert.areEqual('Bug Policy 1 description',
+                desc_node.get('text').trim());
+            var value_node = Y.one('#bug-sharing-policy .value');
+            Y.Assert.areEqual(
+                    'Bug Policy 1', value_node.get('text').trim());
+        },
+
+        // If the view is readonly, no edit links are available.
+        test_sharing_policy_render_read_only: function() {
+            window.LP.cache.sharing_write_enabled = false;
+            this.view = this._create_Widget();
+            this.view.render();
+            this._assert_sharing_policies_editable(false);
+        },
+
+        // If there is only one policy choice, no edit links are available.
+        test_sharing_policy_render_only_one_choice: function() {
+            // Add a model value so the legacy choice is not used.
+            window.LP.cache.context.branch_sharing_policy
+                    = 'Branch Policy 1';
+            this.view = this._create_Widget();
+            this.view.render();
+            var branch_edit_link = Y.one('#branch-sharing-policy .edit');
+            Y.Assert.isTrue(branch_edit_link.hasClass('hidden'));
+
+        }
+    })));
 
 }, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate',
         'lp.testing.mockio', 'lp.registry.sharing.granteepicker',

=== modified file 'lib/lp/registry/services/sharingservice.py'
--- lib/lp/registry/services/sharingservice.py	2012-08-07 02:31:56 +0000
+++ lib/lp/registry/services/sharingservice.py	2012-08-14 04:59:25 +0000
@@ -30,6 +30,8 @@
 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
 from lp.code.interfaces.branchcollection import IAllBranches
 from lp.registry.enums import (
+    BranchSharingPolicy,
+    BugSharingPolicy,
     InformationType,
     SharingPermission,
     )
@@ -245,6 +247,19 @@
 
         return set(people).difference(set(result_set))
 
+    def _makeEnumData(self, enums):
+        # Make a dict of data for the a view request cache.
+        result_data = []
+        for x, enum in enumerate(enums):
+            item = dict(
+                index=x,
+                value=enum.name,
+                title=enum.title,
+                description=enum.description
+            )
+            result_data.append(item)
+        return result_data
+
     def getInformationTypes(self, pillar):
         """See `ISharingService`."""
         allowed_types = [
@@ -256,16 +271,37 @@
                 pillar.has_current_commercial_subscription):
             allowed_types.append(InformationType.PROPRIETARY)
 
-        result_data = []
-        for x, policy in enumerate(allowed_types):
-            item = dict(
-                index=x,
-                value=policy.name,
-                title=policy.title,
-                description=policy.description
-            )
-            result_data.append(item)
-        return result_data
+        return self._makeEnumData(allowed_types)
+
+    def getBranchSharingPolicies(self, pillar):
+        """See `ISharingService`."""
+        # Only Products have branch sharing policies.
+        if not IProduct.providedBy(pillar):
+            return []
+        allowed_policies = [BranchSharingPolicy.PUBLIC]
+        # Commercial projects also allow proprietary branches.
+        if pillar.has_current_commercial_subscription:
+            allowed_policies.extend([
+                BranchSharingPolicy.PUBLIC_OR_PROPRIETARY,
+                BranchSharingPolicy.PROPRIETARY_OR_PUBLIC,
+                BranchSharingPolicy.PROPRIETARY])
+
+        return self._makeEnumData(allowed_policies)
+
+    def getBugSharingPolicies(self, pillar):
+        """See `ISharingService`."""
+        # Only Products have bug sharing policies.
+        if not IProduct.providedBy(pillar):
+            return []
+        allowed_policies = [BugSharingPolicy.PUBLIC]
+        # Commercial projects also allow proprietary bugs.
+        if pillar.has_current_commercial_subscription:
+            allowed_policies.extend([
+                BugSharingPolicy.PUBLIC_OR_PROPRIETARY,
+                BugSharingPolicy.PROPRIETARY_OR_PUBLIC,
+                BugSharingPolicy.PROPRIETARY])
+
+        return self._makeEnumData(allowed_policies)
 
     def getSharingPermissions(self):
         """See `ISharingService`."""

=== modified file 'lib/lp/registry/services/tests/test_sharingservice.py'
--- lib/lp/registry/services/tests/test_sharingservice.py	2012-08-08 11:48:29 +0000
+++ lib/lp/registry/services/tests/test_sharingservice.py	2012-08-14 04:59:25 +0000
@@ -20,6 +20,8 @@
     )
 from lp.code.interfaces.branch import IBranch
 from lp.registry.enums import (
+    BranchSharingPolicy,
+    BugSharingPolicy,
     InformationType,
     SharingPermission,
     )
@@ -111,18 +113,21 @@
         for x, permission in enumerate(expected_permissions):
             self.assertEqual(permissions[x]['value'], permission.name)
 
-    def _assert_getInformationTypes(self, pillar, expected_policies):
-        policy_data = self.service.getInformationTypes(pillar)
+    def _assert_enumData(self, expected_enums, enum_data):
         expected_data = []
-        for x, policy in enumerate(expected_policies):
+        for x, enum in enumerate(expected_enums):
             item = dict(
                 index=x,
-                value=policy.name,
-                title=policy.title,
-                description=policy.description
+                value=enum.name,
+                title=enum.title,
+                description=enum.description
             )
             expected_data.append(item)
-        self.assertContentEqual(expected_data, policy_data)
+        self.assertContentEqual(expected_data, enum_data)
+
+    def _assert_getInformationTypes(self, pillar, expected_policies):
+        policy_data = self.service.getInformationTypes(pillar)
+        self._assert_enumData(expected_policies, policy_data)
 
     def test_getInformationTypes_product(self):
         product = self.factory.makeProduct()
@@ -152,6 +157,62 @@
             distro,
             [InformationType.PRIVATESECURITY, InformationType.USERDATA])
 
+    def _assert_getBranchSharingPolicies(self, pillar, expected_policies):
+        policy_data = self.service.getBranchSharingPolicies(pillar)
+        self._assert_enumData(expected_policies, policy_data)
+
+    def test_getBranchSharingPolicies_product(self):
+        product = self.factory.makeProduct()
+        self._assert_getBranchSharingPolicies(
+            product, [BranchSharingPolicy.PUBLIC])
+
+    def test_getBranchSharingPolicies_expired_commercial_product(self):
+        product = self.factory.makeProduct()
+        self.factory.makeCommercialSubscription(product, expired=True)
+        self._assert_getBranchSharingPolicies(
+            product, [BranchSharingPolicy.PUBLIC])
+
+    def test_getBranchSharingPolicies_commercial_product(self):
+        product = self.factory.makeProduct()
+        self.factory.makeCommercialSubscription(product)
+        self._assert_getBranchSharingPolicies(
+            product,
+            [BranchSharingPolicy.PUBLIC,
+             BranchSharingPolicy.PUBLIC_OR_PROPRIETARY,
+             BranchSharingPolicy.PROPRIETARY_OR_PUBLIC,
+             BranchSharingPolicy.PROPRIETARY])
+
+    def test_getBranchSharingPolicies_distro(self):
+        distro = self.factory.makeDistribution()
+        self._assert_getBranchSharingPolicies(distro, [])
+
+    def _assert_getBugSharingPolicies(self, pillar, expected_policies):
+        policy_data = self.service.getBugSharingPolicies(pillar)
+        self._assert_enumData(expected_policies, policy_data)
+
+    def test_getBugSharingPolicies_product(self):
+        product = self.factory.makeProduct()
+        self._assert_getBugSharingPolicies(product, [BugSharingPolicy.PUBLIC])
+
+    def test_getBugSharingPolicies_expired_commercial_product(self):
+        product = self.factory.makeProduct()
+        self.factory.makeCommercialSubscription(product, expired=True)
+        self._assert_getBugSharingPolicies(product, [BugSharingPolicy.PUBLIC])
+
+    def test_getBugSharingPolicies_commercial_product(self):
+        product = self.factory.makeProduct()
+        self.factory.makeCommercialSubscription(product)
+        self._assert_getBugSharingPolicies(
+            product,
+            [BugSharingPolicy.PUBLIC,
+             BugSharingPolicy.PUBLIC_OR_PROPRIETARY,
+             BugSharingPolicy.PROPRIETARY_OR_PUBLIC,
+             BugSharingPolicy.PROPRIETARY])
+
+    def test_getBugSharingPolicies_distro(self):
+        distro = self.factory.makeDistribution()
+        self._assert_getBugSharingPolicies(distro, [])
+
     def test_jsonGranteeData_with_Some(self):
         # jsonGranteeData returns the expected data for a grantee with
         # permissions which include SOME.
@@ -784,13 +845,13 @@
             self.assertIn(another, bug.getDirectSubscribers())
 
     def test_granteeUnsubscribedWhenDeleted(self):
-        # The grantee is unsubscribed from any inaccessible artifacts when their
-        # access is revoked.
+        # The grantee is unsubscribed from any inaccessible artifacts when
+        # their access is revoked.
         self._assert_deleteGranteeRemoveSubscriptions()
 
     def test_granteeUnsubscribedWhenDeletedSelectedPolicies(self):
-        # The grantee is unsubscribed from any inaccessible artifacts when their
-        # access to selected policies is revoked.
+        # The grantee is unsubscribed from any inaccessible artifacts when
+        # their access to selected policies is revoked.
         self._assert_deleteGranteeRemoveSubscriptions(
             [InformationType.USERDATA])
 

=== modified file 'lib/lp/registry/templates/pillar-sharing.pt'
--- lib/lp/registry/templates/pillar-sharing.pt	2012-07-21 03:04:06 +0000
+++ lib/lp/registry/templates/pillar-sharing.pt	2012-08-14 04:59:25 +0000
@@ -3,7 +3,7 @@
   xmlns:tal="http://xml.zope.org/namespaces/tal";
   xmlns:metal="http://xml.zope.org/namespaces/metal";
   xmlns:i18n="http://xml.zope.org/namespaces/i18n";
-  metal:use-macro="view/macro:page/main_only"
+  metal:use-macro="view/macro:page/main_side"
   i18n:domain="launchpad"
 >
 
@@ -31,15 +31,33 @@
         </p>
     </div>
     <ul class="horizontal">
-      <li><a id='add-grantee-link' class='sprite add js-action' href="#">Share
+      <li><a id='add-grantee-link' class='sprite add js-action hidden' href="#">Share
         with someone</a></li>
     </ul>
 
     <div tal:define="batch_navigator view/grantees">
       <tal:granteelisting content="structure batch_navigator/@@+grantee-table-view" />
     </div>
-
   </div>
-
+  <tal:side metal:fill-slot="side">
+      <div id="branch-sharing-policy-portlet" class="portlet"
+           tal:condition="view/branch_sharing_policies">
+           <span id="branch-sharing-policy">
+             Sharing policy for this project's branches:&nbsp;
+             <strong><span class="value"></span></strong>
+             <a class="editicon sprite edit action-icon">Edit</a>
+           </span>
+          <div id="branch-sharing-policy-description" style="padding-top: 5px"></div>
+      </div>
+      <div id="bug-sharing-policy-portlet" class="portlet"
+           tal:condition="view/bug_sharing_policies">
+           <span id="bug-sharing-policy">
+             Sharing policy for this project's bugs:&nbsp;
+             <strong><span class="value"></span></strong>
+             <a class="editicon sprite edit action-icon">Edit</a>
+           </span>
+          <div id="bug-sharing-policy-description" style="padding-top: 5px"></div>
+      </div>
+  </tal:side>
 </body>
 </html>

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-08-10 06:40:08 +0000
+++ lib/lp/testing/factory.py	2012-08-14 04:59:25 +0000
@@ -955,7 +955,8 @@
         licenses=None, owner=None, registrant=None,
         title=None, summary=None, official_malone=None,
         translations_usage=None, bug_supervisor=None, private_bugs=False,
-        driver=None, security_contact=None, icon=None):
+        driver=None, security_contact=None, icon=None,
+        bug_sharing_policy=None, branch_sharing_policy=None):
         """Create and return a new, arbitrary Product."""
         if owner is None:
             owner = self.makePerson()
@@ -998,6 +999,10 @@
             naked_product.security_contact = security_contact
         if private_bugs:
             naked_product.private_bugs = private_bugs
+        if branch_sharing_policy:
+            naked_product.branch_sharing_policy = branch_sharing_policy
+        if bug_sharing_policy:
+            naked_product.bug_sharing_policy = bug_sharing_policy
         return product
 
     def makeProductSeries(self, product=None, name=None, owner=None,


Follow ups