~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
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
Question Workflow
=================

The status of a question changes based on the action done by users on
it. To demonstrate the workflow, we will use the existing question #2 on
the Firefox product which was filed by 'Sample Person'.

    # We will use one browser objects for the owner, and one for the user
    # providing support, 'No Privileges Person' here.

    >>> owner_browser = setupBrowser(auth='Basic test@canonical.com:test')

    >>> support_browser = setupBrowser(
    ...     auth='Basic no-priv@canonical.com:test')

    # Define some utility functions to retrieve easily the last comment
    # added and the status of the question.

    >>> def find_request_status(contents):
    ...     print extract_text(
    ...         find_tag_by_id(contents, 'question-status'))

    >>> def  find_last_comment(contents):
    ...     soup = find_main_content(contents)
    ...     return soup.fetch('div', 'boardCommentBody')[-1]

    >>> def print_last_comment(contents):
    ...     print extract_text(find_last_comment(contents))


Logging In
----------

To participate in a question, the user must be logged in.

    >>> anon_browser.open('http://launchpad.dev/firefox/+question/2')
    >>> print anon_browser.contents
    <!DOCTYPE...
    ...
    To post a message you must <a href="+login">log in</a>.
    ...


Requesting for More Information
-------------------------------

It's not unusual that the original message of a question is terse and
quite vague. In these cases, to help the user, some more information
will be required.

No Privileges Person visits the question. He see the heading 'Can you
help with this problem?'. The problem is not clear, he needs more
information. To request for more information from the question owner, No
Privileges Person enters his question in the 'Message' field and clicks
on the 'Add Information Request' button.

    >>> support_browser.open(
    ...     'http://launchpad.dev/firefox/+question/2')
    >>> content = find_tag_by_id(
    ...     support_browser.contents, 'can-you-help-with-this-problem')
    >>> print content.h2.renderContents()
    Can you help with this problem?

    >>> print extract_text(
    ...     find_tag_by_id(support_browser.contents, 'horizontal-menu'))
    Link existing bug
    Create bug report
    Link to a FAQ
    Create a new FAQ

    >>> support_browser.getControl('Message').value = (
    ...     "Can you provide an example of an URL displaying the problem?")
    >>> support_browser.getControl('Add Information Request').click()

The message was added to the question and its status was changed to
'Needs information':

    >>> find_request_status(support_browser.contents)
    Status: Needs information

    >>> print_last_comment(support_browser.contents)
    Can you provide an example of an URL displaying the problem?

Of course, if you don't add a message, clicking on the button will give
you an error.

    >>> support_browser.getControl('Add Information Request').click()
    >>> soup = find_main_content(support_browser.contents)
    >>> print soup.first('div', 'message').renderContents()
    Please enter a message.


Adding a Comment
----------------

A comment can be added at any point without altering the status. The
user simply enters the comment in the 'Message' box and clicks the 'Just
Add a Comment' button.

    >>> support_browser.getControl('Message').value = (
    ...     "I forgot to mention, in the meantime here is a workaround...")
    >>> support_browser.getControl('Just Add a Comment').click()

This appends the comment to the question and it doesn't change its
status:

    >>> print find_request_status(support_browser.contents)
    Status: Needs information ...

    >>> print_last_comment(support_browser.contents)
    I forgot to mention, in the meantime here is a workaround...


Answering with More Information
-------------------------------

When the question is in the 'Needs information' state, it means that the
question owner should come back and provide more information. He can do
so by entering the reply in the 'Message' box and clicking on the "I'm
Providing More Information" button. Note that the question owner cannot
see the 'Can you help with this problem?' heading because it is not
relevant to his tasks.

    >>> owner_browser.open(
    ...     'http://launchpad.dev/firefox/+question/2')
    >>> content = find_tag_by_id(
    ...     owner_browser.contents, 'can-you-help-with-this-problem')
    >>> content is None
    True

    >>> owner_browser.getControl('Message').value = (
    ...     "The following SVG doesn't display properly:\n"
    ...     "http://www.w3.org/2001/08/rdfweb/rdfweb-chaals-and-dan.svg")
    >>> owner_browser.getControl("I'm Providing More Information").click()

Once the owner replied with the, hopefully, requested information, the
status is changed to Open and his answer appended to the question
discussion.

    >>> print find_request_status(owner_browser.contents)
    Status: Open ...

    >>> print_last_comment(owner_browser.contents)
    The following SVG doesn't display properly:
    http://www.w3.org/2001/08/rdfweb/rdfweb-chaals-and-dan.svg


Giving an Answer
----------------

Once the question is clarified, it is easier for a user to give an
answer. This is done by entering the answer in the 'Message' box and
clicking the 'Propose Answer' button.

    >>> support_browser.open(
    ...     'http://launchpad.dev/firefox/+question/2')
    >>> support_browser.getControl('Message').value = (
    ...     "New version of the firefox package are available with SVG "
    ...     "support enabled. You can use apt-get or adept to upgrade.")
    >>> support_browser.getControl('Propose Answer').click()

