Merge lp:~stevenk/launchpad/bugdelta-information_type into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 15173
Proposed branch: lp:~stevenk/launchpad/bugdelta-information_type
Merge into: lp:launchpad
Diff against target: 348 lines (+147/-31)
7 files modified
lib/lp/bugs/adapters/bugchange.py (+41/-5)
lib/lp/bugs/adapters/bugdelta.py (+5/-4)
lib/lp/bugs/browser/bugtask.py (+1/-1)
lib/lp/bugs/interfaces/bug.py (+4/-2)
lib/lp/bugs/stories/bugs/xx-bug-activity.txt (+18/-0)
lib/lp/bugs/subscribers/bug.py (+10/-4)
lib/lp/bugs/tests/test_bugchanges.py (+68/-15)
To merge this branch: bzr merge lp:~stevenk/launchpad/bugdelta-information_type
Reviewer Review Type Date Requested Status
Ian Booth (community) Approve
Review via email: mp+103802@code.launchpad.net

Commit message

Add support for information_type to BugDelta and BugChange, controlled with a feature flag.

Description of the change

Add support for changing information_type to BugDelta and BugChange.

I have left BugVisibilityChange and BugSecurityChange alone (and actually added the latter to __all__), and left the current tests alone, and added a few tests for the new behaviour.

I have gotten annoyed and ripped out the Bug{Visibility,Security}Change tests from bug-change.txt, since as far as I can tell it is duplicated between test_bugchange.py and bug-change.txt. I'm happy to completly rip out bug-change.txt in this branch if asked.

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

Looks mighty fine. As discussed on irc, best to add TestBugTaskInterestingActivity as a doc test so everything is kept together and the tests don't become fragmented. Add add XXX for the removal of BugVisibilityChange and BugSecurityChange when the time is right.

