Merge lp:~intellectronica/launchpad/bug-531963 into lp:launchpad

Proposed by Eleanor Berger
Status: Merged
Approved by: Eleanor Berger
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~intellectronica/launchpad/bug-531963
Merge into: lp:launchpad
Diff against target: 193 lines (+148/-3)
3 files modified
lib/canonical/launchpad/javascript/bugs/bugtask-index.js (+78/-3)
lib/lp/bugs/browser/bugtask.py (+1/-0)
lib/lp/bugs/windmill/tests/test_bug_status.py (+69/-0)
To merge this branch: bzr merge lp:~intellectronica/launchpad/bug-531963
Reviewer Review Type Date Requested Status
Michael Nelson (community) ui Approve
Abel Deuring (community) code Approve
Review via email: mp+21628@code.launchpad.net

Commit message

When changing bug status, require confirmation from users who are not in the project team.

Description of the change

This branch adds a confirmation step when a user who has no privileges in a project tries to edit the bug status. It does that by creating a specialised version of the ChoiceEdit widget that displays a javascript confirm dialog before saving if a new parameter is set. The only gotcha here is the use of confirmAnswer in the windmill test. This is how we simulate confirming (or cancelling). Windmill overrides all calls to modal dialogs in order to avoid blocking execution and uses the value in confirmAnswer instead. This value can't be set from python directly, so we use execJS to inject into the JS environment.

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

Hi Tom,

the code is fine, but "make lint" has a few complaints. Could you fix them?

review: Approve (code)
Revision history for this message
Eleanor Berger (intellectronica) wrote :

Michael, thanks for taking on this UI review. You can see a screenshot at http://launchpadlibrarian.net/41602788/status-confirmation-screenshot.png or if you want to test it yourself on launchpad.dev, go to a bug logged in a no-priv and try to change a status. This is a pretty simple implementation. I would have liked to do this using a LAZR widget, but the work for implementing this is too much, and this should solve the immediate problem and only happen infrequently.

Revision history for this message
Michael Nelson (michael.nelson) wrote :

> Michael, thanks for taking on this UI review. You can see a screenshot at
> http://launchpadlibrarian.net/41602788/status-confirmation-screenshot.png or
> if you want to test it yourself on launchpad.dev, go to a bug logged in a no-
> priv and try to change a status. This is a pretty simple implementation. I
> would have liked to do this using a LAZR widget, but the work for implementing
> this is too much, and this should solve the immediate problem and only happen
> infrequently.

Hi Tom,

I agree that it would have been nice to use a LAZR widget, but understand that it's not within scope right now. I do however think it's still worth hiding the choice list before bringing up the confirmation dialog, and don't see why you say you'd need to update the widget to do so. Here's a (crude) diff that gives the behaviour that I mentioned:

http://pastebin.ubuntu.com/399274/

Other than that, I just mentioned in our conversation the addition of "whether" to the sentence (check with mrevell if you want a second opinion).

Thanks!

