1
= Launchpad Answer Tracker =
1
========================
2
Launchpad Answer Tracker
3
========================
3
Launchpad includes an Answer Tracker where users can post questions
4
(usually about problems they encounter with projects) and other can
5
answer them.) Questions are created and accessed using the
6
IQuestionTarget interface. This interface is available on Products,
7
Distributions and DistributionSourcePackages.
5
Launchpad includes an Answer Tracker where users can post questions, usually
6
about problems they encounter with projects, and other people can answer them.
7
Questions are created and accessed using the IQuestionTarget interface. This
8
interface is available on Products, Distributions and
9
DistributionSourcePackages.
9
11
>>> login('test@canonical.com')
11
13
>>> from canonical.launchpad.webapp.testing import verifyObject
12
>>> from canonical.launchpad.interfaces import (
13
... IDistributionSet, IProductSet, IPersonSet, IQuestionTarget)
14
>>> from lp.answers.interfaces.questiontarget import IQuestionTarget
15
>>> from lp.registry.interfaces.product import IProductSet
15
17
>>> firefox = getUtility(IProductSet)['firefox']
16
18
>>> verifyObject(IQuestionTarget, firefox)
21
>>> from lp.registry.interfaces.distribution import IDistributionSet
19
22
>>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
20
23
>>> verifyObject(IQuestionTarget, ubuntu)
56
59
You create a new question by calling the newQuestion() method of an
57
60
IQuestionTarget attribute.
62
>>> from lp.registry.interfaces.person import IPersonSet
59
63
>>> sample_person = getUtility(IPersonSet).getByEmail('test@canonical.com')
60
64
>>> firefox_question = firefox.newQuestion(
61
65
... sample_person, "Firefox question", "Unable to use Firefox")
63
(The complete IQuestionTarget interface is documented in
64
../interfaces/ftests/questiontarget.txt.)
68
A product or distribution may be offically supported by the community
69
using the Answer Tracker. This status is set by the official_answers
70
attribute on the IProduct and IDistribution.
67
The complete IQuestionTarget interface is documented in questiontarget.txt.
73
A product or distribution may be officially supported by the community using
74
the Answer Tracker. This status is set by the official_answers attribute on
75
the IProduct and IDistribution.
72
77
>>> ubuntu.official_answers
74
79
>>> firefox.official_answers
79
Questions are manipulated through the IQuestion interface:
81
>>> from canonical.launchpad.interfaces import IQuestion
86
Questions are manipulated through the IQuestion interface.
88
>>> from lp.answers.interfaces.question import IQuestion
82
89
>>> from zope.security.proxy import removeSecurityProxy
84
91
# The complete interface is not necessarily available to the
89
96
The person who submitted the question is available in the owner field.
91
>>> firefox_question.owner == sample_person
98
>>> firefox_question.owner
99
<Person at ... name12 (Sample Person)>
94
101
When the question is created, the owner is added to the question's
97
>>> sample_person in [s.person for s in firefox_question.subscriptions]
100
The question status is 'Open':
102
>>> firefox_question.status.title
105
And the creation time is recorded in the datecreated attribute:
104
>>> from operator import attrgetter
105
>>> def print_subscribers(question):
106
... people = [subscription.person
107
... for subscription in question.subscriptions]
108
... for person in sorted(people, key=attrgetter('name')):
109
... print person.displayname
110
>>> print_subscribers(firefox_question)
113
The question status is 'Open'.
115
>>> print firefox_question.status.title
118
The question has a creation time.
107
120
>>> from datetime import datetime, timedelta
108
121
>>> from pytz import UTC
166
== Subscriptions and Notifications ==
178
Subscriptions and notifications
179
===============================
168
181
Whenever a question is created or changed, email notifications will be
169
sent. To receive such notification, one can subscribe to the bug using
182
sent. To receive such notification, one can subscribe to the bug using
170
183
the subscribe() method.
172
185
>>> no_priv = getUtility(IPersonSet).getByName('no-priv')
173
186
>>> subscription = firefox_question.subscribe(no_priv)
175
The list of subscriptions is available in the subscriptions attribute.
176
In the current case, the subscribers will include the owner
177
('Sample Person') and the newly subscribed person.
188
The subscribers include the owner and the newly subscribed person.
179
>>> [s.person.displayname for s in firefox_question.subscriptions]
180
[u'Sample Person', u'No Privileges Person']
190
>>> print_subscribers(firefox_question)
182
194
The getDirectSubscribers() method returns a sorted list of subscribers.
183
195
This method iterates like the NotificationRecipientSet returned by the
184
196
getDirectRecipients() method.
186
>>> [person.displayname
187
... for person in firefox_question.getDirectSubscribers()]
188
[u'No Privileges Person', u'Sample Person']
198
>>> for person in firefox_question.getDirectSubscribers():
199
... print person.displayname
190
203
To remove a person from the subscriptions list, we use the unsubscribe()
193
206
>>> firefox_question.unsubscribe(no_priv)
194
>>> [s.person.displayname for s in firefox_question.subscriptions]
207
>>> print_subscribers(firefox_question)
197
The persons who are on the subscription list are said to be directly
198
subscribed to the question. They explicitly choose to get notifications
199
about that particular question. This list of persons is available through
200
the getDirectRecipients() method.
210
The people on the subscription list are said to be directly subscribed to the
211
question. They explicitly chose to get notifications about that particular
212
question. This list of people is available through the getDirectRecipients()
202
215
>>> subscribers = firefox_question.getDirectRecipients()
204
217
That method returns an INotificationRecipientSet, containing the direct
205
subscribers along the rationale for contacting them:
218
subscribers along with the rationale for contacting them.
207
220
>>> from canonical.launchpad.interfaces import INotificationRecipientSet
208
221
>>> verifyObject(INotificationRecipientSet, subscribers)
210
>>> [person.displayname for person in subscribers]
212
>>> subscribers.getReason(sample_person)
213
('You received this question notification because you are a direct
214
subscriber of the question.', 'Subscriber')
223
>>> def print_reason(subscribers):
224
... for person in subscribers:
225
... text, header = subscribers.getReason(person)
226
... print header, person.displayname, text
227
>>> print_reason(subscribers)
228
Subscriber Sample Person You received this question notification
229
because you are a direct subscriber of the question.
216
There is also a list of 'indirect' subscribers to the question. These
217
are persons that didn't explicitly subscribed to the question, but that
218
will receive notifications for other reason. Answer contacts for the
219
question target are part of the indirect subscribers list.
231
There is also a list of 'indirect' subscribers to the question. These are
232
people that didn't explicitly subscribe to the question, but that will receive
233
notifications for other reasons. Answer contacts for the question target are
234
part of the indirect subscribers list.
221
236
# There are no answer contacts on the firefox product.
222
>>> [person.displayname
223
... for person in firefox_question.getIndirectRecipients()]
237
>>> list(firefox_question.getIndirectRecipients())
225
>>> from canonical.launchpad.interfaces import ILanguageSet
240
>>> from lp.services.worlddata.interfaces.language import ILanguageSet
226
241
>>> english = getUtility(ILanguageSet)['en']
227
242
>>> no_priv.addLanguage(english)
228
243
>>> firefox.addAnswerContact(no_priv)
231
246
>>> indirect_subscribers = firefox_question.getIndirectRecipients()
232
247
>>> verifyObject(INotificationRecipientSet, indirect_subscribers)
234
>>> [person.displayname for person in indirect_subscribers]
235
[u'No Privileges Person']
236
>>> indirect_subscribers.getReason(no_priv)
237
(u'You received this question notification because you are an answer
238
contact for Mozilla Firefox.',
239
u'Answer Contact (Mozilla Firefox)')
249
>>> print_reason(indirect_subscribers)
250
Answer Contact (Mozilla Firefox) No Privileges Person
251
You received this question notification because you are an answer
252
contact for Mozilla Firefox.
241
There is a special case for when the question's is associated to a
242
source package. The answer contacts for both the distribution and the
243
source package are part of the indirect subscribers list.
254
There is a special case for when the question is associated with a source
255
package. The answer contacts for both the distribution and the source package
256
are part of the indirect subscribers list.
245
258
# Let's register some answer contacts for the distribution and
257
270
>>> package_question = evolution_in_ubuntu.newQuestion(
258
271
... sample_person, 'Upgrading to Evolution 1.4 breaks plug-ins',
259
272
... "The FnordsHighlighter plug-in doesn't work after upgrade.")
260
>>> [s.person.displayname for s in package_question.subscriptions]
274
>>> print_subscribers(package_question)
262
277
>>> indirect_subscribers = package_question.getIndirectRecipients()
263
>>> [person.displayname for person in indirect_subscribers]
264
[u'No Privileges Person', u'Ubuntu Team']
265
>>> indirect_subscribers.getReason(ubuntu_team)
266
(u'You received this question notification because you are a member of
267
Ubuntu Team, which is an answer contact for Ubuntu.',
268
u'Answer Contact (ubuntu) @ubuntu-team')
269
>>> indirect_subscribers.getReason(no_priv)
270
(u'You received this question notification because you are an answer
271
contact for evolution in ubuntu.',
272
u'Answer Contact (evolution in ubuntu)')
278
>>> for person in indirect_subscribers:
279
... print person.displayname
283
>>> text, header = indirect_subscribers.getReason(ubuntu_team)
284
>>> print header, text
285
Answer Contact (ubuntu) @ubuntu-team
286
You received this question notification because you are a member of
287
Ubuntu Team, which is an answer contact for Ubuntu.
274
289
The question's assignee is also part of the indirect subscription list:
276
>>> login('foo.bar@canonical.com')
291
>>> login('admin@canonical.com')
277
292
>>> package_question.assignee = getUtility(IPersonSet).getByName('name16')
278
293
>>> indirect_subscribers = package_question.getIndirectRecipients()
279
>>> [person.displayname for person in indirect_subscribers]
280
[u'Foo Bar', u'No Privileges Person', u'Ubuntu Team']
281
>>> indirect_subscribers.getReason(package_question.assignee)
282
('You received this question notification because you are the assignee
294
>>> for person in indirect_subscribers:
295
... print person.displayname
300
>>> text, header = indirect_subscribers.getReason(
301
... package_question.assignee)
302
>>> print header, text
304
You received this question notification because you are the assignee for
286
307
The getIndirectSubscribers() method iterates like the getIndirectRecipients()
287
308
method, but it returns a sorted list instead of a NotificationRecipientSet.
288
309
It too contains the question assignee.
290
311
>>> indirect_subscribers = package_question.getIndirectSubscribers()
291
>>> [person.displayname for person in indirect_subscribers]
292
[u'Foo Bar', u'No Privileges Person', u'Ubuntu Team']
312
>>> for person in indirect_subscribers:
313
... print person.displayname
294
Notifications are sent to the list of direct and indirect subscribers.
295
The notification recipients list can be obtained by using the
296
getRecipients() method.
318
Notifications are sent to the list of direct and indirect subscribers. The
319
notification recipients list can be obtained by using the getRecipients()
298
322
>>> login('no-priv@canonical.com')
299
323
>>> subscribers = firefox_question.getRecipients()
300
324
>>> verifyObject(INotificationRecipientSet, subscribers)
302
>>> [person.displayname for person in subscribers]
303
[u'No Privileges Person', u'Sample Person']
305
(More documentation on the question notifications can be found in
306
'answer-tracker-notifications.txt'.)
326
>>> for person in subscribers:
327
... print person.displayname
331
More documentation on the question notifications can be found in
332
`answer-tracker-notifications.txt`.
311
338
A question status should not be manipulated directly but through the
312
339
workflow methods.
314
341
The complete question workflow is documented in
315
'answer-tracker-workflow.txt'.
342
`answer-tracker-workflow.txt`.
319
348
Question implements the IBugLinkTarget interface which makes it possible
320
349
to link bug report to question.
323
352
>>> verifyObject(IBugLinkTarget, firefox_question)
326
(See ../interfaces/ftests/buglinktarget.txt for the documentation and
327
test of the IBugLinkTarget interface.)
329
When a bug is linked to a question, the question's owner is subscribed to
332
>>> from canonical.launchpad.interfaces import IBugSet
355
See ../../bugs/tests/buglinktarget.txt for the documentation and test of the
356
IBugLinkTarget interface.
358
When a bug is linked to a question, the question's owner is subscribed to the
361
>>> from lp.bugs.interfaces.bug import IBugSet
333
362
>>> bug7 = getUtility(IBugSet).get(7)
334
363
>>> bug7.isSubscribed(firefox_question.owner)
338
367
>>> bug7.isSubscribed(firefox_question.owner)
341
When the link is removed, the owner is unsubscribed:
370
When the link is removed, the owner is unsubscribed.
343
372
>>> firefox_question.unlinkBug(bug7)
345
374
>>> bug7.isSubscribed(firefox_question.owner)
348
== Unsupported Questions ==
350
While a Person may ask questions in his language of choice, that does
351
not mean that indirect subscribers (Answer Contacts) to an
352
IQuestionTarget speak that language. IQuestionTarget can return a list
353
Questions in languages that are not supported
378
Unsupported questions
379
=====================
381
While a Person may ask questions in his language of choice, that does not mean
382
that indirect subscribers (Answer Contacts) to an IQuestionTarget speak that
383
language. IQuestionTarget can return a list Questions in languages that are
355
386
>>> unsupported_questions = firefox.searchQuestions(unsupported=True)
356
>>> sorted([question.title for question in unsupported_questions])
387
>>> sorted(question.title for question in unsupported_questions)
357
388
[u'Problemas de Impress\xe3o no Firefox']
359
390
>>> unsupported_questions = evolution_in_ubuntu.searchQuestions(
360
391
... unsupported=True)
361
>>> sorted([question.title for question in unsupported_questions])
392
>>> sorted(question.title for question in unsupported_questions)
364
395
>>> warty_question_target = IQuestionTarget(ubuntu_warty)
365
396
>>> unsupported_questions = warty_question_target.searchQuestions(
366
397
... unsupported=True)
367
>>> sorted([question.title for question in unsupported_questions])
398
>>> sorted(question.title for question in unsupported_questions)
368
399
[u'Problema al recompilar kernel con soporte smp (doble-n\xfacleo)',
369
400
u'\u0639\u0643\u0633 \u0627\u0644\u062a\u063a\u064a\u064a\u0631...]