~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
Bug Nomination Pages
====================

Series targeting is done on the +nominate page of a bug. From here,
bug supervisors can propose that the bug be fixed in specific distribution
and product series, and release managers can directly target
the bug to series for which they are drivers.

    >>> from zope.security.proxy import removeSecurityProxy
    >>> from zope.component import getUtility, getMultiAdapter

    >>> from lp.testing import login_person
    >>> from lp.services.webapp.interfaces import IOpenLaunchBag
    >>> from lp.services.webapp.servers import LaunchpadTestRequest
    >>> from lp.bugs.interfaces.bugtask import IBugTaskSet
    >>> from lp.registry.interfaces.distribution import IDistributionSet
    >>> from lp.registry.interfaces.person import IPersonSet
    >>> from lp.registry.interfaces.product import IProductSet
    >>> from lp.testing.sampledata import (ADMIN_EMAIL)

    >>> login(ADMIN_EMAIL)
    >>> nominator = factory.makePerson(name='nominator')
    >>> ubuntu = getUtility(IDistributionSet).getByName("ubuntu")
    >>> ubuntu = removeSecurityProxy(ubuntu)
    >>> ubuntu.bug_supervisor = nominator
    >>> firefox = getUtility(IProductSet).getByName("firefox")
    >>> firefox = removeSecurityProxy(firefox)
    >>> firefox.bug_supervisor = nominator

    >>> login_person(nominator)
    >>> request = LaunchpadTestRequest()
    >>> bug_one_in_ubuntu_firefox = getUtility(IBugTaskSet).get(17)
    >>> print bug_one_in_ubuntu_firefox.bug.id
    1
    >>> print bug_one_in_ubuntu_firefox.target.bugtargetdisplayname
    mozilla-firefox (Ubuntu)

    >>> nomination_view = getMultiAdapter(
    ...     (bug_one_in_ubuntu_firefox, request), name="+nominate")
    >>> launchbag = getUtility(IOpenLaunchBag)


Submitting Nominations
----------------------

BugNominationView is a LaunchpadFormView. It expects a "submit" action
to process nominations.

Here's an example of nominating a bug for a distroseries.

    >>> login_person(nominator)

    >>> request = LaunchpadTestRequest(
    ...     method="POST",
    ...     form={"field.actions.submit": "Submit Nominations",
    ...           "field.nominatable_series": ["warty"]})

    >>> nomination_view = getMultiAdapter(
    ...     (bug_one_in_ubuntu_firefox, request), name="+nominate")

(Add objects to the LaunchBag that will be used by the view.)

    >>> launchbag.clear()
    >>> launchbag.add(bug_one_in_ubuntu_firefox)
    >>> launchbag.add(bug_one_in_ubuntu_firefox.distribution)

    >>> ubuntu_warty = ubuntu.getSeries("warty")

    >>> bug_one = bug_one_in_ubuntu_firefox.bug
    >>> bug_one.canBeNominatedFor(ubuntu_warty)
    True

(Process the nominations.)

    >>> nomination_view.initialize()

    >>> bug_one.canBeNominatedFor(ubuntu_warty)
    False

    >>> len(request.response.notifications)
    1

    >>> print request.response.notifications[0].message
    Added nominations for: Ubuntu Warty

Here's an example of nominating a bug for a productseries.

    >>> bug_one_in_firefox = getUtility(IBugTaskSet).get(2)
    >>> print bug_one_in_firefox.bug.id
    1
    >>> print bug_one_in_firefox.target.bugtargetdisplayname
    Mozilla Firefox

    >>> firefox = bug_one_in_firefox.target

    >>> request = LaunchpadTestRequest(
    ...     method="POST",
    ...     form={"field.actions.submit": "Submit Nominations",
    ...           "field.nominatable_series": ["trunk"]})

    >>> nomination_view = getMultiAdapter(
    ...     (bug_one_in_firefox, request), name="+nominate")

(Add objects to the LaunchBag that will be used by the view.)

    >>> launchbag.clear()
    >>> launchbag.add(bug_one_in_firefox)
    >>> launchbag.add(bug_one_in_firefox.product)

    >>> firefox_trunk = firefox.getSeries("trunk")
    >>> bug_one.canBeNominatedFor(firefox_trunk)
    True

