~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/doc/bug-change.txt

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2004-06-28 10:08:03 UTC
  • mfrom: (unknown (missing))
  • Revision ID: Arch-1:rocketfuel@canonical.com%soyuz--devel--0--patch-8
add ./sourcecode directory
Patches applied:

 * david.allouche@canonical.com--2004/soyuz--devel--0--base-0
   tag of rocketfuel@canonical.com/soyuz--devel--0--patch-7

 * david.allouche@canonical.com--2004/soyuz--devel--0--patch-1
   add ./sourcecode directory

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
= Tracking changes to a bug =
2
 
 
3
 
The base class for BugChanges doesn't actually implement anything.
4
 
 
5
 
    >>> import pytz
6
 
    >>> from canonical.launchpad.webapp.testing import verifyObject
7
 
    >>> from datetime import datetime
8
 
    >>> from lp.bugs.adapters.bugchange import BugChangeBase
9
 
    >>> from lp.bugs.interfaces.bugchange import IBugChange
10
 
 
11
 
    >>> from lp.testing.factory import LaunchpadObjectFactory
12
 
    >>> factory = LaunchpadObjectFactory()
13
 
    >>> login("test@canonical.com")
14
 
    >>> example_person = factory.makePerson(
15
 
    ...     name="ford-prefect", displayname="Ford Prefect")
16
 
 
17
 
    >>> nowish = datetime(2009, 3, 13, 10, 9, tzinfo=pytz.timezone('UTC'))
18
 
    >>> base_instance = BugChangeBase(when=nowish, person=example_person)
19
 
    >>> verifyObject(IBugChange, base_instance)
20
 
    True
21
 
 
22
 
    >>> base_instance.getBugNotification()
23
 
    Traceback (most recent call last):
24
 
      ...
25
 
    NotImplementedError...
26
 
 
27
 
    >>> base_instance.getBugActivity()
28
 
    Traceback (most recent call last):
29
 
      ...
30
 
    NotImplementedError...
31
 
 
32
 
But the basic attributes are still available.
33
 
 
34
 
    >>> print base_instance.when
35
 
    2009-03-13 10:09:00+00:00
36
 
 
37
 
    >>> print base_instance.person.displayname
38
 
    Ford Prefect
39
 
 
40
 
Because the base class is abstract, you can't pass it to
41
 
Bug.addChange().
42
 
 
43
 
    >>> example_product = factory.makeProduct(
44
 
    ...     owner=example_person, name="heart-of-gold",
45
 
    ...     displayname="Heart of Gold")
46
 
    >>> example_bug = factory.makeBug(
47
 
    ...     product=example_product, owner=example_person,
48
 
    ...     title="Reality is on the blink again",
49
 
    ...     description="I'm tired of thinking up funny strings for tests")
50
 
    >>> example_bug.addChange(base_instance)
51
 
    Traceback (most recent call last):
52
 
      ...
53
 
    NotImplementedError...
54
 
 
55
 
We'll create a test class that actually implements the methods we need.
56
 
 
57
 
    >>> from lp.bugs.mail.bugnotificationrecipients import (
58
 
    ...     BugNotificationRecipients)
59
 
 
60
 
    >>> example_message = factory.makeMessage(content="Hello, world")
61
 
    >>> example_person_2 = factory.makePerson(
62
 
    ...     displayname="Zaphod Beeblebrox")
63
 
 
64
 
    >>> recipients = BugNotificationRecipients()
65
 
    >>> recipients.addDirectSubscriber(example_person_2)
66
 
 
67
 
    >>> class TestBugChange(BugChangeBase):
68
 
    ...
69
 
    ...     bug_activity_data = {
70
 
    ...         'whatchanged': 'Nothing',
71
 
    ...         'oldvalue': 'OldValue',
72
 
    ...         'newvalue': 'NewValue',
73
 
    ...         }
74
 
    ...
75
 
    ...     bug_notification_data = {
76
 
    ...         'text': 'Some message text',
77
 
    ...         }
78
 
    ...
79
 
    ...     def getBugActivity(self):
80
 
    ...         return self.bug_activity_data
81
 
    ...
