Merge lp:~stevenk/launchpad/destroy-old-bugactivity into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 16146
Proposed branch: lp:~stevenk/launchpad/destroy-old-bugactivity
Merge into: lp:launchpad
Diff against target: 838 lines (+79/-374)
12 files modified
database/sampledata/current-dev.sql (+0/-1)
database/schema/Makefile (+2/-1)
lib/lp/bugs/adapters/bugchange.py (+2/-76)
lib/lp/bugs/doc/bug-change.txt (+0/-87)
lib/lp/bugs/doc/bugnotification-email.txt (+9/-15)
lib/lp/bugs/doc/bugnotification-sending.txt (+21/-20)
lib/lp/bugs/doc/bugnotification-threading.txt (+8/-7)
lib/lp/bugs/scripts/tests/test_bugnotification.py (+5/-7)
lib/lp/bugs/stories/bugs/xx-bug-activity.txt (+13/-56)
lib/lp/bugs/subscribers/bug.py (+2/-8)
lib/lp/bugs/tests/test_bugchanges.py (+17/-89)
lib/lp/services/features/flags.py (+0/-7)
To merge this branch: bzr merge lp:~stevenk/launchpad/destroy-old-bugactivity
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+129339@code.launchpad.net

Commit message

Destroy Bug{Security,Visibility}Change and only make use of BugInformationTypeChange.

Description of the change

Destroy Bug{Security,Visibility}Change and only make use of BugInformationTypeChange.

I have changed all of the tests I could find, but I suspect ec2 may find some that I may have missed.