IRC conversation:
13:01 < noodles775> intellectronica: why is it that if I'm logged in as cprov (<email address hidden>:cprov) I'm unable to modify the bug status (the mouse-over tells me "Changeable only by a project maintainer or a bug supervisor"), yet I can as no-privs? Is no-privs a bug supervisor without privs in the project?
[snip]
13:07 < intellectronica> noodles775: that's because that bug is assigned to a bugwatch
13:07 < noodles775> Ah ok. Thanks. Two more things:
13:08 < noodles775> First, I think the wording might be missing "whether"? eg. "If you're not certain whether changing the status of this bug is correct,..." What you you think?
13:09 < noodles775> And second, how difficult would it be to first close the overlay before popping up the confirmation?
13:09 < intellectronica> noodles775: as a non-native speaker i'll have to trust you on that. both some equally good to me
13:09 < noodles775> I think that's quite important (a popup within a popup is really bad).
13:09 < intellectronica> noodles775: very difficult
13:09 < noodles775> :(
13:09 < noodles775> intellectronica: couldn't in just be hidden with CSS when you bring up the confirmation?
13:10 < noodles775> (ie. you shouldn't need to change the behaviour of the widget?)
13:10 < intellectronica> that would be a great solution, and even better one would be to handle it within the widget itself. unfortunately both require a complete restructuring of that widget which we can't afford right now
13:11 < intellectronica> noodles775: the problem is the way the widget interacts with the web service. it saves before closing, and changing that would require changing the widget, and quite possibly changing other widgets that use the api patch plugin

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js'
2--- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2010-03-03 00:38:38 +0000
3+++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2010-03-23 10:47:49 +0000
4@@ -1325,13 +1325,14 @@
5 }
6
7 if (conf.user_can_edit_status) {
8- var status_choice_edit = new Y.ChoiceSource({
9+ var status_choice_edit = new BugStatusEdit({
10 contentBox: status_content,
11 value: conf.status_value,
12 title: 'Change status to',
13 items: conf.status_widget_items,
14 elementToFlash: status_content.get('parentNode'),
15- backgroundColor: tr.hasClass('highlight') ? '#FFFF99' : '#FFFFFF'
16+ backgroundColor: tr.hasClass('highlight') ? '#FFFF99' : '#FFFFFF',
17+ requires_confirmation: conf.status_requires_confirmation
18 });
19 status_choice_edit.showError = function(err) {
20 Y.lp.display_error(null, err);
21@@ -1634,6 +1635,80 @@
22 }
23 });
24
25+var BugStatusEdit = function() {
26+ BugStatusEdit.superclass.constructor.apply(this, arguments);
27+}
28+
29+BugStatusEdit.NAME = 'bugstatusedit';
30+BugStatusEdit.NS = 'bugstatusedit';
31+
32+BugStatusEdit.ATTRS = {
33+ requires_confirmation: {
34+ value: false
35+ }
36+};
37+
38+Y.extend(BugStatusEdit, Y.ChoiceSource, {
39+ initializer: function(cfg) {
40+ BugStatusEdit.superclass.initializer.apply(this, arguments);
41+ },
42+
43+ onClick: function(e) {
44+
45+ // Only continue if the down button is the left one.
46+ if (e.button != 1) {
47+ return;
48+ }
49+
50+ this._choice_list = new Y.ChoiceList({
51+ value: this.get("value"),
52+ title: this.get("title"),
53+ items: this.get("items"),
54+ value_location: this.get("value_location"),
55+ progressbar: false
56+ });
57+
58+ var that = this;
59+ this._choice_list.on("valueChosen", function(e) {
60+ if (that.get("requires_confirmation")) {
61+ // Ensure that the choicelist is hidden before the
62+ // confirmation box appears. Note: there must be
63+ // a better way to get the choice list?
64+ cl = Y.get("table.yui-ichoicelist-focused");
65+ cl.replaceClass(
66+ 'yui-ichoicelist-focused', 'yui-ichoicelist-hidden');
67+ var confirmed = confirm([
68+ 'Change bug status?',
69+ '\n\n',
70+ 'Bug status is normally set by the project ',
71+ 'team, based on the current state of the work ',
72+ 'involved in diagnosing and fixing the bug.',
73+ '\n\n',
74+ 'If you\'re not certain whether changing the status of ',
75+ 'this bug is correct, you can cancel and instead ',
76+ 'contact the project team.'].join(''));
77+ } else {
78+ var confirmed = true;
79+ }
80+ if (confirmed) {
81+ that._chosen_value = e.details[0];
82+ that._saveData(e.details[0]);
83+ } else {
84+ return false;
85+ }
86+ });
87+
88+ // Stuff the mouse coordinates into the list object,
89+ // by the time we'll need them, they won't be available.
90+ this._choice_list._mouseX = e.clientX + window.pageXOffset;
91+ this._choice_list._mouseY = e.clientY + window.pageYOffset;
92+
93+ this._choice_list.render();
94+
95+ e.halt();
96+ }
97+});
98+
99 /*
100 * Check if the current user can unsubscribe the person
101 * being subscribed.
102@@ -1861,4 +1936,4 @@
103 "substitute", "widget-position-ext", "lazr.formoverlay",
104 "lazr.anim", "lazr.base", "lazr.overlay",
105 "lazr.choiceedit", "lp.picker", "lp.client.plugins",
106- "lp.subscriber", "lp.errors"]});
107+ "lp.subscriber", "lp.errors", "bugs.status_edit"]});
108
109=== modified file 'lib/lp/bugs/browser/bugtask.py'
110--- lib/lp/bugs/browser/bugtask.py 2010-03-15 17:59:47 +0000
111+++ lib/lp/bugs/browser/bugtask.py 2010-03-23 10:47:49 +0000
112@@ -3437,6 +3437,7 @@
113 None),
114 'user_can_edit_milestone': self.user_can_edit_milestone,
115 'user_can_edit_status': not self.context.bugwatch,
116+ 'status_requires_confirmation': not self.user_can_edit_importance,
117 'user_can_edit_importance': (
118 self.user_can_edit_importance and
119 not self.context.bugwatch)})
120
121=== added file 'lib/lp/bugs/windmill/tests/test_bug_status.py'
122--- lib/lp/bugs/windmill/tests/test_bug_status.py 1970-01-01 00:00:00 +0000
123+++ lib/lp/bugs/windmill/tests/test_bug_status.py 2010-03-23 10:47:49 +0000
124@@ -0,0 +1,69 @@
125+# Copyright 2010 Canonical Ltd. This software is licensed under the
126+# GNU Affero General Public License version 3 (see the file LICENSE).
127+
128+"""Test for the bug status entry."""
129+
130+__metaclass__ = type
131+__all__ = []
132+
133+import unittest
134+
135+from canonical.launchpad.windmill.testing import constants, lpuser
136+from lp.bugs.windmill.testing import BugsWindmillLayer
137+from lp.testing import WindmillTestCase
138+
139+class TestBugStatusConfirmation(WindmillTestCase):
140+
141+ layer = BugsWindmillLayer
142+ suite_name = "Bug status confirmation step test"
143+
144+ def test_bug_status_confirmation(self):
145+ """Test the confirmation step of the bug status entry."""
146+ client = self.client
147+
148+ # Open a bug page and wait for it to finish loading
149+ client.open(url=u'http://bugs.launchpad.dev:8085/bugs/4')
150+ client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
151+ # NO_PRIV must confirm editting the bug status in this project.
152+ lpuser.NO_PRIV.ensure_login(client)
153+ client.waits.forElement(
154+ xpath=u"//div[@class='status-content yui-bugstatusedit-content']//img[@class='editicon']",
155+ timeout=constants.FOR_ELEMENT)
156+
157+ # Initially, the bug status is New.
158+ client.asserts.assertText(
159+ xpath=u"//div[contains(@class, 'status-content')]//a[1]",
160+ validator=u"New")
161+ client.click(
162+ xpath=u"//div[contains(@class, 'status-content')]//img[@class='editicon']")
163+ client.waits.forElement(
164+ id=u'yui-pretty-overlay-modal', timeout=constants.FOR_ELEMENT)
165+ # We open the status editor and change the status to In Progress.
166+ # When a confirmation dialog is shown, we will choose to cancel.
167+ client.commands.execJS(code=u"windmill.confirmAnswer = false")
168+ client.click(
169+ xpath=u"//div[@id='yui-pretty-overlay-modal']"
170+ u"//a[contains(@href, '#In Progress')]")
171+ # The status didn't change.
172+ client.asserts.assertText(
173+ xpath=u"//div[contains(@class, 'status-content')]//a[1]",
174+ validator=u"New")
175+
176+ # We repeat the same sequence.
177+ client.click(
178+ xpath=u"//div[contains(@class, 'status-content')]//img[@class='editicon']")
179+ client.waits.forElement(
180+ id=u'yui-pretty-overlay-modal', timeout=constants.FOR_ELEMENT)
181+ # This time, when the confirmation dialog appears, we will confirm.
182+ client.commands.execJS(code=u"windmill.confirmAnswer = true")
183+ client.click(
184+ xpath=u"//div[@id='yui-pretty-overlay-modal']"
185+ u"//a[contains(@href, '#In Progress')]")
186+ # The status changed to In Progress.
187+ client.asserts.assertText(
188+ xpath=u"//div[contains(@class, 'status-content')]//a[1]",
189+ validator=u"In Progress")
190+
191+
192+def test_suite():
193+ return unittest.TestLoader().loadTestsFromName(__name__)