(Process the nominations.)

    >>> nomination_view.initialize()

    >>> bug_one.canBeNominatedFor(firefox_trunk)
    False

    >>> len(request.response.notifications)
    1

    >>> print request.response.notifications[0].message
    Added nominations for: Mozilla Firefox trunk


Approving and Declining Nominations
-----------------------------------

On the bug page
...............

Nominations are listed in the same table on the bug page that shows
bugtasks, rendered in a way that makes them look obviously different
from bugtasks.

The +edit-form renders the form that lets a driver approve and decline
nominations.

    >>> ubuntu_hoary = ubuntu.getSeries("hoary")
    >>> hoary_nomination = bug_one.getNominationFor(ubuntu_hoary)

A Proposed nomination shows as "Nominated", including an approve/decline
buttons for a user with release management privileges.

    >>> login("foo.bar@canonical.com")

    >>> hoary_nomination_edit_form = getMultiAdapter(
    ...     (hoary_nomination, request), name="+edit-form")

    >>> hoary_nomination_edit_form.shouldShowApproveButton(None)
    True
    >>> hoary_nomination_edit_form.shouldShowDeclineButton(None)
    True

If the nomination is declined, the only possible status change is
approval.

    >>> hoary_nomination.decline(launchbag.user)
    >>> print hoary_nomination.status.title
    Declined

    >>> hoary_nomination_edit_form.shouldShowApproveButton(None)
    True
    >>> hoary_nomination_edit_form.shouldShowDeclineButton(None)
    False


From a series listing
.....................

Nominations are also listed on the +nomination page for a series.

    >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
    >>> grumpy = ubuntu.getSeries('grumpy')
    >>> request = LaunchpadTestRequest()
    >>> grumpy_nominations = getMultiAdapter(
    ...     (grumpy, request), name='+nominations')
    >>> grumpy_nominations.initialize()
    >>> list(grumpy_nominations.search().batch)
    []

The batch navigator used returns a BugTaskListingItem for each of the
distribution's bugtasks that are nominated for this release, with a
widget set up for accepting/declining the nomination. The widget is only
set up if the user has permission to approve the nomination.

    >>> from lp.bugs.interfaces.bug import CreateBugParams
    >>> login_person(nominator)
    >>> no_priv = getUtility(ILaunchBag).user
    >>> ubuntu_bug = ubuntu.createBug(CreateBugParams(
    ...     no_priv, "Ubuntu bug", comment="Test bug."))
    >>> grumpy_nomination = ubuntu_bug.addNomination(no_priv, grumpy)
    >>> grumpy_nomination.canApprove(no_priv)
    False

    >>> grumpy_nominations = getMultiAdapter(
    ...     (grumpy, request), name='+nominations')
    >>> grumpy_nominations.initialize()
    >>> for listing_item in grumpy_nominations.search().getBugListingItems():
    ...     has_widget = listing_item.review_action_widget is not None
    ...     print "%s: %s" % (listing_item.bug.title, has_widget)
    Ubuntu bug: False

    >>> login("foo.bar@canonical.com")
    >>> foo_bar = getUtility(ILaunchBag).user
    >>> grumpy_nomination.canApprove(foo_bar)
    True
    >>> grumpy_nominations = getMultiAdapter(
    ...     (grumpy, request), name='+nominations')
    >>> grumpy_nominations.initialize()
    >>> for listing_item in grumpy_nominations.search().getBugListingItems():
    ...     has_widget = listing_item.review_action_widget is not None
    ...     print "%s: %s" % (listing_item.bug.title, has_widget)
    Ubuntu bug: True


Series Targeting For Release Managers
-------------------------------------

When a release manager "nominates" a bug, the nomination is immediately
approved. The nomination is created only to communicate when and by whom
the bug was proposed, so users aren't left wondering why some tasks have
nominations and others don't.

For example, bug one is currently nominated for Hoary and Warty.

    >>> ubuntu_nominations = bug_one.getNominations(ubuntu)
    >>> for nomination in ubuntu_nominations:
    ...     print nomination.target.bugtargetdisplayname
    Ubuntu Hoary
    Ubuntu Warty

Bug #1 currently has three tasks.

    >>> for bugtask in bug_one.bugtasks:
    ...     print bugtask.bugtargetdisplayname
    Mozilla Firefox
    mozilla-firefox (Ubuntu)
    mozilla-firefox (Debian)