82
 
    ...     def getBugNotification(self):
83
 
    ...         return self.bug_notification_data
84
 
 
85
 
    >>> activity_to_ignore = set()
86
 
    >>> def print_bug_activity(activity):
87
 
    ...     for activity in activity:
88
 
    ...         if activity not in activity_to_ignore:
89
 
    ...             print "%s: %s %s => %s (%s)" % (
90
 
    ...                 activity.datechanged, activity.whatchanged,
91
 
    ...                 activity.oldvalue, activity.newvalue,
92
 
    ...                 activity.person.displayname)
93
 
 
94
 
Creating bugs generates activity records, indirectly, using the
95
 
addChange() API, but we want to ignore them for now.
96
 
 
97
 
    >>> activity_to_ignore.update(example_bug.activity)
98
 
 
99
 
BugActivity entries are added when addChange() is called.
100
 
 
101
 
    >>> example_bug.addChange(
102
 
    ...     TestBugChange(when=nowish, person=example_person),
103
 
    ...     recipients=recipients)
104
 
    >>> print_bug_activity(example_bug.activity)
105
 
    2009-03-13...: Nothing OldValue => NewValue (Ford Prefect)
106
 
 
107
 
As are BugNotifications.
108
 
 
109
 
    >>> from lp.bugs.model.bugnotification import BugNotification
110
 
    >>> latest_notification = BugNotification.selectFirst(orderBy='-id')
111
 
    >>> print latest_notification.message.text_contents
112
 
    Some message text
113
 
 
114
 
The notification's recipients are taken from the recipients parameter
115
 
passed to addChange().
116
 
 
117
 
    >>> for recipient in latest_notification.recipients:
118
 
    ...     print recipient.person.displayname
119
 
    Zaphod Beeblebrox
120
 
 
121
 
But if getBugActivity() returns None, no activity entries will be added.
122
 
 
123
 
    >>> class NoActionBugChange(TestBugChange):
124
 
    ...     bug_activity_data = None
125
 
    ...     bug_notification_data = None
126
 
 
127
 
    >>> example_bug.addChange(
128
 
    ...     NoActionBugChange(when=nowish, person=example_person))
129
 
    >>> print_bug_activity(example_bug.activity)
130
 
    2009-03-13...: Nothing OldValue => NewValue (Ford Prefect)
131
 
 
132
 
And if getBugNotification() returns None, no notification will be added.
133
 
 
134
 
    >>> new_latest_notification = BugNotification.selectFirst(orderBy='-id')
135
 
    >>> new_latest_notification.id == latest_notification.id
136
 
    True
137
 
 
138
 
If no recipients are passed to addChange() the default recipient list
139
 
for the Bug will be used. This includes people subscribed to the
140
 
bug's target for Meta data changes, but not for lifecycle changes.
141
 
 
142
 
 
143
 
    >>> from lp.testing import person_logged_in
144
 
    >>> from lp.bugs.enum import BugNotificationLevel
145
 
    >>> lifecycle_subscriber = factory.makePerson(
146
 
    ...         displayname='Lifecycle subscriber')
147
 
    >>> metadata_subscriber = factory.makePerson(
148
 
    ...         displayname='Meta-data subscriber')
149
 
    >>> subscription = example_bug.bugtasks[0].target.addBugSubscription(
150
 
    ...     lifecycle_subscriber, lifecycle_subscriber)
151
 
    >>> with person_logged_in(lifecycle_subscriber):
152
 
    ...     filter = subscription.bug_filters.one()
153
 
    ...     filter.bug_notification_level = BugNotificationLevel.LIFECYCLE
154
 
    >>> subscription = example_bug.bugtasks[0].target.addBugSubscription(
155
 
    ...     metadata_subscriber, metadata_subscriber)
156
 
    >>> with person_logged_in(metadata_subscriber):
157
 
    ...     filter = subscription.bug_filters.one()
158
 
    ...     filter.bug_notification_level = BugNotificationLevel.METADATA
159
 
    >>> example_bug.addChange(
160
 
    ...     TestBugChange(when=nowish, person=example_person))
