Merge lp:~wallyworld/launchpad/branch-infotype-portlet-1040999 into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Ian Booth
Approved revision: no longer in the source branch.
Merged at revision: 15875
Proposed branch: lp:~wallyworld/launchpad/branch-infotype-portlet-1040999
Merge into: lp:launchpad
Diff against target: 348 lines (+95/-59)
5 files modified
lib/lp/code/browser/branch.py (+31/-13)
lib/lp/code/browser/configure.zcml (+7/-0)
lib/lp/code/browser/tests/test_branch.py (+45/-40)
lib/lp/code/stories/branches/xx-branch-edit-privacy.txt (+4/-4)
lib/lp/code/templates/branch-portlet-privacy.pt (+8/-2)
To merge this branch: bzr merge lp:~wallyworld/launchpad/branch-infotype-portlet-1040999
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+121404@code.launchpad.net

Commit message

Allow branch's information type to be changed from the privacy portlet; always allow branch to be marked as security related.

Description of the change

== Implementation ==

This branch does 2 things:

1. add an edit link to the branch privacy portlet to allow information type to be edited

This branch does not wire up any javascript (that will be done next branch). So the user clicks on the edit link and is taken to a new edit page where they can edit the information type. ie the same non js infrastructure as used elsewhere for lifecycle status etc. The view used to process the information type change does have the ajax processing in place so the next branch will be only javascript.

2. change the algorithm used by getInformationTypesToShow() to determine the allowed information types

Previously, a branch could not be made private/public security unless there were such bugs linked. Now, a branch can always be marked as security. The hooks for checking linked bugs have been left in place though because even though not used right now, very soon user data branches will only be allowed when there are user data linked bugs.

== Tests ==

Add test to TestBranchEditView to check the XHR updating of information type work correctly.

Update tests in TestBranchEditViewInformationTypes to reflect new getInformationTypesToShow algorithm.

Update TestBranchPrivacyPortlet to check for edit link.

== Lint ==

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/browser/branch.py
  lib/lp/code/browser/configure.zcml
  lib/lp/code/browser/tests/test_branch.py
  lib/lp/code/templates/branch-portlet-privacy.pt

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

16 + @enabled_with_permission('launchpad.Edit')
17 + def visibility(self):
18 + """Return the 'Set information type' Link."""
19 + text = 'Change information type'
20 + return Link('+edit-information-type', text)

Should this be information_type instead of visibility?

55 InformationType.PUBLICSECURITY,
56 InformationType.PRIVATESECURITY,
57 )

Could you fix these to be in the traditional order (between PUBLIC and USERDATA)?

129 + class="lp.code.browser.branch.BranchEditInformationTypeView"
130 + permission="launchpad.Edit"

This should probably be launchpad.Moderate. Curtis added it last week to let project owners change the information type too.

review: Approve (code)
Revision history for this message
Ian Booth (wallyworld) wrote :

Thanks!

> 16 + @enabled_with_permission('launchpad.Edit')
> 17 + def visibility(self):
> 18 + """Return the 'Set information type' Link."""
> 19 + text = 'Change information type'
> 20 + return Link('+edit-information-type', text)
>
> Should this be information_type instead of visibility?
>

I considered it but bugs uses 'visibility' for exactly the same thing and I wanted to be consistent.
How strongly do you feel about changing it?

Revision history for this message
William Grant (wgrant) wrote :

On 28/08/12 13:39, Ian Booth wrote:
> Thanks!
>
>> 16 + @enabled_with_permission('launchpad.Edit')
>> 17 + def visibility(self):
>> 18 + """Return the 'Set information type' Link."""
>> 19 + text = 'Change information type'
>> 20 + return Link('+edit-information-type', text)
>>
>> Should this be information_type instead of visibility?
>>
>
> I considered it but bugs uses 'visibility' for exactly the same thing and I wanted to be consistent.
> How strongly do you feel about changing it?

