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
=== modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js'
--- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2010-03-03 00:38:38 +0000
+++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2010-03-23 10:47:49 +0000
@@ -1325,13 +1325,14 @@
1325 }1325 }
13261326
1327 if (conf.user_can_edit_status) {1327 if (conf.user_can_edit_status) {
1328 var status_choice_edit = new Y.ChoiceSource({1328 var status_choice_edit = new BugStatusEdit({
1329 contentBox: status_content,1329 contentBox: status_content,
1330 value: conf.status_value,1330 value: conf.status_value,
1331 title: 'Change status to',1331 title: 'Change status to',
1332 items: conf.status_widget_items,1332 items: conf.status_widget_items,
1333 elementToFlash: status_content.get('parentNode'),1333 elementToFlash: status_content.get('parentNode'),
1334 backgroundColor: tr.hasClass('highlight') ? '#FFFF99' : '#FFFFFF'1334 backgroundColor: tr.hasClass('highlight') ? '#FFFF99' : '#FFFFFF',
1335 requires_confirmation: conf.status_requires_confirmation
1335 });1336 });
1336 status_choice_edit.showError = function(err) {1337 status_choice_edit.showError = function(err) {
1337 Y.lp.display_error(null, err);1338 Y.lp.display_error(null, err);
@@ -1634,6 +1635,80 @@
1634 }1635 }
1635});1636});
16361637
1638var BugStatusEdit = function() {
1639 BugStatusEdit.superclass.constructor.apply(this, arguments);
1640}
1641
1642BugStatusEdit.NAME = 'bugstatusedit';
1643BugStatusEdit.NS = 'bugstatusedit';
1644
1645BugStatusEdit.ATTRS = {
1646 requires_confirmation: {
1647 value: false
1648 }
1649};
1650
1651Y.extend(BugStatusEdit, Y.ChoiceSource, {
1652 initializer: function(cfg) {
1653 BugStatusEdit.superclass.initializer.apply(this, arguments);
1654 },
1655
1656 onClick: function(e) {
1657
1658 // Only continue if the down button is the left one.
1659 if (e.button != 1) {
1660 return;
1661 }
1662
1663 this._choice_list = new Y.ChoiceList({
1664 value: this.get("value"),
1665 title: this.get("title"),
1666 items: this.get("items"),
1667 value_location: this.get("value_location"),
1668 progressbar: false
1669 });
1670
1671 var that = this;
1672 this._choice_list.on("valueChosen", function(e) {
1673 if (that.get("requires_confirmation")) {
1674 // Ensure that the choicelist is hidden before the
1675 // confirmation box appears. Note: there must be
1676 // a better way to get the choice list?
1677 cl = Y.get("table.yui-ichoicelist-focused");
1678 cl.replaceClass(
1679 'yui-ichoicelist-focused', 'yui-ichoicelist-hidden');
1680 var confirmed = confirm([
1681 'Change bug status?',
1682 '\n\n',
1683 'Bug status is normally set by the project ',
1684 'team, based on the current state of the work ',
1685 'involved in diagnosing and fixing the bug.',
1686 '\n\n',
1687 'If you\'re not certain whether changing the status of ',
1688 'this bug is correct, you can cancel and instead ',
1689 'contact the project team.'].join(''));
1690 } else {
1691 var confirmed = true;
1692 }
1693 if (confirmed) {
1694 that._chosen_value = e.details[0];
1695 that._saveData(e.details[0]);
1696 } else {
1697 return false;
1698 }
1699 });
1700
1701 // Stuff the mouse coordinates into the list object,
1702 // by the time we'll need them, they won't be available.
1703 this._choice_list._mouseX = e.clientX + window.pageXOffset;
1704 this._choice_list._mouseY = e.clientY + window.pageYOffset;
1705
1706 this._choice_list.render();
1707
1708 e.halt();
1709 }
1710});
1711
1637/*1712/*
1638 * Check if the current user can unsubscribe the person1713 * Check if the current user can unsubscribe the person
1639 * being subscribed.1714 * being subscribed.
@@ -1861,4 +1936,4 @@
1861 "substitute", "widget-position-ext", "lazr.formoverlay",1936 "substitute", "widget-position-ext", "lazr.formoverlay",
1862 "lazr.anim", "lazr.base", "lazr.overlay",1937 "lazr.anim", "lazr.base", "lazr.overlay",
1863 "lazr.choiceedit", "lp.picker", "lp.client.plugins",1938 "lazr.choiceedit", "lp.picker", "lp.client.plugins",
1864 "lp.subscriber", "lp.errors"]});1939 "lp.subscriber", "lp.errors", "bugs.status_edit"]});
18651940
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2010-03-15 17:59:47 +0000
+++ lib/lp/bugs/browser/bugtask.py 2010-03-23 10:47:49 +0000
@@ -3437,6 +3437,7 @@
3437 None),3437 None),
3438 'user_can_edit_milestone': self.user_can_edit_milestone,3438 'user_can_edit_milestone': self.user_can_edit_milestone,
3439 'user_can_edit_status': not self.context.bugwatch,3439 'user_can_edit_status': not self.context.bugwatch,
3440 'status_requires_confirmation': not self.user_can_edit_importance,
3440 'user_can_edit_importance': (3441 'user_can_edit_importance': (
3441 self.user_can_edit_importance and3442 self.user_can_edit_importance and
3442 not self.context.bugwatch)})3443 not self.context.bugwatch)})
34433444
=== added file 'lib/lp/bugs/windmill/tests/test_bug_status.py'
--- lib/lp/bugs/windmill/tests/test_bug_status.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/windmill/tests/test_bug_status.py 2010-03-23 10:47:49 +0000
@@ -0,0 +1,69 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test for the bug status entry."""
5
6__metaclass__ = type
7__all__ = []
8
9import unittest
10
11from canonical.launchpad.windmill.testing import constants, lpuser
12from lp.bugs.windmill.testing import BugsWindmillLayer
13from lp.testing import WindmillTestCase
14
15class TestBugStatusConfirmation(WindmillTestCase):
16
17 layer = BugsWindmillLayer
18 suite_name = "Bug status confirmation step test"
19
20 def test_bug_status_confirmation(self):
21 """Test the confirmation step of the bug status entry."""
22 client = self.client
23
24 # Open a bug page and wait for it to finish loading
25 client.open(url=u'http://bugs.launchpad.dev:8085/bugs/4')
26 client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
27 # NO_PRIV must confirm editting the bug status in this project.
28 lpuser.NO_PRIV.ensure_login(client)
29 client.waits.forElement(
30 xpath=u"//div[@class='status-content yui-bugstatusedit-content']//img[@class='editicon']",
31 timeout=constants.FOR_ELEMENT)
32
33 # Initially, the bug status is New.
34 client.asserts.assertText(
35 xpath=u"//div[contains(@class, 'status-content')]//a[1]",
36 validator=u"New")
37 client.click(
38 xpath=u"//div[contains(@class, 'status-content')]//img[@class='editicon']")
39 client.waits.forElement(
40 id=u'yui-pretty-overlay-modal', timeout=constants.FOR_ELEMENT)
41 # We open the status editor and change the status to In Progress.
42 # When a confirmation dialog is shown, we will choose to cancel.
43 client.commands.execJS(code=u"windmill.confirmAnswer = false")
44 client.click(
45 xpath=u"//div[@id='yui-pretty-overlay-modal']"
46 u"//a[contains(@href, '#In Progress')]")
47 # The status didn't change.
48 client.asserts.assertText(
49 xpath=u"//div[contains(@class, 'status-content')]//a[1]",
50 validator=u"New")
51
52 # We repeat the same sequence.
53 client.click(
54 xpath=u"//div[contains(@class, 'status-content')]//img[@class='editicon']")
55 client.waits.forElement(
56 id=u'yui-pretty-overlay-modal', timeout=constants.FOR_ELEMENT)
57 # This time, when the confirmation dialog appears, we will confirm.
58 client.commands.execJS(code=u"windmill.confirmAnswer = true")
59 client.click(
60 xpath=u"//div[@id='yui-pretty-overlay-modal']"
61 u"//a[contains(@href, '#In Progress')]")
62 # The status changed to In Progress.
63 client.asserts.assertText(
64 xpath=u"//div[contains(@class, 'status-content')]//a[1]",
65 validator=u"In Progress")
66
67
68def test_suite():
69 return unittest.TestLoader().loadTestsFromName(__name__)