Merge lp:~allenap/launchpad/show-me-too-counts into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~allenap/launchpad/show-me-too-counts
Merge into: lp:launchpad
Diff against target: 604 lines (+380/-64)
7 files modified
lib/canonical/launchpad/javascript/bugs/bugtask-index.js (+63/-9)
lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js (+34/-1)
lib/lp/bugs/browser/bugtask.py (+56/-0)
lib/lp/bugs/browser/tests/test_bugtask.py (+136/-0)
lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt (+26/-4)
lib/lp/bugs/templates/bugtasks-and-nominations-table.pt (+63/-48)
lib/lp/bugs/windmill/tests/test_bug_me_too.py (+2/-2)
To merge this branch: bzr merge lp:~allenap/launchpad/show-me-too-counts
Reviewer Review Type Date Requested Status
Gavin Panella (community) js Abstain
Abel Deuring (community) code Approve
Michael Nelson (community) ui Approve
Review via email: mp+15947@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

This branch does two things: shows the count of users affected by a
bug (previously this has not been shown), and moves the "Does this bug
affect you" line to above the bug task table.

Lint free.

To test:

  bin/test -vvct 'test_bugtask|xx-bug-affects-me-too'
  bin/test -vvct test_bug_me_too.py --layer BugsWindmillLayer
  firefox lib/canonical/launchpad/javascript/bugs/tests/test_me_too.html

File by file:

lib/canonical/launchpad/javascript/bugs/bugtask-index.js

  Pass a new argument, others_affected_count, into the
  MeTooChoiceSource constructor. Accept it too :)

  New method, _getNewSourceNames(), that figures out the strings that
  will be used in the page to represent the current state of the
  choice source in the page. The results of this are used to override
  the defaults in the choice source's "items" array.

lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js

  Test the above.

lib/lp/bugs/browser/bugtask.py

  New property BugTasksAndNominationsView.other_users_affected_count
  which returns the number of users affected by the current bug
  *excluding* the logged-in user, if he/she is affected.

  New property BugTasksAndNominationsView.affected_statement, which
  returns a sentence or two to describe what the current state is. It
  takes into consideration whether or not the current user has voted
  either way or not, and also how many other users are affected.

lib/lp/bugs/browser/tests/test_bugtask.py

  Test the above.

lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt

  The affected_statement is used for both clients with and without
  Javascript enabled, so this pagetest was updated.

lib/lp/bugs/templates/bugtasks-and-nominations-table.pt

  Move the me-too widget to above the task table. Apart from that
  there are very few changes. The affected_statement replaces the
  static text in the static and dynamic parts of the supporting HTML,
  and other_users_affected_count is passed into setup_me_too().

lib/lp/bugs/windmill/tests/test_bug_me_too.py

  Windmill test updated to reflect wording changes.

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

> This branch does two things: shows the count of users affected by a
> bug (previously this has not been shown), and moves the "Does this bug
> affect you" line to above the bug task table.

Wow - that's really nice Gavin, especially the way you've ensure the language is very natural in every situation! (eg. "This bug affects you and 3 other people" versus "This bug affects 3 people, but not you")

We chatted about included the count for anon users:

<noodles775> allenap: wow, that feature is great!
<noodles775> allenap: I was wondering whether there was a decision behind *not* displaying the count for anon users?
<allenap> noodles775: No deliberate decision, just didn't actually think about it, doh.
<allenap> noodles775: I should probably add it!
<noodles775> allenap: that'd be great!
<allenap> noodles775: I'll make it simply say "This bug affects 27 people".
<noodles775> Perfect

review: Approve (ui)
Revision history for this message
Gavin Panella (allenap) wrote :

Thanks for the UI review Michael! I've implemented that. It now says
"This bug affects 1 person" or "This bug affects x people" when x > 1.
If no one is affected, nothing is shown and there's no extraneous
white-space left in its wake.

I've attached a diff of the changes, though perhaps that's more for
the benefit of the code and js reviewer.

=== modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js'
--- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-12-10 14:23:23 +0000
+++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-12-10 16:36:41 +0000
@@ -1791,7 +1791,7 @@
1791 */1791 */
1792function setup_add_attachment() {1792function setup_add_attachment() {
1793 // Find zero or more links to modify.1793 // Find zero or more links to modify.
1794 var attachment_link = Y.get('.menu-link-addcomment');1794 var attachment_link = Y.all('.menu-link-addcomment');
1795 attachment_link.on('click', function(e) {1795 attachment_link.on('click', function(e) {
1796 var comment_input = Y.one('[id="field.comment"]');1796 var comment_input = Y.one('[id="field.comment"]');
1797 if (comment_input.get('value') !== '') {1797 if (comment_input.get('value') !== '') {
17981798
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2009-12-10 11:51:24 +0000
+++ lib/lp/bugs/browser/bugtask.py 2009-12-10 15:41:16 +0000
@@ -3134,6 +3134,17 @@
3134 else:3134 else:
3135 return "This bug doesn't affect you"3135 return "This bug doesn't affect you"
31363136
3137 @property
3138 def anon_affected_statement(self):
3139 """The "this bug affects" statement to show to anonymous users."""
3140 if self.context.users_affected_count == 1:
3141 return "This bug affects 1 person"
3142 elif self.context.users_affected_count > 1:
3143 return "This bug affects %d people" % (
3144 self.context.users_affected_count)
3145 else:
3146 return None
3147
31373148
3138class BugTaskTableRowView(LaunchpadView):3149class BugTaskTableRowView(LaunchpadView):
3139 """Browser class for rendering a bugtask row on the bug page."""3150 """Browser class for rendering a bugtask row on the bug page."""
31403151
=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
--- lib/lp/bugs/browser/tests/test_bugtask.py 2009-12-10 11:51:24 +0000
+++ lib/lp/bugs/browser/tests/test_bugtask.py 2009-12-10 15:46:47 +0000
@@ -186,6 +186,24 @@
186 "This bug affects 2 people, but not you",186 "This bug affects 2 people, but not you",
187 self.view.affected_statement)187 self.view.affected_statement)
188188
189 def test_anon_affected_statement_no_one_affected(self):
190 self.bug.markUserAffected(self.bug.owner, False)
191 self.failUnlessEqual(0, self.bug.users_affected_count)
192 self.assertIs(None, self.view.anon_affected_statement)
193
194 def test_anon_affected_statement_1_user_affected(self):
195 self.failUnlessEqual(1, self.bug.users_affected_count)
196 self.failUnlessEqual(
197 "This bug affects 1 person",
198 self.view.anon_affected_statement)
199
200 def test_anon_affected_statement_2_users_affected(self):
201 self.view.context.markUserAffected(self.view.user, True)
202 self.failUnlessEqual(2, self.bug.users_affected_count)
203 self.failUnlessEqual(
204 "This bug affects 2 people",
205 self.view.anon_affected_statement)
206
189207
190def test_suite():208def test_suite():
191 suite = unittest.TestSuite()209 suite = unittest.TestSuite()
192210
=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt'
--- lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt 2009-12-10 11:54:48 +0000
+++ lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt 2009-12-10 16:12:43 +0000
@@ -73,6 +73,27 @@
73 This bug affects 1 person, but not you73 This bug affects 1 person, but not you
7474
7575
76== Anonymous users ==
77
78Anonymous users just see the number of affected users.
79
80 >>> anon_browser.open(test_bug_url)
81 >>> print extract_text(find_tag_by_id(
82 ... anon_browser.contents, 'affectsmetoo'))
83 This bug affects 1 person
84
85If no one is marked as affected by the bug, the message does not
86appear at all to anonymous users.
87
88 >>> login('test@canonical.com')
89 >>> test_bug.markUserAffected(test_bug.owner, False)
90 >>> logout()
91
92 >>> anon_browser.open(test_bug_url)
93 >>> print find_tag_by_id(anon_browser.contents, 'affectsmetoo')
94 None
95
96
76== Static and dynamic support ==97== Static and dynamic support ==
7798
78A bug page contains markup to support both static (no Javascript) and99A bug page contains markup to support both static (no Javascript) and
79100
=== modified file 'lib/lp/bugs/templates/bugtasks-and-nominations-table.pt'
--- lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-12-10 13:20:50 +0000
+++ lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-12-10 16:04:44 +0000
@@ -4,53 +4,60 @@
4 define="context_menu context/menu:context"4 define="context_menu context/menu:context"
5 omit-tag="">5 omit-tag="">
66
7<div class="actions">7<tal:affects-editable condition="context_menu/affectsmetoo/enabled">
8 <span id="affectsmetoo" style="display: inline"8 <div class="actions">
9 tal:condition="link/enabled"9 <span id="affectsmetoo" style="display: inline"
10 tal:define="link context_menu/affectsmetoo;10 tal:define="affected view/current_user_affected_status">
11 affected view/current_user_affected_status">11
12 12 <tal:comment condition="nothing">
13 <tal:comment condition="nothing">13 This .static section is shown in browsers with javascript
14 This .static section is shown in browsers with javascript14 enabled, and before setup_me_too is run.
15 enabled, and before setup_me_too is run.15 </tal:comment>
16 </tal:comment>16 <span class="static">
17 <span class="static">17 <tal:affected condition="affected">
18 <tal:affected condition="affected">18 <img width="14" height="14" src="/@@/flame-icon" alt="" />
19 <img width="14" height="14" src="/@@/flame-icon" alt="" />19 <tal:statement replace="view/affected_statement" />
20 <tal:statement replace="view/affected_statement" />20 </tal:affected>
21 </tal:affected>21 <tal:not-affected condition="not:affected">
22 <tal:not-affected condition="not:affected">22 <tal:statement replace="view/affected_statement" />
23 <tal:statement replace="view/affected_statement" />23 </tal:not-affected>
24 </tal:not-affected>24 <a href="+affectsmetoo">
25 <a href="+affectsmetoo">25 <img class="editicon" src="/@@/edit" alt="Edit" />
26 </a>
27 </span>
28
29 <tal:comment condition="nothing">
30 This .dynamic section is used by setup_me_too to display
31 controls and information in the correct places.
32 </tal:comment>
33 <span class="dynamic unseen">
34 <img src="/@@/flame-icon" alt=""/>
35 <a href="+affectsmetoo" class="js-action"
36 ><span class="value" tal:content="view/affected_statement" /></a>
26 <img class="editicon" src="/@@/edit" alt="Edit" />37 <img class="editicon" src="/@@/edit" alt="Edit" />
27 </a>38 </span>
28 </span>39
29 40 <script type="text/javascript" tal:content="string:
30 <tal:comment condition="nothing">41 LPS.use('event', 'bugs.bugtask_index', function(Y) {
31 This .dynamic section is used by setup_me_too to display42 Y.on('load', function(e) {
32 controls and information in the correct places.43 Y.bugs.setup_me_too(
33 </tal:comment>44 ${view/current_user_affected_js_status},
34 <span class="dynamic unseen">45 ${view/other_users_affected_count});
35 <img src="/@@/flame-icon" alt=""/>46 }, window);
36 <a href="+affectsmetoo" class="js-action"47 });
37 ><span class="value" tal:content="view/affected_statement" /></a>48 ">
38 <img class="editicon" src="/@@/edit" alt="Edit" />49 </script>
39 </span>50
40 51 </span>
41 <script type="text/javascript" tal:content="string:52 </div>
42 LPS.use('event', 'bugs.bugtask_index', function(Y) {53</tal:affects-editable>
43 Y.on('load', function(e) {54<tal:affects-not-editable condition="not:context_menu/affectsmetoo/enabled">
44 Y.bugs.setup_me_too(55 <div class="actions"
45 ${view/current_user_affected_js_status},56 tal:define="statement view/anon_affected_statement"
46 ${view/other_users_affected_count});57 tal:condition="statement">
47 }, window);58 <span id="affectsmetoo" style="display: inline" tal:content="statement" />
48 });59 </div>
49 ">60</tal:affects-not-editable>
50 </script>
51
52 </span>
53</div>
5461
55<script type="text/javascript">62<script type="text/javascript">
56 function toggleFormVisibility(row_id) {63 function toggleFormVisibility(row_id) {
Revision history for this message
Gavin Panella (allenap) wrote :

The cover letter needs some additional information:

lib/lp/bugs/browser/bugtask.py

  New property anon_affected_statement which is shown to anonymous
  users. Explains how many people are affected by the bug.

lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt

  Tests added for the template changes below.

lib/lp/bugs/templates/bugtasks-and-nominations-table.pt

  A simple statement about the number of affected users is now shown
  to anonymous users.

Revision history for this message
Abel Deuring (adeuring) wrote :

Wow, like Michael, I am really impressed by the effort you put into proper wording for the different cases if the current user is affected or not, iand how many other users are affected!

Thanks for the additional "normalisation", as dicussed on IRC:

(18:20:33) abel: allenap: shouldn't _getNewSourceNames() also be able to deal with others_affected_count == 0?
(18:21:41) allenap: abel: In that case it returns nothing. I'll add a comment to make that clear.
(18:23:13) abel: allenap: sure, is does nothing in this case. My question is: What will be displayed in this case?
(18:23:48) abel: allenap: ahm, now I got it...
(18:23:51) allenap: abel: The source_name values at around line 1552 in the same file.
(18:24:14) abel: allenap: thanks, sorry for being a bit confused today...
(18:24:25) allenap: abel: Perhaps I should change _getNewSourceNames to _getSourceNames and do it all from there?
(18:24:56) abel: allenap: right, I was thinking roughly in this direction too. sounds good

review: Approve (code)
Revision history for this message
Gavin Panella (allenap) wrote :

Thanks Abel!

I noticed (using LP_DEBUG_SQL=1) that the many references to current_user_affected_status were causing queries to be issued to the db. I had assumed these would be cached, because they're against the primary key. Anyway, it's not critical, but I have a branch - lp:~allenap/launchpad/is-user-affected-performance - to address it, and I'll get that landed tomorrow.

Revision history for this message
Gavin Panella (allenap) wrote :

Abel reviewed the Javascript already.

review: Abstain (js)
Revision history for this message
Gavin Panella (allenap) wrote :

Abel also approved revision 10039 on IRC, albeit in a version with white-space changes eliminated.

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 2009-12-01 12:08:29 +0000
+++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-12-11 14:04:24 +0000
@@ -1495,7 +1495,7 @@
1495 *1495 *
1496 * @method setup_me_too1496 * @method setup_me_too
1497 */1497 */
1498bugs.setup_me_too = function(user_is_affected) {1498bugs.setup_me_too = function(user_is_affected, others_affected_count) {
1499 // IE (7 & 8 tested) is stupid, stupid, stupid.1499 // IE (7 & 8 tested) is stupid, stupid, stupid.
1500 if (Y.UA.ie) {1500 if (Y.UA.ie) {
1501 return;1501 return;
@@ -1504,7 +1504,8 @@
1504 var me_too_edit = new MeTooChoiceSource({1504 var me_too_edit = new MeTooChoiceSource({
1505 contentBox: me_too_content, value: user_is_affected,1505 contentBox: me_too_content, value: user_is_affected,
1506 elementToFlash: me_too_content,1506 elementToFlash: me_too_content,
1507 editicon: ".dynamic img[src$=/@@/edit]"1507 editicon: ".dynamic img[src$=/@@/edit]",
1508 others_affected_count: others_affected_count
1508 });1509 });
1509 me_too_edit.render();1510 me_too_edit.render();
1510};1511};
@@ -1547,12 +1548,10 @@
1547 */1548 */
1548 items: {1549 items: {
1549 value: [1550 value: [
1550 { name: 'Yes, it affects me', value: true,1551 { name: 'Yes, it affects me',
1551 source_name: 'This bug affects me too',1552 value: true, disabled: false },
1552 disabled: false },1553 { name: "No, it doesn't affect me",
1553 { name: "No, it doesn't affect me", value: false,1554 value: false, disabled: false }
1554 source_name: "This bug doesn't affect me",
1555 disabled: false }
1556 ]1555 ]
1557 },1556 },
15581557
@@ -1572,6 +1571,16 @@
1572 set: function(v) {1571 set: function(v) {
1573 return Y.one(v);1572 return Y.one(v);
1574 }1573 }
1574 },
1575
1576 /**
1577 * The number of other users currently affected by this bug.
1578 *
1579 * @attribute others_affected_count
1580 * @type Number
1581 */
1582 others_affected_count: {
1583 value: null
1575 }1584 }
1576};1585};
15771586
@@ -1588,6 +1597,50 @@
1588 this.error_handler.showError = function(error_msg) {1597 this.error_handler.showError = function(error_msg) {
1589 widget.showError(error_msg);1598 widget.showError(error_msg);
1590 };1599 };
1600 // Set source_names.
1601 var others_affected_count = this.get('others_affected_count');
1602 var source_names = this._getSourceNames(others_affected_count);
1603 Y.each(this.get('items'), function(item) {
1604 if (item.value in source_names) {
1605 item.source_name = source_names[item.value];
1606 }
1607 });
1608 },
1609
1610 /*
1611 * The results of _getSourceNames() should closely mirror the
1612 * results of BugTasksAndNominationsView.affected_statement and
1613 * anon_affected_statement.
1614 */
1615 _getSourceNames: function(others_affected_count) {
1616 var source_names = {};
1617 // What to say when the user is marked as affected.
1618 if (others_affected_count == 1) {
1619 source_names[true] = (
1620 'This bug affects you and 1 other person');
1621 }
1622 else if (others_affected_count > 1) {
1623 source_names[true] = (
1624 'This bug affects you and ' +
1625 others_affected_count + ' other people');
1626 }
1627 else {
1628 source_names[true] = 'This bug affects you';
1629 }
1630 // What to say when the user is marked as not affected.
1631 if (others_affected_count == 1) {
1632 source_names[false] = (
1633 'This bug affects 1 person, but not you');
1634 }
1635 else if (others_affected_count > 1) {
1636 source_names[false] = (
1637 'This bug affects ' + others_affected_count +
1638 ' people, but not you');
1639 }
1640 else {
1641 source_names[false] = "This bug doesn't affect you";
1642 }
1643 return source_names;
1591 },1644 },
15921645
1593 showError: function(err) {1646 showError: function(err) {
@@ -1742,7 +1795,8 @@
1742 * @method setup_add_attachment1795 * @method setup_add_attachment
1743 */1796 */
1744function setup_add_attachment() {1797function setup_add_attachment() {
1745 var attachment_link = Y.one('.menu-link-addcomment');1798 // Find zero or more links to modify.
1799 var attachment_link = Y.all('.menu-link-addcomment');
1746 attachment_link.on('click', function(e) {1800 attachment_link.on('click', function(e) {
1747 var comment_input = Y.one('[id="field.comment"]');1801 var comment_input = Y.one('[id="field.comment"]');
1748 if (comment_input.get('value') !== '') {1802 if (comment_input.get('value') !== '') {
17491803
=== modified file 'lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js'
--- lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js 2009-12-02 16:23:42 +0000
+++ lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js 2009-12-11 14:04:24 +0000
@@ -72,7 +72,7 @@
72 var me_too_content = Y.one('#affectsmetoo');72 var me_too_content = Y.one('#affectsmetoo');
73 this.config = {73 this.config = {
74 contentBox: me_too_content, value: null,74 contentBox: me_too_content, value: null,
75 elementToFlash: me_too_content75 elementToFlash: me_too_content, others_affected_count: 5
76 };76 };
77 this.choice_edit = new Y.bugs._MeTooChoiceSource(this.config);77 this.choice_edit = new Y.bugs._MeTooChoiceSource(this.config);
78 this.choice_edit.render();78 this.choice_edit.render();
@@ -205,6 +205,39 @@
205 Assert.isNull(205 Assert.isNull(
206 edit_icon.get('src').match(/\/spinner$/),206 edit_icon.get('src').match(/\/spinner$/),
207 "The edit icon is displaying a spinner once the choice has been made.");207 "The edit icon is displaying a spinner once the choice has been made.");
208 },
209
210 test__getSourceNames: function() {
211 var names;
212 // No other users affected.
213 names = this.choice_edit._getSourceNames(0);
214 Assert.areEqual(
215 'This bug affects you', names[true]);
216 Assert.areEqual(
217 "This bug doesn't affect you", names[false]);
218 // 1 other user affected.
219 names = this.choice_edit._getSourceNames(1);
220 Assert.areEqual(
221 'This bug affects you and 1 other person', names[true]);
222 Assert.areEqual(
223 'This bug affects 1 person, but not you', names[false]);
224 // 2 other users affected.
225 names = this.choice_edit._getSourceNames(2);
226 Assert.areEqual(
227 'This bug affects you and 2 other people', names[true]);
228 Assert.areEqual(
229 'This bug affects 2 people, but not you', names[false]);
230 },
231
232 test_new_names_are_applied: function() {
233 var names = {};
234 Y.each(this.choice_edit.get('items'), function(item) {
235 names[item.value] = item.source_name;
236 });
237 Assert.areEqual(
238 'This bug affects you and 5 other people', names[true]);
239 Assert.areEqual(
240 'This bug affects 5 people, but not you', names[false]);
208 }241 }
209242
210}));243}));
211244
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2009-11-28 08:11:20 +0000
+++ lib/lp/bugs/browser/bugtask.py 2009-12-11 14:04:24 +0000
@@ -3097,6 +3097,62 @@
3097 else:3097 else:
3098 return 'false'3098 return 'false'
30993099
3100 @property
3101 def other_users_affected_count(self):
3102 """The number of other users affected by this bug."""
3103 if self.current_user_affected_status:
3104 return self.context.users_affected_count - 1
3105 else:
3106 return self.context.users_affected_count
3107
3108 @property
3109 def affected_statement(self):
3110 """The default "this bug affects" statement to show.
3111
3112 The outputs of this method should be mirrored in
3113 MeTooChoiceSource._getSourceNames() (Javascript).
3114 """
3115 if self.other_users_affected_count == 1:
3116 if self.current_user_affected_status is None:
3117 return "This bug affects 1 person. Does this bug affect you?"
3118 elif self.current_user_affected_status:
3119 return "This bug affects you and 1 other person"
3120 else:
3121 return "This bug affects 1 person, but not you"
3122 elif self.other_users_affected_count > 1:
3123 if self.current_user_affected_status is None:
3124 return (
3125 "This bug affects %d people. Does this bug "
3126 "affect you?" % (self.other_users_affected_count))
3127 elif self.current_user_affected_status:
3128 return "This bug affects you and %d other people" % (
3129 self.other_users_affected_count)
3130 else:
3131 return "This bug affects %d people, but not you" % (
3132 self.other_users_affected_count)
3133 else:
3134 if self.current_user_affected_status is None:
3135 return "Does this bug affect you?"
3136 elif self.current_user_affected_status:
3137 return "This bug affects you"
3138 else:
3139 return "This bug doesn't affect you"
3140
3141 @property
3142 def anon_affected_statement(self):
3143 """The "this bug affects" statement to show to anonymous users.
3144
3145 The outputs of this method should be mirrored in
3146 MeTooChoiceSource._getSourceNames() (Javascript).
3147 """
3148 if self.context.users_affected_count == 1:
3149 return "This bug affects 1 person"
3150 elif self.context.users_affected_count > 1:
3151 return "This bug affects %d people" % (
3152 self.context.users_affected_count)
3153 else:
3154 return None
3155
31003156
3101class BugTaskTableRowView(LaunchpadView):3157class BugTaskTableRowView(LaunchpadView):
3102 """Browser class for rendering a bugtask row on the bug page."""3158 """Browser class for rendering a bugtask row on the bug page."""
31033159
=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
--- lib/lp/bugs/browser/tests/test_bugtask.py 2009-09-18 20:23:46 +0000
+++ lib/lp/bugs/browser/tests/test_bugtask.py 2009-12-11 14:04:24 +0000
@@ -68,6 +68,142 @@
68 self.bug.default_bugtask, False, False)68 self.bug.default_bugtask, False, False)
69 self.failUnless(row_view.many_bugtasks)69 self.failUnless(row_view.many_bugtasks)
7070
71 def test_other_users_affected_count(self):
72 # The number of other users affected does not change when the
73 # logged-in user marked him or herself as affected or not.
74 self.failUnlessEqual(
75 1, self.view.other_users_affected_count)
76 self.view.context.markUserAffected(self.view.user, True)
77 self.failUnlessEqual(
78 1, self.view.other_users_affected_count)
79 self.view.context.markUserAffected(self.view.user, False)
80 self.failUnlessEqual(
81 1, self.view.other_users_affected_count)
82
83 def test_other_users_affected_count_other_users(self):
84 # The number of other users affected only changes when other
85 # users mark themselves as affected.
86 self.failUnlessEqual(
87 1, self.view.other_users_affected_count)
88 other_user_1 = self.factory.makePerson()
89 self.view.context.markUserAffected(other_user_1, True)
90 self.failUnlessEqual(
91 2, self.view.other_users_affected_count)
92 other_user_2 = self.factory.makePerson()
93 self.view.context.markUserAffected(other_user_2, True)
94 self.failUnlessEqual(
95 3, self.view.other_users_affected_count)
96 self.view.context.markUserAffected(other_user_1, False)
97 self.failUnlessEqual(
98 2, self.view.other_users_affected_count)
99 self.view.context.markUserAffected(self.view.user, True)
100 self.failUnlessEqual(
101 2, self.view.other_users_affected_count)
102
103 def test_affected_statement_no_one_affected(self):
104 self.bug.markUserAffected(self.bug.owner, False)
105 self.failUnlessEqual(
106 0, self.view.other_users_affected_count)
107 self.failUnlessEqual(
108 "Does this bug affect you?",
109 self.view.affected_statement)
110
111 def test_affected_statement_only_you(self):
112 self.view.context.markUserAffected(self.view.user, True)
113 self.failUnless(self.bug.isUserAffected(self.view.user))
114 self.view.context.markUserAffected(self.bug.owner, False)
115 self.failUnlessEqual(
116 0, self.view.other_users_affected_count)
117 self.failUnlessEqual(
118 "This bug affects you",
119 self.view.affected_statement)
120
121 def test_affected_statement_only_not_you(self):
122 self.view.context.markUserAffected(self.view.user, False)
123 self.failIf(self.bug.isUserAffected(self.view.user))
124 self.view.context.markUserAffected(self.bug.owner, False)
125 self.failUnlessEqual(
126 0, self.view.other_users_affected_count)
127 self.failUnlessEqual(
128 "This bug doesn't affect you",
129 self.view.affected_statement)
130
131 def test_affected_statement_1_person_not_you(self):
132 self.assertIs(None, self.bug.isUserAffected(self.view.user))
133 self.failUnlessEqual(
134 1, self.view.other_users_affected_count)
135 self.failUnlessEqual(
136 "This bug affects 1 person. Does this bug affect you?",
137 self.view.affected_statement)
138
139 def test_affected_statement_1_person_and_you(self):
140 self.view.context.markUserAffected(self.view.user, True)
141 self.failUnless(self.bug.isUserAffected(self.view.user))
142 self.failUnlessEqual(
143 1, self.view.other_users_affected_count)
144 self.failUnlessEqual(
145 "This bug affects you and 1 other person",
146 self.view.affected_statement)
147
148 def test_affected_statement_1_person_and_not_you(self):
149 self.view.context.markUserAffected(self.view.user, False)
150 self.failIf(self.bug.isUserAffected(self.view.user))
151 self.failUnlessEqual(
152 1, self.view.other_users_affected_count)
153 self.failUnlessEqual(
154 "This bug affects 1 person, but not you",
155 self.view.affected_statement)
156
157 def test_affected_statement_more_than_1_person_not_you(self):
158 self.assertIs(None, self.bug.isUserAffected(self.view.user))
159 other_user = self.factory.makePerson()
160 self.view.context.markUserAffected(other_user, True)
161 self.failUnlessEqual(
162 2, self.view.other_users_affected_count)
163 self.failUnlessEqual(
164 "This bug affects 2 people. Does this bug affect you?",
165 self.view.affected_statement)
166
167 def test_affected_statement_more_than_1_person_and_you(self):
168 self.view.context.markUserAffected(self.view.user, True)
169 self.failUnless(self.bug.isUserAffected(self.view.user))
170 other_user = self.factory.makePerson()
171 self.view.context.markUserAffected(other_user, True)
172 self.failUnlessEqual(
173 2, self.view.other_users_affected_count)
174 self.failUnlessEqual(
175 "This bug affects you and 2 other people",
176 self.view.affected_statement)
177
178 def test_affected_statement_more_than_1_person_and_not_you(self):
179 self.view.context.markUserAffected(self.view.user, False)
180 self.failIf(self.bug.isUserAffected(self.view.user))
181 other_user = self.factory.makePerson()
182 self.view.context.markUserAffected(other_user, True)
183 self.failUnlessEqual(
184 2, self.view.other_users_affected_count)
185 self.failUnlessEqual(
186 "This bug affects 2 people, but not you",
187 self.view.affected_statement)
188
189 def test_anon_affected_statement_no_one_affected(self):
190 self.bug.markUserAffected(self.bug.owner, False)
191 self.failUnlessEqual(0, self.bug.users_affected_count)
192 self.assertIs(None, self.view.anon_affected_statement)
193
194 def test_anon_affected_statement_1_user_affected(self):
195 self.failUnlessEqual(1, self.bug.users_affected_count)
196 self.failUnlessEqual(
197 "This bug affects 1 person",
198 self.view.anon_affected_statement)
199
200 def test_anon_affected_statement_2_users_affected(self):
201 self.view.context.markUserAffected(self.view.user, True)
202 self.failUnlessEqual(2, self.bug.users_affected_count)
203 self.failUnlessEqual(
204 "This bug affects 2 people",
205 self.view.anon_affected_statement)
206
71207
72def test_suite():208def test_suite():
73 suite = unittest.TestSuite()209 suite = unittest.TestSuite()
74210
=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt'
--- lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt 2009-07-31 13:49:53 +0000
+++ lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt 2009-12-11 14:04:24 +0000
@@ -10,13 +10,14 @@
10 >>> logout()10 >>> logout()
1111
12The user goes to the bug's index page, and finds a statement that the12The user goes to the bug's index page, and finds a statement that the
13bug is not marked as affecting them.13bug affects one other person (in this instance, the person who filed
14the bug).
1415
15 >>> user_browser.open(test_bug_url)16 >>> user_browser.open(test_bug_url)
16 >>> print extract_text(find_tag_by_id(17 >>> print extract_text(find_tag_by_id(
17 ... user_browser.contents, 'affectsmetoo').find(18 ... user_browser.contents, 'affectsmetoo').find(
18 ... None, 'static'))19 ... None, 'static'))
19 This bug doesn't affect me20 This bug affects 1 person. Does this bug affect you?
2021
21Next to the statement is a link containing an edit icon.22Next to the statement is a link containing an edit icon.
2223
@@ -45,7 +46,7 @@
45 >>> print extract_text(find_tag_by_id(46 >>> print extract_text(find_tag_by_id(
46 ... user_browser.contents, 'affectsmetoo').find(47 ... user_browser.contents, 'affectsmetoo').find(
47 ... None, 'static'))48 ... None, 'static'))
48 This bug affects me too49 This bug affects you and 1 other person
4950
50Next to it, we also see the 'hot bug' icon, to indicate that the user51Next to it, we also see the 'hot bug' icon, to indicate that the user
51has marked the bug as affecting them.52has marked the bug as affecting them.
@@ -69,7 +70,28 @@
69 >>> print extract_text(find_tag_by_id(70 >>> print extract_text(find_tag_by_id(
70 ... user_browser.contents, 'affectsmetoo').find(71 ... user_browser.contents, 'affectsmetoo').find(
71 ... None, 'static'))72 ... None, 'static'))
72 This bug doesn't affect me73 This bug affects 1 person, but not you
74
75
76== Anonymous users ==
77
78Anonymous users just see the number of affected users.
79
80 >>> anon_browser.open(test_bug_url)
81 >>> print extract_text(find_tag_by_id(
82 ... anon_browser.contents, 'affectsmetoo'))
83 This bug affects 1 person
84
85If no one is marked as affected by the bug, the message does not
86appear at all to anonymous users.
87
88 >>> login('test@canonical.com')
89 >>> test_bug.markUserAffected(test_bug.owner, False)
90 >>> logout()
91
92 >>> anon_browser.open(test_bug_url)
93 >>> print find_tag_by_id(anon_browser.contents, 'affectsmetoo')
94 None
7395
7496
75== Static and dynamic support ==97== Static and dynamic support ==
7698
=== modified file 'lib/lp/bugs/templates/bugtasks-and-nominations-table.pt'
--- lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-12-03 18:36:37 +0000
+++ lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-12-11 14:04:24 +0000
@@ -2,6 +2,63 @@
2 xmlns:tal="http://xml.zope.org/namespaces/tal"2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 omit-tag="">4 omit-tag="">
5
6<tal:affects-me-too
7 tal:condition="view/displayAlsoAffectsLinks">
8 <tal:editable
9 condition="context/menu:context/affectsmetoo/enabled">
10 <div class="actions">
11 <span id="affectsmetoo" style="display: inline"
12 tal:define="affected view/current_user_affected_status">
13 <tal:comment condition="nothing">
14 This .static section is shown in browsers with javascript
15 enabled, and before setup_me_too is run.
16 </tal:comment>
17 <span class="static">
18 <tal:affected condition="affected">
19 <img width="14" height="14" src="/@@/flame-icon" alt="" />
20 <tal:statement replace="view/affected_statement" />
21 </tal:affected>
22 <tal:not-affected condition="not:affected">
23 <tal:statement replace="view/affected_statement" />
24 </tal:not-affected>
25 <a href="+affectsmetoo">
26 <img class="editicon" src="/@@/edit" alt="Edit" />
27 </a>
28 </span>
29 <tal:comment condition="nothing">
30 This .dynamic section is used by setup_me_too to display
31 controls and information in the correct places.
32 </tal:comment>
33 <span class="dynamic unseen">
34 <img src="/@@/flame-icon" alt=""/>
35 <a href="+affectsmetoo" class="js-action"
36 ><span class="value" tal:content="view/affected_statement" /></a>
37 <img class="editicon" src="/@@/edit" alt="Edit" />
38 </span>
39 <script type="text/javascript" tal:content="string:
40 LPS.use('event', 'bugs.bugtask_index', function(Y) {
41 Y.on('load', function(e) {
42 Y.bugs.setup_me_too(
43 ${view/current_user_affected_js_status},
44 ${view/other_users_affected_count});
45 }, window);
46 });
47 ">
48 </script>
49 </span>
50 </div>
51 </tal:editable>
52 <tal:not-editable
53 condition="not:context/menu:context/affectsmetoo/enabled">
54 <div class="actions"
55 tal:define="statement view/anon_affected_statement"
56 tal:condition="statement">
57 <span id="affectsmetoo" style="display: inline" tal:content="statement" />
58 </div>
59 </tal:not-editable>
60</tal:affects-me-too>
61
5<script type="text/javascript">62<script type="text/javascript">
6 function toggleFormVisibility(row_id) {63 function toggleFormVisibility(row_id) {
7 row = document.getElementById(row_id)64 row = document.getElementById(row_id)
@@ -41,63 +98,21 @@
41<div class="actions"98<div class="actions"
42 tal:define="current_bugtask view/current_bugtask"99 tal:define="current_bugtask view/current_bugtask"
43 tal:condition="view/displayAlsoAffectsLinks">100 tal:condition="view/displayAlsoAffectsLinks">
44 <tal:also-affects-links define="context_menu context/menu:context">101 <tal:also-affects-links
45 <tal:addupstream102 define="context_menu context/menu:context">
103 <tal:addupstream
46 define="link context_menu/addupstream"104 define="link context_menu/addupstream"
47 condition="link/enabled"105 condition="link/enabled"
48 replace="structure link/render" />106 replace="structure link/render" />
49 <tal:adddistro107 <tal:adddistro
50 define="link context_menu/adddistro"108 define="link context_menu/adddistro"
51 condition="link/enabled"109 condition="link/enabled"
52 replace="structure link/render" />110 replace="structure link/render" />
53 <tal:nominate111 <tal:nominate
54 define="link context_menu/nominate"112 define="link context_menu/nominate"
55 condition="link/enabled"113 condition="link/enabled"
56 replace="structure link/render" />114 replace="structure link/render" />
57 <span id="affectsmetoo" style="display: inline"115 </tal:also-affects-links>
58 tal:condition="link/enabled"
59 tal:define="link context_menu/affectsmetoo;
60 affected view/current_user_affected_status">
61
62 <tal:comment condition="nothing">
63 This .static section is shown in browsers with javascript
64 enabled, and before setup_me_too is run.
65 </tal:comment>
66 <span class="static">
67 <tal:affected condition="affected">
68 <img width="14" height="14" src="/@@/flame-icon" alt="" />
69 This bug affects me too
70 </tal:affected>
71 <tal:not-affected condition="not:affected">
72 This bug doesn't affect me
73 </tal:not-affected>
74 <a href="+affectsmetoo">
75 <img class="editicon" src="/@@/edit" alt="Edit" />
76 </a>
77 </span>
78
79 <tal:comment condition="nothing">
80 This .dynamic section is used by setup_me_too to display
81 controls and information in the correct places.
82 </tal:comment>
83 <span class="dynamic unseen">
84 <img src="/@@/flame-icon" alt=""/>
85 <a href="+affectsmetoo" class="js-action"
86 ><span class="value">Does this bug affect you?</span></a>
87 <img class="editicon" src="/@@/edit" alt="Edit" />
88 </span>
89
90 <script type="text/javascript" tal:content="string:
91 LPS.use('event', 'bugs.bugtask_index', function(Y) {
92 Y.on('load', function(e) {
93 Y.bugs.setup_me_too(${view/current_user_affected_js_status});
94 }, window);
95 });
96 ">
97 </script>
98
99 </span>
100 </tal:also-affects-links>
101</div>116</div>
102117
103</tal:root>118</tal:root>
104119
=== modified file 'lib/lp/bugs/windmill/tests/test_bug_me_too.py'
--- lib/lp/bugs/windmill/tests/test_bug_me_too.py 2009-11-04 14:06:04 +0000
+++ lib/lp/bugs/windmill/tests/test_bug_me_too.py 2009-12-11 14:04:24 +0000
@@ -111,7 +111,7 @@
111 def check_for_save_not_affects(client):111 def check_for_save_not_affects(client):
112 client.asserts.assertText(112 client.asserts.assertText(
113 xpath=VALUE_LOCATION_XPATH,113 xpath=VALUE_LOCATION_XPATH,
114 validator=u"This bug doesn't affect me")114 validator=u"This bug doesn't affect you")
115115
116 # Hah! But this bug does affect the logged-in user! The logged-in116 # Hah! But this bug does affect the logged-in user! The logged-in
117 # user made a mistake, oh noes. Better fix that.117 # user made a mistake, oh noes. Better fix that.
@@ -125,7 +125,7 @@
125 def check_for_save_does_affect(client):125 def check_for_save_does_affect(client):
126 client.asserts.assertText(126 client.asserts.assertText(
127 xpath=VALUE_LOCATION_XPATH,127 xpath=VALUE_LOCATION_XPATH,
128 validator=u"This bug affects me too")128 validator=u"This bug affects you")
129129
130 # The flame icon is now visible.130 # The flame icon is now visible.
131 client.asserts.assertElemJS(131 client.asserts.assertElemJS(