Merge lp:~brian-murray/launchpad/bug-556489-distro-struct-sub into lp:launchpad
- bug-556489-distro-struct-sub
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Deryck Hodge | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 11385 | ||||
Proposed branch: | lp:~brian-murray/launchpad/bug-556489-distro-struct-sub | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
673 lines (+295/-148) 10 files modified
lib/lp/bugs/browser/bug.py (+1/-1) lib/lp/bugs/browser/bugtask.py (+3/-1) lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt (+1/-1) lib/lp/registry/browser/structuralsubscription.py (+5/-1) lib/lp/registry/configure.zcml (+14/-7) lib/lp/registry/interfaces/distribution.py (+1/-1) lib/lp/registry/interfaces/structuralsubscription.py (+3/-0) lib/lp/registry/model/structuralsubscription.py (+24/-1) lib/lp/registry/tests/structural-subscription-target.txt (+5/-134) lib/lp/registry/tests/test_structuralsubscriptiontarget.py (+238/-1) |
||||
To merge this branch: | bzr merge lp:~brian-murray/launchpad/bug-556489-distro-struct-sub | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Deryck Hodge (community) | code | Approve | |
Review via email:
|
Commit message
If a distribution has a bug supervisor set, only allow the bug supervisor or members of that team to subscribe to all of the distribution's bug reports.
Description of the change
= Summary =
The Ubuntu distribution has a large number of subscribers whose accounts are
deactivated, https:/
to the volume of the bug mail they ended up receiving. This is problematic to
some degree as all the subscribers show up on every bug report about Ubuntu.
Subscribing to all of Ubuntu bugs can also cause an unpleasant user experience
due to the volume of email they end up receiving and it not being clear how to
stop all of it. Additionally, if someone really wants to subscribe to every
bug report about Ubuntu there is a mailing list
(https:/
== Proposed fix ==
To prevent people from over subscribing themselves a permissions check should
be added to distribution stuctural subscriptions to ensure that only admins or
bug supervisors, if one exists, can subscribe to the distribution.
== Pre-implementation notes ==
This was discussed with Deryck who reported bug 556489 regarding this issue
and we discussed the process for removing existing subscribers to Ubuntu.
== Implementation details ==
While writing a test for the special case of distribution structural
subscriptions, I discovered that test_structural
used structural-
test.
lib/lp/
* Added distribution check to addBugSubscription
lib/lp/
* Refactored most of structural-
lib/lp/
* Removed portions implemented in test_structural
== Tests ==
bin/test -vv -t test_structural

Robert Collins (lifeless) wrote : | # |

Brian Murray (brian-murray) wrote : | # |
On Wed, Jul 28, 2010 at 05:13:42AM -0000, Robert Collins wrote:
> I'm not clear why this makes sense.
>
> - users can subscribe to the bugs list [same amount of mail]
However, it is harder to subscribe to the mailing list than structurally
subscribe to Ubuntu.
> - there are uses to being notified of every bug, same as things like
> gmane are subscribed to lists
> - this makes lp less flexible not more.
Yes, but this only applies to distribution's using Launchpad that have a
bug supervisor. Additionally, it just so happens that Ubuntu has a team
set as the bug supervisor which has the email address set to a mailing
list. Other distributions do not, or should not, have to have a bug
supervisor set and in the event that they do that could be a team
without a mailing list set for the contact address.
> I suspect you're doing a bandaid to the real issues:
> - subscriber lists take up a lot of space and are rarely important
> - we don't prune deactivated accounts effectively
> - the implications of structural subscription are not clear to users
>
> Could you please enlarge on why the approach you've put forward makes
> sense long term? If we do need a bandaid, I'm positive doing that, but
> I get the impression you don't consider this a bandaid, and given the
> downsides: more interrupts from valid subscribers, more code to handle
> special-
> explain, I'd really prefer to see a path to a long term unified fix!
I do agree that is a band-aid in that we are preventing people from
doing something they don't really understand the consequences of.
Looking at my personal email archive I have some emails from people who
mistakenly subscribed to all bug reports about Ubuntu, did not know how
to unsubscribe and were receiving hundreds of emails a day. While
emails provide an unsubscribe link for individual bug reports there is
no link for how to remove a structural subscription.
I'd guess that we will get less requests from people wanting to
subscribe to all bug reports about Ubuntu than we will about people
wanting to be unsubscribed. Perhaps we should track this somehow?
--
Brian Murray

Brian Murray (brian-murray) wrote : | # |
A structural subscription unsubscribe link is covered in bug 484319.

Abel Deuring (adeuring) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 28.07.2010 19:41, Brian Murray wrote:
> I do agree that is a band-aid in that we are preventing people from
> doing something they don't really understand the consequences of.
> Looking at my personal email archive I have some emails from people who
> mistakenly subscribed to all bug reports about Ubuntu, did not know how
> to unsubscribe and were receiving hundreds of emails a day. While
> emails provide an unsubscribe link for individual bug reports there is
> no link for how to remove a structural subscription.
Another example: <email address hidden> receives more or less regulary
complaints from people who feel "drowning" by the amount of mails they
get from bug subscriptions.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://
iD8DBQFMUIF/
LI/vZ8xrFAShGAD
=50CY
-----END PGP SIGNATURE-----

Deryck Hodge (deryck) wrote : | # |
While I understand Rob's concerns, I think in this case we do want to be opinionated and keep people from getting themselves into trouble. "less flexible" is a feature not a bug in this case. :-)
Yes, we should fix the surrounding issues, some of which will be fixed by our current work to improve subscriptions. But there is rarely a case where subscribing to all of a distribution's bugs is what people actually want to do. For the times where there is -- e.g., someone wants to archive all bug activity for a distribution -- there is an easy work around by subscribing to the mailing list or join the bug supervisor team. Perhaps this adds some UI complexity to communicate how one would go about this, but I think this risk is acceptable given the alternatives and the few number of people who desire this.
I'll also take a look at the code now to review this for Brian, to get him unblocked on this work.
Cheers,
deryck

Deryck Hodge (deryck) wrote : | # |
Hi, Brian.
I've had a chance to review the code itself now. Thanks for the great job here you did refactoring the tests! Well done!
I do, however, see a couple problems that need to be addressed before this is ready to land.
First, you do a great job of avoiding sample data in the test except for your admin test, which uses Foo Bar. Your commented out attempt at using a with block and logging in the admin celebrity is the right way to go. You can reference the currently logged in user inside that block. Look in lp.testing.
Second, this work ensures that addBugSubscription won't succeed, but the user will still have a subscribe to bug mail link, which will then OOPS if that click-path is followed and the user tries to subscribe. The code that checks that the user can subscribe should be refactored into a helper method which can be called from the model and the view so that the link is never presented in the first place. See lp.bugs.
Again, feel free to ping me if you need to talk more about these changes.
Cheers,
deryck

Deryck Hodge (deryck) wrote : | # |
The code itself looks fine now. But I led you wrong on where to check to display the link, I think. This didn't work for me testing locally. Did it work for you?
I didn't realize there is a mix at lp.registry.

Robert Collins (lifeless) wrote : | # |
So, to make sure I understand:
- a distro without a bug supervisor does not permit structural subscription?
We are expecting many more distros though - derived distros are being worked on.
I have no problem with being opinionated; I'd -much- rather we warned
than stopped people - simply because if we prohibit something we
*will* get bug reports that its prohibited (the same as we get users
who were not warned about the firehose - who were *invited* into doing
something silly).
-Rob

Brian Murray (brian-murray) wrote : | # |
On Tue, Aug 03, 2010 at 10:11:04PM -0000, Robert Collins wrote:
> So, to make sure I understand:
> - a distro without a bug supervisor does not permit structural subscription?
No, this is backwards.
A distro without a bug supervisor allows structural subscription.
A distro with a bug supervisor only allows the bug supervisor, or
members of that team, to structurally subscribe.

Deryck Hodge (deryck) wrote : | # |
On Tue, Aug 3, 2010 at 5:11 PM, Robert Collins
<email address hidden> wrote:
> So, to make sure I understand:
> - a distro without a bug supervisor does not permit structural subscription?
>
> We are expecting many more distros though - derived distros are being worked on.
>
> I have no problem with being opinionated; I'd -much- rather we warned
> than stopped people - simply because if we prohibit something we
> *will* get bug reports that its prohibited (the same as we get users
> who were not warned about the firehose - who were *invited* into doing
> something silly).
>
Right, but we get bug reports regardless of what we do. :-) The
threat of bug reports makes me neither fear nor regret the change. ;)
The real question is "should we allow users to subscribe to all of a
distributions bugs?" I think for distributions with a large number of
users and bugs this makes no sense. Brian's choice to limit this to
distros that have defined a bug supervisor seems about the best check
we have for "is this a large distro?" Not perfect, sure, but not bad
either.
The cases of someone wanting to subscribe to every bug email for
everything in a distro are few -- people like Brian and the Debian PTS
-- and all of these cases have an option to get at the notifications
(subscribe to mailing list or join bug supervisor team), and in the
common case, we prevent people from getting themselves into trouble
and giving Launchpad a bad reputation for spamming people. Also,
we're not changing anyone's current subscription, just preventing them
in the future. We're only going to clean up deactivated accounts once
this work lands.
People don't read warnings. There is already a warning.
Finally, I really don't understand why this change is a big deal. In
the world of Launchpad, this is a very minor area of potential
complexity and confusion, and it brings great benefit in terms of
sparing less-experienced users getting spammed by the bug tracker.
It's well known and well documented that, despite our best efforts,
people end up subscribing to all of Ubuntu's bugs. Why not prevent
that and be done with it? :-)
Cheers,
deryck
--
Deryck Hodge
https:/
http://