161
 
    >>> latest_notification = BugNotification.selectFirst(orderBy='-id')
162
 
    >>> print latest_notification.message.text_contents
163
 
    Some message text
164
 
 
165
 
    >>> recipients = [
166
 
    ...     recipient.person.displayname
167
 
    ...     for recipient in latest_notification.recipients]
168
 
    >>> for name in sorted(recipients):
169
 
    ...     print name
170
 
    Ford Prefect
171
 
    Meta-data subscriber
172
 
 
173
 
If you try to send a notification without adding a text body for the
174
 
notification you'll get an error.
175
 
 
176
 
    >>> class NoNotificationTextBugChange(TestBugChange):
177
 
    ...
178
 
    ...     bug_notification_data = {
179
 
    ...         'text': None,
180
 
    ...         }
181
 
 
182
 
    >>> example_bug.addChange(
183
 
    ...     NoNotificationTextBugChange(when=nowish, person=example_person))
184
 
    Traceback (most recent call last):
185
 
      ...
186
 
    AssertionError: notification_data must include a `text` value.
187
 
 
188
 
 
189
 
== BugChange subclasses ==
190
 
 
191
 
=== Getting the right bug change class ===
192
 
 
193
 
Given that we know what's changing and the name of the field that is
194
 
being changed, we can find a suitable IBugChange implementation to
195
 
help us describe the change.
196
 
 
197
 
    >>> from lp.bugs.adapters.bugchange import (
198
 
    ...     get_bug_change_class)
199
 
 
200
 
If get_bug_change_class() is asked for a BugChange for an object or
201
 
field that it doesn't know about, it will raise a NoBugChangeFoundError.
202
 
 
203
 
    >>> get_bug_change_class(object(), 'fooix')
204
 
    Traceback (most recent call last):
205
 
      ...
206
 
    NoBugChangeFoundError: Unable to find a suitable BugChange for field
207
 
    'fooix' on object <object object at ...>
208
 
 
209
 
For fields it knows about, it will return a more suitable class.
210
 
 
211
 
    >>> get_bug_change_class(example_bug, 'title')
212
 
    <class '...BugTitleChange'>
213
 
 
214
 
get_bug_change_class will also work for BugTasks.
215
 
 
216
 
    >>> get_bug_change_class(example_bug.bugtasks[0], 'importance')
217
 
    <class '...BugTaskImportanceChange'>
218
 
 
219
 
See component/ftests/test_bugchange.py for some sanity checks.
220
 
 
221
 
 
222
 
=== AttributeChange ===
223
 
 
224
 
The AttributeChange class offers basic functionality for dealing with
225
 
bug attribute changes.
226
 
 
227
 
    >>> from lp.bugs.adapters.bugchange import (
228
 
    ...     AttributeChange)
229
 
 
230
 
    >>> simple_change = AttributeChange(
231
 
    ...     when=nowish, person=example_person, what_changed='title',
232
 
    ...     old_value=example_bug.title, new_value='Spam')
233
 
 
234
 
In its getBugActivity() method AttributeChange merely returns the
235
 
field name, old value and new value as passed to its __init__()
236
 
method.
237
 
 
238
 
    >>> activity_data = simple_change.getBugActivity()
239
 
    >>> print pretty(activity_data)
240
 
    {'newvalue': 'Spam',
241
 
     'oldvalue': u'Reality is on the blink again',
242
 
     'whatchanged': 'title'}
243
 
 
244
 
 
245
 
=== BugDescriptionChange ===
246
 
 
247
 
This describes a change to the description of a
248
 
bug. getBugNotification() returns a formatted description of the
249
 
change.
250
 
 
251
 
    >>> from lp.bugs.adapters.bugchange import (
252
 
    ...     BugDescriptionChange)
253
 
 
254
 
    >>> bug_desc_change = BugDescriptionChange(
255
 
    ...     when=nowish, person=example_person,
256
 
    ...     what_changed='description', old_value=example_bug.description,
257
 
    ...     new_value='Well, maybe not')
258
 
    >>> print bug_desc_change.getBugNotification()['text']
259
 
    ** Description changed:
260
 
    <BLANKLINE>
