Merge lp:~gmb/launchpad/fix-advanced-bug-sub-ui-bug-673288 into lp:launchpad

Proposed by Graham Binns on 2010-11-11
Status: Merged
Approved by: Gavin Panella on 2010-11-12
Approved revision: no longer in the source branch.
Merged at revision: 11924
Proposed branch: lp:~gmb/launchpad/fix-advanced-bug-sub-ui-bug-673288
Merge into: lp:launchpad
Diff against target: 164 lines (+112/-7)
2 files modified
lib/lp/bugs/browser/bugsubscription.py (+33/-7)
lib/lp/bugs/browser/tests/test_bugsubscription_views.py (+79/-0)
To merge this branch: bzr merge lp:~gmb/launchpad/fix-advanced-bug-sub-ui-bug-673288
Reviewer Review Type Date Requested Status
Gavin Panella (community) 2010-11-11 Approve on 2010-11-12
Review via email: mp+40631@code.launchpad.net

Commit Message

[r=allenap][ui=none][bug=673288] Users who are not directly subscribed to a bug will no longer be offered the option to update their (nonexistent) subscription.

Description of the Change

This branch fixes bug 673288 by making the "Update my subscription"
option on the +subscribe page disappear if the user is not directly
subscribed to the bug.

I've also made the bug_notification_level field invisible if the user
isn't directly subscribed, since having it there makes no sense.

== lib/lp/bugs/browser/bugsubscription.py ==

 - I've made the changes necessary to fix bug 673288.

== lib/lp/bugs/browser/tests/test_bugsubscription_views.py ==

 - I've added a regresession test for the bug as well as two other tests
   to ensure proper behaviour of the view.

To post a comment you must log in.
Graham Binns (gmb) wrote :
Download full text (7.1 KiB)

Here's a conflict-free diff.

=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
--- lib/lp/bugs/browser/bugsubscription.py 2010-11-09 19:41:24 +0000
+++ lib/lp/bugs/browser/bugsubscription.py 2010-11-11 14:46:00 +0000
@@ -225,10 +225,11 @@
         self_subscribed = False
         for person in self._subscribers_for_current_user:
             if person.id == self.user.id:
- if self._use_advanced_features:
+ if (self._use_advanced_features and
+ self.current_user_subscription is not None):
                     subscription_terms.append(self._update_subscription_term)
- subscription_terms.append(
- SimpleTerm(
+ subscription_terms.insert(
+ 0, SimpleTerm(
                         person, person.name,
                         'Unsubscribe me from this bug'))
                 self_subscribed = True
@@ -244,7 +245,8 @@
                 SimpleTerm(
                     self.user, self.user.name, 'Subscribe me to this bug'))
         subscription_vocabulary = SimpleVocabulary(subscription_terms)
- if self.user_is_subscribed and self._use_advanced_features:
+ if (self._use_advanced_features and
+ self.current_user_subscription is not None):
             default_subscription_value = self._update_subscription_term.value
         else:
             default_subscription_value = (
@@ -284,6 +286,13 @@
                 # subscribe theirself or unsubscribe their team.
                 self.widgets['subscription'].visible = True

+ if (self.user_is_subscribed and
+ self.current_user_subscription is None):
+ # If the user is subscribed via a duplicate but is not
+ # directly subscribed, we hide the
+ # bug_notification_level field, since it's not used.
+ self.widgets['bug_notification_level'].visible = False
+
     @cachedproperty
     def user_is_subscribed(self):
         """Is the user subscribed to this bug?"""

=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-11 10:37:48 +0000
+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-11 14:46:42 +0000
@@ -10,6 +10,7 @@

 from lp.bugs.browser.bugsubscription import (
     BugPortletSubcribersIds,
+ BugSubscriptionAddView,
     BugSubscriptionSubscribeSelfView,
     )
 from lp.registry.enum import BugNotificationLevel
@@ -170,6 +171,99 @@
                     "METADATA, is actually %s"
                     % default_notification_level_value)

+ def test_update_subscription_fails_if_user_not_subscribed(self):
+ # If the user is not directly subscribed to the bug, trying to
+ # update the subscription will fail (since you can't update a
+ # subscription that doesn't exist).
+ bug = self.factory.makeBug()
+ person = self.factory.makePerson()
+ with feature_flags():
+ with person_logged_in(person):
+ level = BugNotificationLevel.METADATA
+ harness = LaunchpadFormHarness(
+ ...

Read more...

Gavin Panella (allenap) wrote :

Looks good, but I have a suggestion about making the code a little
clearer and I have some concerns about the tests.

[1]

+ if (self._use_advanced_features and
+ self.current_user_subscription is not None):
...
+ if (self._use_advanced_features and
+ self.current_user_subscription is not None):
...
+ if (self.user_is_subscribed and
+ self.current_user_subscription is None):

user_is_subscribed is defined as:

    @cachedproperty
    def user_is_subscribed(self):
        """Is the user subscribed to this bug?"""
        return (
            self.context.bug.isSubscribed(self.user) or
            self.context.bug.isSubscribedToDupes(self.user))

I think it would probably be easier to understand some of this logic
if this were split up:

    @cachedproperty
    def user_is_subscribed_directly(self):
        """Is the user subscribed directly to this bug?"""
        return self.context.bug.isSubscribed(self.user)

    @cachedproperty
    def user_is_subscribed_to_dupes(self):
        """Is the user subscribed to dupes of this bug?"""
        return self.context.bug.isSubscribedToDupes(self.user)

    def user_is_subscribed(self):
        """Is the user subscribed to this bug?"""
        return (self.user_is_subscribed_directly or
                self.user_is_subscribed_to_dupes)

    def user_is_subscribed_to_dupes_only(self):
        """Is the user subscribed to this bug only via a dupe?"""
        return (self.user_is_subscribed_to_dupes and
                not self.user_is_subscribed_directly)

Then the three examples become:

                if (self._use_advanced_features and
                    self.user_is_subscribed_directly):
...
        if (self._use_advanced_features and
            self.user_is_subscribed_directly):
...
            if (self.user_is_subscribed_to_dupes_only):

I know it's more typing - hehe, done for you :) - but it reads
better. This view is complicated and difficult to follow, so I think
it could do with some help in this regard.

[2]

+ def test_update_subscription_fails_if_user_not_subscribed(self):
+ def test_update_subscription_fails_for_users_subscribed_via_teams(self):
+ def test_bug_673288(self):

The tests are fine, but I wonder if they're the right way to test
this bug.

From reading the bug, it seems that the view was already doing
something like the right thing: it was breaking when presented with a
nonsensical request. The real bug, as far as I can tell, is that the
form presented to the user is wrong.

I think the tests should be demonstrating what fields the view expects
in the various scenarios. I don't think it's even necessary to submit
forms to the view.

review: Needs Fixing
Graham Binns (gmb) wrote :

Hi Gavin,

On Thu, Nov 11, 2010 at 04:32:54PM -0000, Gavin Panella wrote:
> Review: Needs Fixing
> Looks good, but I have a suggestion about making the code a little
> clearer and I have some concerns about the tests.

Cool, thanks.

> [1]
>[...]
> I know it's more typing - hehe, done for you :) - but it reads
> better. This view is complicated and difficult to follow, so I think
> it could do with some help in this regard.

I agree. I've done as you suggested (thanks for doing the work for me
;)).