Robert Collins (lifeless) wrote : | # |
>> Finally, I really don't understand why this change is a big deal.
Its not a big deal :- I'm still getting a handle on a couple of things.
One of them is how to be clear what hat I'm when I say something; in
this conversation its a bit unclear - sorry. I wasn't trying to swing
the TA bat at this particular mp - rather I trying to really
understand the tradeoffs we're making, and why - and expressing my
personal opinions freely as I learn.
The other thing *is* architectural, which added to the confusion i
generated with the first thing : I'm still refining my understanding /
story around what bits of our UI are for machines, and what aren't :
the email interfaces are kindof-both and as such there are tensions
around making it really excellent. (Contrast with the web UI - humans
only, and the rest UI - machines only).
-Rob

Brian Murray (brian-murray) wrote : | # |
It should be ready for re-review now. I tested it locally by setting a bug supervisor (guadamen) for Ubuntu and then confirming that the link did not appear for someone (sample person) who is not a member of that bug supervisor team.

Deryck Hodge (deryck) wrote : | # |
Looks good. Thanks for all your work getting this ready.
Cheers,
deryck
Preview Diff
1 | === modified file 'lib/lp/bugs/browser/bug.py' |
2 | --- lib/lp/bugs/browser/bug.py 2010-08-07 14:54:40 +0000 |
3 | +++ lib/lp/bugs/browser/bug.py 2010-08-17 14:18:48 +0000 |
4 | @@ -251,7 +251,7 @@ |
5 | return Link('+addbranch', text, icon='add') |
6 | |
7 | def linktocve(self): |
8 | - """Return the 'Link tp CVE' Link.""" |
9 | + """Return the 'Link to CVE' Link.""" |
10 | text = structured( |
11 | 'Link to ' |
12 | '<abbr title="Common Vulnerabilities and Exposures Index">' |
13 | |
14 | === modified file 'lib/lp/bugs/browser/bugtask.py' |
15 | --- lib/lp/bugs/browser/bugtask.py 2010-08-03 08:49:19 +0000 |
16 | +++ lib/lp/bugs/browser/bugtask.py 2010-08-17 14:18:48 +0000 |
17 | @@ -2320,7 +2320,9 @@ |
18 | '+securitycontact', 'Change security contact', icon='edit') |
19 | |
20 | def subscribe(self): |
21 | - return Link('+subscribe', 'Subscribe to bug mail', icon='edit') |
22 | + user = getUtility(ILaunchBag).user |
23 | + if self.context.userCanAlterBugSubscription(user): |
24 | + return Link('+subscribe', 'Subscribe to bug mail', icon='edit') |
25 | |
26 | def nominations(self): |
27 | return Link('+nominations', 'Review nominations', icon='bug') |
28 | |
29 | === modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt' |
30 | --- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-07 15:00:59 +0000 |
31 | +++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2010-08-17 14:18:48 +0000 |
32 | @@ -11,7 +11,7 @@ |
33 | tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" /> |
34 | <tbody tal:define="menu context/menu:bugs"> |
35 | <tr tal:define="subscribe_link menu/subscribe|nothing" |
36 | - tal:condition="subscribe_link"> |
37 | + tal:condition="python: subscribe_link and subscribe_link.enabled"> |
38 | <td class="bugs-count" style="padding-top: 1em"> |
39 | <a tal:attributes="href subscribe_link/url"> |
40 | <img tal:attributes="src subscribe_link/icon_url" /> |
41 | |
42 | === modified file 'lib/lp/registry/browser/structuralsubscription.py' |
43 | --- lib/lp/registry/browser/structuralsubscription.py 2010-04-16 15:06:55 +0000 |
44 | +++ lib/lp/registry/browser/structuralsubscription.py 2010-08-17 14:18:48 +0000 |
45 | @@ -313,4 +313,8 @@ |
46 | else: |
47 | text = 'Subscribe to bug mail' |
48 | icon = 'add' |
49 | - return Link('+subscribe', text, icon=icon, enabled=enabled) |
50 | + if enabled == False or ( |
51 | + not sst.userCanAlterBugSubscription(self.user, self.user)): |
52 | + return Link('+subscribe', text, icon=icon, enabled=False) |
53 | + else: |
54 | + return Link('+subscribe', text, icon=icon, enabled=enabled) |
55 | |
56 | === modified file 'lib/lp/registry/configure.zcml' |
57 | --- lib/lp/registry/configure.zcml 2010-08-06 14:49:35 +0000 |
58 | +++ lib/lp/registry/configure.zcml 2010-08-17 14:18:48 +0000 |
59 | @@ -150,7 +150,8 @@ |
60 | getSubscriptions |
61 | parent_subscription_target |
62 | target_type_display |
63 | - userHasBugSubscriptions"/> |
64 | + userHasBugSubscriptions |
65 | + userCanAlterBugSubscription"/> |
66 | <require |
67 | permission="launchpad.AnyPerson" |
68 | attributes=" |
69 | @@ -294,7 +295,8 @@ |
70 | getBugNotificationsRecipients |
71 | parent_subscription_target |
72 | target_type_display |
73 | - userHasBugSubscriptions"/> |
74 | + userHasBugSubscriptions |
75 | + userCanAlterBugSubscription"/> |
76 | <require |
77 | permission="launchpad.AnyPerson" |
78 | attributes=" |
79 | @@ -417,7 +419,8 @@ |
80 | official_bug_tags |
81 | findRelatedArchives |
82 | findRelatedArchivePublications |
83 | - userHasBugSubscriptions"/> |
84 | + userHasBugSubscriptions |
85 | + userCanAlterBugSubscription"/> |
86 | <require |
87 | permission="launchpad.AnyPerson" |
88 | attributes=" |
89 | @@ -924,7 +927,8 @@ |
90 | getBugNotificationsRecipients |
91 | parent_subscription_target |
92 | target_type_display |
93 | - userHasBugSubscriptions"/> |
94 | + userHasBugSubscriptions |
95 | + userCanAlterBugSubscription"/> |
96 | <require |
97 | permission="launchpad.AnyPerson" |
98 | attributes=" |
99 | @@ -1195,7 +1199,8 @@ |
100 | getBugNotificationsRecipients |
101 | parent_subscription_target |
102 | target_type_display |
103 | - userHasBugSubscriptions"/> |
104 | + userHasBugSubscriptions |
105 | + userCanAlterBugSubscription"/> |
106 | <require |
107 | permission="launchpad.AnyPerson" |
108 | attributes=" |
109 | @@ -1344,7 +1349,8 @@ |
110 | getSubscriptions |
111 | parent_subscription_target |
112 | target_type_display |
113 | - userHasBugSubscriptions"/> |
114 | + userHasBugSubscriptions |
115 | + userCanAlterBugSubscription"/> |
116 | <require |
117 | permission="launchpad.AnyPerson" |
118 | attributes=" |
119 | @@ -1467,7 +1473,8 @@ |
120 | getBugNotificationsRecipients |
121 | parent_subscription_target |
122 | target_type_display |
123 | - userHasBugSubscriptions"/> |
124 | + userHasBugSubscriptions |
125 | + userCanAlterBugSubscription"/> |
126 | <require |
127 | permission="launchpad.AnyPerson" |
128 | attributes=" |
129 | |
130 | === modified file 'lib/lp/registry/interfaces/distribution.py' |
131 | --- lib/lp/registry/interfaces/distribution.py 2010-08-12 19:35:11 +0000 |
132 | +++ lib/lp/registry/interfaces/distribution.py 2010-08-17 14:18:48 +0000 |
133 | @@ -183,7 +183,7 @@ |
134 | "The person or team responsible for decisions about features " |
135 | "and bugs that will be targeted for any series in this " |
136 | "distribution. Note that you can also specify a driver " |
137 | - "on each series who's permissions will be limited to that " |
138 | + "on each series whose permissions will be limited to that " |
139 | "specific series."), |
140 | required=False, vocabulary='ValidPersonOrTeam')) |
141 | drivers = Attribute( |
142 | |
143 | === modified file 'lib/lp/registry/interfaces/structuralsubscription.py' |
144 | --- lib/lp/registry/interfaces/structuralsubscription.py 2010-07-28 20:44:25 +0000 |
145 | +++ lib/lp/registry/interfaces/structuralsubscription.py 2010-08-17 14:18:48 +0000 |
146 | @@ -184,6 +184,9 @@ |
147 | :return: The new subscription. |
148 | """ |
149 | |
150 | + def userCanAlterBugSubscription(subscriber, subscribed_by): |
151 | + """Check if a user can change a bug subscription for a person.""" |
152 | + |
153 | @operation_parameters( |
154 | subscriber=Reference( |
155 | schema=IPerson, |
156 | |
157 | === modified file 'lib/lp/registry/model/structuralsubscription.py' |
158 | --- lib/lp/registry/model/structuralsubscription.py 2010-07-29 20:16:23 +0000 |
159 | +++ lib/lp/registry/model/structuralsubscription.py 2010-08-17 14:18:48 +0000 |
160 | @@ -179,12 +179,35 @@ |
161 | subscribed_by=subscribed_by, |
162 | **self._target_args) |
163 | |
164 | + def userCanAlterBugSubscription(self, subscriber, subscribed_by): |
165 | + """See `IStructuralSubscriptionTarget`.""" |
166 | + |
167 | + admins = getUtility(ILaunchpadCelebrities).admin |
168 | + # If the object to be structurally subscribed to for bug |
169 | + # notifications is a distribution and that distribution has a |
170 | + # bug supervisor then only the bug supervisor or a member of |
171 | + # that team or, of course, admins, can subscribe someone to it. |
172 | + if IDistribution.providedBy(self) and self.bug_supervisor is not None: |
173 | + if subscriber is None or subscribed_by is None: |
174 | + return False |
175 | + elif (subscriber != self.bug_supervisor |
176 | + and not subscriber.inTeam(self.bug_supervisor) |
177 | + and not subscribed_by.inTeam(admins)): |
178 | + return False |
179 | + return True |
180 | + |
181 | def addBugSubscription(self, subscriber, subscribed_by): |
182 | """See `IStructuralSubscriptionTarget`.""" |
183 | # This is a helper method for creating a structural |
184 | # subscription and immediately giving it a full |
185 | # bug notification level. It is useful so long as |
186 | # subscriptions are mainly used to implement bug contacts. |
187 | + |
188 | + if not self.userCanAlterBugSubscription(subscriber, subscribed_by): |
189 | + raise UserCannotSubscribePerson( |
190 | + '%s does not have permission to subscribe %s' % ( |
191 | + subscribed_by.name, subscriber.name)) |
192 | + |
193 | sub = self.addSubscription(subscriber, subscribed_by) |
194 | sub.bug_notification_level = BugNotificationLevel.COMMENTS |
195 | return sub |
196 | @@ -239,7 +262,7 @@ |
197 | for key, value in self._target_args.items(): |
198 | if value is None: |
199 | target_clause_parts.append( |
200 | - "StructuralSubscription.%s IS NULL " % (key,)) |
201 | + "StructuralSubscription.%s IS NULL " % (key, )) |
202 | else: |
203 | target_clause_parts.append( |
204 | "StructuralSubscription.%s = %s " % (key, quote(value))) |
205 | |
206 | === modified file 'lib/lp/registry/tests/structural-subscription-target.txt' |
207 | --- lib/lp/registry/tests/structural-subscription-target.txt 2010-04-19 08:11:52 +0000 |
208 | +++ lib/lp/registry/tests/structural-subscription-target.txt 2010-08-17 14:18:48 +0000 |
209 | @@ -1,144 +1,15 @@ |
210 | = IStructuralSubscriptionTarget = |
211 | |
212 | -Structural subscriptions allow users to subscribe to notifications about |
213 | -new items and changes to existing items for a Launchpad structure such as |
214 | -Product, ProjectGroup, Distribution, Milestone and series. |
215 | - |
216 | - >>> from canonical.launchpad.interfaces import ( |
217 | - ... IStructuralSubscriptionTarget) |
218 | - >>> from canonical.launchpad.webapp.testing import verifyObject |
219 | - >>> verifyObject(IStructuralSubscriptionTarget, target) |
220 | - True |
221 | - |
222 | -== Bug Subscriptions == |
223 | - |
224 | -Bug subscriptions are structural subscriptions to notifications about |
225 | -bug activity. |
226 | - |
227 | - >>> target.bug_subscriptions |
228 | - [] |
229 | - |
230 | -First, we try to create a new subscription. |
231 | + |
232 | +Let's subscribe ubuntu-team. |
233 | |
234 | >>> from canonical.launchpad.interfaces import IPersonSet |
235 | >>> personset = getUtility(IPersonSet) |
236 | + >>> ubuntu_team = personset.getByName("ubuntu-team") |
237 | >>> no_priv = personset.getByName("no-priv") |
238 | - |
239 | -Only authenticated users can create subscriptions. |
240 | - |
241 | - >>> target.addBugSubscription(no_priv, no_priv) |
242 | - Traceback (most recent call last): |
243 | - ... |
244 | - Unauthorized: ... |
245 | - |
246 | -Let's login then to add a subscription: |
247 | - |
248 | + >>> foobar = personset.getByName("name16") |
249 | >>> from canonical.launchpad.ftests import login |
250 | >>> login("foo.bar@canonical.com") |
251 | - |
252 | - >>> target.addBugSubscription(no_priv, no_priv) |
253 | - <StructuralSubscription ...> |
254 | - >>> [sub.subscriber.name for sub in target.bug_subscriptions] |
255 | - [u'no-priv'] |
256 | - |
257 | -Trying to add a subscription to a target when that person |
258 | -or team is already subscribed to that target will return |
259 | -the existing subscription. |
260 | - |
261 | - >>> target.addBugSubscription(no_priv, no_priv) |
262 | - <StructuralSubscription ...> |
263 | - |
264 | -People can only be subscribed by themselves, and only the team admins may |
265 | -subscribe a team. |
266 | - |
267 | -no-priv, who has no relationship to ubuntu-team, cannot subscribe it. |
268 | - |
269 | - >>> ubuntu_team = personset.getByName("ubuntu-team") |
270 | - >>> target.addBugSubscription(ubuntu_team, no_priv) |
271 | - Traceback (most recent call last): |
272 | - ... |
273 | - UserCannotSubscribePerson: no-priv does not have permission to subscribe ubuntu-team. |
274 | - |
275 | -But kamion, an admin of the team, can. |
276 | - |
277 | - >>> kamion = personset.getByName("kamion") |
278 | - >>> target.addBugSubscription(ubuntu_team, kamion) |
279 | - <StructuralSubscription ...> |
280 | - |
281 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
282 | - [u'no-priv', u'ubuntu-team'] |
283 | - |
284 | -foobar, a Launchpad administrator, can as well. |
285 | - |
286 | - >>> foobar = personset.getByName("name16") |
287 | - >>> target.addBugSubscription(ubuntu_team, foobar) |
288 | - <StructuralSubscription ...> |
289 | - |
290 | -A non-admin cannot subscribe a person other than themselves. |
291 | - |
292 | - >>> target.addBugSubscription(kamion, no_priv) |
293 | - Traceback (most recent call last): |
294 | - ... |
295 | - UserCannotSubscribePerson: no-priv does not have permission to subscribe kamion. |
296 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
297 | - [u'no-priv', u'ubuntu-team'] |
298 | - |
299 | -But again, an admin can. |
300 | - |
301 | - >>> target.addBugSubscription(kamion, foobar) |
302 | - <StructuralSubscription ...> |
303 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
304 | - [u'kamion', u'no-priv', u'ubuntu-team'] |
305 | - |
306 | -To remove a bug subscription, use |
307 | -IStructuralSubscriptionTarget.removeBugSubscription: |
308 | - |
309 | - >>> target.removeBugSubscription(no_priv, no_priv) |
310 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
311 | - [u'kamion', u'ubuntu-team'] |
312 | - |
313 | -The subscription rules apply to unsubscription as well. |
314 | - |
315 | -An unprivileged user cannot unsubscribe a team. |
316 | - |
317 | - >>> target.removeBugSubscription(ubuntu_team, no_priv) |
318 | - Traceback (most recent call last): |
319 | - ... |
320 | - UserCannotSubscribePerson: no-priv does not have permission to unsubscribe ubuntu-team. |
321 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
322 | - [u'kamion', u'ubuntu-team'] |
323 | - |
324 | -But a team admin can. |
325 | - |
326 | - >>> target.removeBugSubscription(ubuntu_team, kamion) |
327 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
328 | - [u'kamion'] |
329 | - |
330 | -An unprivileged user also cannot unsubscribe another user. |
331 | - |
332 | - >>> target.removeBugSubscription(kamion, no_priv) |
333 | - Traceback (most recent call last): |
334 | - ... |
335 | - UserCannotSubscribePerson: no-priv does not have permission to unsubscribe kamion. |
336 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
337 | - [u'kamion'] |
338 | - |
339 | -But the user themselves can. |
340 | - |
341 | - >>> target.removeBugSubscription(kamion, kamion) |
342 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
343 | - [] |
344 | - |
345 | -Trying to remove a subscription that doesn't exist on a target raises a |
346 | -DeleteSubscriptionError. |
347 | - |
348 | - >>> target.removeBugSubscription(foobar, foobar) |
349 | - Traceback (most recent call last): |
350 | - ... |
351 | - DeleteSubscriptionError: ... |
352 | - |
353 | -Let's subscribe ubuntu-team again. |
354 | - |
355 | >>> target.addBugSubscription(ubuntu_team, foobar) |
356 | <StructuralSubscription ...> |
357 | |
358 | @@ -191,7 +62,7 @@ |
359 | |
360 | |
361 | === Structural subscriptions and indirect bug subscriptions === |
362 | - |
363 | + |
364 | >>> bug = filebug(target, 'test bug one') |
365 | >>> indirect_subscribers = set( |
366 | ... subscriber.name for subscriber in bug.getIndirectSubscribers()) |
367 | |
368 | === modified file 'lib/lp/registry/tests/test_structuralsubscriptiontarget.py' |
369 | --- lib/lp/registry/tests/test_structuralsubscriptiontarget.py 2009-12-05 18:37:28 +0000 |
370 | +++ lib/lp/registry/tests/test_structuralsubscriptiontarget.py 2010-08-17 14:18:48 +0000 |
371 | @@ -4,12 +4,14 @@ |
372 | """Test harness for running tests against IStructuralsubscriptionTarget |
373 | implementations. |
374 | """ |
375 | +__metaclass__ = type |
376 | |
377 | import unittest |
378 | |
379 | from zope.component import getUtility |
380 | +from zope.security.interfaces import Unauthorized |
381 | +from zope.security.proxy import ProxyFactory, removeSecurityProxy |
382 | |
383 | -from canonical.launchpad.webapp.interfaces import ILaunchBag |
384 | from lp.bugs.interfaces.bug import CreateBugParams |
385 | from lp.registry.interfaces.distribution import IDistributionSet |
386 | from lp.registry.interfaces.product import IProductSet |
387 | @@ -18,7 +20,234 @@ |
388 | bugtarget_filebug) |
389 | from canonical.launchpad.testing.systemdocs import ( |
390 | LayeredDocFileSuite, setUp, tearDown) |
391 | +from canonical.launchpad.webapp.interfaces import ILaunchBag |
392 | +from canonical.launchpad.webapp.testing import verifyObject |
393 | from canonical.testing import LaunchpadFunctionalLayer |
394 | +from lp.testing import ( |
395 | + ANONYMOUS, login, login_person, TestCaseWithFactory) |
396 | +from lp.testing._login import login_celebrity |
397 | +from lp.registry.interfaces.structuralsubscription import ( |
398 | + DeleteSubscriptionError, IStructuralSubscriptionTarget, |
399 | + UserCannotSubscribePerson) |
400 | +from lp.registry.model.structuralsubscription import StructuralSubscription |
401 | + |
402 | + |
403 | +class StructuralSubscriptionTestBase: |
404 | + |
405 | + def setUp(self): |
406 | + super(StructuralSubscriptionTestBase, self).setUp() |
407 | + self.ordinary_subscriber = self.factory.makePerson() |
408 | + self.bug_supervisor_subscriber = self.factory.makePerson() |
409 | + self.team_owner = self.factory.makePerson() |
410 | + self.team = self.factory.makeTeam(owner=self.team_owner) |
411 | + |
412 | + def test_target_implements_structural_subscription_target(self): |
413 | + self.assertTrue(verifyObject(IStructuralSubscriptionTarget, |
414 | + self.target)) |
415 | + |
416 | + def test_anonymous_cannot_subscribe_anyone(self): |
417 | + # only authenticated users can create structural subscriptions |
418 | + login(ANONYMOUS) |
419 | + self.assertRaises(Unauthorized, getattr, self.target, |
420 | + 'addBugSubscription') |
421 | + |
422 | + def test_person_structural_subscription_by_other_person(self): |
423 | + # a person can not subscribe someone else willy nilly |
424 | + login_person(self.ordinary_subscriber) |
425 | + self.assertRaises(UserCannotSubscribePerson, |
426 | + self.target.addBugSubscription, |
427 | + self.team_owner, self.ordinary_subscriber) |
428 | + |
429 | + def test_team_structural_subscription_by_non_team_member(self): |
430 | + # a person not related to a team cannot subscribe it |
431 | + login_person(self.ordinary_subscriber) |
432 | + self.assertRaises(UserCannotSubscribePerson, |
433 | + self.target.addBugSubscription, |
434 | + self.team, self.ordinary_subscriber) |
435 | + |
436 | + def test_admin_can_subscribe_anyone(self): |
437 | + # a launchpad admin can create a structural subscription for |
438 | + # anyone |
439 | + admin = login_celebrity('admin') |
440 | + self.assertIsInstance( |
441 | + self.target.addBugSubscription(self.ordinary_subscriber, admin), |
442 | + StructuralSubscription) |
443 | + |
444 | + def test_secondary_structural_subscription(self): |
445 | + # creating a structural subscription a 2nd time returns the |
446 | + # first structural subscription |
447 | + login_person(self.bug_supervisor_subscriber) |
448 | + subscription1 = self.target.addBugSubscription( |
449 | + self.bug_supervisor_subscriber, self.bug_supervisor_subscriber) |
450 | + subscription2 = self.target.addBugSubscription( |
451 | + self.bug_supervisor_subscriber, self.bug_supervisor_subscriber) |
452 | + self.assertIs(subscription1.id, subscription2.id) |
453 | + |
454 | + def test_remove_structural_subscription(self): |
455 | + # an unprivileged user cannot unsubscribe a team |
456 | + login_person(self.ordinary_subscriber) |
457 | + self.assertRaises(UserCannotSubscribePerson, |
458 | + self.target.removeBugSubscription, |
459 | + self.team, self.ordinary_subscriber) |
460 | + |
461 | + def test_remove_nonexistant_structural_subscription(self): |
462 | + # removing a nonexistant subscription raises a |
463 | + # DeleteSubscriptionError |
464 | + login_person(self.ordinary_subscriber) |
465 | + self.assertRaises(DeleteSubscriptionError, |
466 | + self.target.removeBugSubscription, |
467 | + self.ordinary_subscriber, self.ordinary_subscriber) |
468 | + |
469 | + |
470 | +class UnrestrictedStructuralSubscription(StructuralSubscriptionTestBase): |
471 | + |
472 | + def test_structural_subscription_by_ordinary_user(self): |
473 | + # ordinary users can subscribe themselves |
474 | + login_person(self.ordinary_subscriber) |
475 | + self.assertIsInstance( |
476 | + self.target.addBugSubscription( |
477 | + self.ordinary_subscriber, self.ordinary_subscriber), |
478 | + StructuralSubscription) |
479 | + |
480 | + def test_remove_structural_subscription_by_ordinary_user(self): |
481 | + # ordinary users can unsubscribe themselves |
482 | + login_person(self.ordinary_subscriber) |
483 | + self.assertIsInstance( |
484 | + self.target.addBugSubscription( |
485 | + self.ordinary_subscriber, self.ordinary_subscriber), |
486 | + StructuralSubscription) |
487 | + self.assertEqual( |
488 | + self.target.removeBugSubscription( |
489 | + self.ordinary_subscriber, self.ordinary_subscriber), |
490 | + None) |
491 | + |
492 | + def test_team_structural_subscription_by_team_owner(self): |
493 | + # team owners can subscribe their team |
494 | + login_person(self.team_owner) |
495 | + self.assertIsInstance( |
496 | + self.target.addBugSubscription( |
497 | + self.team, self.team_owner), |
498 | + StructuralSubscription) |
499 | + |
500 | + def test_remove_team_structural_subscription_by_team_owner(self): |
501 | + # team owners can unsubscribe their team |
502 | + login_person(self.team_owner) |
503 | + self.assertIsInstance( |
504 | + self.target.addBugSubscription( |
505 | + self.team, self.team_owner), |
506 | + StructuralSubscription) |
507 | + self.assertEqual( |
508 | + self.target.removeBugSubscription( |
509 | + self.team, self.team_owner), |
510 | + None) |
511 | + |
512 | + |
513 | +class TestStructuralSubscriptionForDistro(StructuralSubscriptionTestBase, |
514 | + TestCaseWithFactory): |
515 | + |
516 | + layer = LaunchpadFunctionalLayer |
517 | + |
518 | + def setUp(self): |
519 | + super(TestStructuralSubscriptionForDistro, self).setUp() |
520 | + self.target = self.factory.makeDistribution() |
521 | + naked_distro = removeSecurityProxy(self.target) |
522 | + naked_distro.bug_supervisor = self.bug_supervisor_subscriber |
523 | + |
524 | + def test_distribution_subscription_by_ordinary_user(self): |
525 | + # ordinary users can not subscribe themselves to a distribution |
526 | + login_person(self.ordinary_subscriber) |
527 | + self.assertRaises(UserCannotSubscribePerson, |
528 | + self.target.addBugSubscription, |
529 | + self.ordinary_subscriber, self.ordinary_subscriber) |
530 | + |
531 | + def test_team_distribution_structural_subscription_by_team_owner(self): |
532 | + # team owners cannot subscribe their team to a distribution |
533 | + login_person(self.team_owner) |
534 | + self.assertRaises(UserCannotSubscribePerson, |
535 | + self.target.addBugSubscription, |
536 | + self.team, self.team_owner) |
537 | + |
538 | + def test_distribution_subscription_by_bug_supervisor(self): |
539 | + # bug supervisor can subscribe themselves |
540 | + login_person(self.bug_supervisor_subscriber) |
541 | + self.assertIsInstance( |
542 | + self.target.addBugSubscription( |
543 | + self.bug_supervisor_subscriber, |
544 | + self.bug_supervisor_subscriber), |
545 | + StructuralSubscription) |
546 | + |
547 | + def test_distribution_subscription_by_bug_supervisor_team(self): |
548 | + # team admins can subscribe team if team is bug supervisor |
549 | + removeSecurityProxy(self.target).bug_supervisor = self.team |
550 | + login_person(self.team_owner) |
551 | + self.assertIsInstance( |
552 | + self.target.addBugSubscription(self.team, self.team_owner), |
553 | + StructuralSubscription) |
554 | + |
555 | + def test_distribution_unsubscription_by_bug_supervisor_team(self): |
556 | + # team admins can unsubscribe team if team is bug supervisor |
557 | + removeSecurityProxy(self.target).bug_supervisor = self.team |
558 | + login_person(self.team_owner) |
559 | + self.assertIsInstance( |
560 | + self.target.addBugSubscription(self.team, self.team_owner), |
561 | + StructuralSubscription) |
562 | + self.assertEqual( |
563 | + self.target.removeBugSubscription(self.team, self.team_owner), |
564 | + None) |
565 | + |
566 | + def test_distribution_subscription_without_bug_supervisor(self): |
567 | + # for a distribution without a bug supervisor anyone can |
568 | + # subscribe |
569 | + removeSecurityProxy(self.target).bug_supervisor = None |
570 | + login_person(self.ordinary_subscriber) |
571 | + self.assertIsInstance( |
572 | + self.target.addBugSubscription( |
573 | + self.ordinary_subscriber, self.ordinary_subscriber), |
574 | + StructuralSubscription) |
575 | + |
576 | + |
577 | +class TestStructuralSubscriptionForProduct( |
578 | + UnrestrictedStructuralSubscription, TestCaseWithFactory): |
579 | + |
580 | + layer = LaunchpadFunctionalLayer |
581 | + |
582 | + def setUp(self): |
583 | + super(TestStructuralSubscriptionForProduct, self).setUp() |
584 | + self.target = self.factory.makeProduct() |
585 | + |
586 | + |
587 | +class TestStructuralSubscriptionForDistroSourcePackage( |
588 | + UnrestrictedStructuralSubscription, TestCaseWithFactory): |
589 | + |
590 | + layer = LaunchpadFunctionalLayer |
591 | + |
592 | + def setUp(self): |
593 | + super(TestStructuralSubscriptionForDistroSourcePackage, self).setUp() |
594 | + self.target = self.factory.makeDistributionSourcePackage() |
595 | + self.target = ProxyFactory(self.target) |
596 | + |
597 | + |
598 | +class TestStructuralSubscriptionForMilestone( |
599 | + UnrestrictedStructuralSubscription, TestCaseWithFactory): |
600 | + |
601 | + layer = LaunchpadFunctionalLayer |
602 | + |
603 | + def setUp(self): |
604 | + super(TestStructuralSubscriptionForMilestone, self).setUp() |
605 | + self.target = self.factory.makeMilestone() |
606 | + self.target = ProxyFactory(self.target) |
607 | + |
608 | + |
609 | +class TestStructuralSubscriptionForDistroSeries( |
610 | + UnrestrictedStructuralSubscription, TestCaseWithFactory): |
611 | + |
612 | + layer = LaunchpadFunctionalLayer |
613 | + |
614 | + def setUp(self): |
615 | + super(TestStructuralSubscriptionForDistroSeries, self).setUp() |
616 | + self.target = self.factory.makeDistroSeries() |
617 | + self.target = ProxyFactory(self.target) |
618 | + |
619 | |
620 | def distributionSourcePackageSetUp(test): |
621 | setUp(test) |
622 | @@ -27,27 +256,32 @@ |
623 | test.globs['other_target'] = ubuntu.getSourcePackage('pmount') |
624 | test.globs['filebug'] = bugtarget_filebug |
625 | |
626 | + |
627 | def productSetUp(test): |
628 | setUp(test) |
629 | test.globs['target'] = getUtility(IProductSet).getByName('firefox') |
630 | test.globs['filebug'] = bugtarget_filebug |
631 | |
632 | + |
633 | def distributionSetUp(test): |
634 | setUp(test) |
635 | test.globs['target'] = getUtility(IDistributionSet).getByName('ubuntu') |
636 | test.globs['filebug'] = bugtarget_filebug |
637 | |
638 | + |
639 | def milestone_filebug(milestone, summary, status=None): |
640 | bug = bugtarget_filebug(milestone.target, summary, status=status) |
641 | bug.bugtasks[0].milestone = milestone |
642 | return bug |
643 | |
644 | + |
645 | def milestoneSetUp(test): |
646 | setUp(test) |
647 | firefox = getUtility(IProductSet).getByName('firefox') |
648 | test.globs['target'] = firefox.getMilestone('1.0') |
649 | test.globs['filebug'] = milestone_filebug |
650 | |
651 | + |
652 | def distroseries_sourcepackage_filebug(distroseries, summary, status=None): |
653 | params = CreateBugParams( |
654 | getUtility(ILaunchBag).user, summary, comment=summary, status=status) |
655 | @@ -60,15 +294,18 @@ |
656 | nomination.approve(distroseries.distribution.owner) |
657 | return bug |
658 | |
659 | + |
660 | def distroSeriesSourcePackageSetUp(test): |
661 | setUp(test) |
662 | test.globs['target'] = ( |
663 | getUtility(IDistributionSet).getByName('ubuntu').getSeries('hoary')) |
664 | test.globs['filebug'] = distroseries_sourcepackage_filebug |
665 | |
666 | + |
667 | def test_suite(): |
668 | """Return the `IStructuralSubscriptionTarget` TestSuite.""" |
669 | suite = unittest.TestSuite() |
670 | + suite.addTest(unittest.TestLoader().loadTestsFromName(__name__)) |
671 | |
672 | setUpMethods = [ |
673 | distributionSourcePackageSetUp, |
I'm not clear why this makes sense.
- users can subscribe to the bugs list [same amount of mail]
- there are uses to being notified of every bug, same as things like
gmane are subscribed to lists
- this makes lp less flexible not more.
I suspect you're doing a bandaid to the real issues:
- subscriber lists take up a lot of space and are rarely important
- we don't prune deactivated accounts effectively
- the implications of structural subscription are not clear to users
Could you please enlarge on why the approach you've put forward makes only-in- scale problems, more ui complexity that is hard to
sense long term? If we do need a bandaid, I'm positive doing that, but
I get the impression you don't consider this a bandaid, and given the
downsides: more interrupts from valid subscribers, more code to handle
special-
explain, I'd really prefer to see a path to a long term unified fix!
-Rob