261
 
    - I'm tired of thinking up funny strings for tests
262
 
    + Well, maybe not
263
 
 
264
 
 
265
 
=== BugTitleChange ===
266
 
 
267
 
This, surprisingly, describes a title change for a bug. Again,
268
 
getBugNotification() returns a specially formatted description of
269
 
what's changed.
270
 
 
271
 
    >>> from lp.bugs.adapters.bugchange import (
272
 
    ...     BugTitleChange)
273
 
 
274
 
    >>> bug_title_change = BugTitleChange(
275
 
    ...     when=nowish, person=example_person,
276
 
    ...     what_changed='title', old_value=example_bug.title,
277
 
    ...     new_value='Spam')
278
 
    >>> print bug_title_change.getBugNotification()['text']
279
 
    ** Summary changed:
280
 
    <BLANKLINE>
281
 
    - Reality is on the blink again
282
 
    + Spam
283
 
 
284
 
BugTitleChange mutates the `what_changed` field and will return
285
 
'summary' rather than 'title'. This is to maintain naming consistency
286
 
within the UI.
287
 
 
288
 
    >>> print bug_title_change.getBugActivity()['whatchanged']
289
 
    summary
290
 
 
291
 
 
292
 
=== BugDuplicateChange ===
293
 
 
294
 
This describes a change to the duplicate marker for a bug.
295
 
 
296
 
    >>> from lp.bugs.adapters.bugchange import (
297
 
    ...     BugDuplicateChange)
298
 
 
299
 
    >>> duplicate_bug = factory.makeBug(title="Fish can't walk")
300
 
 
301
 
    >>> bug_duplicate_change = BugDuplicateChange(
302
 
    ...     when=nowish, person=example_person,
303
 
    ...     what_changed='duplicateof', old_value=None,
304
 
    ...     new_value=duplicate_bug)
305
 
    >>> print bug_duplicate_change.getBugNotification()['text']
306
 
    ** This bug has been marked a duplicate of bug ...
307
 
       Fish can't walk
308
 
 
309
 
BugDuplicateChange overrides getBugActivity() to customize all the
310
 
returned fields.
311
 
 
312
 
    >>> print pretty(bug_duplicate_change.getBugActivity())
313
 
    {'newvalue': '...',
314
 
     'whatchanged': 'marked as duplicate'}
315
 
 
316
 
 
317
 
=== BugVisibilityChange ===
318
 
 
319
 
BugVisibilityChange is used to represent a change in a Bug's `private`
320
 
attribute.
321
 
 
322
 
    >>> from lp.bugs.adapters.bugchange import (
323
 
    ...     BugVisibilityChange)
324
 
 
325
 
    >>> bug_visibility_change = BugVisibilityChange(
326
 
    ...     when=nowish, person=example_person,
327
 
    ...     what_changed='private', old_value=example_bug.private,
328
 
    ...     new_value=True)
329
 
 
330
 
IBug.private is a boolean but to make it more readable we express it in
331
 
activity and notification records as a string, where True = 'Private'
332
 
and False = 'Public'. We also refer to it as "visibility" rather than
333
 
privacy.
334
 
 
335
 
    >>> print pretty(bug_visibility_change.getBugActivity())
336
 
    {'newvalue': 'private',
337
 
     'oldvalue': 'public',
338
 
     'whatchanged': 'visibility'}
339
 
 
340
 
We also use the 'Private', 'Public' and 'Visibility' terms in the
341
 
notification text.
342
 
 
343
 
    >>> print bug_visibility_change.getBugNotification()['text']
344
 
    ** Visibility changed to: Private
345
 
 
346
 
If we reverse the changes we'll see the opposite values in the
347
 
notification and activity entries.
348
 
 
349
 
    >>> bug_visibility_change = BugVisibilityChange(
350
 
    ...     when=nowish, person=example_person,
351
 
    ...     what_changed='private', old_value=True, new_value=False)
352
 
    >>> print pretty(bug_visibility_change.getBugActivity())
353
 
    {'newvalue': 'public',
354
 
     'oldvalue': 'private',
355
 
     'whatchanged': 'visibility'}