> [2]
>
> + def test_update_subscription_fails_if_user_not_subscribed(self):
> + def test_update_subscription_fails_for_users_subscribed_via_teams(self):
> + def test_bug_673288(self):
>
> The tests are fine, but I wonder if they're the right way to test
> this bug.
>
> >From reading the bug, it seems that the view was already doing
> something like the right thing: it was breaking when presented with a
> nonsensical request. The real bug, as far as I can tell, is that the
> form presented to the user is wrong.
>
> I think the tests should be demonstrating what fields the view expects
> in the various scenarios. I don't think it's even necessary to submit
> forms to the view.
>

Hmm. I see your point. I've updated the tests as you suggest (and in the
process discovered how to test the options in the fields, which is
cool).

Gavin Panella (allenap) wrote :

It's a beauty :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
2--- lib/lp/bugs/browser/bugsubscription.py 2010-11-09 19:41:24 +0000
3+++ lib/lp/bugs/browser/bugsubscription.py 2010-11-12 11:07:34 +0000
4@@ -225,10 +225,11 @@
5 self_subscribed = False
6 for person in self._subscribers_for_current_user:
7 if person.id == self.user.id:
8- if self._use_advanced_features:
9+ if (self._use_advanced_features and
10+ self.user_is_subscribed_directly):
11 subscription_terms.append(self._update_subscription_term)
12- subscription_terms.append(
13- SimpleTerm(
14+ subscription_terms.insert(
15+ 0, SimpleTerm(
16 person, person.name,
17 'Unsubscribe me from this bug'))
18 self_subscribed = True
19@@ -244,7 +245,8 @@
20 SimpleTerm(
21 self.user, self.user.name, 'Subscribe me to this bug'))
22 subscription_vocabulary = SimpleVocabulary(subscription_terms)
23- if self.user_is_subscribed and self._use_advanced_features:
24+ if (self._use_advanced_features and
25+ self.user_is_subscribed_directly):
26 default_subscription_value = self._update_subscription_term.value
27 else:
28 default_subscription_value = (
29@@ -284,12 +286,36 @@
30 # subscribe theirself or unsubscribe their team.
31 self.widgets['subscription'].visible = True
32
33- @cachedproperty
34+ if (self.user_is_subscribed and
35+ self.user_is_subscribed_to_dupes_only):
36+ # If the user is subscribed via a duplicate but is not
37+ # directly subscribed, we hide the
38+ # bug_notification_level field, since it's not used.
39+ self.widgets['bug_notification_level'].visible = False
40+
41+ @cachedproperty
42+ def user_is_subscribed_directly(self):
43+ """Is the user subscribed directly to this bug?"""
44+ return self.context.bug.isSubscribed(self.user)
45+
46+ @cachedproperty
47+ def user_is_subscribed_to_dupes(self):
48+ """Is the user subscribed to dupes of this bug?"""
49+ return self.context.bug.isSubscribedToDupes(self.user)
50+
51+ @property
52 def user_is_subscribed(self):
53 """Is the user subscribed to this bug?"""
54 return (
55- self.context.bug.isSubscribed(self.user) or
56- self.context.bug.isSubscribedToDupes(self.user))
57+ self.user_is_subscribed_directly or
58+ self.user_is_subscribed_to_dupes)
59+
60+ @property
61+ def user_is_subscribed_to_dupes_only(self):
62+ """Is the user subscribed to this bug only via a dupe?"""
63+ return (
64+ self.user_is_subscribed_to_dupes and
65+ not self.user_is_subscribed_directly)
66
67 def shouldShowUnsubscribeFromDupesWarning(self):
68 """Should we warn the user about unsubscribing and duplicates?
69
70=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
71--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-11 10:37:48 +0000
72+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-12 11:07:34 +0000
73@@ -10,6 +10,7 @@
74
75 from lp.bugs.browser.bugsubscription import (
76 BugPortletSubcribersIds,
77+ BugSubscriptionAddView,
78 BugSubscriptionSubscribeSelfView,
79 )
80 from lp.registry.enum import BugNotificationLevel
81@@ -170,6 +171,84 @@
82 "METADATA, is actually %s"
83 % default_notification_level_value)
84
85+ def test_update_subscription_fails_if_user_not_subscribed(self):
86+ # If the user is not directly subscribed to the bug, trying to
87+ # update the subscription will fail (since you can't update a
88+ # subscription that doesn't exist).
89+ bug = self.factory.makeBug()
90+ person = self.factory.makePerson()
91+ with feature_flags():
92+ with person_logged_in(person):
93+ level = BugNotificationLevel.METADATA
94+ harness = LaunchpadFormHarness(
95+ bug.default_bugtask, BugSubscriptionSubscribeSelfView)
96+ subscription_field = (
97+ harness.view.form_fields['subscription'].field)
98+ # The update-subscription option won't appear.
99+ self.assertNotIn(
100+ 'update-subscription',
101+ subscription_field.vocabulary.by_token)
102+
103+ def test_update_subscription_fails_for_users_subscribed_via_teams(self):
104+ # If the user is not directly subscribed, but is subscribed via
105+ # a team, they will not be able to use the "Update my
106+ # subscription" option.
107+ bug = self.factory.makeBug()
108+ person = self.factory.makePerson()
109+ team = self.factory.makeTeam(owner=person)
110+ with feature_flags():
111+ with person_logged_in(person):
112+ bug.subscribe(team, person)
113+ level = BugNotificationLevel.METADATA
114+ harness = LaunchpadFormHarness(
115+ bug.default_bugtask, BugSubscriptionSubscribeSelfView)
116+ subscription_field = (
117+ harness.view.form_fields['subscription'].field)
118+ # The update-subscription option won't appear.
119+ self.assertNotIn(
120+ 'update-subscription',
121+ subscription_field.vocabulary.by_token)
122+
123+ def test_bug_673288(self):
124+ # If the user is not directly subscribed, but is subscribed via
125+ # a team and via a duplicate, they will not be able to use the
126+ # "Update my subscription" option.
127+ # This is a regression test for bug 673288.
128+ bug = self.factory.makeBug()
129+ duplicate = self.factory.makeBug()
130+ person = self.factory.makePerson()
131+ team = self.factory.makeTeam(owner=person)
132+ with feature_flags():
133+ with person_logged_in(person):
134+ duplicate.markAsDuplicate(bug)
135+ duplicate.subscribe(person, person)
136+ bug.subscribe(team, person)
137+
138+ level = BugNotificationLevel.METADATA
139+ harness = LaunchpadFormHarness(
140+ bug.default_bugtask, BugSubscriptionSubscribeSelfView)
141+ subscription_field = (
142+ harness.view.form_fields['subscription'].field)
143+ # The update-subscription option won't appear.
144+ self.assertNotIn(
145+ 'update-subscription',
146+ subscription_field.vocabulary.by_token)
147+
148+ def test_bug_notification_level_field_hidden_for_dupe_subs(self):
149+ # If the user is subscribed to the bug via a duplicate, the
150+ # bug_notification_level field won't be visible on the form.
151+ bug = self.factory.makeBug()
152+ duplicate = self.factory.makeBug()
153+ person = self.factory.makePerson()
154+ with feature_flags():
155+ with person_logged_in(person):
156+ duplicate.markAsDuplicate(bug)
157+ duplicate.subscribe(person, person)
158+ harness = LaunchpadFormHarness(
159+ bug.default_bugtask, BugSubscriptionSubscribeSelfView)
160+ self.assertFalse(
161+ harness.view.widgets['bug_notification_level'].visible)
162+
163
164 class BugPortletSubcribersIdsTests(TestCaseWithFactory):
165