This moves the the question to the Answered state and adds the answer to
the end of the discussion:

    >>> print find_request_status(support_browser.contents)
    Status: Answered ...

    >>> print_last_comment(support_browser.contents)
    New version of the firefox package are available with SVG support
    enabled. You can use apt-get or adept to upgrade.


Confirming an Answer
--------------------

When the owner comes back on the question page, he will now see a new
'This Solved My Problem' button near the answer.

    >>> owner_browser.open(
    ...     'http://launchpad.dev/firefox/+question/2')
    >>> soup = find_main_content(owner_browser.contents)
    >>> soup.fetch('div', 'boardComment')[-1].first('input', type='submit')
    <input type="submit" name="field.actions.confirm"
     value="This Solved My Problem" />

(Note although we have three comments on the question, that's the only
one that has this button. Only answers have this button.)

There is also a hint below the form to the question owner about using
the 'This Solved My Problem' button.

    >>> answer_button_paragraph = find_tag_by_id(
    ...     owner_browser.contents, 'answer-button-hint')
    >>> print extract_text(answer_button_paragraph)
    To confirm an answer, use the 'This Solved My Problem' button located at
    the bottom of the answer.

Clicking that button will confirm that the answer solved the problem.

    >>> owner_browser.getControl('This Solved My Problem').click()

This changes the status of the question to 'Solved' and mark 'No
Privileges Person' as the solver.

    >>> print find_request_status(owner_browser.contents)
    Status: Solved ...

Since no message can be provided when that button is clicked. A default
confirmation message was appended to the question discussion:

    >>> print_last_comment(owner_browser.contents)
    Thanks No Privileges Person, that solved my question.

The confirmed answer is also highlighted.

    >>> soup = find_main_content(owner_browser.contents)
    >>> bestAnswer = soup.fetch('div', 'boardComment')[-2]
    >>> print bestAnswer.first('img')
    <img src="/@@/favourite-yes" ... title="Marked as best answer" />

    >>> print soup.first(
    ...     'div', 'boardCommentBody highlighted').renderContents()
    <p>New version of the firefox package are available with SVG support
    enabled. You can use apt-get or adept to upgrade.</p>

The History link should now show up.

    >>> print extract_text(
    ...     find_tag_by_id(support_browser.contents, 'horizontal-menu'))
    History
    Link existing bug
    Create bug report
    Link to a FAQ
    Create a new FAQ


Adding another Comment
----------------------

When the question is Solved, it is still possible to add comments to it.
The user simply enters the comment in the 'Message' box and clicks the
'Just Add a Comment' button.

    >>> owner_browser.getControl('Message').value = (
    ...     "The example now displays correctly. Thanks.")
    >>> owner_browser.getControl('Just Add a Comment').click()

This appends the comment to the question and it doesn't change its
status:

    >>> print find_request_status(owner_browser.contents)
    Status: Solved ...

    >>> print_last_comment(owner_browser.contents)
    The example now displays correctly. Thanks.


Reopening
---------

It can happen that, altough the owner confirmed the question was solved,
the original problem reappears. In this case, he can reopen the question
by entering a new message and clicking the "I Still Need an Answer"
button.

    >>> owner_browser.getControl('Message').value = (
    ...     "Actually, there are still SVGs that do not display correctly. "
    ...     "For example, the following\n"
    ...     "http://people.w3.org/maxf/ChessGML/immortal.svg doesn't display "
    ...     "correctly.")
    >>> owner_browser.getControl("I Still Need an Answer").click()

This appends the new information to the question discussion and changes
its status back to 'Open'.

    >>> print find_request_status(owner_browser.contents)
    Status: Open ...

    >>> print_last_comment(owner_browser.contents)
    Actually, there are still SVGs that do not display correctly.
    For example, the following
    http://people.w3.org/maxf/ChessGML/immortal.svg doesn't
    display correctly.

This also removes the highlighting from the previous answer and sets the
answerer back to None.

    >>> soup = find_main_content(owner_browser.contents)
    >>> bestAnswer = soup.fetch('div', 'boardComment')[-4]
    >>> bestAnswer.first('strong') is None
    True

    >>> bestAnswer.first('div', 'boardCommentBody')
    <div class="boardCommentBody" itemprop="commentText"><p>New version
    of the firefox package
    are available with SVG support enabled. You can use apt-get or adept to
    upgrade.</p></div>

In addition, this creates a reopening record that is displayed in the
reopening portlet.

    >>> print extract_text(
    ...     find_tag_by_id(owner_browser.contents, 'portlet-reopenings'))
    This question was reopened ... Sample Person


Self-Answer
-----------

The owner can also gives the solution to his own question. He simply has
to enter his solution in the 'Message' box and click the 'Problem
Solved' button.

    >>> owner_browser.getControl('Message').value = (
    ...     "OK, this example requires some SVG features that will only be "
    ...     "available in Firefox 2.0.")
    >>> owner_browser.getControl("Problem Solved").click()