356
 
 
357
 
    >>> print bug_visibility_change.getBugNotification()['text']
358
 
    ** Visibility changed to: Public
359
 
 
360
 
 
361
 
== BugTagsChange ==
362
 
 
363
 
BugTagsChange is used to represent a change in a Bug's tag list.
364
 
 
365
 
    >>> from lp.bugs.adapters.bugchange import (
366
 
    ...     BugTagsChange)
367
 
 
368
 
    >>> tags_change = BugTagsChange(
369
 
    ...     when=nowish, person=example_person,
370
 
    ...     what_changed='tags',
371
 
    ...     old_value=[u'first-tag', u'second-tag', u'third-tag'],
372
 
    ...     new_value=[u'second-tag', u'third-tag', u'zillionth-tag'])
373
 
 
374
 
This change is expressed in the activity entry in the same way as any
375
 
other attribute change. The list of tags is converted to a
376
 
space-separated string for display.
377
 
 
378
 
    >>> print pretty(tags_change.getBugActivity())
379
 
    {'newvalue': u'second-tag third-tag zillionth-tag',
380
 
     'oldvalue': u'first-tag second-tag third-tag',
381
 
     'whatchanged': 'tags'}
382
 
 
383
 
Addtions and removals are expressed separately in the notification.
384
 
 
385
 
    >>> print tags_change.getBugNotification()['text']
386
 
    ** Tags added: zillionth-tag
387
 
    ** Tags removed: first-tag
388
 
 
389
 
 
390
 
=== BugSecurityChange ===
391
 
 
392
 
BugSecurityChange is used to represent a change in a Bug's
393
 
`security_related` attribute.
394
 
 
395
 
    >>> from lp.bugs.adapters.bugchange import (
396
 
    ...     BugSecurityChange)
397
 
 
398
 
    >>> bug_security_change = BugSecurityChange(
399
 
    ...     when=nowish, person=example_person,
400
 
    ...     what_changed='security_related',
401
 
    ...     old_value=False, new_value=True)
402
 
 
403
 
IBug.security_related is a boolean but to make it more readable we
404
 
express it in activity and notification records as a short phrase.
405
 
 
406
 
Marking a bug as security related causes one set of terms/phrases to
407
 
be used.
408
 
 
409
 
    >>> print pretty(bug_security_change.getBugActivity())
410
 
    {'newvalue': 'yes',
411
 
     'oldvalue': 'no',
412
 
     'whatchanged': 'security vulnerability'}
413
 
 
414
 
    >>> print bug_security_change.getBugNotification()['text']
415
 
    ** This bug has been flagged as a security vulnerability
416
 
 
417
 
Going the other way the phrases are similar.
418
 
 
419
 
    >>> bug_security_change = BugSecurityChange(
420
 
    ...     when=nowish, person=example_person,
421
 
    ...     what_changed='security_related',
422
 
    ...     old_value=True, new_value=False)
423
 
 
424
 
    >>> print pretty(bug_security_change.getBugActivity())
425
 
    {'newvalue': 'no',
426
 
     'oldvalue': 'yes',
427
 
     'whatchanged': 'security vulnerability'}
428
 
 
429
 
    >>> print bug_security_change.getBugNotification()['text']
430
 
    ** This bug is no longer flagged as a security vulnerability
431
 
 
432
 
 
433
 
=== CveLinkedToBug / CveUnlinkedFromBug ===
434
 
 
435
 
These describe the linking or unlinking of a CVE to a bug.
436
 
 
437
 
    >>> from lp.bugs.interfaces.cve import ICveSet
438
 
    >>> cve = getUtility(ICveSet)['1999-8979']
439
 
 
440
 
getBugNotification() returns a formatted description of the change
441
 
when a CVE is linked to a bug.
442
 
 
443
 
    >>> from lp.bugs.adapters.bugchange import (
444
 
    ...     CveLinkedToBug, CveUnlinkedFromBug)
445
 
 
446
 
    >>> bug_cve_linked = CveLinkedToBug(
447
 
    ...     when=nowish, person=example_person, cve=cve)
