Merge lp:~thumper/launchpad/inline-lifecycle-status-edit into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Merged at revision: not available
Proposed branch: lp:~thumper/launchpad/inline-lifecycle-status-edit
Merge into: lp:launchpad
Diff against target: 317 lines
9 files modified
lib/canonical/launchpad/icing/style.css (+6/-6)
lib/canonical/launchpad/javascript/code/branchstatus.js (+48/-0)
lib/canonical/launchpad/windmill/testing/lpuser.py (+7/-0)
lib/lp/code/browser/branch.py (+29/-2)
lib/lp/code/browser/configure.zcml (+7/-0)
lib/lp/code/stories/branches/xx-branch-edit.txt (+1/-1)
lib/lp/code/templates/branch-index.pt (+16/-0)
lib/lp/code/templates/branch-information.pt (+9/-4)
lib/lp/code/windmill/tests/test_branch_index.py (+64/-0)
To merge this branch: bzr merge lp:~thumper/launchpad/inline-lifecycle-status-edit
Reviewer Review Type Date Requested Status
Deryck Hodge (community) code js Approve
Paul Hummer Pending
Review via email: mp+12707@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

This is the branch that adds the lazr.js popup chooser for the branch status.

Go for gold

Revision history for this message
Deryck Hodge (deryck) wrote :

Looks good. As we talked about together, drop the ending comma in the JS object literal, and you may not need to import node in the js file, but otherwise it looks very solid.

Cheers,
deryck

