~launchpad-pqm/launchpad/devel

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
BugTarget-QuestionTarget compatibility
======================================

Bugs can be converted into questions when a person ascertains that that
is the nature of the issue. The bug's target must be adaptable to
IQuestionTarget.


BugTargets can be adapted to QuestionTargets
--------------------------------------------

Valid BugTargets except for Projects can be adapted to QuestionTargets.
The test fixture (test_bugtarget.py) provides bugtarget that is used in
this interface test for creating the bug. The target may be a: Product,
Distribution, ProductSeries, DistributionSeries, SourcePackage, or
DistributionSourcePackages.

    >>> from lp.answers.interfaces.questiontarget import IQuestionTarget
    >>> from lp.bugs.interfaces.bugtarget import IBugTarget

    >>> login('foo.bar@canonical.com')
    >>> IBugTarget.providedBy(bugtarget)
    True

    >>> IQuestionTarget.providedBy(IQuestionTarget(bugtarget))
    True


Create a question from a bug
----------------------------

The primary use case for converting a bug into a question is when a bug
contact recognises a bug is really a question. No Privileges Person
create a a new bug on the bugtarget. It will be converted to a question.

    >>> login('no-priv@canonical.com')
    >>> from lp.bugs.interfaces.bugtask import BugTaskStatus
    >>> bug = filebug(bugtarget, "Print is broken", status=BugTaskStatus.NEW)


canBeAQuestion()
----------------

The canBeAQuestion() method can be used to check if a question can be
created from a bug (but it will not state why). The most important
prerequisite for a bug to become a question is that the bugtarget's
pillar must use Launchpad to track bugs.

    >>> bug.affected_pillars[0].bug_tracking_usage
    <DBItem ServiceUsage.LAUNCHPAD, (20) Launchpad>

    >>> bug.canBeAQuestion()
    True

A Firefox bug in Debian cannot be converted to a question because the
distribution does not use Launchpad to track bugs.

    >>> from lp.bugs.interfaces.bug import IBugSet
    >>> firefox_bug = getUtility(IBugSet).get(8)
    >>> firefox = firefox_bug.bugtasks[0].target
    >>> IQuestionTarget.providedBy(firefox)
    True

    >>> firefox.distribution.bug_tracking_usage
    <DBItem ServiceUsage.UNKNOWN, (10) Unknown>

    >>> firefox_bug.canBeAQuestion()
    False


convertToQuestion()
-------------------

Sample Person recognises that this bug is a question while reviewing the
bugtarget's bugs, and choose to make it into a question. The UI would
pass Sample Person as the Person changing the status. He may provide a
message about why the report is a question.

    >>> from lp.registry.interfaces.person import IPersonSet

    >>> login('test@canonical.com')
    >>> sample_person = getUtility(IPersonSet).getByName('name12')
    >>> bug_subscription = bug.subscribe(sample_person, sample_person)

    >>> question = bug.convertToQuestion(
    ...     sample_person, "This is a question.")

The bug and the question share identical attributes.

    >>> question.target == question_target
    True

    >>> question.owner == bug.owner
    True

    >>> question.title == bug.title
    True

    >>> question.description == bug.description
    True

    >>> question.datecreated == bug.datecreated
    True

    >>> question.owner.displayname
    u'No Privileges Person'

    >>> question.title
    u'Print is broken'

    >>> question.description
    u'Print is broken'

The bug's messages are copied to the question. The comment parameter for
convertToQuestion is optional. When it is provided, it is added to the
bug.

    # Bugs save the Bug.description as the first message;
    # questions do not.

    >>> question.messages.count() == bug.messages.count() - 1
    True

    >>> question.messages[-1].text_contents == bug.messages[-1].text_contents
    True

    >>> question.messages[-1].text_contents
    u'This is a question.'

Once converted to a question, the bugtask status is Invalid.

    >>> bug.bugtasks[-1].status.title
    'Invalid'

Subscribers to the bug are notified that the bug was made into a
question and that the bugtasks are Invalid.

    >>> recipients = bug.getBugNotificationRecipients()
    >>> 'no-priv@canonical.com' in recipients.getEmails()
    True

    >>> 'test@canonical.com' in recipients.getEmails()
    True

    >>> from lp.bugs.model.bugnotification import BugNotification
    >>> bug_notifications = BugNotification.select(orderBy='-id')
    >>> for notification in bug_notifications:
    ...     print notification.message.text_contents
    ** Converted to question:
       http://answers.launchpad.dev/.../+question/...
    ** Changed in: ...
       Status: New => Invalid
    This is a question.
    Print is broken

A bug can only be converted to a question once.

    >>> question = bug.convertToQuestion(sample_person, "Fail.")
    Traceback (most recent call last):
    ...
    AssertionError: This bug was already converted to question #...


getQuestionCreatedFromBug()
---------------------------

The question created from the bug is automatically linked to the
original bug. A bug can also retrieve all the questions that link to it
to, and vice versa. The getQuestionCreatedFromBug() method will return
just the question created from the bug.

    >>> question == bug.getQuestionCreatedFromBug()
    True

    >>> question in bug.questions
    True

    >>> bug.title
    u'Print is broken'

    >>> [bug_link.question.title for bug_link in question.bug_links]
    [u'Print is broken']

    >>> [question.title for question in bug.questions]
    [u'Print is broken']


Only one bugtask must be valid
------------------------------

In the rare instance where a bug has more than one bugtask, there must
be exactly one bugtask having a non-Invalid status. The question's
target come from the bugtask's target.

    >>> login('no-priv@canonical.com')
    >>> big_bug = filebug(
    ...     bugtarget, "Print is borked", status=BugTaskStatus.NEW)

    >>> evo_project = factory.makeProduct()
    >>> evo_bugtask = factory.makeBugTask(bug=big_bug, target=evo_project)
    >>> bugtasks = big_bug.bugtasks
    >>> len(bugtasks) > 1
    True

    >>> len([bt for bt in bugtasks if bt.status.title != 'Invalid']) > 1
    True

    >>> big_bug.canBeAQuestion()
    False

The user can choose to Invalidate one or more bugtasks so that only one
bugtask can provide the QuestionTarget. Note that the comment is not
provided

    >>> evo_bugtask.transitionToStatus(BugTaskStatus.INVALID, sample_person)
    >>> len([bt for bt in bugtasks
    ...     if bt.status.title == 'New' and bt.conjoined_master is None])
    1

    >>> big_bug.canBeAQuestion()
    True

    >>> question = big_bug.convertToQuestion(sample_person)
    >>> question.title
    u'Print is borked'

    >>> len(bugtasks) == len([
    ...     bt for bt in bugtasks if bt.status.title == 'Invalid'])
    True