448
 
 
449
 
    >>> print pretty(bug_cve_linked.getBugActivity())
450
 
    {'newvalue': u'1999-8979',
451
 
     'whatchanged': 'cve linked'}
452
 
 
453
 
    >>> print bug_cve_linked.getBugNotification()['text']
454
 
    ** CVE added: http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=1999-8979
455
 
 
456
 
And when a CVE is unlinked from a bug.
457
 
 
458
 
    >>> bug_cve_unlinked = CveUnlinkedFromBug(
459
 
    ...     when=nowish, person=example_person, cve=cve)
460
 
 
461
 
    >>> print pretty(bug_cve_unlinked.getBugActivity())
462
 
    {'oldvalue': u'1999-8979',
463
 
     'whatchanged': 'cve unlinked'}
464
 
 
465
 
    >>> print bug_cve_unlinked.getBugNotification()['text']
466
 
    ** CVE removed: http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=1999-8979
467
 
 
468
 
 
469
 
== BugAttachmentChange ==
470
 
 
471
 
BugAttachmentChange is used to handle the addition and removal of
472
 
attachments from a bug.
473
 
 
474
 
    >>> from lp.bugs.adapters.bugchange import (
475
 
    ...     BugAttachmentChange)
476
 
 
477
 
You can add an attachment...
478
 
 
479
 
    >>> attachment = factory.makeBugAttachment(
480
 
    ...     description='sample-attachment')
481
 
    >>> attachment_change = BugAttachmentChange(
482
 
    ...     when=nowish, person=example_person,
483
 
    ...     what_changed='security_related',
484
 
    ...     old_value=None, new_value=attachment)
485
 
 
486
 
    >>> print pretty(attachment_change.getBugActivity())
487
 
    {'newvalue':
488
 
         u'sample-attachment http://bugs.launchpad.dev/bugs/...+files/...',
489
 
     'oldvalue': None,
490
 
     'whatchanged': 'attachment added'}
491
 
 
492
 
    >>> print attachment_change.getBugNotification()['text']
493
 
    ** Attachment added: "sample-attachment"
494
 
    http://bugs.launchpad.dev/bugs/.../+attachment/1/+files/...
495
 
 
496
 
Or remove one.
497
 
 
498
 
    >>> attachment_change = BugAttachmentChange(
499
 
    ...     when=nowish, person=example_person,
500
 
    ...     what_changed='security_related',
501
 
    ...     old_value=attachment, new_value=None)
502
 
 
503
 
    >>> print pretty(attachment_change.getBugActivity())
504
 
    {'newvalue': None,
505
 
     'oldvalue':
506
 
         u'sample-attachment http://bugs.launchpad.dev/bugs/...+files/...',
507
 
     'whatchanged': 'attachment removed'}
508
 
 
509
 
    >>> print attachment_change.getBugNotification()['text']
510
 
    ** Attachment removed: "sample-attachment"
511
 
    http://bugs.launchpad.dev/bugs/.../+attachment/1/+files/...
512
 
 
513
 
 
514
 
== BugTaskAttributeChange ==
515
 
 
516
 
BugTaskAttributeChange is a generic BugChange that can be used to
517
 
represent a change in the attributes of one of a Bug's BugTasks. It is
518
 
intended to be subclassed.
519
 
 
520
 
    >>> from lp.bugs.interfaces.bugtask import (
521
 
    ...     BugTaskStatus, BugTaskImportance)
522
 
    >>> from lp.bugs.adapters.bugchange import (
523
 
    ...     BugTaskAttributeChange)
524
 
 
525
 
BugTaskAttributeChange takes an instance of BugTask. It uses this to
526
 
work out how to describe to the user which BugTask's attributes have
527
 
changed.
528
 
 
529
 
Subclasses must at least define `display_attribute`.
530
 
 
531
 
    >>> class ExampleBugTaskAttributeChange(BugTaskAttributeChange):
532
 
    ...     display_attribute = 'title'
533
 
 
534
 
    >>> example_bug_task = example_bug.bugtasks[0]