I am a little concerned with the bug-change.txt deletions since the visibility and security changes which are deleted don't appear to be tested anywhere else. Adding a ff check to the doc test as is done elsewhere may be more appropriate so that we have full test coverage.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/adapters/bugchange.py'
2--- lib/lp/bugs/adapters/bugchange.py 2012-03-13 00:45:33 +0000
3+++ lib/lp/bugs/adapters/bugchange.py 2012-04-30 02:03:28 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Implementations for bug changes."""
10@@ -23,6 +23,8 @@
11 'BugConvertedToQuestion',
12 'BugDescriptionChange',
13 'BugDuplicateChange',
14+ 'BugInformationTypeChange',
15+ 'BugSecurityChange',
16 'BugTagsChange',
17 'BugTaskAdded',
18 'BugTaskAssigneeChange',
19@@ -56,7 +58,9 @@
20 RESOLVED_BUGTASK_STATUSES,
21 UNRESOLVED_BUGTASK_STATUSES,
22 )
23+from lp.registry.enums import InformationType
24 from lp.registry.interfaces.product import IProduct
25+from lp.services.features import getFeatureFlag
26 from lp.services.librarian.browser import ProxiedLibraryFileAlias
27 from lp.services.webapp.publisher import canonical_url
28
29@@ -102,10 +106,13 @@
30 # The order of the field names in this list is important; this is
31 # the order in which changes will appear both in the bug activity
32 # log and in notification emails.
33- bug_change_field_names = [
34- 'duplicateof', 'title', 'description', 'private', 'security_related',
35- 'tags', 'attachment',
36- ]
37+ bug_change_field_names = ['duplicateof', 'title', 'description']
38+ if bool(getFeatureFlag(
39+ 'disclosure.show_information_type_in_ui.enabled')):
40+ bug_change_field_names.append('information_type')
41+ else:
42+ bug_change_field_names.extend(('private', 'security_related'))
43+ bug_change_field_names.extend(('tags', 'attachment'))
44 for field_name in bug_change_field_names:
45 field_delta = getattr(bug_delta, field_name)
46 if field_delta is not None:
47@@ -540,6 +547,33 @@
48 return {'text': notification_text}
49
50
51+class BugInformationTypeChange(AttributeChange):
52+ """Used to represent a change to the information_type of an `IBug`."""
53+
54+ def title(self, value):
55+ # This function is unnecessary when display_userdata_as_private is
56+ # removed.
57+ show_userdata_as_private = bool(getFeatureFlag(
58+ 'disclosure.display_userdata_as_private.enabled'))
59+ title = value.title
60+ if value == InformationType.USERDATA and show_userdata_as_private:
61+ title = 'Private'
62+ return title
63+
64+ def getBugActivity(self):
65+ return {
66+ 'newvalue': self.title(self.new_value),
67+ 'oldvalue': self.title(self.old_value),
68+ 'whatchanged': 'information type'
69+ }
70+
71+ def getBugNotification(self):
72+ return {
73+ 'text': "** Information type changed from %s to %s" % (
74+ self.title(self.old_value), self.title(self.new_value))}
75+
76+
77+# XXX: This can be deleted when show_information_type_in_ui is removed.
78 class BugVisibilityChange(AttributeChange):
79 """Describes a change to a bug's visibility."""
80
81@@ -571,6 +605,7 @@
82 return {'text': "** Visibility changed to: %s" % visibility_string}
83
84
85+# XXX: This can be deleted when show_information_type_in_ui is removed.
86 class BugSecurityChange(AttributeChange):
87 """Describes a change to a bug's security setting."""
88
89@@ -918,6 +953,7 @@
90 'description': BugDescriptionChange,
91 'private': BugVisibilityChange,
92 'security_related': BugSecurityChange,
93+ 'information_type': BugInformationTypeChange,
94 'tags': BugTagsChange,
95 'title': BugTitleChange,
96 'attachment': BugAttachmentChange,
97
98=== modified file 'lib/lp/bugs/adapters/bugdelta.py'
99--- lib/lp/bugs/adapters/bugdelta.py 2011-12-22 17:24:23 +0000
100+++ lib/lp/bugs/adapters/bugdelta.py 2012-04-30 02:03:28 +0000
101@@ -1,4 +1,4 @@
102-# Copyright 2009 Canonical Ltd. This software is licensed under the
103+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
104 # GNU Affero General Public License version 3 (see the file LICENSE).
105
106 """Components related to bugs."""
107@@ -16,9 +16,9 @@
108
109 def __init__(self, bug, bugurl, user,
110 title=None, description=None, name=None,
111- private=None, security_related=None, duplicateof=None,
112- external_reference=None, bugwatch=None, cve=None,
113- attachment=None, tags=None,
114+ private=None, security_related=None, information_type=None,
115+ duplicateof=None, external_reference=None, bugwatch=None,
116+ cve=None, attachment=None, tags=None,
117 added_bugtasks=None, bugtask_deltas=None,
118 bug_before_modification=None):
119 self.bug = bug
120@@ -30,6 +30,7 @@
121 self.name = name
122 self.private = private
123 self.security_related = security_related
124+ self.information_type = information_type
125 self.duplicateof = duplicateof
126 self.external_reference = external_reference
127 self.bugwatch = bugwatch
128
129=== modified file 'lib/lp/bugs/browser/bugtask.py'
130--- lib/lp/bugs/browser/bugtask.py 2012-04-18 03:56:54 +0000
131+++ lib/lp/bugs/browser/bugtask.py 2012-04-30 02:03:28 +0000
132@@ -789,7 +789,7 @@
133 else:
134 activity = self.context.bug.activity
135 bug_change_re = (
136- 'affects|description|security vulnerability|'
137+ 'affects|description|security vulnerability|information type|'
138 'summary|tags|visibility|bug task deleted')
139 bugtask_change_re = (
140 '[a-z0-9][a-z0-9\+\.\-]+( \([A-Za-z0-9\s]+\))?: '
141
142=== modified file 'lib/lp/bugs/interfaces/bug.py'
143--- lib/lp/bugs/interfaces/bug.py 2012-04-18 05:52:41 +0000
144+++ lib/lp/bugs/interfaces/bug.py 2012-04-30 02:03:28 +0000
145@@ -1075,18 +1075,20 @@
146 bugurl = Attribute("The absolute URL to the bug.")
147 user = Attribute("The IPerson that did the editing.")
148
149- # fields on the bug itself
150+ # Fields on the bug itself.
151 title = Attribute("A dict with two keys, 'old' and 'new', or None.")
152 description = Attribute("A dict with two keys, 'old' and 'new', or None.")
153 private = Attribute("A dict with two keys, 'old' and 'new', or None.")
154 security_related = Attribute(
155 "A dict with two keys, 'old' and 'new', or None.")
156+ information_type = Attribute(
157+ "A dict with two keys, 'old' and 'new', or None.")
158 name = Attribute("A dict with two keys, 'old' and 'new', or None.")
159 duplicateof = Attribute(
160 "A dict with two keys, 'old' and 'new', or None. Key values are "
161 "IBug's")
162
163- # other things linked to the bug
164+ # Other things linked to the bug.
165 bugwatch = Attribute(
166 "A dict with two keys, 'old' and 'new', or None. Key values are "
167 "IBugWatch's.")
168
169=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-activity.txt'
170--- lib/lp/bugs/stories/bugs/xx-bug-activity.txt 2012-04-04 05:46:26 +0000
171+++ lib/lp/bugs/stories/bugs/xx-bug-activity.txt 2012-04-30 02:03:28 +0000
172@@ -318,3 +318,21 @@
173 no longer affects:
174 ubuntu
175 --------
176+
177+Changes to information_type are shown when the feature flag is set.
178+
179+ >>> from lp.services.features.testing import FeatureFixture
180+ >>> feature_flag = {
181+ ... 'disclosure.show_information_type_in_ui.enabled': 'on'}
182+ >>> with FeatureFixture(feature_flag):
183+ ... admin_browser.open(
184+ ... "http://bugs.launchpad.dev/jokosher/+bug/14/+secrecy")
185+ ... admin_browser.getControl("User Data").selected = True
186+ ... admin_browser.getControl('Change').click()
187+ ... admin_browser.open("http://bugs.launchpad.dev/jokosher/+bug/14")
188+ ... print_comments(admin_browser.contents)
189+ Foo Bar (name16)
190+ ... ago
191+ information type:
192+ Embargoed Security => User Data
193+ --------
194
195=== modified file 'lib/lp/bugs/subscribers/bug.py'
196--- lib/lp/bugs/subscribers/bug.py 2012-03-13 00:45:33 +0000
197+++ lib/lp/bugs/subscribers/bug.py 2012-04-30 02:03:28 +0000
198@@ -1,4 +1,4 @@
199-# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
200+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
201 # GNU Affero General Public License version 3 (see the file LICENSE).
202
203 __metaclass__ = type
204@@ -32,6 +32,7 @@
205 from lp.registry.interfaces.product import IProduct
206 from lp.services.config import config
207 from lp.services.database.sqlbase import block_implicit_flushes
208+from lp.services.features import getFeatureFlag
209 from lp.services.mail.helpers import get_contact_email_addresses
210 from lp.services.mail.sendmail import (
211 format_address,
212@@ -115,9 +116,14 @@
213 IBugDelta if there are changes, or None if there were no changes.
214 """
215 changes = {}
216-
217- for field_name in ("title", "description", "name", "private",
218- "security_related", "duplicateof", "tags"):
219+ fields = ["title", "description", "name"]
220+ if bool(getFeatureFlag(
221+ 'disclosure.show_information_type_in_ui.enabled')):
222+ fields.append('information_type')
223+ else:
224+ fields.extend(('private', 'security_related'))
225+ fields.extend(("duplicateof", "tags"))
226+ for field_name in fields:
227 # fields for which we show old => new when their values change
228 old_val = getattr(old_bug, field_name)
229 new_val = getattr(new_bug, field_name)
230
231=== modified file 'lib/lp/bugs/tests/test_bugchanges.py'
232--- lib/lp/bugs/tests/test_bugchanges.py 2012-04-19 21:15:25 +0000
233+++ lib/lp/bugs/tests/test_bugchanges.py 2012-04-30 02:03:28 +0000
234@@ -1,4 +1,4 @@
235-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
236+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
237 # GNU Affero General Public License version 3 (see the file LICENSE).
238
239 """Tests for recording changes done to a bug."""
240@@ -8,7 +8,10 @@
241 ObjectModifiedEvent,
242 )
243 from lazr.lifecycle.snapshot import Snapshot
244-from testtools.matchers import StartsWith
245+from testtools.matchers import (
246+ MatchesStructure,
247+ StartsWith,
248+ )
249 from zope.component import getUtility
250 from zope.event import notify
251 from zope.interface import providedBy
252@@ -21,6 +24,8 @@
253 from lp.bugs.interfaces.cve import ICveSet
254 from lp.bugs.model.bugnotification import BugNotification
255 from lp.bugs.scripts.bugnotification import construct_email_notifications
256+from lp.registry.enums import InformationType
257+from lp.services.features.testing import FeatureFixture
258 from lp.services.librarian.browser import ProxiedLibraryFileAlias
259 from lp.services.webapp.interfaces import ILaunchBag
260 from lp.services.webapp.publisher import canonical_url
261@@ -139,19 +144,12 @@
262 self.assertEqual(len(expected_activities), len(new_activities))
263 for expected_activity in expected_activities:
264 added_activity = new_activities.pop(0)
265- self.assertEqual(
266- expected_activity['person'], added_activity.person)
267- self.assertEqual(
268- expected_activity['whatchanged'],
269- added_activity.whatchanged)
270- self.assertEqual(
271- expected_activity.get('oldvalue'),
272- added_activity.oldvalue)
273- self.assertEqual(
274- expected_activity.get('newvalue'),
275- added_activity.newvalue)
276- self.assertEqual(
277- expected_activity.get('message'), added_activity.message)
278+ self.assertThat(added_activity, MatchesStructure.byEquality(
279+ person=expected_activity['person'],
280+ whatchanged=expected_activity['whatchanged'],
281+ oldvalue=expected_activity.get('oldvalue'),
282+ newvalue=expected_activity.get('newvalue'),
283+ message=expected_activity.get('message')))
284
285 if expected_notification is None:
286 self.assertEqual(0, len(new_notifications))
287@@ -607,6 +605,61 @@
288 expected_notification=visibility_change_notification,
289 bug=private_bug)
290
291+ def test_change_information_type(self):
292+ # Changing the information type of a bug adds items to the activity
293+ # log and notifications.
294+ bug = self.factory.makeBug()
295+ self.saveOldChanges(bug=bug)
296+ feature_flag = {
297+ 'disclosure.show_information_type_in_ui.enabled': 'on'}
298+ with FeatureFixture(feature_flag):
299+ bug.transitionToInformationType(
300+ InformationType.EMBARGOEDSECURITY, self.user)
301+
302+ information_type_change_activity = {
303+ 'person': self.user,
304+ 'whatchanged': 'information type',
305+ 'oldvalue': 'Public',
306+ 'newvalue': 'Embargoed Security',
307+ }
308+ information_type_change_notification = {
309+ 'text': '** Information type changed from Public to Embargoed '
310+ 'Security',
311+ 'person': self.user,
312+ }
313+ self.assertRecordedChange(
314+ expected_activity=information_type_change_activity,
315+ expected_notification=information_type_change_notification,
316+ bug=bug)
317+
318+ def test_change_information_type_userdata_as_private(self):
319+ # Changing the information type of a bug to User Data with the
320+ # display_userdata_as_private flag enabled adds the change as
321+ # 'Private' to the activity log and notifications.
322+ bug = self.factory.makeBug()
323+ self.saveOldChanges(bug=bug)
324+ feature_flags = {
325+ 'disclosure.show_information_type_in_ui.enabled': 'on',
326+ 'disclosure.display_userdata_as_private.enabled': 'on'}
327+ with FeatureFixture(feature_flags):
328+ bug.transitionToInformationType(
329+ InformationType.USERDATA, self.user)
330+
331+ information_type_change_activity = {
332+ 'person': self.user,
333+ 'whatchanged': 'information type',
334+ 'oldvalue': 'Public',
335+ 'newvalue': 'Private',
336+ }
337+ information_type_change_notification = {
338+ 'text': '** Information type changed from Public to Private',
339+ 'person': self.user,
340+ }
341+ self.assertRecordedChange(
342+ expected_activity=information_type_change_activity,
343+ expected_notification=information_type_change_notification,
344+ bug=bug)
345+
346 def test_tags_added(self):
347 # Adding tags to a bug will add BugActivity and BugNotification
348 # entries.