review: Approve (code js)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/icing/style.css'
2--- lib/canonical/launchpad/icing/style.css 2009-10-06 17:45:46 +0000
3+++ lib/canonical/launchpad/icing/style.css 2009-10-10 23:06:17 +0000
4@@ -1851,12 +1851,12 @@
5 color: #d18b39;
6 }
7
8-.branchstatusMATURE {color: #090;}
9-.branchstatusDEVELOPMENT {color: #900;}
10-.branchstatusEXPERIMENTAL {color: #930;}
11-.branchstatusMERGED {color: #666;}
12-.branchstatusABANDONED {color: #666;}
13-.branchstatusNEW {color: black;}
14+.branchstatusMATURE, .branchstatusMATURE a {color: #090;}
15+.branchstatusDEVELOPMENT, .branchstatusDEVELOPMENT a {color: #900;}
16+.branchstatusEXPERIMENTAL, .branchstatusEXPERIMENTAL a {color: #930;}
17+.branchstatusMERGED, .branchstatusMERGED a {color: #666;}
18+.branchstatusABANDONED, .branchstatusABANDONED a {color: #666;}
19+.branchstatusNEW, .branchstatusNEW a {color: black;}
20
21 .voteAPPROVE {color: green;}
22 .voteNEEDS_FIXING {color: #930;}
23
24=== added file 'lib/canonical/launchpad/javascript/code/branchstatus.js'
25--- lib/canonical/launchpad/javascript/code/branchstatus.js 1970-01-01 00:00:00 +0000
26+++ lib/canonical/launchpad/javascript/code/branchstatus.js 2009-10-10 23:06:16 +0000
27@@ -0,0 +1,48 @@
28+/** Copyright (c) 2009, Canonical Ltd. All rights reserved.
29+ *
30+ * Code for handling the update of the branch status.
31+ *
32+ * @module branchstatus
33+ * @requires node, lazr.choiceedit, lp.client.plugins
34+ */
35+
36+YUI.add('code.branchstatus', function(Y) {
37+
38+Y.branchstatus = Y.namespace('code.branchstatus');
39+
40+/*
41+ * Connect the branch status to the javascript events.
42+ */
43+Y.branchstatus.connect_status = function(conf) {
44+
45+ var status_content = Y.get('#branch-details-status-value');
46+
47+ if
48+ (conf.user_can_edit_status) {
49+ var status_choice_edit = new Y.ChoiceSource({
50+ contentBox: status_content,
51+ value: conf.status_value,
52+ title: 'Change status to',
53+ items: conf.status_widget_items});
54+ status_choice_edit.showError = function(err) {
55+ display_error(null, err);
56+ };
57+ status_choice_edit.on('save', function(e) {
58+ var cb = status_choice_edit.get('contentBox');
59+ Y.Array.each(conf.status_widget_items, function(item) {
60+ if (item.value == status_choice_edit.get('value')) {
61+ cb.query('span').addClass(item.css_class);
62+ } else {
63+ cb.query('span').removeClass(item.css_class);
64+ }
65+ });
66+ });
67+ status_choice_edit.plug({
68+ fn: Y.lp.client.plugins.PATCHPlugin, cfg: {
69+ patch: 'lifecycle_status',
70+ resource: conf.branch_path}});
71+ status_choice_edit.render();
72+ }
73+};
74+
75+}, '0.1', {requires: ['node', 'lazr.choiceedit', 'lp.client.plugins']});
76
77=== modified file 'lib/canonical/launchpad/windmill/testing/lpuser.py'
78--- lib/canonical/launchpad/windmill/testing/lpuser.py 2009-08-19 11:13:26 +0000
79+++ lib/canonical/launchpad/windmill/testing/lpuser.py 2009-10-10 23:06:17 +0000
80@@ -53,6 +53,13 @@
81 client.waits.forPageLoad(timeout=u'100000')
82
83
84+def login_person(person, password, client):
85+ """Create a LaunchpadUser for a person and password."""
86+ user = LaunchpadUser(
87+ person.displayname, person.preferredemail.email, password)
88+ user.ensure_login(client)
89+
90+
91 # Well Known Users
92 ANONYMOUS = AnonymousUser()
93
94
95=== modified file 'lib/lp/code/browser/branch.py'
96--- lib/lp/code/browser/branch.py 2009-10-09 15:44:36 +0000
97+++ lib/lp/code/browser/branch.py 2009-10-10 23:06:17 +0000
98@@ -9,6 +9,7 @@
99 'BranchAddView',
100 'BranchContextMenu',
101 'BranchDeletionView',
102+ 'BranchEditStatusView',
103 'BranchEditView',
104 'BranchEditWhiteboardView',
105 'BranchRequestImportView',
106@@ -71,10 +72,12 @@
107 from canonical.lazr.utils import smartquote
108 from canonical.widgets.branch import TargetBranchWidget
109 from canonical.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
110+from canonical.widgets.lazrjs import vocabulary_to_choice_edit_items
111
112 from lp.bugs.interfaces.bug import IBug
113 from lp.code.browser.branchref import BranchRef
114-from lp.code.enums import BranchType, UICreatableBranchType
115+from lp.code.enums import (
116+ BranchLifecycleStatus, BranchType, UICreatableBranchType)
117 from lp.code.interfaces.branch import (
118 BranchCreationForbidden, BranchExists, IBranch)
119 from lp.code.interfaces.branchmergeproposal import (
120@@ -223,7 +226,13 @@
121 facet = 'branches'
122 links = [
123 'associations', 'add_subscriber', 'browse_revisions', 'link_bug',
124- 'link_blueprint', 'register_merge', 'source', 'subscription']
125+ 'link_blueprint', 'register_merge', 'source', 'subscription',
126+ 'edit_status']
127+
128+ @enabled_with_permission('launchpad.Edit')
129+ def edit_status(self):
130+ text = 'Change branch status'
131+ return Link('+edit-status', text, icon='edit')
132
133 def browse_revisions(self):
134 """Return a link to the branch's revisions on codebrowse."""
135@@ -558,6 +567,18 @@
136 # Actually only ProductSeries currently do that.
137 return list(self.context.getProductSeriesPushingTranslations())
138
139+ @property
140+ def status_config(self):
141+ """The config to configure the ChoiceSource JS widget."""
142+ return simplejson.dumps({
143+ 'status_widget_items': vocabulary_to_choice_edit_items(
144+ BranchLifecycleStatus,
145+ css_class_prefix='branchstatus'),
146+ 'status_value': self.context.lifecycle_status.title,
147+ 'user_can_edit_status': check_permission('launchpad.Edit', self.context),
148+ 'branch_path': '/' + self.context.unique_name,
149+ })
150+
151
152 class DecoratedMergeProposal:
153 """Provide some additional attributes to a normal branch merge proposal.
154@@ -701,6 +722,12 @@
155 field_names = ['whiteboard']
156
157
158+class BranchEditStatusView(BranchEditFormView):
159+ """A view for editing the lifecycle status only."""
160+
161+ field_names = ['lifecycle_status']
162+
163+
164 class BranchMirrorStatusView(LaunchpadFormView):
165 """This view displays the mirror status of a branch.
166
167
168=== modified file 'lib/lp/code/browser/configure.zcml'
169--- lib/lp/code/browser/configure.zcml 2009-09-22 18:45:02 +0000
170+++ lib/lp/code/browser/configure.zcml 2009-10-10 23:06:16 +0000
171@@ -444,6 +444,13 @@
172 permission="launchpad.AnyPerson"
173 template="../../app/templates/generic-edit.pt"/>
174 <browser:page
175+ name="+edit-status"
176+ for="lp.code.interfaces.branch.IBranch"
177+ class="lp.code.browser.branch.BranchEditStatusView"
178+ facet="branches"
179+ permission="launchpad.Edit"
180+ template="../../app/templates/generic-edit.pt"/>
181+ <browser:page
182 name="+edit"
183 for="lp.code.interfaces.branch.IBranch"
184 class="lp.code.browser.branch.BranchEditView"
185
186=== modified file 'lib/lp/code/stories/branches/xx-branch-edit.txt'
187--- lib/lp/code/stories/branches/xx-branch-edit.txt 2009-09-22 19:04:02 +0000
188+++ lib/lp/code/stories/branches/xx-branch-edit.txt 2009-10-10 23:06:17 +0000
189@@ -107,7 +107,7 @@
190 'http://code.launchpad.dev/~name12/gnome-terminal/klingon'
191 >>> contents = browser.contents
192 >>> status_tag = find_tag_by_id(contents, 'branch-details-status-value')
193- >>> print status_tag.renderContents()
194+ >>> print extract_text(status_tag)
195 Merged
196
197 Set the branch status back to its initial state.
198
199=== modified file 'lib/lp/code/templates/branch-index.pt'
200--- lib/lp/code/templates/branch-index.pt 2009-09-30 12:14:24 +0000
201+++ lib/lp/code/templates/branch-index.pt 2009-10-10 23:06:17 +0000
202@@ -37,6 +37,22 @@
203 tal:define="lp_js string:${icingroot}/build"
204 tal:attributes="src string:${lp_js}/code/branchlinks.js">
205 </script>
206+ <script type="text/javascript"
207+ tal:condition="devmode"
208+ tal:define="lp_js string:${icingroot}/build"
209+ tal:attributes="src string:${lp_js}/code/branchstatus.js">
210+ </script>
211+ <script type="text/javascript"
212+ tal:content="string:
213+ YUI().use('node', 'event', 'widget', 'plugin', 'overlay',
214+ 'lazr.choiceedit', 'code.branchstatus', function(Y) {
215+ Y.on('load',
216+ function(e) {
217+ Y.branchstatus.connect_status(${view/status_config});
218+ },
219+ window);
220+ });
221+ "/>
222 </metal:block>
223
224 <body>
225
226=== modified file 'lib/lp/code/templates/branch-information.pt'
227--- lib/lp/code/templates/branch-information.pt 2009-09-22 17:03:24 +0000
228+++ lib/lp/code/templates/branch-information.pt 2009-10-10 23:06:16 +0000
229@@ -22,10 +22,15 @@
230
231 <dl id="status">
232 <dt>Status:</dt>
233- <dd
234- id="branch-details-status-value"
235- tal:attributes="class string:branchstatus${context/lifecycle_status/name}"
236- tal:content="structure context/lifecycle_status/title" />
237+ <dd>
238+ <span id="branch-details-status-value">
239+ <span tal:attributes="class string:value branchstatus${context/lifecycle_status/name}"
240+ tal:content="structure context/lifecycle_status/title" />&nbsp;
241+ <a href="+edit-status">
242+ <img class="editicon" src="/@@/edit"/>
243+ </a>
244+ </span>
245+ </dd>
246 </dl>
247 </div>
248
249
250=== added file 'lib/lp/code/windmill/tests/test_branch_index.py'
251--- lib/lp/code/windmill/tests/test_branch_index.py 1970-01-01 00:00:00 +0000
252+++ lib/lp/code/windmill/tests/test_branch_index.py 2009-10-10 23:06:17 +0000
253@@ -0,0 +1,64 @@
254+# Copyright 2009 Canonical Ltd. This software is licensed under the
255+# GNU Affero General Public License version 3 (see the file LICENSE).
256+
257+"""Test for the main branch page."""
258+
259+__metaclass__ = type
260+__all__ = []
261+
262+import transaction
263+import unittest
264+
265+import windmill
266+from windmill.authoring import WindmillTestClient
267+
268+from canonical.launchpad.windmill.testing.constants import (
269+ PAGE_LOAD, SLEEP)
270+from canonical.launchpad.windmill.testing.lpuser import login_person
271+from lp.code.windmill.testing import CodeWindmillLayer
272+from lp.testing import TestCaseWithFactory
273+
274+
275+class TestBranchStatus(TestCaseWithFactory):
276+
277+ layer = CodeWindmillLayer
278+
279+ def test_inline_branch_status_setting(self):
280+ """Test branch bug links."""
281+ eric = self.factory.makePerson(
282+ name="eric", displayname="Eric the Viking", password="test",
283+ email="eric@example.com")
284+ branch = self.factory.makeBranch(owner=eric)
285+ transaction.commit()
286+
287+ client = WindmillTestClient("Branch status setting")
288+
289+ login_person(eric, "test", client)
290+
291+ start_url = (
292+ windmill.settings['TEST_URL'] + branch.unique_name)
293+ client.open(url=start_url)
294+ client.waits.forPageLoad(timeout=PAGE_LOAD)
295+
296+ # Click on the element containing the branch status.
297+ client.click(id=u'branch-details-status-value')
298+ client.waits.forElement(
299+ xpath=u'//div[contains(@class, "yui-ichoicelist-content")]')
300+
301+ # Change the status to experimental.
302+ client.click(link=u'Experimental')
303+ client.waits.sleep(milliseconds=SLEEP)
304+
305+ client.asserts.assertText(
306+ xpath=u'//span[@id="branch-details-status-value"]/span',
307+ validator=u'Experimental')
308+
309+ # Reload the page and make sure the change sticks.
310+ client.open(url=start_url)
311+ client.asserts.assertText(
312+ xpath=u'//span[@id="branch-details-status-value"]/span',
313+ validator=u'Experimental')
314+
315+
316+def test_suite():
317+ return unittest.TestLoader().loadTestsFromName(__name__)