535
 
    >>> task_attribute_change = ExampleBugTaskAttributeChange(
536
 
    ...     when=nowish, person=example_person,
537
 
    ...     what_changed='status',
538
 
    ...     old_value=BugTaskStatus.NEW,
539
 
    ...     new_value=BugTaskStatus.FIXRELEASED,
540
 
    ...     bug_task=example_bug_task)
541
 
 
542
 
    >>> print task_attribute_change.display_activity_label
543
 
    status
544
 
    >>> print task_attribute_change.display_notification_label
545
 
    Status
546
 
    >>> print task_attribute_change.display_old_value
547
 
    New
548
 
    >>> print task_attribute_change.display_new_value
549
 
    Fix Released
550
 
 
551
 
Several types of attribute change can be handled by
552
 
BugTaskAttributeChange.
553
 
 
554
 
 
555
 
=== Status changes ===
556
 
 
557
 
Status changes use a BugTaskStatus's `title` attribute to describe to
558
 
the user what has changed.
559
 
 
560
 
    >>> from lp.bugs.adapters.bugchange import (
561
 
    ...     BugTaskStatusChange)
562
 
 
563
 
    >>> status_change = BugTaskStatusChange(
564
 
    ...     bug_task=example_bug_task, when=nowish, person=example_person,
565
 
    ...     what_changed='status', old_value=BugTaskStatus.NEW,
566
 
    ...     new_value=BugTaskStatus.FIXRELEASED)
567
 
    >>> print pretty(status_change.getBugActivity())
568
 
    {'newvalue': 'Fix Released',
569
 
     'oldvalue': 'New',
570
 
     'whatchanged': u'heart-of-gold: status'}
571
 
 
572
 
    >>> notification_text = status_change.getBugNotification()['text']
573
 
    >>> print notification_text #doctest: -NORMALIZE_WHITESPACE
574
 
    ** Changed in: heart-of-gold
575
 
           Status: New => Fix Released
576
 
 
577
 
 
578
 
=== Importance changes ===
579
 
 
580
 
Importance changes use a BugTaskImportance's `title` attribute to
581
 
describe to the user what has changed.
582
 
 
583
 
    >>> from lp.bugs.adapters.bugchange import (
584
 
    ...     BugTaskImportanceChange)
585
 
 
586
 
    >>> importance_change = BugTaskImportanceChange(
587
 
    ...     bug_task=example_bug_task, when=nowish, person=example_person,
588
 
    ...     what_changed='importance',
589
 
    ...     old_value=BugTaskImportance.UNDECIDED,
590
 
    ...     new_value=BugTaskImportance.CRITICAL)
591
 
    >>> print pretty(importance_change.getBugActivity())
592
 
    {'newvalue': 'Critical',
593
 
     'oldvalue': 'Undecided',
594
 
     'whatchanged': u'heart-of-gold: importance'}
595
 
 
596
 
    >>> notification_text = importance_change.getBugNotification()['text']
597
 
    >>> print notification_text #doctest: -NORMALIZE_WHITESPACE
598
 
    ** Changed in: heart-of-gold
599
 
       Importance: Undecided => Critical
600
 
 
601
 
 
602
 
=== Milestone changes ===
603
 
 
604
 
Milestone changes use a Milestone's `name` attribute to describe to
605
 
the user what has changed.
606
 
 
607
 
    >>> from lp.bugs.adapters.bugchange import (
608
 
    ...     BugTaskMilestoneChange)
609
 
 
610
 
    >>> milestone = factory.makeMilestone(
611
 
    ...     product=example_bug_task.product,
612
 
    ...     name="example-milestone")
613
 
 
614
 
    >>> milestone_change = BugTaskMilestoneChange(
615
 
    ...     bug_task=example_bug_task, when=nowish,
616
 
    ...     person=example_person, what_changed='milestone',
617
 
    ...     old_value=None, new_value=milestone)
618
 
    >>> print pretty(milestone_change.getBugActivity())
619
 
    {'newvalue': u'example-milestone',
620
 
     'oldvalue': None,
621
 
     'whatchanged': u'heart-of-gold: milestone'}
622
 
 
623
 
    >>> notification_text = milestone_change.getBugNotification()['text']
