1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
|
BugNotificationRecipients instances store email addresses mapped to the
reason for which they are being notified in a certain bug. It implements
the INotificationRecipientSet interface for bug notifications. (See
notification-recipient-set.txt for the details.)
How it's used
=============
The IBug.getBugNotificationRecipients() implementation creates and uses
BugNotificationRecipients instances. Here's an example of this in
action:
>>> from lp.bugs.model.bug import Bug
>>> from lp.registry.model.distribution import Distribution
>>> from lp.registry.model.product import Product
>>> bug_one = Bug.get(1)
>>> recipients = bug_one.getBugNotificationRecipients()
The instance of BugNotificationRecipients we get back correctly
implements INotificationRecipientSet:
>>> from canonical.launchpad.interfaces.launchpad import (
... INotificationRecipientSet,
... )
>>> from canonical.launchpad.webapp.testing import verifyObject
>>> verifyObject(INotificationRecipientSet, recipients)
True
This instance contains email addresses and rationales. Let's define a
helper function so we can format this output:
>>> def print_rationales(rationale):
... for address in rationale.getEmails():
... text, header = rationale.getReason(address)
... print address
... print " %s" % header
... print " %s" % text
And print them out. The first line is the email address; second is the
text appropriate to be used in an X- header, and the last is the text
appropriate for an email footer.
>>> print_rationales(recipients)
foo.bar@canonical.com
Subscriber (mozilla-firefox in Ubuntu)
You received this bug notification because you are subscribed
to mozilla-firefox in Ubuntu.
mark@example.com
Assignee
You received this bug notification because you are a bug assignee.
support@ubuntu.com
Registrant (Ubuntu) @ubuntu-team
You received this bug notification because you are a member of
Ubuntu Team, which is the registrant for Ubuntu.
test@canonical.com
Subscriber
You received this bug notification because you are a direct
subscriber of the bug.
The Bug-BugNotificationRecipients API
=====================================
Most of the API of BugNotificationRecipients is actually kept private
between the Bug class and itself. Let's now demonstrate the API that Bug
and BugNotificationRecipients use to set up the rationales; this is
essentially what happens under the wraps when you call
IBug.getBugNotificationRecipients().
Let's up some data for our test:
>>> from lp.registry.interfaces.person import IPersonSet
>>> from lp.bugs.mail.bugnotificationrecipients import (
... BugNotificationRecipients)
>>> debian = Distribution.selectOneBy(name="debian")
>>> pmount = debian.getSourcePackage("pmount")
>>> alsa_utils = Product.selectOneBy(name="alsa-utils")
>>> gnomebaker = Product.selectOneBy(name="gnomebaker")
>>> personset = getUtility(IPersonSet)
Here's where getBugNotificationRecipients() starts off. First, a
BugNotificationRecipients instance is created:
>>> recipients = BugNotificationRecipients()
Then, subscribers of various types are added:
>>> foo_bar = personset.getByEmail("foo.bar@canonical.com")
>>> recipients.addDupeSubscriber(foo_bar)
>>> test = personset.getByEmail("test@canonical.com")
>>> recipients.addDirectSubscriber(test)
>>> no_priv = personset.getByEmail("no-priv@canonical.com")
>>> recipients.addAssignee(no_priv)
>>> carlos = personset.getByEmail("carlos@canonical.com")
>>> recipients.addStructuralSubscriber(carlos, pmount)
>>> testtest = personset.getByEmail("testtest@canonical.com")
>>> recipients.addRegistrant(testtest, gnomebaker)
If we print out the recipients and rationales, here's what we get:
>>> print_rationales(recipients)
carlos@canonical.com
Subscriber (pmount in Debian)
You received this bug notification because you are subscribed
to pmount in Debian.
foo.bar@canonical.com
Subscriber of Duplicate
You received this bug notification because you are a direct
subscriber of a duplicate bug.
no-priv@canonical.com
Assignee
You received this bug notification because you are a bug
assignee.
test@canonical.com
Subscriber
You received this bug notification because you are a direct
subscriber of the bug.
Note how we account for every important variation in bug subscriptions
here: bug supervisors, subscribers, dupe subscribers and more.
A duplicate bug modification notifies its main bug
==================================================
If the bug we are changing is actually a duplicate of another bug, an
additional step is involved. A BugNotificationRecipients instance is
created, annotating that it represents a master bug (of which we are a
duplicate of).
>>> bug_two = Bug.get(2)
>>> recipients = BugNotificationRecipients(duplicateof=bug_two)
>>> foo_bar = personset.getByEmail("foo.bar@canonical.com")
>>> recipients.addDupeSubscriber(foo_bar)
>>> test = personset.getByEmail("test@canonical.com")
>>> recipients.addDirectSubscriber(test)
>>> no_priv = personset.getByEmail("no-priv@canonical.com")
>>> recipients.addAssignee(no_priv)
>>> carlos = personset.getByEmail("carlos@canonical.com")
>>> recipients.addStructuralSubscriber(carlos, pmount)
>>> testtest = personset.getByEmail("testtest@canonical.com")
>>> recipients.addRegistrant(testtest, gnomebaker)
If you print out rationales in this situation, you'll see that the
message says "via Bug 2". The reason for this is that the people being
notified here are actually subscribed to bug 2, and they may be asking
themselves why the hell they are getting email for bug 1.
>>> print_rationales(recipients)
carlos@canonical.com
Subscriber (pmount in Debian) via Bug 2
You received this bug notification because you are subscribed
to pmount in Debian (via bug 2).
foo.bar@canonical.com
Subscriber of Duplicate via Bug 2
You received this bug notification because you are a direct
subscriber of a duplicate bug (via bug 2).
no-priv@canonical.com
Assignee via Bug 2
You received this bug notification because you are a bug
assignee (via bug 2).
test@canonical.com
Subscriber via Bug 2
You received this bug notification because you are a direct
subscriber of the bug (via bug 2).
Team subscribers are special
============================
In the case where the teams are subscribers, things vary according to
whether the team has a contact email address or not. When there is no
contact email address, all team members (cascaded down) get emailed
directly, and the person getting the notification may not know of this
immediately.
Here's an example of this situation:
>>> recipients = BugNotificationRecipients()
>>> testing_spanish_team = personset.getByName("testing-spanish-team")
>>> recipients.addDupeSubscriber(testing_spanish_team)
>>> guadamen = personset.getByName("guadamen")
>>> recipients.addAssignee(guadamen)
>>> name20 = personset.getByName("name20")
>>> recipients.addStructuralSubscriber(name20, pmount)
>>> vcs_imports = personset.getByName("vcs-imports")
>>> recipients.addRegistrant(vcs_imports, gnomebaker)
>>> commercial_admins = personset.getByName("commercial-admins")
>>> recipients.addDirectSubscriber(commercial_admins)
You'll notice that the rationales this time state clearly which team
membership is causing us to send mail.
>>> print_rationales(recipients)
carlos@canonical.com
Subscriber of Duplicate @testing-spanish-team
You received this bug notification because you are a member
of testing Spanish team, which is a subscriber of a
duplicate bug.
commercial-member@canonical.com
Subscriber @commercial-admins
You received this bug notification because you are a member
of Commercial Subscription Admins, which is a direct subscriber.
david.allouche@canonical.com
Registrant (gnomebaker) @vcs-imports
You received this bug notification because you are a member
of VCS imports, which is the registrant for gnomebaker.
foo.bar@canonical.com
Subscriber of Duplicate @testing-spanish-team
You received this bug notification because you are a member
of testing Spanish team, which is a subscriber of a
duplicate bug.
kurem@debian.cz
Subscriber of Duplicate @testing-spanish-team
You received this bug notification because you are a member
of testing Spanish team, which is a subscriber of a
duplicate bug.
mark@example.com
Subscriber of Duplicate @testing-spanish-team
You received this bug notification because you are a member
of testing Spanish team, which is a subscriber of a
duplicate bug.
robertc@robertcollins.net
Registrant (gnomebaker) @vcs-imports
You received this bug notification because you are a member
of VCS imports, which is the registrant for gnomebaker.
support@ubuntu.com
Assignee @guadamen
You received this bug notification because you are a member
of GuadaMen, which is a bug assignee.
test@canonical.com
Subscriber (pmount in Debian) @name20
You received this bug notification because you are a member
of Warty Security Team, which is subscribed to pmount in
Debian.
tsukimi@quaqua.net
Subscriber of Duplicate @testing-spanish-team
You received this bug notification because you are a member
of testing Spanish team, which is a subscriber of a
duplicate bug.
This doesn't help the end-user too much if he's a member of this team
indirectly (for instance, if he's a member of a team which is in turn a
member of another team); however, in that case, the user can still visit
the team page and see the membership graph directly. This may be worth
fixing in the future.
First impressions stick
=======================
Another important property of BugNotificationRecipients is that the
first rationale presented to it is the one that is presented -- even if
the recipient has multiple reasons for which he might be emailed. Here's
a pathological example:
>>> recipients = BugNotificationRecipients()
>>> recipients.addDirectSubscriber(test)
>>> recipients.addAssignee(test)
>>> recipients.addRegistrant(test, gnomebaker)
>>> recipients.addDirectSubscriber(foo_bar)
This guy is emailed because he's a direct subscriber, an assignee and an
upstream registrant. However, if we ask the rationales instance:
>>> print_rationales(recipients)
foo.bar@canonical.com
Subscriber
You received this bug notification because you are a direct
subscriber of the bug.
test@canonical.com
Subscriber
You received this bug notification because you are a direct
subscriber of the bug.
Only the first rationale is presented. This is the case even if we
update this set of recipients with another one:
>>> recipients2 = BugNotificationRecipients()
>>> recipients2.addDupeSubscriber(test)
>>> recipients2.update(recipients)
The rationales for test@canonical.com in the 'recipients' instance just
don't matter:
>>> print_rationales(recipients2)
foo.bar@canonical.com
Subscriber
You received this bug notification because you are a direct
subscriber of the bug.
test@canonical.com
Subscriber of Duplicate
You received this bug notification because you are a direct
subscriber of a duplicate bug.
This may be seen as a limitation, but you don't want a 10-line rationale
footer for people who are central to Launchpad, so for now it's the way
it is.
|