But when we submit a "nomination" for Grumpy as a privileged user, it is
immediately approved, and a new task added. By "privileged user" in this
context, we mean a user that has, either directly or through a team,
launchpad.Driver permission on the nomination.

    >>> ubuntu_team = getUtility(IPersonSet).getByName("ubuntu-team")

    >>> login("celso.providelo@canonical.com")

    >>> cprov = launchbag.user
    >>> print cprov.name
    cprov

    >>> cprov.inTeam(ubuntu_team)
    True

    >>> request = LaunchpadTestRequest(
    ...     method="POST",
    ...     form={"field.actions.submit": "Submit Nominations",
    ...           "field.nominatable_series": ["grumpy"]})

    >>> nomination_view = getMultiAdapter(
    ...     (bug_one_in_ubuntu_firefox, request), name="+nominate")
    >>> launchbag.clear()
    >>> launchbag.add(bug_one_in_ubuntu_firefox)
    >>> launchbag.add(bug_one_in_ubuntu_firefox.distribution)

    >>> def print_nominations(nominations):
    ...     for nomination in nominations:
    ...         print "%s, %s" % (
    ...             nomination.target.bugtargetdisplayname,
    ...             nomination.status.title)
    >>> print_nominations(bug_one.getNominations(ubuntu))
    Ubuntu Hoary, Declined
    Ubuntu Warty, Nominated

(Process the nominations.)

    >>> nomination_view.initialize()

An approved nomination, for Ubuntu Grumpy, has been added, and another
bugtask has been added.

    >>> print_nominations(bug_one.getNominations(ubuntu))
    Ubuntu Grumpy, Approved
    Ubuntu Hoary, Declined
    Ubuntu Warty, Nominated

    >>> for bugtask in bug_one.bugtasks:
    ...     print bugtask.bugtargetdisplayname
    Mozilla Firefox
    mozilla-firefox (Ubuntu)
    mozilla-firefox (Ubuntu Grumpy)
    mozilla-firefox (Debian)

The notification message also changes slightly.

    >>> print request.response.notifications[0].message
    Targeted bug to: Ubuntu Grumpy


IPrimaryContext for BugNominations
----------------------------------

A BugNomination can be adapted to its IPrimaryContext for use with
menus. The IPrimaryContext for a BugNomination is the IBugTarget
traversed in the URL.

    # XXX sinzui 2008-08-12 bug=257341:
    # We need a test helper to "publish" the request's traversed objects
    # and update the launchbag.
    >>> from lp.services.webapp.interfaces import IPrimaryContext
    >>> from lp.bugs.interfaces.bugnomination import (
    ...     IBugNominationSet)

    >>> bugnominationset = getUtility(IBugNominationSet)

The product 'firefox' is the IBugTarget in
/firefox/+bug/1/nominations/1/+editstatus.

    >>> firefox_nomination = bugnominationset.get(1)
    >>> firefox_nomination.bug.id
    1
    >>> firefox_nomination.bug == bug_one_in_firefox.bug
    True

    >>> launchbag.clear()
    >>> launchbag.add(bug_one_in_firefox.target)
    >>> launchbag.add(bug_one_in_firefox)
    >>> launchbag.bugtask.target.name
    u'firefox'

    >>> bugtarget = launchbag.bugtask.target
    >>> IPrimaryContext(firefox_nomination).context == bugtarget
    True

The source package 'mozilla-firefox in Ubuntu' is the IBugTarget in
/ubuntu/+source/mozilla-firefox/+bug/1/nominations/2/+editstatus.

    >>> ubuntu_firefox_nomination = bugnominationset.get(2)
    >>> ubuntu_firefox_nomination.bug.id
    1
    >>> ubuntu_firefox_nomination.bug == bug_one_in_ubuntu_firefox.bug
    True

    >>> launchbag.clear()
    >>> launchbag.add(bug_one_in_ubuntu_firefox.target)
    >>> launchbag.add(bug_one_in_ubuntu_firefox)
    >>> launchbag.bugtask.target.displayname
    u'mozilla-firefox in Ubuntu'

    >>> bugtarget = launchbag.bugtask.target
    >>> IPrimaryContext(ubuntu_firefox_nomination).context == bugtarget
    True