624
 
    >>> print notification_text #doctest: -NORMALIZE_WHITESPACE
625
 
    ** Changed in: heart-of-gold
626
 
        Milestone: None => example-milestone
627
 
 
628
 
 
629
 
=== Bugwatch changes ===
630
 
 
631
 
Bugwatch changes use a Bugwatch's `title` attribute to describe to the
632
 
user what has changed.
633
 
 
634
 
    >>> from lp.bugs.adapters.bugchange import (
635
 
    ...     BugTaskBugWatchChange)
636
 
 
637
 
    >>> bug_tracker = factory.makeBugTracker(
638
 
    ...     base_url="http://bugs.example.com/")
639
 
    >>> bug_watch = factory.makeBugWatch(
640
 
    ...     bug=example_bug_task.bug, bugtracker=bug_tracker,
641
 
    ...     remote_bug="1245")
642
 
 
643
 
    >>> bug_watch_change = BugTaskBugWatchChange(
644
 
    ...     bug_task=example_bug_task, when=nowish,
645
 
    ...     person=example_person, what_changed='bugwatch',
646
 
    ...     old_value=None, new_value=bug_watch)
647
 
    >>> print pretty(bug_watch_change.getBugActivity())
648
 
    {'newvalue': u'bugs.example.com/ #1245',
649
 
     'oldvalue': None,
650
 
     'whatchanged': u'heart-of-gold: remote watch'}
651
 
 
652
 
    >>> notification_text = bug_watch_change.getBugNotification()['text']
653
 
    >>> print notification_text #doctest: -NORMALIZE_WHITESPACE
654
 
    ** Changed in: heart-of-gold
655
 
     Remote watch: None => bugs.example.com/ #1245
656
 
 
657
 
 
658
 
=== Assignee changes ===
659
 
 
660
 
Assignee changes use the assignee's `unique_displayname` attribute to
661
 
describe to the user what has changed.
662
 
 
663
 
    >>> from lp.bugs.adapters.bugchange import (
664
 
    ...     BugTaskAssigneeChange)
665
 
 
666
 
    >>> assignee_change = BugTaskAssigneeChange(
667
 
    ...     bug_task=example_bug_task, when=nowish,
668
 
    ...     person=example_person, what_changed='assignee',
669
 
    ...     old_value=None, new_value=example_person)
670
 
    >>> print pretty(assignee_change.getBugActivity())
671
 
    {'newvalue': u'Ford Prefect (ford-prefect)',
672
 
     'oldvalue': None,
673
 
     'whatchanged': u'heart-of-gold: assignee'}
674
 
 
675
 
    >>> notification_text = assignee_change.getBugNotification()['text']
676
 
    >>> print notification_text #doctest: -NORMALIZE_WHITESPACE
677
 
    ** Changed in: heart-of-gold
678
 
         Assignee: (unassigned) => Ford Prefect (ford-prefect)
679
 
 
680
 
 
681
 
=== Target (Affects) changes ===
682
 
 
683
 
Changes to the bug task target (aka affects) use the BugTaskTargetChange
684
 
class to describe the change. It inspects the `bugtargetname`
685
 
attribute for the values to use in the activity log.
686
 
 
687
 
    >>> from lp.bugs.adapters.bugchange import (
688
 
    ...     BugTaskTargetChange)
689
 
 
690
 
    >>> new_target = factory.makeProduct(name="magrathea")
691
 
 
692
 
    >>> target_change = BugTaskTargetChange(
693
 
    ...     bug_task=example_bug_task, when=nowish, person=example_person,
694
 
    ...     what_changed='target',
695
 
    ...     old_value=example_bug_task.target,
696
 
    ...     new_value=new_target)
697
 
    >>> print pretty(target_change.getBugActivity())
698
 
    {'newvalue': u'magrathea',
699
 
     'oldvalue': u'heart-of-gold',
700
 
     'whatchanged': 'affects'}
701
 
 
702
 
    >>> notification_text = target_change.getBugNotification()['text']
703
 
    >>> print notification_text #doctest: -NORMALIZE_WHITESPACE
704
 
    ** Project changed: heart-of-gold => magrathea