This appends the message to the question and sets its status to
'Solved', and the answerer as the owner. We do not however mark a
message as the "Best answer".

    >>> find_request_status(owner_browser.contents)
    Status: Solved ...

    >>> soup = find_tag_by_id(owner_browser.contents, 'portlet-details')
    >>> soup = find_main_content(owner_browser.contents)
    >>> bestAnswer = soup.first('img', {'title': 'Marked as best answer'})
    >>> None == bestAnswer
    True

A message is displayed to the user confirming that the question is
solved and suggesting that the user choose an answer that helped the
question owner to solve the his problem.

    >>> for message in soup.findAll('div', 'informational message'):
    ...     print extract_text(message)
    Your question is solved. If a particular message helped you solve the
    problem, use the 'This solved my problem' button.

If the user chooses a best answer, the author of that answer is
attributed as the answerer.

    >>> owner_browser.getControl('This Solved My Problem').click()
    >>> find_request_status(owner_browser.contents)
    Status: Solved ...

The answer's message is also highlighted as the best answer.

    >>> soup = find_main_content(owner_browser.contents)
    >>> bestAnswer = soup.find('img', {'title' : 'Marked as best answer'})
    >>> print bestAnswer
    <img src="/@@/favourite-yes" ... title="Marked as best answer" />

    >>> answerer = bestAnswer.parent.find('a')
    >>> print extract_text(answerer)
    No Privileges Person (no-priv)

    >>> message = soup.find(
    ...     'div', 'boardCommentBody highlighted')
    >>> print message
    <div class="boardCommentBody highlighted"
    itemprop="commentText"><p>New version of the firefox package are
    available with SVG support enabled. You can use apt-get or adept to
    upgrade.</p></div>
    >>> print extract_text(message)
    New version of the firefox package are available with SVG support
    enabled. You can use apt-get or adept to upgrade.


History
=======

The history of the question is available on the 'History' page.

    >>> anon_browser.open(
    ...     'http://launchpad.dev/firefox/+question/2')
    >>> anon_browser.getLink('History').click()
    >>> print anon_browser.title
    History of question #2...

It lists all the actions performed through workflow on the question:

    >>> soup = find_main_content(anon_browser.contents)
    >>> action_listing = soup.first('table', 'listing')
    >>> for header in action_listing.fetch('th'):
    ...     print header.renderContents()
    When
    Who
    Action
    New State

    >>> for row in action_listing.first('tbody').fetch('tr'):
    ...     cells = row.fetch('td')
    ...     who = extract_text(cells[1].first('a'))
    ...     action = cells[2].renderContents()
    ...     new_status = cells[3].renderContents()
    ...     print who.lstrip('&nbsp;'), action, new_status
    No Privileges Person Request for more information Needs information
    No Privileges Person Comment Needs information
    Sample Person        Give more information        Open
    No Privileges Person Answer                       Answered
    Sample Person        Confirm                      Solved
    Sample Person        Comment                      Solved
    Sample Person        Reopen                       Open
    Sample Person        Confirm                      Solved
    Sample Person        Confirm                      Solved


Solving a question without an answer
------------------------------------

The user that asks a questions may solve the question before another
user can submit an answer. Without any answer messages, the user does
not see a notification to choose a 'This solved my problem' button.

Carlos has an open question that no one has submitted an answer for. He
is able to solve the problem on his own, and submits the solution for
other users with similar problems. He does not see a notice about
choosing an answer that helped him solve his problem.

    >>> carlos_browser = setupBrowser(auth='Basic carlos@canonical.com:test')
    >>> carlos_browser.open('http://launchpad.dev/firefox/+question/12')
    >>> print find_request_status(carlos_browser.contents)
    Status: Open ...

    >>> answer_button_paragraph = find_tag_by_id(
    ...     carlos_browser.contents, 'answer-button-hint')
    >>> answer_button_paragraph is None
    True

    >>> carlos_browser.getControl('Message').value = (
    ...     "There is a bug in that version. SMP is fine after upgrading.")
    >>> carlos_browser.getControl("Problem Solved").click()
    >>> print find_request_status(carlos_browser.contents)
    Status: Solved ...

    >>> content = find_main_content(carlos_browser.contents)
    >>> messages = content.findAll('div', 'informational message')
    >>> messages
    []


Asking a separate question
--------------------------

A user that is new to Answers is not familiar with the workflow. He may
have a problem of his own, and has discovered an existing question. We
want him to ask his own question instead of intruding into the workflow
of existing questions.

No Privileges Person (a different user from the one above) discovers the
Firefox question. The solution does not work, but he thinks he has a
similar problem so he asks his own question.

    >>> user_browser.open('http://launchpad.dev/firefox/+question/2')

    >>> content = find_main_content(user_browser.contents)
    >>> print content.find(id='can-you-help-with-this-problem')
    None

    >>> user_browser.getLink('Ask a question').click()
    >>> print user_browser.title
    Ask a question about...