Not strongly at all, but Bugs probably isn't good precedent, as the name
derives from the old hybrid privacy/security portlet.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branch.py'
2--- lib/lp/code/browser/branch.py 2012-08-16 05:55:48 +0000
3+++ lib/lp/code/browser/branch.py 2012-08-29 04:50:28 +0000
4@@ -308,13 +308,19 @@
5 'add_subscriber', 'browse_revisions', 'create_recipe', 'link_bug',
6 'link_blueprint', 'register_merge', 'source', 'subscription',
7 'edit_status', 'edit_import', 'upgrade_branch', 'view_recipes',
8- 'create_queue']
9+ 'create_queue', 'visibility']
10
11 @enabled_with_permission('launchpad.Edit')
12 def edit_status(self):
13 text = 'Change branch status'
14 return Link('+edit-status', text, icon='edit')
15
16+ @enabled_with_permission('launchpad.Moderate')
17+ def visibility(self):
18+ """Return the 'Set information type' Link."""
19+ text = 'Change information type'
20+ return Link('+edit-information-type', text)
21+
22 def browse_revisions(self):
23 """Return a link to the branch's revisions on codebrowse."""
24 text = 'All revisions'
25@@ -430,8 +436,8 @@
26 return branch.url
27
28
29-class BranchView(LaunchpadView, FeedsMixin, BranchMirrorMixin,
30- InformationTypePortletMixin):
31+class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
32+ LaunchpadView):
33
34 feed_types = (
35 BranchFeedLink,
36@@ -731,22 +737,21 @@
37 # If we're stacked on a private branch, only show that
38 # information type.
39 if self.context.stacked_on and self.context.stacked_on.private:
40- shown_types = set([self.context.stacked_on.information_type])
41+ shown_types = {self.context.stacked_on.information_type}
42 else:
43 shown_types = (
44 InformationType.PUBLIC,
45+ InformationType.PUBLICSECURITY,
46+ InformationType.PRIVATESECURITY,
47 InformationType.USERDATA,
48 InformationType.PROPRIETARY,
49 InformationType.EMBARGOED,
50 )
51
52- # We only show Private Security and Public Security
53- # if the branch is linked to a bug with one of those types,
54- # as they're confusing and not generally useful otherwise.
55- # Once Proprietary is fully deployed, it should be added here.
56+ # XXX Once Branch Visibility Policies are removed, we only want to
57+ # show Private (USERDATA) if the branch is linked to such a bug.
58 hidden_types = (
59- InformationType.PUBLICSECURITY,
60- InformationType.PRIVATESECURITY,
61+ # InformationType.USERDATA,
62 )
63 if set(allowed_types).intersection(hidden_types):
64 params = BugTaskSearchParams(
65@@ -800,7 +805,8 @@
66 """See `LaunchpadFormView`"""
67 return {self.schema: self.context}
68
69- @action('Change Branch', name='change')
70+ @action('Change Branch', name='change',
71+ failure=LaunchpadFormView.ajax_failure_handler)
72 def change_action(self, action, data):
73 # If the owner or product has changed, add an explicit notification.
74 # We take our own snapshot here to make sure that the snapshot records
75@@ -849,9 +855,15 @@
76 # was in fact a change.
77 self.context.date_last_modified = UTC_NOW
78
79+ if self.request.is_ajax:
80+ return ''
81+
82 @property
83 def next_url(self):
84- return canonical_url(self.context)
85+ """Return the next URL to call when this call completes."""
86+ if not self.request.is_ajax:
87+ return canonical_url(self.context)
88+ return None
89
90 cancel_url = next_url
91
92@@ -868,6 +880,12 @@
93 field_names = ['lifecycle_status']
94
95
96+class BranchEditInformationTypeView(BranchEditFormView):
97+ """A view for editing the information type only."""
98+
99+ field_names = ['information_type']
100+
101+
102 class BranchMirrorStatusView(LaunchpadFormView):
103 """This view displays the mirror status of a branch.
104
105@@ -1114,9 +1132,9 @@
106 def validate(self, data):
107 # Check that we're not moving a team branch to the +junk
108 # pseudo project.
109- owner = data['owner']
110 if 'name' in data:
111 # Only validate if the name has changed or the owner has changed.
112+ owner = data['owner']
113 if ((data['name'] != self.context.name) or
114 (owner != self.context.owner)):
115 # We only allow moving within the same branch target for now.
116
117=== modified file 'lib/lp/code/browser/configure.zcml'
118--- lib/lp/code/browser/configure.zcml 2012-05-29 06:05:16 +0000
119+++ lib/lp/code/browser/configure.zcml 2012-08-29 04:50:28 +0000
120@@ -480,6 +480,13 @@
121 permission="launchpad.Edit"
122 template="../../app/templates/generic-edit.pt"/>
123 <browser:page
124+ name="+edit-information-type"
125+ for="lp.code.interfaces.branch.IBranch"
126+ layer="lp.code.publisher.CodeLayer"
127+ class="lp.code.browser.branch.BranchEditInformationTypeView"
128+ permission="launchpad.Moderate"
129+ template="../../app/templates/generic-edit.pt"/>
130+ <browser:page
131 name="+edit"
132 for="lp.code.interfaces.branch.IBranch"
133 layer="lp.code.publisher.CodeLayer"
134
135=== modified file 'lib/lp/code/browser/tests/test_branch.py'
136--- lib/lp/code/browser/tests/test_branch.py 2012-08-27 23:58:18 +0000
137+++ lib/lp/code/browser/tests/test_branch.py 2012-08-29 04:50:28 +0000
138@@ -71,7 +71,10 @@
139 setupBrowser,
140 setupBrowserForUser,
141 )
142-from lp.testing.views import create_initialized_view
143+from lp.testing.views import (
144+ create_initialized_view,
145+ create_view,
146+ )
147
148
149 class TestBranchMirrorHidden(TestCaseWithFactory):
150@@ -357,7 +360,8 @@
151 def test_linked_bugs_series_branch_query_scaling(self):
152 # As we add linked bugs, the query count for a branch index page stays
153 # constant.
154- product = self.factory.makeProduct()
155+ product = self.factory.makeProduct(
156+ branch_sharing_policy=BranchSharingPolicy.PUBLIC)
157 branch = self.factory.makeProductBranch(product=product)
158 browses_under_limit = BrowsesWithQueryLimit(54, branch.owner)
159 with person_logged_in(product.owner):
160@@ -964,7 +968,7 @@
161 admin = admins.teamowner
162 browser = self.getUserBrowser(
163 canonical_url(branch) + '/+edit', user=admin)
164- browser.getControl("Private").click()
165+ browser.getControl("Private", index=1).click()
166 browser.getControl("Change Branch").click()
167 with person_logged_in(person):
168 self.assertEqual(
169@@ -986,6 +990,29 @@
170 self.assertRaises(
171 LookupError, browser.getControl, "Information Type")
172
173+ def test_edit_view_ajax_render(self):
174+ # An information type change request is processed as expected when an
175+ # XHR request is made to the view.
176+ person = self.factory.makePerson()
177+ branch = self.factory.makeProductBranch(owner=person)
178+
179+ extra = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
180+ request = LaunchpadTestRequest(
181+ method='POST', form={
182+ 'field.actions.change': 'Change Branch',
183+ 'field.information_type': 'PUBLICSECURITY'},
184+ **extra)
185+ with person_logged_in(person):
186+ view = create_view(
187+ branch, name='+edit-information-type',
188+ request=request, principal=person)
189+ request.traversed_objects = [person, branch.product, branch, view]
190+ view.initialize()
191+ result = view.render()
192+ self.assertEqual('', result)
193+ self.assertEqual(
194+ branch.information_type, InformationType.PUBLICSECURITY)
195+
196
197 class TestBranchEditViewInformationTypes(TestCaseWithFactory):
198 """Tests for BranchEditView.getInformationTypesToShow."""
199@@ -1000,28 +1027,14 @@
200 self.assertContentEqual(types, view.getInformationTypesToShow())
201
202 def test_public_branch(self):
203- # A normal public branch on a public project can only be public.
204- # We don't show information types like Public Security
205- # unless there's a linked branch of that type, as they're not
206- # useful or unconfusing otherwise.
207+ # A normal public branch on a public project can only be a public
208+ # information type.
209 # The model doesn't enforce this, so it's just a UI thing.
210 branch = self.factory.makeBranch(
211 information_type=InformationType.PUBLIC)
212- self.assertShownTypes([InformationType.PUBLIC], branch)
213-
214- def test_public_branch_with_security_bug(self):
215- # A public branch can be set to Public Security if it has a
216- # linked Public Security bug. The project policy doesn't
217- # allow private branches, so Private Security and Private
218- # are unavailable.
219- branch = self.factory.makeBranch(
220- information_type=InformationType.PUBLIC)
221- bug = self.factory.makeBug(
222- information_type=InformationType.PUBLICSECURITY)
223- with admin_logged_in():
224- branch.linkBug(bug, branch.owner)
225 self.assertShownTypes(
226- [InformationType.PUBLIC, InformationType.PUBLICSECURITY],
227+ [InformationType.PUBLIC,
228+ InformationType.PUBLICSECURITY],
229 branch)
230
231 def test_branch_with_disallowed_type(self):
232@@ -1033,7 +1046,10 @@
233 branch = self.factory.makeBranch(
234 product=product, information_type=InformationType.PROPRIETARY)
235 self.assertShownTypes(
236- [InformationType.PUBLIC, InformationType.PROPRIETARY], branch)
237+ [InformationType.PUBLIC,
238+ InformationType.PUBLICSECURITY,
239+ InformationType.PROPRIETARY],
240+ branch)
241
242 def test_stacked_on_private(self):
243 # A branch stacked on a private branch has its choices limited
244@@ -1060,24 +1076,10 @@
245 branch.product.setBranchVisibilityTeamPolicy(
246 branch.owner, BranchVisibilityRule.PRIVATE)
247 self.assertShownTypes(
248- [InformationType.PUBLIC, InformationType.USERDATA], branch)
249-
250- def test_private_branch_with_security_bug(self):
251- # Branches on projects that allow private branches can use the
252- # Private Security information type if they have a security
253- # bug linked.
254- branch = self.factory.makeBranch(
255- information_type=InformationType.PUBLIC)
256- with admin_logged_in():
257- branch.product.setBranchVisibilityTeamPolicy(
258- branch.owner, BranchVisibilityRule.PRIVATE)
259- bug = self.factory.makeBug(
260- information_type=InformationType.PUBLICSECURITY)
261- with admin_logged_in():
262- branch.linkBug(bug, branch.owner)
263- self.assertShownTypes(
264- [InformationType.PUBLIC, InformationType.PUBLICSECURITY,
265- InformationType.PRIVATESECURITY, InformationType.USERDATA],
266+ [InformationType.PUBLIC,
267+ InformationType.PUBLICSECURITY,
268+ InformationType.PRIVATESECURITY,
269+ InformationType.USERDATA],
270 branch)
271
272 def test_branch_for_project_with_embargoed_and_proprietary(self):
273@@ -1140,6 +1142,7 @@
274 owner=owner, information_type=InformationType.USERDATA)
275 with person_logged_in(owner):
276 view = create_initialized_view(branch, '+portlet-privacy')
277+ edit_url = '/' + branch.unique_name + '/+edit-information-type'
278 soup = BeautifulSoup(view.render())
279 information_type = soup.find('strong')
280 description = soup.find('div', id='information-type-description')
281@@ -1147,3 +1150,5 @@
282 InformationType.USERDATA.title, information_type.renderContents())
283 self.assertTextMatchesExpressionIgnoreWhitespace(
284 InformationType.USERDATA.description, description.renderContents())
285+ self.assertIsNotNone(
286+ soup.find('a', id='privacy-link', attrs={'href': edit_url}))
287
288=== modified file 'lib/lp/code/stories/branches/xx-branch-edit-privacy.txt'
289--- lib/lp/code/stories/branches/xx-branch-edit-privacy.txt 2012-07-17 14:10:53 +0000
290+++ lib/lp/code/stories/branches/xx-branch-edit-privacy.txt 2012-08-29 04:50:28 +0000
291@@ -39,7 +39,7 @@
292
293 >>> admin_browser.open('http://code.launchpad.dev/~eric/fooix/trunk')
294 >>> admin_browser.getLink('Change branch details').click()
295- >>> admin_browser.getControl('Private').click()
296+ >>> admin_browser.getControl('Private', index=1).click()
297 >>> admin_browser.getControl('Change Branch').click()
298
299 The branch is now private, so it assumes the standard private presentation.
300@@ -68,7 +68,7 @@
301 >>> browser = setupBrowser(auth='Basic test@canonical.com:test')
302 >>> browser.open('http://code.launchpad.dev/~name12/firefox/main')
303 >>> browser.getLink('Change branch details').click()
304- >>> browser.getControl('Private').selected = True
305+ >>> browser.getControl('Private', index=1).selected = True
306 >>> browser.getControl('Change Branch').click()
307
308 Since the branch is now private, it will have the standard Launchpad
309@@ -81,7 +81,7 @@
310 reverts to its normal presentation.
311
312 >>> browser.getLink('Change branch details').click()
313- >>> browser.getControl('Public').selected = True
314+ >>> browser.getControl('Public', index=0).selected = True
315 >>> browser.getControl('Change Branch').click()
316 >>> print extract_text(find_tag_by_id(browser.contents, 'privacy'))
317 This branch contains Public information...
318@@ -103,7 +103,7 @@
319 Mark the public branch private.
320
321 >>> browser.getLink('Change branch details').click()
322- >>> browser.getControl('Private').selected = True
323+ >>> browser.getControl('Private', index=1).selected = True
324 >>> browser.getControl('Change Branch').click()
325
326 The branch is now private, so it assumes the standard private presentation.
327
328=== modified file 'lib/lp/code/templates/branch-portlet-privacy.pt'
329--- lib/lp/code/templates/branch-portlet-privacy.pt 2012-06-20 05:25:44 +0000
330+++ lib/lp/code/templates/branch-portlet-privacy.pt 2012-08-29 04:50:28 +0000
331@@ -6,9 +6,15 @@
332 tal:attributes="
333 class python: path('context/private') and 'portlet private' or 'portlet public'
334 "
335+ tal:define="link context/menu:context/visibility"
336 >
337 <span id="information-type-summary"
338 tal:attributes="class view/information_type_css;">This branch
339- contains <strong id="information-type" tal:content="view/information_type"></strong> information</span>
340- <div id="information-type-description" style="padding-top: 5px" tal:content="view/information_type_description"></div>
341+ contains
342+ <strong id="information-type" tal:content="view/information_type"></strong>
343+ information</span>&nbsp;<a class="sprite edit action-icon" id="privacy-link"
344+ tal:attributes="href link/path" tal:condition="link/enabled"
345+ >Edit</a>
346+ <div id="information-type-description" style="padding-top: 5px"
347+ tal:content="view/information_type_description"></div>
348 </div>