I have corrected a small bug in database/schema/Makefile -- it will now use the current year when regenerating sampledata, like update-copyright does.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'database/sampledata/current-dev.sql'
--- database/sampledata/current-dev.sql 2012-10-12 05:43:38 +0000
+++ database/sampledata/current-dev.sql 2012-10-15 03:36:20 +0000
@@ -4321,7 +4321,6 @@
43214321
4322ALTER TABLE featureflag DISABLE TRIGGER ALL;4322ALTER TABLE featureflag DISABLE TRIGGER ALL;
43234323
4324INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 0, 'disclosure.information_type_notifications.enabled', 'true', '2012-05-18 07:34:39.239649');
4325INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 0, 'js.combo_loader.enabled', 'true', '2012-05-18 07:34:39.239649');4324INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 0, 'js.combo_loader.enabled', 'true', '2012-05-18 07:34:39.239649');
4326INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 1, 'longpoll.merge_proposals.enabled', 'true', '2012-05-18 07:34:39.239649');4325INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 1, 'longpoll.merge_proposals.enabled', 'true', '2012-05-18 07:34:39.239649');
43274326
43284327
=== modified file 'database/schema/Makefile'
--- database/schema/Makefile 2012-07-12 09:58:32 +0000
+++ database/schema/Makefile 2012-10-15 03:36:20 +0000
@@ -38,7 +38,8 @@
38# The command we use to drop (if exists) and recreate a database.38# The command we use to drop (if exists) and recreate a database.
39CREATEDB=${PYTHON} ../../utilities/pgmassacre.py -t39CREATEDB=${PYTHON} ../../utilities/pgmassacre.py -t
4040
41HEADER="-- Copyright 2010-2011 Canonical Ltd. This software is licensed \41YEAR=$(shell date +'%Y')
42HEADER="-- Copyright 2010-${YEAR} Canonical Ltd. This software is licensed \
42 under the\n-- GNU Affero General Public License version 3 (see the file \43 under the\n-- GNU Affero General Public License version 3 (see the file \
43 LICENSE)."44 LICENSE)."
4445
4546
=== modified file 'lib/lp/bugs/adapters/bugchange.py'
--- lib/lp/bugs/adapters/bugchange.py 2012-07-17 03:57:26 +0000
+++ lib/lp/bugs/adapters/bugchange.py 2012-10-15 03:36:20 +0000
@@ -24,7 +24,6 @@
24 'BugDescriptionChange',24 'BugDescriptionChange',
25 'BugDuplicateChange',25 'BugDuplicateChange',
26 'BugInformationTypeChange',26 'BugInformationTypeChange',
27 'BugSecurityChange',
28 'BugTagsChange',27 'BugTagsChange',
29 'BugTaskAdded',28 'BugTaskAdded',
30 'BugTaskAssigneeChange',29 'BugTaskAssigneeChange',
@@ -35,7 +34,6 @@
35 'BugTaskStatusChange',34 'BugTaskStatusChange',
36 'BugTaskTargetChange',35 'BugTaskTargetChange',
37 'BugTitleChange',36 'BugTitleChange',
38 'BugVisibilityChange',
39 'BugWatchAdded',37 'BugWatchAdded',
40 'BugWatchRemoved',38 'BugWatchRemoved',
41 'CveLinkedToBug',39 'CveLinkedToBug',
@@ -59,7 +57,6 @@
59 UNRESOLVED_BUGTASK_STATUSES,57 UNRESOLVED_BUGTASK_STATUSES,
60 )58 )
61from lp.registry.interfaces.product import IProduct59from lp.registry.interfaces.product import IProduct
62from lp.services.features import getFeatureFlag
63from lp.services.librarian.browser import ProxiedLibraryFileAlias60from lp.services.librarian.browser import ProxiedLibraryFileAlias
64from lp.services.webapp.publisher import canonical_url61from lp.services.webapp.publisher import canonical_url
6562
@@ -105,13 +102,8 @@
105 # The order of the field names in this list is important; this is102 # The order of the field names in this list is important; this is
106 # the order in which changes will appear both in the bug activity103 # the order in which changes will appear both in the bug activity
107 # log and in notification emails.104 # log and in notification emails.
108 bug_change_field_names = ['duplicateof', 'title', 'description']105 bug_change_field_names = ['duplicateof', 'title', 'description',
109 if bool(getFeatureFlag(106 'information_type', 'tags', 'attachment']
110 'disclosure.information_type_notifications.enabled')):
111 bug_change_field_names.append('information_type')
112 else:
113 bug_change_field_names.extend(('private', 'security_related'))
114 bug_change_field_names.extend(('tags', 'attachment'))
115 for field_name in bug_change_field_names:107 for field_name in bug_change_field_names:
116 field_delta = getattr(bug_delta, field_name)108 field_delta = getattr(bug_delta, field_name)
117 if field_delta is not None:109 if field_delta is not None:
@@ -562,70 +554,6 @@
562 self.old_value.title, self.new_value.title)}554 self.old_value.title, self.new_value.title)}
563555
564556
565# XXX: This can be deleted when information_type_notifications is removed.
566class BugVisibilityChange(AttributeChange):
567 """Describes a change to a bug's visibility."""
568
569 def _getVisibilityString(self, private):
570 """Return a string representation of `private`.
571
572 :return: 'Public' if private is False, 'Private' if
573 private is True.
574 """
575 if private:
576 return 'Private'
577 else:
578 return 'Public'
579
580 def getBugActivity(self):
581 # Use _getVisibilityString() to set old and new values
582 # correctly. We lowercase them for UI consistency in the
583 # activity log.
584 old_value = self._getVisibilityString(self.old_value)
585 new_value = self._getVisibilityString(self.new_value)
586 return {
587 'oldvalue': old_value.lower(),
588 'newvalue': new_value.lower(),
589 'whatchanged': 'visibility',
590 }
591
592 def getBugNotification(self):
593 visibility_string = self._getVisibilityString(self.new_value)
594 return {'text': "** Visibility changed to: %s" % visibility_string}
595
596
597# XXX: This can be deleted when information_type_notifications is removed.
598class BugSecurityChange(AttributeChange):
599 """Describes a change to a bug's security setting."""
600
601 activity_mapping = {
602 (False, True): ('no', 'yes'),
603 (True, False): ('yes', 'no'),
604 }
605
606 notification_mapping = {
607 (False, True):
608 u"** This bug has been flagged as a security vulnerability",
609 (True, False):
610 u"** This bug is no longer flagged as a security vulnerability",
611 }
612
613 def getBugActivity(self):
614 old_value, new_value = self.activity_mapping[
615 (self.old_value, self.new_value)]
616 return {
617 'oldvalue': old_value,
618 'newvalue': new_value,
619 'whatchanged': 'security vulnerability',
620 }
621
622 def getBugNotification(self):
623 return {
624 'text': self.notification_mapping[
625 (self.old_value, self.new_value)],
626 }
627
628
629class BugTagsChange(AttributeChange):557class BugTagsChange(AttributeChange):
630 """Used to represent a change to an `IBug`s tags."""558 """Used to represent a change to an `IBug`s tags."""
631559
@@ -940,8 +868,6 @@
940868
941BUG_CHANGE_LOOKUP = {869BUG_CHANGE_LOOKUP = {
942 'description': BugDescriptionChange,870 'description': BugDescriptionChange,
943 'private': BugVisibilityChange,
944 'security_related': BugSecurityChange,
945 'information_type': BugInformationTypeChange,871 'information_type': BugInformationTypeChange,
946 'tags': BugTagsChange,872 'tags': BugTagsChange,
947 'title': BugTitleChange,873 'title': BugTitleChange,
948874
=== modified file 'lib/lp/bugs/doc/bug-change.txt'
--- lib/lp/bugs/doc/bug-change.txt 2012-08-08 11:48:29 +0000
+++ lib/lp/bugs/doc/bug-change.txt 2012-10-15 03:36:20 +0000
@@ -312,50 +312,6 @@
312 'whatchanged': 'marked as duplicate'}312 'whatchanged': 'marked as duplicate'}
313313
314314
315=== BugVisibilityChange ===
316
317BugVisibilityChange is used to represent a change in a Bug's `private`
318attribute.
319
320 >>> from lp.bugs.adapters.bugchange import (
321 ... BugVisibilityChange)
322
323 >>> bug_visibility_change = BugVisibilityChange(
324 ... when=nowish, person=example_person,
325 ... what_changed='private', old_value=example_bug.private,
326 ... new_value=True)
327
328IBug.private is a boolean but to make it more readable we express it in
329activity and notification records as a string, where True = 'Private'
330and False = 'Public'. We also refer to it as "visibility" rather than
331privacy.
332
333 >>> print pretty(bug_visibility_change.getBugActivity())
334 {'newvalue': 'private',
335 'oldvalue': 'public',
336 'whatchanged': 'visibility'}
337
338We also use the 'Private', 'Public' and 'Visibility' terms in the
339notification text.
340
341 >>> print bug_visibility_change.getBugNotification()['text']
342 ** Visibility changed to: Private
343
344If we reverse the changes we'll see the opposite values in the
345notification and activity entries.
346
347 >>> bug_visibility_change = BugVisibilityChange(
348 ... when=nowish, person=example_person,
349 ... what_changed='private', old_value=True, new_value=False)
350 >>> print pretty(bug_visibility_change.getBugActivity())
351 {'newvalue': 'public',
352 'oldvalue': 'private',
353 'whatchanged': 'visibility'}
354
355 >>> print bug_visibility_change.getBugNotification()['text']
356 ** Visibility changed to: Public
357
358
359== BugTagsChange ==315== BugTagsChange ==
360316
361BugTagsChange is used to represent a change in a Bug's tag list.317BugTagsChange is used to represent a change in a Bug's tag list.
@@ -385,49 +341,6 @@
385 ** Tags added: zillionth-tag341 ** Tags added: zillionth-tag
386342
387343
388=== BugSecurityChange ===
389
390BugSecurityChange is used to represent a change in a Bug's
391`security_related` attribute.
392
393 >>> from lp.bugs.adapters.bugchange import (
394 ... BugSecurityChange)
395
396 >>> bug_security_change = BugSecurityChange(
397 ... when=nowish, person=example_person,
398 ... what_changed='security_related',
399 ... old_value=False, new_value=True)
400
401IBug.security_related is a boolean but to make it more readable we
402express it in activity and notification records as a short phrase.
403
404Marking a bug as security related causes one set of terms/phrases to
405be used.
406
407 >>> print pretty(bug_security_change.getBugActivity())
408 {'newvalue': 'yes',
409 'oldvalue': 'no',
410 'whatchanged': 'security vulnerability'}
411
412 >>> print bug_security_change.getBugNotification()['text']
413 ** This bug has been flagged as a security vulnerability
414
415Going the other way the phrases are similar.
416
417 >>> bug_security_change = BugSecurityChange(
418 ... when=nowish, person=example_person,
419 ... what_changed='security_related',
420 ... old_value=True, new_value=False)
421
422 >>> print pretty(bug_security_change.getBugActivity())
423 {'newvalue': 'no',
424 'oldvalue': 'yes',
425 'whatchanged': 'security vulnerability'}
426
427 >>> print bug_security_change.getBugNotification()['text']
428 ** This bug is no longer flagged as a security vulnerability
429
430
431=== CveLinkedToBug / CveUnlinkedFromBug ===344=== CveLinkedToBug / CveUnlinkedFromBug ===
432345
433These describe the linking or unlinking of a CVE to a bug.346These describe the linking or unlinking of a CVE to a bug.
434347
=== modified file 'lib/lp/bugs/doc/bugnotification-email.txt'
--- lib/lp/bugs/doc/bugnotification-email.txt 2012-09-17 16:13:40 +0000
+++ lib/lp/bugs/doc/bugnotification-email.txt 2012-10-15 03:36:20 +0000
@@ -233,10 +233,9 @@
233 ... bug=edited_bug,233 ... bug=edited_bug,
234 ... bugurl="http://www.example.com/bugs/6",234 ... bugurl="http://www.example.com/bugs/6",
235 ... user=sample_person,235 ... user=sample_person,
236 ... private={'old': False, 'new': edited_bug.private},236 ... information_type = {
237 ... security_related={237 ... 'old': InformationType.PUBLIC,
238 ... 'old': False,238 ... 'new': InformationType.PRIVATESECURITY
239 ... 'new': edited_bug.security_related,
240 ... })239 ... })
241240
242 >>> for change in get_bug_changes(bug_delta):241 >>> for change in get_bug_changes(bug_delta):
@@ -244,13 +243,10 @@
244 ... text_representation = notification['text']243 ... text_representation = notification['text']
245 ... print text_representation #doctest: -NORMALIZE_WHITESPACE244 ... print text_representation #doctest: -NORMALIZE_WHITESPACE
246 ... print "-----------------------------"245 ... print "-----------------------------"
247 ** Visibility changed to: Private246 ** Information type changed from Public to Private Security
248 -----------------------------
249 ** This bug has been flagged as a security vulnerability
250 -----------------------------247 -----------------------------
251248
252Now we set the bug public, and not security-related and check if the249Now we set the bug back to public and check if the e-mail sent changed as well.
253e-mail sent changed as well.
254250
255 >>> changed = edited_bug.transitionToInformationType(251 >>> changed = edited_bug.transitionToInformationType(
256 ... InformationType.PUBLIC, getUtility(ILaunchBag).user)252 ... InformationType.PUBLIC, getUtility(ILaunchBag).user)
@@ -259,17 +255,15 @@
259 ... bugurl="http://www.example.com/bugs/6",255 ... bugurl="http://www.example.com/bugs/6",
260 ... user=sample_person,256 ... user=sample_person,
261 ... private={'old': True, 'new': edited_bug.private},257 ... private={'old': True, 'new': edited_bug.private},
262 ... security_related={258 ... information_type={
263 ... 'old': True,259 ... 'old': InformationType.PRIVATESECURITY,
264 ... 'new': edited_bug.security_related,260 ... 'new': InformationType.PUBLIC
265 ... })261 ... })
266 >>> for change in get_bug_changes(bug_delta):262 >>> for change in get_bug_changes(bug_delta):
267 ... notification = change.getBugNotification()263 ... notification = change.getBugNotification()
268 ... print notification['text'] #doctest: -NORMALIZE_WHITESPACE264 ... print notification['text'] #doctest: -NORMALIZE_WHITESPACE
269 ... print "-----------------------------"265 ... print "-----------------------------"
270 ** Visibility changed to: Public266 ** Information type changed from Private Security to Public
271 -----------------------------
272 ** This bug is no longer flagged as a security vulnerability
273 -----------------------------267 -----------------------------
274268
275Let's add some tags to a bug:269Let's add some tags to a bug:
276270
=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
--- lib/lp/bugs/doc/bugnotification-sending.txt 2012-09-19 13:21:13 +0000
+++ lib/lp/bugs/doc/bugnotification-sending.txt 2012-10-15 03:36:20 +0000
@@ -166,16 +166,17 @@
166Let's add a few changes and see how it looks like:166Let's add a few changes and see how it looks like:
167167
168 >>> from lp.bugs.adapters.bugchange import (168 >>> from lp.bugs.adapters.bugchange import (
169 ... BugTitleChange, BugVisibilityChange)169 ... BugTitleChange, BugInformationTypeChange)
170 >>> from lp.app.enums import InformationType
170171
171 >>> bug_one.addChange(172 >>> bug_one.addChange(
172 ... BugTitleChange(173 ... BugTitleChange(
173 ... ten_minutes_ago, sample_person, "title",174 ... ten_minutes_ago, sample_person, "title",
174 ... "Old summary", "New summary"))175 ... "Old summary", "New summary"))
175 >>> bug_one.addChange(176 >>> bug_one.addChange(
176 ... BugVisibilityChange(177 ... BugInformationTypeChange(
177 ... ten_minutes_ago, sample_person, "private",178 ... ten_minutes_ago, sample_person, "information_type",
178 ... False, True))179 ... InformationType.PUBLIC, InformationType.USERDATA))
179 >>> pending_notifications = getUtility(180 >>> pending_notifications = getUtility(
180 ... IBugNotificationSet).getNotificationsToSend()181 ... IBugNotificationSet).getNotificationsToSend()
181 >>> len(pending_notifications)182 >>> len(pending_notifications)
@@ -196,7 +197,7 @@
196 - Old summary197 - Old summary
197 + New summary198 + New summary
198 <BLANKLINE>199 <BLANKLINE>
199 ** Visibility changed to: Private200 ** Information type changed from Public to Private
200 <BLANKLINE>201 <BLANKLINE>
201 --202 --
202 ...203 ...
@@ -216,9 +217,9 @@
216 ... ten_minutes_ago, sample_person, "title",217 ... ten_minutes_ago, sample_person, "title",
217 ... "New summary", "Another summary"))218 ... "New summary", "Another summary"))
218 >>> bug_one.addChange(219 >>> bug_one.addChange(
219 ... BugVisibilityChange(220 ... BugInformationTypeChange(
220 ... ten_minutes_ago, sample_person, "private",221 ... ten_minutes_ago, sample_person, "information_type",
221 ... True, False))222 ... InformationType.USERDATA, InformationType.PUBLIC))
222 >>> pending_notifications = getUtility(223 >>> pending_notifications = getUtility(
223 ... IBugNotificationSet).getNotificationsToSend()224 ... IBugNotificationSet).getNotificationsToSend()
224 >>> len(pending_notifications)225 >>> len(pending_notifications)
@@ -283,9 +284,10 @@
283 >>> now = datetime.now(pytz.timezone('UTC'))284 >>> now = datetime.now(pytz.timezone('UTC'))
284 >>> for minutes_ago in reversed(range(10)):285 >>> for minutes_ago in reversed(range(10)):
285 ... bug_one.addChange(286 ... bug_one.addChange(
286 ... BugVisibilityChange(287 ... BugInformationTypeChange(
287 ... now - timedelta(minutes=minutes_ago), sample_person,288 ... now - timedelta(minutes=minutes_ago), sample_person,
288 ... "private", False, True))289 ... "information_type", InformationType.PUBLIC,
290 ... InformationType.USERDATA))
289 >>> pending_notifications = getUtility(291 >>> pending_notifications = getUtility(
290 ... IBugNotificationSet).getNotificationsToSend()292 ... IBugNotificationSet).getNotificationsToSend()
291 >>> len(pending_notifications)293 >>> len(pending_notifications)
@@ -336,7 +338,6 @@
336We will need a fresh new bug.338We will need a fresh new bug.
337339
338 >>> from lp.bugs.interfaces.bug import CreateBugParams340 >>> from lp.bugs.interfaces.bug import CreateBugParams
339 >>> from lp.app.enums import InformationType
340 >>> from lp.registry.interfaces.distribution import IDistributionSet341 >>> from lp.registry.interfaces.distribution import IDistributionSet
341 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')342 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
342 >>> description = getUtility(IMessageSet).fromText(343 >>> description = getUtility(IMessageSet).fromText(
@@ -471,13 +472,13 @@
471 ... ten_minutes_ago, sample_person, "title",472 ... ten_minutes_ago, sample_person, "title",
472 ... "Old summary", "New summary"))473 ... "Old summary", "New summary"))
473 >>> bug_two.addChange(474 >>> bug_two.addChange(
474 ... BugVisibilityChange(475 ... BugInformationTypeChange(
475 ... ten_minutes_ago, sample_person, "title",476 ... ten_minutes_ago, sample_person, "information_type",
476 ... False, True))477 ... InformationType.PUBLIC, InformationType.USERDATA))
477 >>> bug_two.addChange(478 >>> bug_two.addChange(
478 ... BugVisibilityChange(479 ... BugInformationTypeChange(
479 ... ten_minutes_ago, sample_person, "title",480 ... ten_minutes_ago, sample_person, "information_type",
480 ... True, False))481 ... InformationType.USERDATA, InformationType.PUBLIC))
481482
482 >>> notifications = getUtility(483 >>> notifications = getUtility(
483 ... IBugNotificationSet).getNotificationsToSend()484 ... IBugNotificationSet).getNotificationsToSend()
@@ -559,7 +560,7 @@
559 INFO Notifying test@canonical.com about bug 1.560 INFO Notifying test@canonical.com about bug 1.
560 ...561 ...
561562
562Note that the message omitted the undone visibility change.563Note that the message omitted the undone information type change.
563564
564The cronscript has to be sure to mark all notifications, omitted and565The cronscript has to be sure to mark all notifications, omitted and
565otherwise, as sent. It also marks the omitted notifications with a status,566otherwise, as sent. It also marks the omitted notifications with a status,
@@ -591,8 +592,8 @@
591 summary Sent592 summary Sent
592 comment Sent593 comment Sent
593 summary Sent594 summary Sent
594 visibility Omitted595 information type Omitted
595 visibility Omitted596 information type Omitted
596597
597598
598The X-Launchpad-Bug header599The X-Launchpad-Bug header
599600
=== modified file 'lib/lp/bugs/doc/bugnotification-threading.txt'
--- lib/lp/bugs/doc/bugnotification-threading.txt 2012-07-27 01:15:04 +0000
+++ lib/lp/bugs/doc/bugnotification-threading.txt 2012-10-15 03:36:20 +0000
@@ -18,16 +18,17 @@
18 >>> from datetime import datetime, timedelta18 >>> from datetime import datetime, timedelta
19 >>> from lp.services.messages.interfaces.message import IMessageSet19 >>> from lp.services.messages.interfaces.message import IMessageSet
20 >>> from lp.bugs.interfaces.bug import IBugSet20 >>> from lp.bugs.interfaces.bug import IBugSet
21 >>> from lp.bugs.adapters.bugchange import BugVisibilityChange21 >>> from lp.bugs.adapters.bugchange import BugInformationTypeChange
22 >>> from lp.app.enums import InformationType
2223
23 >>> ten_minutes_ago = (24 >>> ten_minutes_ago = (
24 ... datetime.now(pytz.timezone('UTC')) - timedelta(minutes=10))25 ... datetime.now(pytz.timezone('UTC')) - timedelta(minutes=10))
25 >>> sample_person = getUtility(ILaunchBag).user26 >>> sample_person = getUtility(ILaunchBag).user
26 >>> bug_one = getUtility(IBugSet).get(1)27 >>> bug_one = getUtility(IBugSet).get(1)
27 >>> bug_one.addChange(28 >>> bug_one.addChange(
28 ... BugVisibilityChange(29 ... BugInformationTypeChange(
29 ... ten_minutes_ago, sample_person, "private",30 ... ten_minutes_ago, sample_person, "information_type",
30 ... False, True))31 ... InformationType.PUBLIC, InformationType.USERDATA))
3132
32 >>> from lp.bugs.interfaces.bugnotification import IBugNotificationSet33 >>> from lp.bugs.interfaces.bugnotification import IBugNotificationSet
33 >>> from lp.bugs.scripts.bugnotification import (34 >>> from lp.bugs.scripts.bugnotification import (
@@ -68,9 +69,9 @@
68 >>> bug_one.linkMessage(comment)69 >>> bug_one.linkMessage(comment)
69 <...>70 <...>
70 >>> bug_one.addChange(71 >>> bug_one.addChange(
71 ... BugVisibilityChange(72 ... BugInformationTypeChange(
72 ... ten_minutes_ago, sample_person, "private",73 ... ten_minutes_ago, sample_person, "information_type",
73 ... True, False))74 ... InformationType.USERDATA, InformationType.PUBLIC))
74 >>> notifications = getUtility(75 >>> notifications = getUtility(
75 ... IBugNotificationSet).getNotificationsToSend()76 ... IBugNotificationSet).getNotificationsToSend()
76 >>> messages = [emails for notifications, omitted, emails in77 >>> messages = [emails for notifications, omitted, emails in
7778
=== modified file 'lib/lp/bugs/scripts/tests/test_bugnotification.py'
--- lib/lp/bugs/scripts/tests/test_bugnotification.py 2012-09-18 18:36:09 +0000
+++ lib/lp/bugs/scripts/tests/test_bugnotification.py 2012-10-15 03:36:20 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
3"""Tests for construction bug notification emails for sending."""3"""Tests for construction bug notification emails for sending."""
44
@@ -29,10 +29,10 @@
29 BranchUnlinkedFromBug,29 BranchUnlinkedFromBug,
30 BugAttachmentChange,30 BugAttachmentChange,
31 BugDuplicateChange,31 BugDuplicateChange,
32 BugInformationTypeChange,
32 BugTagsChange,33 BugTagsChange,
33 BugTaskStatusChange,34 BugTaskStatusChange,
34 BugTitleChange,35 BugTitleChange,
35 BugVisibilityChange,
36 BugWatchAdded,36 BugWatchAdded,
37 BugWatchRemoved,37 BugWatchRemoved,
38 CveLinkedToBug,38 CveLinkedToBug,
@@ -96,8 +96,6 @@
96 implements(IBug)96 implements(IBug)
9797
98 duplicateof = None98 duplicateof = None
99 private = False
100 security_related = False
101 information_type = InformationType.PUBLIC99 information_type = InformationType.PUBLIC
102 messages = []100 messages = []
103101
@@ -691,9 +689,9 @@
691689
692 def change_other(self):690 def change_other(self):
693 self.bug.addChange(691 self.bug.addChange(
694 BugVisibilityChange(692 BugInformationTypeChange(
695 self.ten_minutes_ago, self.person, "private",693 self.ten_minutes_ago, self.person, "information_type",
696 False, True))694 InformationType.PUBLIC, InformationType.USERDATA))
697695
698 def test_change_seen(self):696 def test_change_seen(self):
699 # A smoketest.697 # A smoketest.
700698
=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-activity.txt'
--- lib/lp/bugs/stories/bugs/xx-bug-activity.txt 2012-08-03 01:42:13 +0000
+++ lib/lp/bugs/stories/bugs/xx-bug-activity.txt 2012-10-15 03:36:20 +0000
@@ -98,44 +98,6 @@
98 + A new title for this bug98 + A new title for this bug
99 --------99 --------
100100
101Alterations to a bug's privacy will show up as 'visibility' changes.
102
103 >>> admin_browser.open(
104 ... 'http://bugs.launchpad.dev/redfish/+bug/15/+secrecy')
105 >>> admin_browser.getControl("Private", index=1).selected = True
106 >>> admin_browser.getControl("Change").click()
107
108 >>> admin_browser.open('http://launchpad.dev/bugs/15')
109 >>> print_comments(admin_browser.contents)
110 Foo Bar
111 ... ago
112 summary:
113 - Nonsensical bugs are useless
114 + A new title for this bug
115 visibility:
116 public => private
117 --------
118
119Changes to a bug's security_related field will be displayed as
120'security vulnerability' changes, with values of 'yes' or 'no'. Note
121that changes are grouped together if they occur at similar times, but
122are still shown in the order the changes were made.
123
124 >>> admin_browser.open(
125 ... 'http://bugs.launchpad.dev/redfish/+bug/15/+secrecy')
126 >>> admin_browser.getControl("Private Security").selected = True
127 >>> admin_browser.getControl("Change").click()
128
129 >>> admin_browser.open('http://launchpad.dev/bugs/15')
130 >>> print_comments(admin_browser.contents)
131 Foo Bar (name16)
132 ... ago
133 summary:
134 ...
135 security vulnerability:
136 no => yes
137 --------
138
139Changes to the bug's description will simply be displayed as 'description:101Changes to the bug's description will simply be displayed as 'description:
140updated', since such changes can be quite long.102updated', since such changes can be quite long.
141103
@@ -310,31 +272,26 @@
310 ubuntu272 ubuntu
311 --------273 --------
312274
313Changes to information_type are shown when the feature flag is set.275Changes to information_type are shown.
314276
315 >>> from lp.services.features.testing import FeatureFixture277 >>> admin_browser.open(
316 >>> feature_flag = {278 ... "http://bugs.launchpad.dev/evolution/+bug/7/+secrecy")
317 ... 'disclosure.information_type_notifications.enabled': 'on'}279 >>> admin_browser.getControl("Private", index=1).selected = True
318 >>> with FeatureFixture(feature_flag):280 >>> admin_browser.getControl('Change').click()
319 ... admin_browser.open(281 >>> admin_browser.open("http://bugs.launchpad.dev/evolution/+bug/7")
320 ... "http://bugs.launchpad.dev/evolution/+bug/7/+secrecy")282 >>> print_comments(admin_browser.contents)
321 ... admin_browser.getControl("Private", index=1).selected = True
322 ... admin_browser.getControl('Change').click()
323 ... admin_browser.open("http://bugs.launchpad.dev/evolution/+bug/7")
324 ... print_comments(admin_browser.contents)
325 Foo Bar (name16)283 Foo Bar (name16)
326 ... ago284 ... ago
327 information type:285 information type:
328 Public => Private286 Public => Private
329 --------287 --------
330288
331 >>> with FeatureFixture(feature_flag):289 >>> admin_browser.open(
332 ... admin_browser.open(290 ... "http://bugs.launchpad.dev/jokosher/+bug/14/+secrecy")
333 ... "http://bugs.launchpad.dev/jokosher/+bug/14/+secrecy")291 >>> admin_browser.getControl("Private", index=1).selected = True
334 ... admin_browser.getControl("Private", index=1).selected = True292 >>> admin_browser.getControl('Change').click()
335 ... admin_browser.getControl('Change').click()293 >>> admin_browser.open("http://bugs.launchpad.dev/jokosher/+bug/14")
336 ... admin_browser.open("http://bugs.launchpad.dev/jokosher/+bug/14")294 >>> print_comments(admin_browser.contents)
337 ... print_comments(admin_browser.contents)
338 Foo Bar (name16)295 Foo Bar (name16)
339 ... ago296 ... ago
340 information type:297 information type:
341298
=== modified file 'lib/lp/bugs/subscribers/bug.py'
--- lib/lp/bugs/subscribers/bug.py 2012-09-20 04:49:19 +0000
+++ lib/lp/bugs/subscribers/bug.py 2012-10-15 03:36:20 +0000
@@ -30,7 +30,6 @@
30from lp.registry.interfaces.person import IPerson30from lp.registry.interfaces.person import IPerson
31from lp.services.config import config31from lp.services.config import config
32from lp.services.database.sqlbase import block_implicit_flushes32from lp.services.database.sqlbase import block_implicit_flushes
33from lp.services.features import getFeatureFlag
34from lp.services.mail.helpers import get_contact_email_addresses33from lp.services.mail.helpers import get_contact_email_addresses
35from lp.services.mail.sendmail import (34from lp.services.mail.sendmail import (
36 format_address,35 format_address,
@@ -110,13 +109,8 @@
110 IBugDelta if there are changes, or None if there were no changes.109 IBugDelta if there are changes, or None if there were no changes.
111 """110 """
112 changes = {}111 changes = {}
113 fields = ["title", "description", "name"]112 fields = ["title", "description", "name", "information_type",
114 if bool(getFeatureFlag(113 "duplicateof", "tags"]
115 'disclosure.information_type_notifications.enabled')):
116 fields.append('information_type')
117 else:
118 fields.extend(('private', 'security_related'))
119 fields.extend(("duplicateof", "tags"))
120 for field_name in fields:114 for field_name in fields:
121 # fields for which we show old => new when their values change115 # fields for which we show old => new when their values change
122 old_val = getattr(old_bug, field_name)116 old_val = getattr(old_bug, field_name)
123117
=== modified file 'lib/lp/bugs/tests/test_bugchanges.py'
--- lib/lp/bugs/tests/test_bugchanges.py 2012-09-18 18:36:09 +0000
+++ lib/lp/bugs/tests/test_bugchanges.py 2012-10-15 03:36:20 +0000
@@ -25,7 +25,6 @@
25from lp.bugs.interfaces.cve import ICveSet25from lp.bugs.interfaces.cve import ICveSet
26from lp.bugs.model.bugnotification import BugNotification26from lp.bugs.model.bugnotification import BugNotification
27from lp.bugs.scripts.bugnotification import construct_email_notifications27from lp.bugs.scripts.bugnotification import construct_email_notifications
28from lp.services.features.testing import FeatureFixture
29from lp.services.librarian.browser import ProxiedLibraryFileAlias28from lp.services.librarian.browser import ProxiedLibraryFileAlias
30from lp.services.webapp.interfaces import ILaunchBag29from lp.services.webapp.interfaces import ILaunchBag
31from lp.services.webapp.publisher import canonical_url30from lp.services.webapp.publisher import canonical_url
@@ -47,8 +46,7 @@
47 super(TestBugChanges, self).setUp('foo.bar@canonical.com')46 super(TestBugChanges, self).setUp('foo.bar@canonical.com')
48 self.admin_user = getUtility(ILaunchBag).user47 self.admin_user = getUtility(ILaunchBag).user
49 self.user = self.factory.makePerson(48 self.user = self.factory.makePerson(
50 displayname='Arthur Dent',49 displayname='Arthur Dent', selfgenerated_bugnotifications=True)
51 selfgenerated_bugnotifications=True)
52 self.product = self.factory.makeProduct(50 self.product = self.factory.makeProduct(
53 owner=self.user, official_malone=True)51 owner=self.user, official_malone=True)
54 self.bug = self.factory.makeBug(target=self.product, owner=self.user)52 self.bug = self.factory.makeBug(target=self.product, owner=self.user)
@@ -57,11 +55,9 @@
57 # Add some structural subscribers to show that notifications55 # Add some structural subscribers to show that notifications
58 # aren't sent to LIFECYCLE subscribers by default.56 # aren't sent to LIFECYCLE subscribers by default.
59 self.product_lifecycle_subscriber = self.newSubscriber(57 self.product_lifecycle_subscriber = self.newSubscriber(
60 self.product, "product-lifecycle",58 self.product, "product-lifecycle", BugNotificationLevel.LIFECYCLE)
61 BugNotificationLevel.LIFECYCLE)
62 self.product_metadata_subscriber = self.newSubscriber(59 self.product_metadata_subscriber = self.newSubscriber(
63 self.product, "product-metadata",60 self.product, "product-metadata", BugNotificationLevel.METADATA)
64 BugNotificationLevel.METADATA)
6561
66 self.saveOldChanges()62 self.saveOldChanges()
6763
@@ -226,8 +222,7 @@
226 self.assertEqual(activity.target, None)222 self.assertEqual(activity.target, None)
227223
228 unsubscribe_activity = dict(224 unsubscribe_activity = dict(
229 whatchanged='removed subscriber Arthur Dent',225 whatchanged='removed subscriber Arthur Dent', person=self.user)
230 person=self.user)
231 self.assertRecordedChange(expected_activity=unsubscribe_activity)226 self.assertRecordedChange(expected_activity=unsubscribe_activity)
232227
233 def test_unsubscribe_private_bug(self):228 def test_unsubscribe_private_bug(self):
@@ -242,8 +237,7 @@
242 self.saveOldChanges(bug=bug)237 self.saveOldChanges(bug=bug)
243 bug.unsubscribe(subscriber, subscriber)238 bug.unsubscribe(subscriber, subscriber)
244 unsubscribe_activity = dict(239 unsubscribe_activity = dict(
245 whatchanged=u'removed subscriber Mom',240 whatchanged=u'removed subscriber Mom', person=subscriber)
246 person=subscriber)
247 self.assertRecordedChange(241 self.assertRecordedChange(
248 expected_activity=unsubscribe_activity, bug=bug)242 expected_activity=unsubscribe_activity, bug=bug)
249243
@@ -562,77 +556,17 @@
562 self.bug.unlinkBranch(branch, self.user)556 self.bug.unlinkBranch(branch, self.user)
563 self.assertRecordedChange()557 self.assertRecordedChange()
564558
565 def test_make_private(self):
566 # Marking a bug as private adds items to the bug's activity log
567 # and notifications.
568 bug_before_modification = Snapshot(
569 self.bug, providing=providedBy(self.bug))
570 self.bug.setPrivate(True, self.user)
571 notify(ObjectModifiedEvent(
572 self.bug, bug_before_modification, ['private'], user=self.user))
573
574 visibility_change_activity = {
575 'person': self.user,
576 'whatchanged': 'visibility',
577 'oldvalue': 'public',
578 'newvalue': 'private',
579 }
580
581 visibility_change_notification = {
582 'text': '** Visibility changed to: Private',
583 'person': self.user,
584 }
585
586 self.assertRecordedChange(
587 expected_activity=visibility_change_activity,
588 expected_notification=visibility_change_notification)
589
590 def test_make_public(self):
591 # Marking a bug as public adds items to the bug's activity log
592 # and notifications.
593 private_bug = self.factory.makeBug(
594 information_type=InformationType.USERDATA)
595 self.saveOldChanges(private_bug)
596 self.assertTrue(private_bug.private)
597 bug_before_modification = Snapshot(
598 private_bug, providing=providedBy(private_bug))
599 private_bug.transitionToInformationType(
600 InformationType.PUBLIC, self.user)
601 notify(ObjectModifiedEvent(
602 private_bug, bug_before_modification, ['private'],
603 user=self.user))
604
605 visibility_change_activity = {
606 'person': self.user,
607 'whatchanged': 'visibility',
608 'oldvalue': 'private',
609 'newvalue': 'public',
610 }
611
612 visibility_change_notification = {
613 'text': '** Visibility changed to: Public',
614 'person': self.user,
615 }
616
617 self.assertRecordedChange(
618 expected_activity=visibility_change_activity,
619 expected_notification=visibility_change_notification,
620 bug=private_bug)
621
622 def test_change_information_type(self):559 def test_change_information_type(self):
623 # Changing the information type of a bug adds items to the activity560 # Changing the information type of a bug adds items to the activity
624 # log and notifications.561 # log and notifications.
625 bug = self.factory.makeBug()562 bug = self.factory.makeBug()
626 self.saveOldChanges(bug=bug)563 self.saveOldChanges(bug=bug)
627 feature_flag = {
628 'disclosure.information_type_notifications.enabled': 'on'}
629 bug_before_modification = Snapshot(bug, providing=providedBy(bug))564 bug_before_modification = Snapshot(bug, providing=providedBy(bug))
630 with FeatureFixture(feature_flag):565 bug.transitionToInformationType(
631 bug.transitionToInformationType(566 InformationType.PRIVATESECURITY, self.user)
632 InformationType.PRIVATESECURITY, self.user)567 notify(ObjectModifiedEvent(
633 notify(ObjectModifiedEvent(568 bug, bug_before_modification, ['information_type'],
634 bug, bug_before_modification, ['information_type'],569 user=self.user))
635 user=self.user))
636570
637 information_type_change_activity = {571 information_type_change_activity = {
638 'person': self.user,572 'person': self.user,
@@ -656,13 +590,9 @@
656 person = self.factory.makePerson()590 person = self.factory.makePerson()
657 bug = self.factory.makeBug(owner=person)591 bug = self.factory.makeBug(owner=person)
658 self.saveOldChanges(bug=bug)592 self.saveOldChanges(bug=bug)
659 feature_flag = {
660 'disclosure.information_type_notifications.enabled': 'on'}
661 webservice = launchpadlib_for('test', person)593 webservice = launchpadlib_for('test', person)
662 lp_bug = webservice.load(api_url(bug))594 lp_bug = webservice.load(api_url(bug))
663 with FeatureFixture(feature_flag):595 lp_bug.transitionToInformationType(information_type='Private Security')
664 lp_bug.transitionToInformationType(
665 information_type='Private Security')
666596
667 information_type_change_activity = {597 information_type_change_activity = {
668 'person': person,598 'person': person,
@@ -800,7 +730,7 @@
800 # This checks the activity's attribute and target attributes.730 # This checks the activity's attribute and target attributes.
801 activity = self.bug.activity[-1]731 activity = self.bug.activity[-1]
802 self.assertEqual(activity.attribute, 'attachments')732 self.assertEqual(activity.attribute, 'attachments')
803 self.assertEqual(activity.target, None)733 self.assertIsNone(activity.target)
804734
805 attachment_added_activity = {735 attachment_added_activity = {
806 'person': self.user,736 'person': self.user,
@@ -1686,8 +1616,7 @@
16861616
1687 self.assertRecordedChange(1617 self.assertRecordedChange(
1688 expected_activity=expected_activity,1618 expected_activity=expected_activity,
1689 expected_notification=expected_notification,1619 expected_notification=expected_notification, bug=duplicate_bug)
1690 bug=duplicate_bug)
16911620
1692 def test_convert_to_question_no_comment(self):1621 def test_convert_to_question_no_comment(self):
1693 # When a bug task is converted to a question, its status is1622 # When a bug task is converted to a question, its status is
@@ -1754,8 +1683,7 @@
17541683
1755 self.assertRecordedChange(1684 self.assertRecordedChange(
1756 expected_activity=expected_activity,1685 expected_activity=expected_activity,
1757 expected_notification=expected_notification,1686 expected_notification=expected_notification, bug=new_bug)
1758 bug=new_bug)
17591687
1760 def test_description_changed_no_self_email(self):1688 def test_description_changed_no_self_email(self):
1761 # Users who have selfgenerated_bugnotifications set to False1689 # Users who have selfgenerated_bugnotifications set to False
@@ -1805,9 +1733,9 @@
1805 # If a person has a structural METADATA subscription,1733 # If a person has a structural METADATA subscription,
1806 # and a direct LIFECYCLE subscription, they should1734 # and a direct LIFECYCLE subscription, they should
1807 # get no emails for a non-LIFECYCLE change (bug 713382).1735 # get no emails for a non-LIFECYCLE change (bug 713382).
1808 self.bug.subscribe(self.product_metadata_subscriber,1736 self.bug.subscribe(
1809 self.product_metadata_subscriber,1737 self.product_metadata_subscriber, self.product_metadata_subscriber,
1810 level=BugNotificationLevel.LIFECYCLE)1738 level=BugNotificationLevel.LIFECYCLE)
1811 self.changeAttribute(1739 self.changeAttribute(
1812 self.bug, 'description', 'New description')1740 self.bug, 'description', 'New description')
18131741
18141742
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2012-10-04 23:15:35 +0000
+++ lib/lp/services/features/flags.py 2012-10-15 03:36:20 +0000
@@ -220,13 +220,6 @@
220 '',220 '',
221 '',221 '',
222 ''),222 ''),
223 ('disclosure.information_type_notifications.enabled',
224 'boolean',
225 ('If true, calculate and store bugchange notifications to reference '
226 'information_type rather than private/security_related.'),
227 '',
228 '',
229 ''),
230 ('auditor.enabled',223 ('auditor.enabled',
231 'boolean',224 'boolean',
232 'If true, send audit data to an auditor instance.',225 'If true, send audit data to an auditor instance.',