Merge lp:~stevenk/launchpad/db-merge-stable-with-feeling into lp:launchpad/db-devel
- db-merge-stable-with-feeling
- Merge into db-devel
Proposed by
Steve Kowalik
Status: | Merged |
---|---|
Approved by: | Steve Kowalik |
Approved revision: | no longer in the source branch. |
Merged at revision: | 11866 |
Proposed branch: | lp:~stevenk/launchpad/db-merge-stable-with-feeling |
Merge into: | lp:launchpad/db-devel |
Diff against target: |
2267 lines (+358/-723) 42 files modified
lib/lp/app/browser/lazrjs.py (+1/-4) lib/lp/app/browser/tests/test_inlineeditpickerwidget.py (+1/-11) lib/lp/app/browser/tests/test_vocabulary.py (+11/-17) lib/lp/app/javascript/picker/picker_patcher.js (+8/-13) lib/lp/app/widgets/popup.py (+1/-9) lib/lp/app/widgets/tests/test_popup.py (+2/-13) lib/lp/bugs/browser/bug.py (+2/-1) lib/lp/bugs/browser/tests/test_bug_views.py (+2/-0) lib/lp/bugs/browser/tests/test_bugtask.py (+4/-2) lib/lp/bugs/doc/bug.txt (+2/-0) lib/lp/bugs/doc/bugsubscription.txt (+6/-0) lib/lp/bugs/javascript/filebug.js (+1/-1) lib/lp/bugs/javascript/tests/test_filebug.js (+2/-2) lib/lp/bugs/mail/tests/test_commands.py (+1/-0) lib/lp/bugs/model/bug.py (+4/-16) lib/lp/bugs/model/bugtask.py (+5/-8) lib/lp/bugs/model/tests/test_bug.py (+2/-2) lib/lp/bugs/model/tests/test_bugsummary.py (+3/-6) lib/lp/bugs/model/tests/test_bugtask.py (+4/-1) lib/lp/bugs/model/tests/test_bugtasksearch.py (+0/-3) lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt (+1/-0) lib/lp/code/browser/tests/test_branch.py (+3/-1) lib/lp/code/model/branch.py (+4/-8) lib/lp/code/model/tests/test_branchjob.py (+18/-0) lib/lp/code/model/tests/test_branchnamespace.py (+0/-6) lib/lp/registry/browser/distribution.py (+1/-7) lib/lp/registry/browser/pillar.py (+0/-27) lib/lp/registry/browser/product.py (+1/-7) lib/lp/registry/browser/tests/test_pillar_sharing.py (+128/-217) lib/lp/registry/javascript/sharing/pillarsharingview.js (+3/-19) lib/lp/registry/javascript/sharing/sharingdetailsview.js (+1/-15) lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js (+0/-19) lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js (+0/-11) lib/lp/registry/model/accesspolicy.py (+9/-4) lib/lp/registry/model/teammembership.py (+5/-8) lib/lp/registry/services/sharingservice.py (+4/-28) lib/lp/registry/services/tests/test_sharingservice.py (+97/-200) lib/lp/registry/tests/test_accesspolicy.py (+17/-0) lib/lp/registry/tests/test_teammembership.py (+1/-2) lib/lp/services/features/flags.py (+0/-34) lib/lp/testing/factory.py (+2/-0) versions.cfg (+1/-1) |
To merge this branch: | bzr merge lp:~stevenk/launchpad/db-merge-stable-with-feeling |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Steve Kowalik (community) | code | Approve | |
Review via email: mp+121752@code.launchpad.net |
Commit message
Merge stable r15871.
Description of the change
Merge stable r15871.
To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/app/browser/lazrjs.py' |
2 | --- lib/lp/app/browser/lazrjs.py 2012-08-14 01:57:17 +0000 |
3 | +++ lib/lp/app/browser/lazrjs.py 2012-08-29 05:40:35 +0000 |
4 | @@ -40,7 +40,6 @@ |
5 | get_person_picker_entry_metadata, |
6 | vocabulary_filters, |
7 | ) |
8 | -from lp.services.features import getFeatureFlag |
9 | from lp.services.propertycache import cachedproperty |
10 | from lp.services.webapp.interfaces import ILaunchBag |
11 | from lp.services.webapp.publisher import canonical_url |
12 | @@ -423,9 +422,7 @@ |
13 | |
14 | @property |
15 | def show_create_team(self): |
16 | - return (self._show_create_team |
17 | - and getFeatureFlag( |
18 | - "disclosure.add-team-person-picker.enabled")) |
19 | + return self._show_create_team |
20 | |
21 | def getConfig(self): |
22 | config = super(InlinePersonEditPickerWidget, self).getConfig() |
23 | |
24 | === modified file 'lib/lp/app/browser/tests/test_inlineeditpickerwidget.py' |
25 | --- lib/lp/app/browser/tests/test_inlineeditpickerwidget.py 2012-06-21 06:50:10 +0000 |
26 | +++ lib/lp/app/browser/tests/test_inlineeditpickerwidget.py 2012-08-29 05:40:35 +0000 |
27 | @@ -15,7 +15,6 @@ |
28 | InlineEditPickerWidget, |
29 | InlinePersonEditPickerWidget, |
30 | ) |
31 | -from lp.services.features.testing import FeatureFixture |
32 | from lp.testing import ( |
33 | login_person, |
34 | TestCaseWithFactory, |
35 | @@ -118,18 +117,9 @@ |
36 | login_person(self.factory.makePerson()) |
37 | self.assertFalse(widget.config['show_assign_me_button']) |
38 | |
39 | - def test_show_create_team_link_with_feature_flag(self): |
40 | - with FeatureFixture( |
41 | - {'disclosure.add-team-person-picker.enabled': 'true'}): |
42 | - widget = self.getWidget( |
43 | - None, vocabulary='ValidPersonOrTeam', required=True, |
44 | - show_create_team=True) |
45 | - login_person(self.factory.makePerson()) |
46 | - self.assertTrue(widget.config['show_create_team']) |
47 | - |
48 | def test_show_create_team_link(self): |
49 | widget = self.getWidget( |
50 | None, vocabulary='ValidPersonOrTeam', required=True, |
51 | show_create_team=True) |
52 | login_person(self.factory.makePerson()) |
53 | - self.assertFalse(widget.config['show_create_team']) |
54 | + self.assertTrue(widget.config['show_create_team']) |
55 | |
56 | === modified file 'lib/lp/app/browser/tests/test_vocabulary.py' |
57 | --- lib/lp/app/browser/tests/test_vocabulary.py 2012-08-13 19:34:10 +0000 |
58 | +++ lib/lp/app/browser/tests/test_vocabulary.py 2012-08-29 05:40:35 +0000 |
59 | @@ -135,40 +135,35 @@ |
60 | self.assertEqual('sprite person', entry.css) |
61 | self.assertEqual('sprite new-window', entry.link_css) |
62 | |
63 | - def test_PersonPickerEntrySourceAdapter_enhanced_picker_user(self): |
64 | - # The enhanced person picker provides more information for users. |
65 | + def test_PersonPickerEntrySourceAdapter_user(self): |
66 | + # The person picker provides more information for users. |
67 | person = self.factory.makePerson(email='snarf@eg.dom', name='snarf') |
68 | creation_date = datetime( |
69 | 2005, 01, 30, 0, 0, 0, 0, pytz.timezone('UTC')) |
70 | removeSecurityProxy(person).datecreated = creation_date |
71 | getUtility(IIrcIDSet).new(person, 'eg.dom', 'snarf') |
72 | getUtility(IIrcIDSet).new(person, 'ex.dom', 'pting') |
73 | - entry = get_picker_entry( |
74 | - person, None, enhanced_picker_enabled=True, |
75 | - picker_expander_enabled=True) |
76 | + entry = get_picker_entry(person, None, picker_expander_enabled=True) |
77 | self.assertEqual('http://launchpad.dev/~snarf', entry.alt_title_link) |
78 | self.assertEqual( |
79 | ['snarf on eg.dom, pting on ex.dom', 'Member since 2005-01-30'], |
80 | entry.details) |
81 | |
82 | - def test_PersonPickerEntrySourceAdapter_enhanced_picker_team(self): |
83 | - # The enhanced person picker provides more information for teams. |
84 | + def test_PersonPickerEntrySourceAdapter_team(self): |
85 | + # The person picker provides more information for teams. |
86 | team = self.factory.makeTeam(email='fnord@eg.dom', name='fnord') |
87 | - entry = get_picker_entry( |
88 | - team, None, enhanced_picker_enabled=True, |
89 | - picker_expander_enabled=True) |
90 | + entry = get_picker_entry(team, None, picker_expander_enabled=True) |
91 | self.assertEqual('http://launchpad.dev/~fnord', entry.alt_title_link) |
92 | self.assertEqual(['Team members: 1'], entry.details) |
93 | |
94 | - def test_PersonPickerEntryAdapter_enhanced_picker_enabled_badges(self): |
95 | - # The enhanced person picker provides affiliation information. |
96 | + def test_PersonPickerEntryAdapter_badges(self): |
97 | + # The person picker provides affiliation information. |
98 | person = self.factory.makePerson(email='snarf@eg.dom', name='snarf') |
99 | project = self.factory.makeProduct( |
100 | name='fnord', owner=person, bug_supervisor=person) |
101 | bugtask = self.factory.makeBugTask(target=project) |
102 | entry = get_picker_entry( |
103 | - person, bugtask, enhanced_picker_enabled=True, |
104 | - picker_expander_enabled=True, |
105 | + person, bugtask, picker_expander_enabled=True, |
106 | personpicker_affiliation_enabled=True) |
107 | self.assertEqual(3, len(entry.badges)) |
108 | self.assertEqual('/@@/product-badge', entry.badges[0]['url']) |
109 | @@ -182,13 +177,12 @@ |
110 | self.assertEqual('bug supervisor', entry.badges[2]['role']) |
111 | |
112 | def test_PersonPickerEntryAdapter_badges_without_IHasAffiliation(self): |
113 | - # The enhanced person picker handles objects that do not support |
114 | + # The person picker handles objects that do not support |
115 | # IHasAffilliation. |
116 | person = self.factory.makePerson(email='snarf@eg.dom', name='snarf') |
117 | thing = object() |
118 | entry = get_picker_entry( |
119 | - person, thing, enhanced_picker_enabled=True, |
120 | - picker_expander_enabled=True, |
121 | + person, thing, picker_expander_enabled=True, |
122 | personpicker_affiliation_enabled=True) |
123 | self.assertIsNot(None, entry) |
124 | |
125 | |
126 | === modified file 'lib/lp/app/javascript/picker/picker_patcher.js' |
127 | --- lib/lp/app/javascript/picker/picker_patcher.js 2012-07-07 14:00:30 +0000 |
128 | +++ lib/lp/app/javascript/picker/picker_patcher.js 2012-08-29 05:40:35 +0000 |
129 | @@ -25,19 +25,14 @@ |
130 | return; |
131 | } |
132 | var picker_span = show_widget_node.get('parentNode'); |
133 | - if (config.enhanced_picker) { |
134 | - var new_node = Y.Node.create('<span>(<a href="#"></a>)</span>'); |
135 | - show_widget_node = new_node.one('a'); |
136 | - show_widget_node |
137 | - .set('id', show_widget_id) |
138 | - .addClass('js-action') |
139 | - .set('text', 'Choose\u2026'); |
140 | - picker_span.empty(); |
141 | - picker_span.appendChild(new_node); |
142 | - } else { |
143 | - show_widget_node.set('text', 'Choose\u2026'); |
144 | - show_widget_node.addClass('js-action'); |
145 | - } |
146 | + var new_node = Y.Node.create('<span>(<a href="#"></a>)</span>'); |
147 | + show_widget_node = new_node.one('a'); |
148 | + show_widget_node |
149 | + .set('id', show_widget_id) |
150 | + .addClass('js-action') |
151 | + .set('text', 'Choose\u2026'); |
152 | + picker_span.empty(); |
153 | + picker_span.appendChild(new_node); |
154 | picker_span.removeClass('hidden'); |
155 | show_widget_node.on('click', function (e) { |
156 | if (picker === null) { |
157 | |
158 | === modified file 'lib/lp/app/widgets/popup.py' |
159 | --- lib/lp/app/widgets/popup.py 2012-07-07 14:00:30 +0000 |
160 | +++ lib/lp/app/widgets/popup.py 2012-08-29 05:40:35 +0000 |
161 | @@ -23,7 +23,6 @@ |
162 | get_person_picker_entry_metadata, |
163 | vocabulary_filters, |
164 | ) |
165 | -from lp.services.features import getFeatureFlag |
166 | from lp.services.propertycache import cachedproperty |
167 | from lp.services.webapp import canonical_url |
168 | |
169 | @@ -58,12 +57,6 @@ |
170 | # Defaults to self.vocabulary.displayname. |
171 | header = None |
172 | |
173 | - @property |
174 | - def enhanced_picker(self): |
175 | - flag = getFeatureFlag( |
176 | - "disclosure.add-team-person-picker.enabled") |
177 | - return flag and self.show_create_team_link |
178 | - |
179 | @cachedproperty |
180 | def matches(self): |
181 | """Return a list of matches (as ITokenizedTerm) to whatever the |
182 | @@ -152,8 +145,7 @@ |
183 | vocabulary_filters=self.vocabulary_filters, |
184 | input_element=self.input_id, |
185 | show_widget_id=self.show_widget_id, |
186 | - enhanced_picker=self.enhanced_picker, |
187 | - show_create_team=self.enhanced_picker) |
188 | + show_create_team=self.show_create_team_link) |
189 | |
190 | @property |
191 | def json_config(self): |
192 | |
193 | === modified file 'lib/lp/app/widgets/tests/test_popup.py' |
194 | --- lib/lp/app/widgets/tests/test_popup.py 2012-06-28 01:27:06 +0000 |
195 | +++ lib/lp/app/widgets/tests/test_popup.py 2012-08-29 05:40:35 +0000 |
196 | @@ -13,7 +13,6 @@ |
197 | PersonPickerWidget, |
198 | VocabularyPickerWidget, |
199 | ) |
200 | -from lp.services.features.testing import FeatureFixture |
201 | from lp.services.webapp.servers import LaunchpadTestRequest |
202 | from lp.testing import TestCaseWithFactory |
203 | from lp.testing.layers import DatabaseFunctionalLayer |
204 | @@ -170,24 +169,14 @@ |
205 | self.assertFalse(person_picker_widget.config['show_remove_button']) |
206 | |
207 | def test_create_team_link(self): |
208 | - # The person picker widget shows a create team link if the feature flag |
209 | - # is on. |
210 | + # The person picker widget shows a create team link. |
211 | field = ITest['test_valid.item'] |
212 | bound_field = field.bind(self.context) |
213 | |
214 | - with FeatureFixture( |
215 | - {'disclosure.add-team-person-picker.enabled': 'true'}): |
216 | - picker_widget = PersonPickerWidget( |
217 | - bound_field, self.vocabulary, self.request) |
218 | - picker_widget.show_create_team_link = True |
219 | - self.assertTrue(picker_widget.config['show_create_team']) |
220 | - self.assertTrue(picker_widget.config['enhanced_picker']) |
221 | - |
222 | picker_widget = PersonPickerWidget( |
223 | bound_field, self.vocabulary, self.request) |
224 | picker_widget.show_create_team_link = True |
225 | - self.assertFalse(picker_widget.config['show_create_team']) |
226 | - self.assertFalse(picker_widget.config['enhanced_picker']) |
227 | + self.assertTrue(picker_widget.config['show_create_team']) |
228 | |
229 | def test_widget_personvalue_meta(self): |
230 | # The person picker has the correct meta value for a person value. |
231 | |
232 | === modified file 'lib/lp/bugs/browser/bug.py' |
233 | --- lib/lp/bugs/browser/bug.py 2012-08-24 12:49:00 +0000 |
234 | +++ lib/lp/bugs/browser/bug.py 2012-08-29 05:40:35 +0000 |
235 | @@ -2,7 +2,6 @@ |
236 | # GNU Affero General Public License version 3 (see the file LICENSE). |
237 | |
238 | """IBug related view classes.""" |
239 | -from lp.app.interfaces.services import IService |
240 | |
241 | __metaclass__ = type |
242 | |
243 | @@ -72,6 +71,7 @@ |
244 | LaunchpadFormView, |
245 | ) |
246 | from lp.app.errors import NotFoundError |
247 | +from lp.app.interfaces.services import IService |
248 | from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription |
249 | from lp.app.widgets.product import GhostCheckBoxWidget |
250 | from lp.app.widgets.project import ProjectScopeWidget |
251 | @@ -881,6 +881,7 @@ |
252 | if self.request.is_ajax: |
253 | validate_change = data.get('validate_change', False) |
254 | if (validate_change and |
255 | + information_type in PRIVATE_INFORMATION_TYPES and |
256 | self._bug_will_be_invisible(information_type)): |
257 | self.request.response.setStatus(400, "Bug Visibility") |
258 | return '' |
259 | |
260 | === modified file 'lib/lp/bugs/browser/tests/test_bug_views.py' |
261 | --- lib/lp/bugs/browser/tests/test_bug_views.py 2012-08-24 12:49:00 +0000 |
262 | +++ lib/lp/bugs/browser/tests/test_bug_views.py 2012-08-29 05:40:35 +0000 |
263 | @@ -97,6 +97,7 @@ |
264 | # We expect that only the Also Affects Project link is disallowed. |
265 | distro = self.factory.makeDistribution() |
266 | owner = self.factory.makePerson() |
267 | + self.factory.makeAccessPolicy(pillar=distro) |
268 | bug = self.factory.makeBug( |
269 | target=distro, |
270 | information_type=InformationType.PROPRIETARY, owner=owner) |
271 | @@ -402,6 +403,7 @@ |
272 | # bug will become invisible but and no visibility check is performed. |
273 | product = self.factory.makeProduct( |
274 | bug_sharing_policy=BugSharingPolicy.PUBLIC_OR_PROPRIETARY) |
275 | + self.factory.makeAccessPolicy(pillar=product) |
276 | bug = self.factory.makeBug(target=product) |
277 | self._assert_secrecy_view_ajax_render(bug, 'PROPRIETARY', False) |
278 | |
279 | |
280 | === modified file 'lib/lp/bugs/browser/tests/test_bugtask.py' |
281 | --- lib/lp/bugs/browser/tests/test_bugtask.py 2012-08-21 20:41:05 +0000 |
282 | +++ lib/lp/bugs/browser/tests/test_bugtask.py 2012-08-29 05:40:35 +0000 |
283 | @@ -140,12 +140,12 @@ |
284 | self.getUserBrowser(url, person_no_teams) |
285 | # This may seem large: it is; there is easily another 30% fat in |
286 | # there. |
287 | - # If this test is run in isolation, the query count is 89. |
288 | + # If this test is run in isolation, the query count is 94. |
289 | # Other tests in this TestCase could cache the |
290 | # "SELECT id, product, project, distribution FROM PillarName ..." |
291 | # query by previously browsing the task url, in which case the |
292 | # query count is decreased by one. |
293 | - self.assertThat(recorder, HasQueryCount(LessThan(94))) |
294 | + self.assertThat(recorder, HasQueryCount(LessThan(95))) |
295 | count_with_no_teams = recorder.count |
296 | # count with many teams |
297 | self.invalidate_caches(task) |
298 | @@ -1120,6 +1120,7 @@ |
299 | # could affect another package. |
300 | distro = self.factory.makeDistribution() |
301 | owner = self.factory.makePerson() |
302 | + self.factory.makeAccessPolicy(pillar=distro) |
303 | bug = self.factory.makeBug( |
304 | target=distro, owner=owner, |
305 | information_type=InformationType.PROPRIETARY) |
306 | @@ -1140,6 +1141,7 @@ |
307 | sp = self.factory.makeSourcePackage( |
308 | sourcepackagename=sp_name, distroseries=distroseries) |
309 | owner = self.factory.makePerson() |
310 | + self.factory.makeAccessPolicy(pillar=distro) |
311 | bug = self.factory.makeBug( |
312 | target=sp.distribution_sourcepackage, owner=owner, |
313 | information_type=InformationType.PROPRIETARY) |
314 | |
315 | === modified file 'lib/lp/bugs/doc/bug.txt' |
316 | --- lib/lp/bugs/doc/bug.txt 2012-08-08 11:48:29 +0000 |
317 | +++ lib/lp/bugs/doc/bug.txt 2012-08-29 05:40:35 +0000 |
318 | @@ -662,6 +662,7 @@ |
319 | ... comment="a description of the bug", |
320 | ... owner=current_user()) |
321 | >>> private_bug = firefox.createBug(params) |
322 | + >>> ignored = factory.makeAccessPolicy(pillar=firefox) |
323 | >>> private_bug.transitionToInformationType( |
324 | ... InformationType.PROPRIETARY, current_user()) |
325 | True |
326 | @@ -730,6 +731,7 @@ |
327 | We cannot add distro series or source package tasks for different distros. |
328 | |
329 | >>> private_bug = tubuntu.createBug(params) |
330 | + >>> ignored = factory.makeAccessPolicy(pillar=tubuntu) |
331 | >>> private_bug.transitionToInformationType( |
332 | ... InformationType.PROPRIETARY, current_user()) |
333 | True |
334 | |
335 | === modified file 'lib/lp/bugs/doc/bugsubscription.txt' |
336 | --- lib/lp/bugs/doc/bugsubscription.txt 2012-08-22 23:02:40 +0000 |
337 | +++ lib/lp/bugs/doc/bugsubscription.txt 2012-08-29 05:40:35 +0000 |
338 | @@ -364,6 +364,7 @@ |
339 | |
340 | >>> print_displayname(linux_source_bug.getDirectSubscribers()) |
341 | Foo Bar |
342 | + Mark Shuttleworth |
343 | Robert Collins |
344 | |
345 | Direct subscriptions always take precedence over indirect subscriptions. |
346 | @@ -375,6 +376,7 @@ |
347 | |
348 | >>> print_displayname(linux_source_bug.getDirectSubscribers()) |
349 | Foo Bar |
350 | + Mark Shuttleworth |
351 | Robert Collins |
352 | |
353 | >>> print_displayname(linux_source_bug.getIndirectSubscribers()) |
354 | @@ -398,6 +400,7 @@ |
355 | >>> addresses = recipients.getEmails() |
356 | >>> [(address, recipients.getReason(address)[1]) for address in addresses] |
357 | [('foo.bar@canonical.com', 'Subscriber'), |
358 | + ('mark@example.com', 'Subscriber'), |
359 | ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'), |
360 | ('robertc@robertcollins.net', 'Subscriber'), |
361 | ('test@canonical.com', 'Assignee')] |
362 | @@ -411,6 +414,7 @@ |
363 | >>> addresses = recipients.getEmails() |
364 | >>> [(address, recipients.getReason(address)[1]) for address in addresses] |
365 | [('foo.bar@canonical.com', 'Subscriber'), |
366 | + ('mark@example.com', 'Subscriber'), |
367 | ('robertc@robertcollins.net', 'Subscriber'), |
368 | ('test@canonical.com', 'Assignee')] |
369 | |
370 | @@ -424,6 +428,7 @@ |
371 | >>> addresses = recipients.getEmails() |
372 | >>> [(address, recipients.getReason(address)[1]) for address in addresses] |
373 | [('foo.bar@canonical.com', 'Subscriber'), |
374 | + ('mark@example.com', 'Subscriber'), |
375 | ('robertc@robertcollins.net', 'Subscriber'), |
376 | ('test@canonical.com', 'Assignee')] |
377 | |
378 | @@ -435,6 +440,7 @@ |
379 | >>> addresses = recipients.getEmails() |
380 | >>> [(address, recipients.getReason(address)[1]) for address in addresses] |
381 | [('foo.bar@canonical.com', 'Subscriber'), |
382 | + ('mark@example.com', 'Subscriber'), |
383 | ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'), |
384 | ('robertc@robertcollins.net', 'Subscriber'), |
385 | ('test@canonical.com', 'Assignee')] |
386 | |
387 | === modified file 'lib/lp/bugs/javascript/filebug.js' |
388 | --- lib/lp/bugs/javascript/filebug.js 2012-08-21 13:27:00 +0000 |
389 | +++ lib/lp/bugs/javascript/filebug.js 2012-08-29 05:40:35 +0000 |
390 | @@ -69,7 +69,7 @@ |
391 | Y.Array.forEach(LP.cache.information_type_data, function(item) { |
392 | info_type_descriptions[item.value] = item.name; |
393 | }); |
394 | - var text_template = "This report has {info_type} information." + |
395 | + var text_template = "This report contains {info_type} information." + |
396 | " You can change the information type later."; |
397 | value = info_type_descriptions[value]; |
398 | return Y.Lang.substitute(text_template, {'info_type': value}); |
399 | |
400 | === modified file 'lib/lp/bugs/javascript/tests/test_filebug.js' |
401 | --- lib/lp/bugs/javascript/tests/test_filebug.js 2012-07-19 03:18:37 +0000 |
402 | +++ lib/lp/bugs/javascript/tests/test_filebug.js 2012-08-29 05:40:35 +0000 |
403 | @@ -91,7 +91,7 @@ |
404 | Y.Assert.isNull(banner_hidden); |
405 | var banner_text = Y.one('.banner-text').get('text'); |
406 | Y.Assert.areEqual( |
407 | - 'This report has Private Security information. ' + |
408 | + 'This report contains Private Security information. ' + |
409 | 'You can change the information type later.', banner_text); |
410 | }, |
411 | |
412 | @@ -281,7 +281,7 @@ |
413 | Y.Assert.isNull(banner_hidden); |
414 | var banner_text = Y.one('.banner-text').get('text'); |
415 | Y.Assert.areEqual( |
416 | - 'This report has Private information. ' + |
417 | + 'This report contains Private information. ' + |
418 | 'You can change the information type later.', banner_text); |
419 | }, |
420 | |
421 | |
422 | === modified file 'lib/lp/bugs/mail/tests/test_commands.py' |
423 | --- lib/lp/bugs/mail/tests/test_commands.py 2012-08-21 19:19:32 +0000 |
424 | +++ lib/lp/bugs/mail/tests/test_commands.py 2012-08-29 05:40:35 +0000 |
425 | @@ -287,6 +287,7 @@ |
426 | # Test that attempts to invalidly add a new bug task results in the |
427 | # expected error message. |
428 | product = self.factory.makeProduct() |
429 | + self.factory.makeAccessPolicy(pillar=product) |
430 | bug = self.factory.makeBug( |
431 | target=product, information_type=InformationType.PROPRIETARY) |
432 | self.factory.makeProduct(name='fnord') |
433 | |
434 | === modified file 'lib/lp/bugs/model/bug.py' |
435 | --- lib/lp/bugs/model/bug.py 2012-08-23 04:20:48 +0000 |
436 | +++ lib/lp/bugs/model/bug.py 2012-08-29 05:40:35 +0000 |
437 | @@ -200,7 +200,6 @@ |
438 | sqlvalues, |
439 | ) |
440 | from lp.services.database.stormbase import StormBase |
441 | -from lp.services.features import getFeatureFlag |
442 | from lp.services.fields import DuplicateBug |
443 | from lp.services.helpers import shortlist |
444 | from lp.services.librarian.interfaces import ILibraryFileAliasSet |
445 | @@ -1766,18 +1765,10 @@ |
446 | if pillar.driver in subscribers and pillar != ubuntu: |
447 | required_subscribers.add(pillar.driver) |
448 | service = getUtility(IService, 'sharing') |
449 | - subscribers_to_remove = set(service.getPeopleWithoutAccess( |
450 | - self, subscribers)).difference(required_subscribers) |
451 | if len(required_subscribers): |
452 | service.ensureAccessGrants( |
453 | required_subscribers, who, bugs=[self], |
454 | ignore_permissions=True) |
455 | - # There is a job to do the unsubscribe, but it's behind a |
456 | - # flag. If that flag is not set, do it manually. |
457 | - if len(subscribers_to_remove) and not bool( |
458 | - getFeatureFlag('disclosure.unsubscribe_jobs.enabled')): |
459 | - for s in subscribers_to_remove: |
460 | - self.unsubscribe(s, who, ignore_permissions=True) |
461 | |
462 | # Add the required subscribers, but not if they are all already |
463 | # subscribed via a team. |
464 | @@ -1788,13 +1779,10 @@ |
465 | |
466 | self.updateHeat() |
467 | |
468 | - flag = 'disclosure.unsubscribe_jobs.enabled' |
469 | - if bool(getFeatureFlag(flag)): |
470 | - # As a result of the transition, some subscribers may no longer |
471 | - # have access to the bug. We need to run a job to remove any such |
472 | - # subscriptions. |
473 | - getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
474 | - who, [self]) |
475 | + # As a result of the transition, some subscribers may no longer |
476 | + # have access to the bug. We need to run a job to remove any such |
477 | + # subscriptions. |
478 | + getUtility(IRemoveArtifactSubscriptionsJobSource).create(who, [self]) |
479 | |
480 | return True |
481 | |
482 | |
483 | === modified file 'lib/lp/bugs/model/bugtask.py' |
484 | --- lib/lp/bugs/model/bugtask.py 2012-08-24 01:17:35 +0000 |
485 | +++ lib/lp/bugs/model/bugtask.py 2012-08-29 05:40:35 +0000 |
486 | @@ -141,7 +141,6 @@ |
487 | SQLBase, |
488 | sqlvalues, |
489 | ) |
490 | -from lp.services.features import getFeatureFlag |
491 | from lp.services.helpers import shortlist |
492 | from lp.services.propertycache import get_property_cache |
493 | from lp.services.searchbuilder import any |
494 | @@ -1140,13 +1139,11 @@ |
495 | self.maybeConfirm() |
496 | # END TEMPORARY BIT FOR BUGTASK AUTOCONFIRM FEATURE FLAG. |
497 | |
498 | - flag = 'disclosure.unsubscribe_jobs.enabled' |
499 | - if bool(getFeatureFlag(flag)): |
500 | - # As a result of the transition, some subscribers may no longer |
501 | - # have access to the parent bug. We need to run a job to remove any |
502 | - # such subscriptions. |
503 | - getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
504 | - user, [self.bug], pillar=target_before_change.pillar) |
505 | + # As a result of the transition, some subscribers may no longer |
506 | + # have access to the parent bug. We need to run a job to remove any |
507 | + # such subscriptions. |
508 | + getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
509 | + user, [self.bug], pillar=target_before_change.pillar) |
510 | |
511 | def updateTargetNameCache(self, newtarget=None): |
512 | """See `IBugTask`.""" |
513 | |
514 | === modified file 'lib/lp/bugs/model/tests/test_bug.py' |
515 | --- lib/lp/bugs/model/tests/test_bug.py 2012-08-23 04:20:48 +0000 |
516 | +++ lib/lp/bugs/model/tests/test_bug.py 2012-08-29 05:40:35 +0000 |
517 | @@ -639,7 +639,7 @@ |
518 | who = self.factory.makePerson(name='who') |
519 | bug.transitionToInformationType( |
520 | InformationType.PRIVATESECURITY, who=who) |
521 | - subscribers = bug.getDirectSubscribers() |
522 | + subscribers = bug.getDirectSubscribers(filter_visible=True) |
523 | expected_subscribers = set(( |
524 | default_bugtask.pillar.driver, bug_owner, who)) |
525 | self.assertContentEqual(expected_subscribers, subscribers) |
526 | @@ -662,7 +662,7 @@ |
527 | bug.subscribe(subscriber, bug_owner) |
528 | who = self.factory.makePerson(name='who') |
529 | bug.transitionToInformationType(InformationType.USERDATA, who) |
530 | - subscribers = bug.getDirectSubscribers() |
531 | + subscribers = bug.getDirectSubscribers(filter_visible=True) |
532 | expected_subscribers = set(( |
533 | default_bugtask.pillar.bug_supervisor, |
534 | default_bugtask.pillar.driver, |
535 | |
536 | === modified file 'lib/lp/bugs/model/tests/test_bugsummary.py' |
537 | --- lib/lp/bugs/model/tests/test_bugsummary.py 2012-08-08 11:48:29 +0000 |
538 | +++ lib/lp/bugs/model/tests/test_bugsummary.py 2012-08-29 05:40:35 +0000 |
539 | @@ -28,7 +28,6 @@ |
540 | SharingPermission, |
541 | ) |
542 | from lp.services.database.lpstorm import IMasterStore |
543 | -from lp.services.features.testing import FeatureFixture |
544 | from lp.testing import TestCaseWithFactory |
545 | from lp.testing.dbuser import switch_dbuser |
546 | from lp.testing.layers import LaunchpadZopelessLayer |
547 | @@ -192,11 +191,9 @@ |
548 | person_b = self.factory.makePerson() |
549 | person_c = self.factory.makePerson() |
550 | product = self.factory.makeProduct() |
551 | - with FeatureFixture( |
552 | - {'disclosure.enhanced_sharing.writable': 'true'}): |
553 | - getUtility(IService, 'sharing').sharePillarInformation( |
554 | - product, person_c, product.owner, |
555 | - {InformationType.USERDATA: SharingPermission.ALL}) |
556 | + getUtility(IService, 'sharing').sharePillarInformation( |
557 | + product, person_c, product.owner, |
558 | + {InformationType.USERDATA: SharingPermission.ALL}) |
559 | bug = self.factory.makeBug(target=product, owner=person_b) |
560 | |
561 | bug.subscribe(person=person_a, subscribed_by=person_a) |
562 | |
563 | === modified file 'lib/lp/bugs/model/tests/test_bugtask.py' |
564 | --- lib/lp/bugs/model/tests/test_bugtask.py 2012-08-21 15:13:25 +0000 |
565 | +++ lib/lp/bugs/model/tests/test_bugtask.py 2012-08-29 05:40:35 +0000 |
566 | @@ -2678,7 +2678,6 @@ |
567 | |
568 | def setUp(self): |
569 | self.useFixture(FeatureFixture({ |
570 | - 'disclosure.unsubscribe_jobs.enabled': 'true', |
571 | 'jobs.celery.enabled_classes': 'RemoveArtifactSubscriptionsJob', |
572 | })) |
573 | super(TestTransitionsRemovesSubscribersJob, self).setUp() |
574 | @@ -2856,6 +2855,7 @@ |
575 | if not self.multi_tenant_test_one_task_only: |
576 | self.factory.makeBugTask(bug=bug) |
577 | p = self.factory.makeProduct() |
578 | + self.factory.makeAccessPolicy(pillar=d) |
579 | with person_logged_in(bug.owner): |
580 | bug.transitionToInformationType( |
581 | InformationType.PROPRIETARY, bug.owner) |
582 | @@ -2877,6 +2877,7 @@ |
583 | bug = self.factory.makeBug(target=p1) |
584 | if not self.multi_tenant_test_one_task_only: |
585 | self.factory.makeBugTask(bug=bug) |
586 | + self.factory.makeAccessPolicy(pillar=p1) |
587 | with person_logged_in(bug.owner): |
588 | bug.transitionToInformationType( |
589 | InformationType.PROPRIETARY, bug.owner) |
590 | @@ -2905,6 +2906,7 @@ |
591 | bug = self.factory.makeBug(target=p1) |
592 | if not self.multi_tenant_test_one_task_only: |
593 | self.factory.makeBugTask(bug=bug) |
594 | + self.factory.makeAccessPolicy(pillar=p1) |
595 | with person_logged_in(bug.owner): |
596 | bug.transitionToInformationType( |
597 | InformationType.PROPRIETARY, bug.owner) |
598 | @@ -2927,6 +2929,7 @@ |
599 | bug = self.factory.makeBug(target=d1) |
600 | if not self.multi_tenant_test_one_task_only: |
601 | self.factory.makeBugTask(bug=bug) |
602 | + self.factory.makeAccessPolicy(pillar=d1) |
603 | with person_logged_in(bug.owner): |
604 | bug.transitionToInformationType( |
605 | InformationType.PROPRIETARY, bug.owner) |
606 | |
607 | === modified file 'lib/lp/bugs/model/tests/test_bugtasksearch.py' |
608 | --- lib/lp/bugs/model/tests/test_bugtasksearch.py 2012-08-16 05:18:54 +0000 |
609 | +++ lib/lp/bugs/model/tests/test_bugtasksearch.py 2012-08-29 05:40:35 +0000 |
610 | @@ -67,7 +67,6 @@ |
611 | from lp.registry.model.person import Person |
612 | from lp.services.database.lpstorm import IStore |
613 | from lp.services.database.sqlbase import convert_storm_clause_to_string |
614 | -from lp.services.features.testing import FeatureFixture |
615 | from lp.services.searchbuilder import ( |
616 | all, |
617 | any, |
618 | @@ -2410,8 +2409,6 @@ |
619 | # People and teams with AccessPolicyGrants can see the bug. |
620 | self.makePrivacyScenario() |
621 | |
622 | - self.useFixture(FeatureFixture( |
623 | - {'disclosure.enhanced_sharing.writable': 'true'})) |
624 | with admin_logged_in(): |
625 | for princ in (self.grantee_team, self.grantee_person): |
626 | getUtility(IService, 'sharing').sharePillarInformation( |
627 | |
628 | === modified file 'lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt' |
629 | --- lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt 2012-07-10 08:12:53 +0000 |
630 | +++ lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt 2012-08-29 05:40:35 +0000 |
631 | @@ -215,6 +215,7 @@ |
632 | >>> login("foo.bar@canonical.com") |
633 | >>> productset = getUtility(IProductSet) |
634 | >>> firefox = productset.get(4) |
635 | + >>> ignored = factory.makeAccessPolicy(pillar=firefox) |
636 | >>> params = CreateBugParams( |
637 | ... title="a test private bug", |
638 | ... comment="a description of the bug", |
639 | |
640 | === modified file 'lib/lp/code/browser/tests/test_branch.py' |
641 | --- lib/lp/code/browser/tests/test_branch.py 2012-08-22 14:23:12 +0000 |
642 | +++ lib/lp/code/browser/tests/test_branch.py 2012-08-29 05:40:35 +0000 |
643 | @@ -1028,8 +1028,10 @@ |
644 | # We don't force branches with a disallowed type (eg. Proprietary on a |
645 | # non-commercial project) to change, so the current type is |
646 | # shown. |
647 | + product = self.factory.makeProduct() |
648 | + self.factory.makeAccessPolicy(pillar=product) |
649 | branch = self.factory.makeBranch( |
650 | - information_type=InformationType.PROPRIETARY) |
651 | + product=product, information_type=InformationType.PROPRIETARY) |
652 | self.assertShownTypes( |
653 | [InformationType.PUBLIC, InformationType.PROPRIETARY], branch) |
654 | |
655 | |
656 | === modified file 'lib/lp/code/model/branch.py' |
657 | --- lib/lp/code/model/branch.py 2012-08-07 02:31:56 +0000 |
658 | +++ lib/lp/code/model/branch.py 2012-08-29 05:40:35 +0000 |
659 | @@ -171,7 +171,6 @@ |
660 | ArrayAgg, |
661 | ArrayIntersects, |
662 | ) |
663 | -from lp.services.features import getFeatureFlag |
664 | from lp.services.helpers import shortlist |
665 | from lp.services.job.interfaces.job import JobStatus |
666 | from lp.services.job.model.job import Job |
667 | @@ -272,13 +271,10 @@ |
668 | service.ensureAccessGrants( |
669 | blind_subscribers, who, branches=[self], |
670 | ignore_permissions=True) |
671 | - flag = 'disclosure.unsubscribe_jobs.enabled' |
672 | - if bool(getFeatureFlag(flag)): |
673 | - # As a result of the transition, some subscribers may no longer |
674 | - # have access to the branch. We need to run a job to remove any |
675 | - # such subscriptions. |
676 | - getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
677 | - who, [self]) |
678 | + # As a result of the transition, some subscribers may no longer |
679 | + # have access to the branch. We need to run a job to remove any |
680 | + # such subscriptions. |
681 | + getUtility(IRemoveArtifactSubscriptionsJobSource).create(who, [self]) |
682 | |
683 | registrant = ForeignKey( |
684 | dbName='registrant', foreignKey='Person', |
685 | |
686 | === modified file 'lib/lp/code/model/tests/test_branchjob.py' |
687 | --- lib/lp/code/model/tests/test_branchjob.py 2012-06-28 01:14:33 +0000 |
688 | +++ lib/lp/code/model/tests/test_branchjob.py 2012-08-29 05:40:35 +0000 |
689 | @@ -65,6 +65,7 @@ |
690 | from lp.code.model.revision import RevisionSet |
691 | from lp.code.model.tests.test_branch import create_knit |
692 | from lp.codehosting.vfs import branch_id_to_path |
693 | +from lp.registry.enums import InformationType |
694 | from lp.scripts.helpers import TransactionFreeOperation |
695 | from lp.services.config import config |
696 | from lp.services.database.constants import UTC_NOW |
697 | @@ -173,6 +174,23 @@ |
698 | |
699 | self.assertEqual(db_branch.revision_count, 5) |
700 | |
701 | + def test_run_with_private_linked_bug(self): |
702 | + """Ensure the job scans a branch with a private bug in the revprops.""" |
703 | + self.useBzrBranches(direct_database=True) |
704 | + db_branch, bzr_tree = self.create_branch_and_tree() |
705 | + product = self.factory.makeProduct() |
706 | + private_bug = self.factory.makeBug( |
707 | + target=product, information_type=InformationType.USERDATA) |
708 | + bug_line = 'https://launchpad.net/bugs/%s fixed' % private_bug.id |
709 | + with override_environ(BZR_EMAIL='me@example.com'): |
710 | + bzr_tree.commit( |
711 | + 'First commit', rev_id='rev1', revprops={'bugs': bug_line}) |
712 | + job = BranchScanJob.create(db_branch) |
713 | + with dbuser(config.branchscanner.dbuser): |
714 | + job.run() |
715 | + self.assertEqual(db_branch.revision_count, 1) |
716 | + self.assertTrue(private_bug.hasBranch(db_branch)) |
717 | + |
718 | |
719 | class TestBranchUpgradeJob(TestCaseWithFactory): |
720 | """Tests for `BranchUpgradeJob`.""" |
721 | |
722 | === modified file 'lib/lp/code/model/tests/test_branchnamespace.py' |
723 | --- lib/lp/code/model/tests/test_branchnamespace.py 2012-08-22 14:23:12 +0000 |
724 | +++ lib/lp/code/model/tests/test_branchnamespace.py 2012-08-29 05:40:35 +0000 |
725 | @@ -57,7 +57,6 @@ |
726 | ) |
727 | from lp.registry.interfaces.product import NoSuchProduct |
728 | from lp.registry.model.sourcepackage import SourcePackage |
729 | -from lp.services.features.testing import FeatureFixture |
730 | from lp.testing import ( |
731 | person_logged_in, |
732 | TestCaseWithFactory, |
733 | @@ -458,11 +457,6 @@ |
734 | |
735 | layer = DatabaseFunctionalLayer |
736 | |
737 | - def setUp(self): |
738 | - super(TestProductNamespacePrivacyWithInformationType, self).setUp() |
739 | - self.useFixture(FeatureFixture( |
740 | - {'disclosure.enhanced_sharing.writable': 'true'})) |
741 | - |
742 | def makeProductNamespace(self, sharing_policy, person=None): |
743 | if person is None: |
744 | person = self.factory.makePerson() |
745 | |
746 | === modified file 'lib/lp/registry/browser/distribution.py' |
747 | --- lib/lp/registry/browser/distribution.py 2012-08-21 00:34:02 +0000 |
748 | +++ lib/lp/registry/browser/distribution.py 2012-08-29 05:40:35 +0000 |
749 | @@ -108,7 +108,6 @@ |
750 | ) |
751 | from lp.registry.interfaces.series import SeriesStatus |
752 | from lp.services.database.decoratedresultset import DecoratedResultSet |
753 | -from lp.services.features import getFeatureFlag |
754 | from lp.services.feeds.browser import FeedsMixin |
755 | from lp.services.geoip.helpers import ( |
756 | ipaddress_from_request, |
757 | @@ -313,12 +312,7 @@ |
758 | |
759 | @enabled_with_permission('launchpad.Driver') |
760 | def sharing(self): |
761 | - text = 'Sharing' |
762 | - enabled_readonly_flag = 'disclosure.enhanced_sharing.enabled' |
763 | - enabled_writable_flag = 'disclosure.enhanced_sharing.writable' |
764 | - enabled = (bool(getFeatureFlag(enabled_readonly_flag)) |
765 | - or bool(getFeatureFlag(enabled_writable_flag))) |
766 | - return Link('+sharing', text, icon='edit', enabled=enabled) |
767 | + return Link('+sharing', 'Sharing', icon='edit') |
768 | |
769 | @cachedproperty |
770 | def links(self): |
771 | |
772 | === modified file 'lib/lp/registry/browser/pillar.py' |
773 | --- lib/lp/registry/browser/pillar.py 2012-08-23 04:42:37 +0000 |
774 | +++ lib/lp/registry/browser/pillar.py 2012-08-29 05:40:35 +0000 |
775 | @@ -31,7 +31,6 @@ |
776 | getVocabularyRegistry, |
777 | SimpleVocabulary, |
778 | ) |
779 | -from zope.security.interfaces import Unauthorized |
780 | from zope.traversing.browser.absoluteurl import absoluteURL |
781 | |
782 | from lp.app.browser.launchpad import iter_view_registrations |
783 | @@ -60,9 +59,7 @@ |
784 | from lp.registry.interfaces.projectgroup import IProjectGroup |
785 | from lp.registry.model.pillar import PillarPerson |
786 | from lp.services.config import config |
787 | -from lp.services.features import getFeatureFlag |
788 | from lp.services.propertycache import cachedproperty |
789 | -from lp.services.webapp.authorization import check_permission |
790 | from lp.services.webapp.batching import ( |
791 | BatchNavigator, |
792 | StormRangeFactory, |
793 | @@ -278,11 +275,6 @@ |
794 | |
795 | sharing_vocabulary_name = 'NewPillarGrantee' |
796 | |
797 | - related_features = ( |
798 | - 'disclosure.enhanced_sharing.enabled', |
799 | - 'disclosure.enhanced_sharing.writable', |
800 | - ) |
801 | - |
802 | _batch_navigator = None |
803 | |
804 | def _getSharingService(self): |
805 | @@ -348,16 +340,7 @@ |
806 | |
807 | def initialize(self): |
808 | super(PillarSharingView, self).initialize() |
809 | - enabled_readonly_flag = 'disclosure.enhanced_sharing.enabled' |
810 | - enabled_writable_flag = ( |
811 | - 'disclosure.enhanced_sharing.writable') |
812 | - enabled = bool(getFeatureFlag(enabled_readonly_flag)) |
813 | - write_flag_enabled = bool(getFeatureFlag(enabled_writable_flag)) |
814 | - if not enabled and not write_flag_enabled: |
815 | - raise Unauthorized("This feature is not yet available.") |
816 | cache = IJSONRequestCache(self.request) |
817 | - cache.objects['sharing_write_enabled'] = (write_flag_enabled |
818 | - and check_permission('launchpad.Edit', self.context)) |
819 | cache.objects['information_types'] = self.information_types |
820 | cache.objects['sharing_permissions'] = self.sharing_permissions |
821 | cache.objects['bug_sharing_policies'] = self.bug_sharing_policies |
822 | @@ -402,11 +385,6 @@ |
823 | label = "Information shared with person or team" |
824 | |
825 | def initialize(self): |
826 | - enabled_flag = 'disclosure.enhanced_sharing_details.enabled' |
827 | - enabled = bool(getFeatureFlag(enabled_flag)) |
828 | - if not enabled: |
829 | - raise Unauthorized("This feature is not yet available.") |
830 | - |
831 | self.pillar = self.context.pillar |
832 | self.person = self.context.person |
833 | |
834 | @@ -431,11 +409,6 @@ |
835 | cache.objects['pillar'] = pillar_data |
836 | cache.objects['bugs'] = bug_data |
837 | cache.objects['branches'] = branch_data |
838 | - enabled_writable_flag = ( |
839 | - 'disclosure.enhanced_sharing.writable') |
840 | - write_flag_enabled = bool(getFeatureFlag(enabled_writable_flag)) |
841 | - cache.objects['sharing_write_enabled'] = (write_flag_enabled |
842 | - and check_permission('launchpad.Edit', self.pillar)) |
843 | |
844 | def _loadSharedArtifacts(self): |
845 | # As a concrete can by linked via more than one policy, we use sets to |
846 | |
847 | === modified file 'lib/lp/registry/browser/product.py' |
848 | --- lib/lp/registry/browser/product.py 2012-08-23 04:42:37 +0000 |
849 | +++ lib/lp/registry/browser/product.py 2012-08-29 05:40:35 +0000 |
850 | @@ -171,7 +171,6 @@ |
851 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
852 | from lp.services.config import config |
853 | from lp.services.database.decoratedresultset import DecoratedResultSet |
854 | -from lp.services.features import getFeatureFlag |
855 | from lp.services.feeds.browser import FeedsMixin |
856 | from lp.services.fields import ( |
857 | PillarAliases, |
858 | @@ -500,12 +499,7 @@ |
859 | |
860 | @enabled_with_permission('launchpad.Driver') |
861 | def sharing(self): |
862 | - text = 'Sharing' |
863 | - enabled_readonly_flag = 'disclosure.enhanced_sharing.enabled' |
864 | - enabled_writable_flag = 'disclosure.enhanced_sharing.writable' |
865 | - enabled = (bool(getFeatureFlag(enabled_readonly_flag)) |
866 | - or bool(getFeatureFlag(enabled_writable_flag))) |
867 | - return Link('+sharing', text, icon='edit', enabled=enabled) |
868 | + return Link('+sharing', 'Sharing', icon='edit') |
869 | |
870 | |
871 | class IProductEditMenu(Interface): |
872 | |
873 | === modified file 'lib/lp/registry/browser/tests/test_pillar_sharing.py' |
874 | --- lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-08-14 04:48:36 +0000 |
875 | +++ lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-08-29 05:40:35 +0000 |
876 | @@ -17,7 +17,6 @@ |
877 | Raises, |
878 | ) |
879 | from zope.component import getUtility |
880 | -from zope.security.interfaces import Unauthorized |
881 | from zope.traversing.browser.absoluteurl import absoluteURL |
882 | |
883 | from lp.app.interfaces.services import IService |
884 | @@ -30,7 +29,6 @@ |
885 | from lp.registry.model.pillar import PillarPerson |
886 | from lp.services.config import config |
887 | from lp.services.database.lpstorm import IStore |
888 | -from lp.services.features.testing import FeatureFixture |
889 | from lp.services.webapp.interfaces import StormRangeFactoryError |
890 | from lp.services.webapp.publisher import canonical_url |
891 | from lp.testing import ( |
892 | @@ -49,14 +47,6 @@ |
893 | ) |
894 | |
895 | |
896 | -DETAILS_ENABLED_FLAG = {'disclosure.enhanced_sharing_details.enabled': 'true'} |
897 | -DETAILS_WRITE_FLAG = { |
898 | - 'disclosure.enhanced_sharing_details.enabled': 'true', |
899 | - 'disclosure.enhanced_sharing.writable': 'true'} |
900 | -ENABLED_FLAG = {'disclosure.enhanced_sharing.enabled': 'true'} |
901 | -WRITE_FLAG = {'disclosure.enhanced_sharing.writable': 'true'} |
902 | - |
903 | - |
904 | class SharingBaseTestCase(TestCaseWithFactory): |
905 | |
906 | layer = DatabaseFunctionalLayer |
907 | @@ -146,124 +136,94 @@ |
908 | # There are bugs in the sharingdetails view that not everyone with |
909 | # `launchpad.Driver` -- the permission level for the page -- should be |
910 | # able to see. |
911 | - with FeatureFixture(DETAILS_ENABLED_FLAG): |
912 | - pillarperson = self.getPillarPerson(security=True) |
913 | - logout() |
914 | - login_person(self.driver) |
915 | - view = create_initialized_view(pillarperson, '+index') |
916 | - # The page loads |
917 | - self.assertEqual(pillarperson.person.displayname, view.page_title) |
918 | - # The bug, which is not shared with the driver, is not included. |
919 | - self.assertEqual(0, view.shared_bugs_count) |
920 | + pillarperson = self.getPillarPerson(security=True) |
921 | + logout() |
922 | + login_person(self.driver) |
923 | + view = create_initialized_view(pillarperson, '+index') |
924 | + # The page loads |
925 | + self.assertEqual(pillarperson.person.displayname, view.page_title) |
926 | + # The bug, which is not shared with the driver, is not included. |
927 | + self.assertEqual(0, view.shared_bugs_count) |
928 | |
929 | def test_view_traverses_plus_sharingdetails(self): |
930 | # The traversed url in the app is pillar/+sharing/person |
931 | - with FeatureFixture(DETAILS_ENABLED_FLAG): |
932 | - # We have to do some fun url hacking to force the traversal a user |
933 | - # encounters. |
934 | - pillarperson = self.getPillarPerson() |
935 | - expected = "Sharing details for %s : Sharing : %s" % ( |
936 | - pillarperson.person.displayname, |
937 | - pillarperson.pillar.displayname) |
938 | - url = 'http://launchpad.dev/%s/+sharing/%s' % ( |
939 | - pillarperson.pillar.name, pillarperson.person.name) |
940 | - browser = self.getUserBrowser(user=self.owner, url=url) |
941 | - self.assertEqual(expected, browser.title) |
942 | + # We have to do some fun url hacking to force the traversal a user |
943 | + # encounters. |
944 | + pillarperson = self.getPillarPerson() |
945 | + expected = "Sharing details for %s : Sharing : %s" % ( |
946 | + pillarperson.person.displayname, |
947 | + pillarperson.pillar.displayname) |
948 | + url = 'http://launchpad.dev/%s/+sharing/%s' % ( |
949 | + pillarperson.pillar.name, pillarperson.person.name) |
950 | + browser = self.getUserBrowser(user=self.owner, url=url) |
951 | + self.assertEqual(expected, browser.title) |
952 | |
953 | def test_no_sharing_message(self): |
954 | # If there is no sharing between pillar and person, a suitable message |
955 | # is displayed. |
956 | - with FeatureFixture(DETAILS_ENABLED_FLAG): |
957 | - # We have to do some fun url hacking to force the traversal a user |
958 | - # encounters. |
959 | - pillarperson = PillarPerson( |
960 | - self.pillar, self.factory.makePerson()) |
961 | - url = 'http://launchpad.dev/%s/+sharing/%s' % ( |
962 | - pillarperson.pillar.name, pillarperson.person.name) |
963 | - browser = self.getUserBrowser(user=self.owner, url=url) |
964 | - self.assertIn( |
965 | - 'There are no shared bugs or branches.', |
966 | - browser.contents) |
967 | - |
968 | - def test_init_without_feature_flag(self): |
969 | - # We need a feature flag to enable the view. |
970 | - pillarperson = self.getPillarPerson() |
971 | - self.assertRaises( |
972 | - Unauthorized, create_initialized_view, pillarperson, '+index') |
973 | - |
974 | - def test_init_with_feature_flag(self): |
975 | + # We have to do some fun url hacking to force the traversal a user |
976 | + # encounters. |
977 | + pillarperson = PillarPerson( |
978 | + self.pillar, self.factory.makePerson()) |
979 | + url = 'http://launchpad.dev/%s/+sharing/%s' % ( |
980 | + pillarperson.pillar.name, pillarperson.person.name) |
981 | + browser = self.getUserBrowser(user=self.owner, url=url) |
982 | + self.assertIn( |
983 | + 'There are no shared bugs or branches.', browser.contents) |
984 | + |
985 | + def test_init_works(self): |
986 | # The view works with a feature flag. |
987 | - with FeatureFixture(DETAILS_ENABLED_FLAG): |
988 | - pillarperson = self.getPillarPerson() |
989 | - view = create_initialized_view(pillarperson, '+index') |
990 | - self.assertEqual(pillarperson.person.displayname, view.page_title) |
991 | - self.assertEqual(1, view.shared_bugs_count) |
992 | + pillarperson = self.getPillarPerson() |
993 | + view = create_initialized_view(pillarperson, '+index') |
994 | + self.assertEqual(pillarperson.person.displayname, view.page_title) |
995 | + self.assertEqual(1, view.shared_bugs_count) |
996 | |
997 | def test_view_data_model(self): |
998 | # Test that the json request cache contains the view data model. |
999 | - with FeatureFixture(DETAILS_ENABLED_FLAG): |
1000 | - pillarperson = self.getPillarPerson() |
1001 | - view = create_initialized_view(pillarperson, '+index') |
1002 | - bugtask = list(view.bugtasks)[0] |
1003 | - bug = bugtask.bug |
1004 | - cache = IJSONRequestCache(view.request) |
1005 | - request = get_current_web_service_request() |
1006 | - self.assertEqual({ |
1007 | - 'self_link': absoluteURL(pillarperson.person, request), |
1008 | - 'displayname': pillarperson.person.displayname |
1009 | - }, cache.objects.get('grantee')) |
1010 | - self.assertEqual({ |
1011 | - 'self_link': absoluteURL(pillarperson.pillar, request), |
1012 | - }, cache.objects.get('pillar')) |
1013 | - self.assertEqual({ |
1014 | - 'bug_id': bug.id, |
1015 | - 'bug_summary': bug.title, |
1016 | - 'bug_importance': bugtask.importance.title.lower(), |
1017 | - 'information_type': bug.information_type.title, |
1018 | - 'web_link': canonical_url( |
1019 | - bugtask, path_only_if_possible=True), |
1020 | - 'self_link': absoluteURL(bug, request), |
1021 | - }, cache.objects.get('bugs')[0]) |
1022 | - if self.pillar_type == 'product': |
1023 | - branch = list(view.branches)[0] |
1024 | - self.assertEqual({ |
1025 | - 'branch_id': branch.id, |
1026 | - 'branch_name': branch.unique_name, |
1027 | - 'information_type': InformationType.USERDATA.title, |
1028 | - 'web_link': canonical_url( |
1029 | - branch, path_only_if_possible=True), |
1030 | - 'self_link': absoluteURL(branch, request), |
1031 | - }, cache.objects.get('branches')[0]) |
1032 | + pillarperson = self.getPillarPerson() |
1033 | + view = create_initialized_view(pillarperson, '+index') |
1034 | + bugtask = list(view.bugtasks)[0] |
1035 | + bug = bugtask.bug |
1036 | + cache = IJSONRequestCache(view.request) |
1037 | + request = get_current_web_service_request() |
1038 | + self.assertEqual({ |
1039 | + 'self_link': absoluteURL(pillarperson.person, request), |
1040 | + 'displayname': pillarperson.person.displayname |
1041 | + }, cache.objects.get('grantee')) |
1042 | + self.assertEqual({ |
1043 | + 'self_link': absoluteURL(pillarperson.pillar, request), |
1044 | + }, cache.objects.get('pillar')) |
1045 | + self.assertEqual({ |
1046 | + 'bug_id': bug.id, |
1047 | + 'bug_summary': bug.title, |
1048 | + 'bug_importance': bugtask.importance.title.lower(), |
1049 | + 'information_type': bug.information_type.title, |
1050 | + 'web_link': canonical_url( |
1051 | + bugtask, path_only_if_possible=True), |
1052 | + 'self_link': absoluteURL(bug, request), |
1053 | + }, cache.objects.get('bugs')[0]) |
1054 | + if self.pillar_type == 'product': |
1055 | + branch = list(view.branches)[0] |
1056 | + self.assertEqual({ |
1057 | + 'branch_id': branch.id, |
1058 | + 'branch_name': branch.unique_name, |
1059 | + 'information_type': InformationType.USERDATA.title, |
1060 | + 'web_link': canonical_url(branch, path_only_if_possible=True), |
1061 | + 'self_link': absoluteURL(branch, request), |
1062 | + }, cache.objects.get('branches')[0]) |
1063 | |
1064 | def test_view_query_count(self): |
1065 | # Test that the view bulk loads artifacts. |
1066 | - with FeatureFixture(DETAILS_ENABLED_FLAG): |
1067 | - person = self.factory.makePerson() |
1068 | - for x in range(0, 15): |
1069 | - self.makeArtifactGrantee(person, True, True, False) |
1070 | - pillarperson = PillarPerson(self.pillar, person) |
1071 | - |
1072 | - # Invalidate the Storm cache and check the query count. |
1073 | - IStore(self.pillar).invalidate() |
1074 | - with StormStatementRecorder() as recorder: |
1075 | - create_initialized_view(pillarperson, '+index') |
1076 | - self.assertThat(recorder, HasQueryCount(LessThan(12))) |
1077 | - |
1078 | - def test_view_write_enabled_without_feature_flag(self): |
1079 | - # Test that sharing_write_enabled is not set without the feature flag. |
1080 | - with FeatureFixture(DETAILS_ENABLED_FLAG): |
1081 | - pillarperson = self.getPillarPerson() |
1082 | - view = create_initialized_view(pillarperson, '+index') |
1083 | - cache = IJSONRequestCache(view.request) |
1084 | - self.assertFalse(cache.objects.get('sharing_write_enabled')) |
1085 | - |
1086 | - def test_view_write_enabled_with_feature_flag(self): |
1087 | - # Test that sharing_write_enabled is set when required. |
1088 | - with FeatureFixture(DETAILS_WRITE_FLAG): |
1089 | - pillarperson = self.getPillarPerson() |
1090 | - view = create_initialized_view(pillarperson, '+index') |
1091 | - cache = IJSONRequestCache(view.request) |
1092 | - self.assertTrue(cache.objects.get('sharing_write_enabled')) |
1093 | + person = self.factory.makePerson() |
1094 | + for x in range(0, 15): |
1095 | + self.makeArtifactGrantee(person, True, True, False) |
1096 | + pillarperson = PillarPerson(self.pillar, person) |
1097 | + |
1098 | + # Invalidate the Storm cache and check the query count. |
1099 | + IStore(self.pillar).invalidate() |
1100 | + with StormStatementRecorder() as recorder: |
1101 | + create_initialized_view(pillarperson, '+index') |
1102 | + self.assertThat(recorder, HasQueryCount(LessThan(12))) |
1103 | |
1104 | |
1105 | class TestProductSharingDetailsView( |
1106 | @@ -289,132 +249,83 @@ |
1107 | class PillarSharingViewTestMixin: |
1108 | """Test the PillarSharingView.""" |
1109 | |
1110 | - def test_init_without_feature_flag(self): |
1111 | - # We need a feature flag to enable the view. |
1112 | - self.assertRaises( |
1113 | - Unauthorized, create_initialized_view, self.pillar, '+sharing') |
1114 | - |
1115 | - def test_init_with_feature_flag(self): |
1116 | - # The view works with a feature flag. |
1117 | - with FeatureFixture(ENABLED_FLAG): |
1118 | - view = create_initialized_view(self.pillar, '+sharing') |
1119 | - self.assertEqual('Sharing', view.page_title) |
1120 | - |
1121 | - def test_sharing_menu_without_feature_flag(self): |
1122 | + def test_sharing_menu(self): |
1123 | url = canonical_url(self.pillar) |
1124 | browser = setupBrowserForUser(user=self.driver) |
1125 | browser.open(url) |
1126 | soup = BeautifulSoup(browser.contents) |
1127 | - sharing_menu = soup.find('a', {'class': 'menu-link-sharing'}) |
1128 | - self.assertIsNone(sharing_menu) |
1129 | - |
1130 | - def test_sharing_menu_with_feature_flag(self): |
1131 | - with FeatureFixture(ENABLED_FLAG): |
1132 | - url = canonical_url(self.pillar) |
1133 | - browser = setupBrowserForUser(user=self.driver) |
1134 | - browser.open(url) |
1135 | - soup = BeautifulSoup(browser.contents) |
1136 | - sharing_url = canonical_url(self.pillar, view_name='+sharing') |
1137 | - sharing_menu = soup.find('a', {'href': sharing_url}) |
1138 | - self.assertIsNotNone(sharing_menu) |
1139 | + sharing_url = canonical_url(self.pillar, view_name='+sharing') |
1140 | + sharing_menu = soup.find('a', {'href': sharing_url}) |
1141 | + self.assertIsNotNone(sharing_menu) |
1142 | |
1143 | def test_picker_config(self): |
1144 | # Test the config passed to the disclosure sharing picker. |
1145 | - with FeatureFixture(ENABLED_FLAG): |
1146 | - view = create_view(self.pillar, name='+sharing') |
1147 | - picker_config = simplejson.loads(view.json_sharing_picker_config) |
1148 | - self.assertTrue('vocabulary_filters' in picker_config) |
1149 | - self.assertEqual( |
1150 | - 'Share project information', |
1151 | - picker_config['header']) |
1152 | - self.assertEqual( |
1153 | - 'Search for user or exclusive team with whom to share', |
1154 | - picker_config['steptitle']) |
1155 | - self.assertEqual( |
1156 | - 'NewPillarGrantee', picker_config['vocabulary']) |
1157 | + view = create_view(self.pillar, name='+sharing') |
1158 | + picker_config = simplejson.loads(view.json_sharing_picker_config) |
1159 | + self.assertTrue('vocabulary_filters' in picker_config) |
1160 | + self.assertEqual('Share project information', picker_config['header']) |
1161 | + self.assertEqual( |
1162 | + 'Search for user or exclusive team with whom to share', |
1163 | + picker_config['steptitle']) |
1164 | + self.assertEqual('NewPillarGrantee', picker_config['vocabulary']) |
1165 | |
1166 | def test_view_data_model(self): |
1167 | # Test that the json request cache contains the view data model. |
1168 | - with FeatureFixture(ENABLED_FLAG): |
1169 | - view = create_initialized_view(self.pillar, name='+sharing') |
1170 | - cache = IJSONRequestCache(view.request) |
1171 | - self.assertIsNotNone(cache.objects.get('information_types')) |
1172 | - self.assertIsNotNone( |
1173 | - cache.objects.get('branch_sharing_policies')) |
1174 | - self.assertIsNotNone(cache.objects.get('bug_sharing_policies')) |
1175 | - self.assertIsNotNone(cache.objects.get('sharing_permissions')) |
1176 | - batch_size = config.launchpad.default_batch_size |
1177 | - apgfs = getUtility(IAccessPolicyGrantFlatSource) |
1178 | - grantees = apgfs.findGranteePermissionsByPolicy( |
1179 | - [self.access_policy], self.grantees[:batch_size]) |
1180 | - sharing_service = getUtility(IService, 'sharing') |
1181 | - grantee_data = sharing_service.jsonGranteeData(grantees) |
1182 | - self.assertContentEqual( |
1183 | - grantee_data, cache.objects.get('grantee_data')) |
1184 | + view = create_initialized_view(self.pillar, name='+sharing') |
1185 | + cache = IJSONRequestCache(view.request) |
1186 | + self.assertIsNotNone(cache.objects.get('information_types')) |
1187 | + self.assertIsNotNone(cache.objects.get('branch_sharing_policies')) |
1188 | + self.assertIsNotNone(cache.objects.get('bug_sharing_policies')) |
1189 | + self.assertIsNotNone(cache.objects.get('sharing_permissions')) |
1190 | + batch_size = config.launchpad.default_batch_size |
1191 | + apgfs = getUtility(IAccessPolicyGrantFlatSource) |
1192 | + grantees = apgfs.findGranteePermissionsByPolicy( |
1193 | + [self.access_policy], self.grantees[:batch_size]) |
1194 | + sharing_service = getUtility(IService, 'sharing') |
1195 | + grantee_data = sharing_service.jsonGranteeData(grantees) |
1196 | + self.assertContentEqual( |
1197 | + grantee_data, cache.objects.get('grantee_data')) |
1198 | |
1199 | def test_view_batch_data(self): |
1200 | # Test the expected batching data is in the json request cache. |
1201 | - with FeatureFixture(ENABLED_FLAG): |
1202 | - view = create_initialized_view(self.pillar, name='+sharing') |
1203 | - cache = IJSONRequestCache(view.request) |
1204 | - # Test one expected data value (there are many). |
1205 | - next_batch = view.grantees().batch.nextBatch() |
1206 | - self.assertContentEqual( |
1207 | - next_batch.range_memo, cache.objects.get('next')['memo']) |
1208 | + view = create_initialized_view(self.pillar, name='+sharing') |
1209 | + cache = IJSONRequestCache(view.request) |
1210 | + # Test one expected data value (there are many). |
1211 | + next_batch = view.grantees().batch.nextBatch() |
1212 | + self.assertContentEqual( |
1213 | + next_batch.range_memo, cache.objects.get('next')['memo']) |
1214 | |
1215 | def test_view_range_factory(self): |
1216 | # Test the view range factory is properly configured. |
1217 | - with FeatureFixture(ENABLED_FLAG): |
1218 | - view = create_initialized_view(self.pillar, name='+sharing') |
1219 | - range_factory = view.grantees().batch.range_factory |
1220 | - |
1221 | - def test_range_factory(): |
1222 | - row = range_factory.resultset.get_plain_result_set()[0] |
1223 | - range_factory.getOrderValuesFor(row) |
1224 | - |
1225 | - self.assertThat( |
1226 | - test_range_factory, |
1227 | - Not(Raises(MatchesException(StormRangeFactoryError)))) |
1228 | + view = create_initialized_view(self.pillar, name='+sharing') |
1229 | + range_factory = view.grantees().batch.range_factory |
1230 | + |
1231 | + def test_range_factory(): |
1232 | + row = range_factory.resultset.get_plain_result_set()[0] |
1233 | + range_factory.getOrderValuesFor(row) |
1234 | + |
1235 | + self.assertThat( |
1236 | + test_range_factory, |
1237 | + Not(Raises(MatchesException(StormRangeFactoryError)))) |
1238 | |
1239 | def test_view_query_count(self): |
1240 | # Test the query count is within expected limit. |
1241 | - with FeatureFixture(ENABLED_FLAG): |
1242 | - view = create_view(self.pillar, name='+sharing') |
1243 | - with StormStatementRecorder() as recorder: |
1244 | - view.initialize() |
1245 | - self.assertThat(recorder, HasQueryCount(LessThan(9))) |
1246 | - |
1247 | - def test_view_write_enabled_without_feature_flag(self): |
1248 | - # Test that sharing_write_enabled is not set without the feature flag. |
1249 | - with FeatureFixture(ENABLED_FLAG): |
1250 | - login_person(self.owner) |
1251 | - view = create_initialized_view(self.pillar, name='+sharing') |
1252 | - cache = IJSONRequestCache(view.request) |
1253 | - self.assertFalse(cache.objects.get('sharing_write_enabled')) |
1254 | - |
1255 | - def test_view_write_enabled_with_feature_flag(self): |
1256 | - # Test that sharing_write_enabled is set when required. |
1257 | - with FeatureFixture(WRITE_FLAG): |
1258 | - view = create_initialized_view(self.pillar, name='+sharing') |
1259 | - cache = IJSONRequestCache(view.request) |
1260 | - self.assertFalse(cache.objects.get('sharing_write_enabled')) |
1261 | - login_person(self.owner) |
1262 | - view = create_initialized_view(self.pillar, name='+sharing') |
1263 | - cache = IJSONRequestCache(view.request) |
1264 | - self.assertTrue(cache.objects.get('sharing_write_enabled')) |
1265 | + view = create_view(self.pillar, name='+sharing') |
1266 | + with StormStatementRecorder() as recorder: |
1267 | + view.initialize() |
1268 | + self.assertThat(recorder, HasQueryCount(LessThan(9))) |
1269 | |
1270 | def test_view_invisible_information_types(self): |
1271 | # Test the expected invisible information type data is in the |
1272 | # json request cache. |
1273 | - with FeatureFixture(WRITE_FLAG): |
1274 | - with person_logged_in(self.pillar.owner): |
1275 | - getUtility(IService, 'sharing').deletePillarGrantee( |
1276 | - self.pillar, self.pillar.owner, self.pillar.owner) |
1277 | - view = create_initialized_view(self.pillar, name='+sharing') |
1278 | - cache = IJSONRequestCache(view.request) |
1279 | - self.assertContentEqual( |
1280 | - ['Private Security', 'Private'], |
1281 | - cache.objects.get('invisible_information_types')) |
1282 | + with person_logged_in(self.pillar.owner): |
1283 | + getUtility(IService, 'sharing').deletePillarGrantee( |
1284 | + self.pillar, self.pillar.owner, self.pillar.owner) |
1285 | + view = create_initialized_view(self.pillar, name='+sharing') |
1286 | + cache = IJSONRequestCache(view.request) |
1287 | + self.assertContentEqual( |
1288 | + ['Private Security', 'Private'], |
1289 | + cache.objects.get('invisible_information_types')) |
1290 | |
1291 | |
1292 | class TestProductSharingView(PillarSharingViewTestMixin, |
1293 | |
1294 | === modified file 'lib/lp/registry/javascript/sharing/pillarsharingview.js' |
1295 | --- lib/lp/registry/javascript/sharing/pillarsharingview.js 2012-08-23 00:35:25 +0000 |
1296 | +++ lib/lp/registry/javascript/sharing/pillarsharingview.js 2012-08-29 05:40:35 +0000 |
1297 | @@ -19,10 +19,6 @@ |
1298 | value: new Y.lp.client.Launchpad() |
1299 | }, |
1300 | |
1301 | - write_enabled: { |
1302 | - value: false |
1303 | - }, |
1304 | - |
1305 | grantee_picker: { |
1306 | value: null |
1307 | }, |
1308 | @@ -61,12 +57,6 @@ |
1309 | this.set( |
1310 | 'sharing_permissions_by_value', sharing_permissions_by_value); |
1311 | |
1312 | - // No need to do anything else if we are read only. |
1313 | - if (LP.cache.sharing_write_enabled !== true) { |
1314 | - return; |
1315 | - } |
1316 | - this.set('write_enabled', true); |
1317 | - |
1318 | var vocab; |
1319 | var header; |
1320 | var steptitle; |
1321 | @@ -125,13 +115,11 @@ |
1322 | sharing_permissions: |
1323 | this.get('sharing_permissions_by_value'), |
1324 | information_types: this.get('information_types_by_value'), |
1325 | - write_enabled: this.get('write_enabled') |
1326 | + write_enabled: true |
1327 | }); |
1328 | this.set('grantee_table', grantee_table); |
1329 | grantee_table.render(); |
1330 | - if (this.get('write_enabled')) { |
1331 | - Y.one('#add-grantee-link').removeClass('hidden'); |
1332 | - } |
1333 | + Y.one('#add-grantee-link').removeClass('hidden'); |
1334 | this.bug_sharing_policy_widget |
1335 | = this._render_sharing_policy('bug', 'Bug'); |
1336 | this.branch_sharing_policy_widget |
1337 | @@ -164,8 +152,7 @@ |
1338 | } |
1339 | choice_items.push.apply( |
1340 | choice_items, this.getSharingPolicyInformation(artifact_type)); |
1341 | - var editable = LP.cache.sharing_write_enabled |
1342 | - && choice_items.length > 1; |
1343 | + var editable = choice_items.length > 1; |
1344 | var policy_edit = new Y.ChoiceSource({ |
1345 | flashEnabled: false, |
1346 | clickable_content: editable, |
1347 | @@ -186,9 +173,6 @@ |
1348 | }, |
1349 | |
1350 | bindUI: function() { |
1351 | - if (!this.get('write_enabled')) { |
1352 | - return; |
1353 | - } |
1354 | var self = this; |
1355 | var share_link = Y.one('#add-grantee-link'); |
1356 | share_link.on('click', function(e) { |
1357 | |
1358 | === modified file 'lib/lp/registry/javascript/sharing/sharingdetailsview.js' |
1359 | --- lib/lp/registry/javascript/sharing/sharingdetailsview.js 2012-07-21 03:04:06 +0000 |
1360 | +++ lib/lp/registry/javascript/sharing/sharingdetailsview.js 2012-08-29 05:40:35 +0000 |
1361 | @@ -19,10 +19,6 @@ |
1362 | value: new Y.lp.client.Launchpad() |
1363 | }, |
1364 | |
1365 | - write_enabled: { |
1366 | - value: false |
1367 | - }, |
1368 | - |
1369 | sharing_details_table: { |
1370 | value: null |
1371 | } |
1372 | @@ -30,29 +26,19 @@ |
1373 | |
1374 | Y.extend(SharingDetailsView, Y.Widget, { |
1375 | |
1376 | - initializer: function(config) { |
1377 | - if (LP.cache.sharing_write_enabled !== true) { |
1378 | - return; |
1379 | - } |
1380 | - this.set('write_enabled', true); |
1381 | - }, |
1382 | - |
1383 | renderUI: function() { |
1384 | var ns = Y.lp.registry.sharing.sharingdetails; |
1385 | var details_table = new ns.SharingDetailsTable({ |
1386 | bugs: LP.cache.bugs, |
1387 | branches: LP.cache.branches, |
1388 | person_name: LP.cache.grantee.displayname, |
1389 | - write_enabled: this.get('write_enabled') |
1390 | + write_enabled: true |
1391 | }); |
1392 | this.set('sharing_details_table', details_table); |
1393 | details_table.render(); |
1394 | }, |
1395 | |
1396 | bindUI: function() { |
1397 | - if (!this.get('write_enabled')) { |
1398 | - return; |
1399 | - } |
1400 | var self = this; |
1401 | var sharing_details_table = this.get('sharing_details_table'); |
1402 | var ns = Y.lp.registry.sharing.sharingdetails; |
1403 | |
1404 | === modified file 'lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js' |
1405 | --- lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js 2012-08-16 00:19:42 +0000 |
1406 | +++ lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js 2012-08-29 05:40:35 +0000 |
1407 | @@ -56,7 +56,6 @@ |
1408 | title: 'Branch Policy 1', |
1409 | description: 'Branch Policy 1 description'} |
1410 | ], |
1411 | - sharing_write_enabled: true |
1412 | } |
1413 | }; |
1414 | this.mockio = new Y.lp.testing.mockio.MockIo(); |
1415 | @@ -119,16 +118,6 @@ |
1416 | Y.Assert.isNotNull(Y.one('.yui3-grantee_picker')); |
1417 | }, |
1418 | |
1419 | - // Read only mode disables the correct things. |
1420 | - test_readonly: function() { |
1421 | - window.LP.cache.sharing_write_enabled = false; |
1422 | - this.view = this._create_Widget(); |
1423 | - this.view.render(); |
1424 | - Y.Assert.isTrue(Y.one('#add-grantee-link').hasClass('hidden')); |
1425 | - Y.Assert.isFalse( |
1426 | - this.view.get('grantee_table').get('write_enabled')); |
1427 | - }, |
1428 | - |
1429 | // Clicking a update grantee grantee link calls |
1430 | // the update_grantee_interaction method with the correct parameters. |
1431 | test_update_grantee_click: function() { |
1432 | @@ -547,14 +536,6 @@ |
1433 | 'Bug Policy 1', value_node.get('text').trim()); |
1434 | }, |
1435 | |
1436 | - // If the view is readonly, no edit links are available. |
1437 | - test_sharing_policy_render_read_only: function() { |
1438 | - window.LP.cache.sharing_write_enabled = false; |
1439 | - this.view = this._create_Widget(); |
1440 | - this.view.render(); |
1441 | - this._assert_sharing_policies_editable(false); |
1442 | - }, |
1443 | - |
1444 | // If there is only one policy choice, no edit links are available. |
1445 | test_sharing_policy_render_only_one_choice: function() { |
1446 | // Add a model value so the legacy choice is not used. |
1447 | |
1448 | === modified file 'lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js' |
1449 | --- lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js 2012-07-20 03:15:04 +0000 |
1450 | +++ lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js 2012-08-29 05:40:35 +0000 |
1451 | @@ -37,7 +37,6 @@ |
1452 | pillar: { |
1453 | self_link: '/pillar' |
1454 | }, |
1455 | - sharing_write_enabled: true |
1456 | } |
1457 | }; |
1458 | this.fixture = Y.one('#fixture'); |
1459 | @@ -79,16 +78,6 @@ |
1460 | Y.one('#sharing-table-body tr[id=shared-bug-2]')); |
1461 | }, |
1462 | |
1463 | - // Read only mode disables the correct things. |
1464 | - test_readonly: function() { |
1465 | - window.LP.cache.sharing_write_enabled = false; |
1466 | - this.view = this._create_Widget(); |
1467 | - this.view.render(); |
1468 | - Y.Assert.isFalse( |
1469 | - this.view.get('sharing_details_table') |
1470 | - .get('write_enabled')); |
1471 | - }, |
1472 | - |
1473 | // Clicking a bug remove link calls the confirm_grant_removal |
1474 | // method with the correct parameters. |
1475 | test_remove_bug_grant_click: function() { |
1476 | |
1477 | === modified file 'lib/lp/registry/model/accesspolicy.py' |
1478 | --- lib/lp/registry/model/accesspolicy.py 2012-08-22 04:58:49 +0000 |
1479 | +++ lib/lp/registry/model/accesspolicy.py 2012-08-29 05:40:35 +0000 |
1480 | @@ -65,14 +65,19 @@ |
1481 | getUtility(IAccessArtifactSource).delete([artifact]) |
1482 | return |
1483 | [abstract_artifact] = getUtility(IAccessArtifactSource).ensure([artifact]) |
1484 | + aps = getUtility(IAccessPolicySource).find( |
1485 | + (pillar, information_type) for pillar in pillars) |
1486 | + missing_pillars = set(pillars) - set([ap.pillar for ap in aps]) |
1487 | + if len(missing_pillars): |
1488 | + pillar_str = ', '.join([p.name for p in missing_pillars]) |
1489 | + raise AssertionError( |
1490 | + "Pillar(s) %s require an access policy for information type " |
1491 | + "%s." % (pillar_str, information_type.title)) |
1492 | |
1493 | # Now determine the existing and desired links, and make them |
1494 | # match. |
1495 | apasource = getUtility(IAccessPolicyArtifactSource) |
1496 | - wanted_links = set( |
1497 | - (abstract_artifact, policy) for policy in |
1498 | - getUtility(IAccessPolicySource).find( |
1499 | - (pillar, information_type) for pillar in pillars)) |
1500 | + wanted_links = set((abstract_artifact, policy) for policy in aps) |
1501 | existing_links = set([ |
1502 | (apa.abstract_artifact, apa.policy) |
1503 | for apa in apasource.findByArtifact([abstract_artifact])]) |
1504 | |
1505 | === modified file 'lib/lp/registry/model/teammembership.py' |
1506 | --- lib/lp/registry/model/teammembership.py 2012-08-14 23:27:07 +0000 |
1507 | +++ lib/lp/registry/model/teammembership.py 2012-08-29 05:40:35 +0000 |
1508 | @@ -65,7 +65,6 @@ |
1509 | SQLBase, |
1510 | sqlvalues, |
1511 | ) |
1512 | -from lp.services.features import getFeatureFlag |
1513 | from lp.services.mail.helpers import ( |
1514 | get_contact_email_addresses, |
1515 | get_email_template, |
1516 | @@ -343,13 +342,11 @@ |
1517 | _fillTeamParticipation(self.person, self.team) |
1518 | elif old_status in ACTIVE_STATES: |
1519 | _cleanTeamParticipation(self.person, self.team) |
1520 | - flag = 'disclosure.unsubscribe_jobs.enabled' |
1521 | - if bool(getFeatureFlag(flag)): |
1522 | - # A person has left the team so they may no longer have access |
1523 | - # to some artifacts shared with the team. We need to run a job |
1524 | - # to remove any subscriptions to such artifacts. |
1525 | - getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
1526 | - user, grantee=self.person) |
1527 | + # A person has left the team so they may no longer have access |
1528 | + # to some artifacts shared with the team. We need to run a job |
1529 | + # to remove any subscriptions to such artifacts. |
1530 | + getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
1531 | + user, grantee=self.person) |
1532 | else: |
1533 | # Changed from an inactive state to another inactive one, so no |
1534 | # need to fill/clean the TeamParticipation table. |
1535 | |
1536 | === modified file 'lib/lp/registry/services/sharingservice.py' |
1537 | --- lib/lp/registry/services/sharingservice.py 2012-08-16 06:06:36 +0000 |
1538 | +++ lib/lp/registry/services/sharingservice.py 2012-08-29 05:40:35 +0000 |
1539 | @@ -59,7 +59,6 @@ |
1540 | from lp.registry.model.teammembership import TeamParticipation |
1541 | from lp.services.database.lpstorm import IStore |
1542 | from lp.services.database.stormexpr import ColumnSelect |
1543 | -from lp.services.features import getFeatureFlag |
1544 | from lp.services.searchbuilder import any |
1545 | from lp.services.webapp.authorization import ( |
1546 | available_with_permission, |
1547 | @@ -81,12 +80,6 @@ |
1548 | """See `IService`.""" |
1549 | return 'sharing' |
1550 | |
1551 | - @property |
1552 | - def write_enabled(self): |
1553 | - return ( |
1554 | - bool(getFeatureFlag( |
1555 | - 'disclosure.enhanced_sharing.writable'))) |
1556 | - |
1557 | def checkPillarAccess(self, pillar, information_type, person): |
1558 | """See `ISharingService`.""" |
1559 | policy = getUtility(IAccessPolicySource).find( |
1560 | @@ -350,15 +343,12 @@ |
1561 | result = [] |
1562 | request = get_current_web_service_request() |
1563 | browser_request = IWebBrowserOriginatingRequest(request) |
1564 | - details_enabled = bool((getFeatureFlag( |
1565 | - 'disclosure.enhanced_sharing_details.enabled'))) |
1566 | # We need to precache icon and validity information for the batch. |
1567 | grantee_ids = [grantee[0].id for grantee in grant_permissions] |
1568 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( |
1569 | grantee_ids, need_icon=True, need_validity=True)) |
1570 | for (grantee, permissions, shared_artifact_types) in grant_permissions: |
1571 | - some_things_shared = ( |
1572 | - details_enabled and len(shared_artifact_types) > 0) |
1573 | + some_things_shared = len(shared_artifact_types) > 0 |
1574 | grantee_permissions = {} |
1575 | for (policy, permission) in permissions.iteritems(): |
1576 | grantee_permissions[policy.type.name] = permission.name |
1577 | @@ -386,9 +376,6 @@ |
1578 | # We do not support adding grantees to project groups. |
1579 | assert not IProjectGroup.providedBy(pillar) |
1580 | |
1581 | - if not self.write_enabled: |
1582 | - raise Unauthorized("This feature is not yet enabled.") |
1583 | - |
1584 | # Separate out the info types according to permission. |
1585 | information_types = permissions.keys() |
1586 | info_types_for_all = [ |
1587 | @@ -463,9 +450,6 @@ |
1588 | information_types=None): |
1589 | """See `ISharingService`.""" |
1590 | |
1591 | - if not self.write_enabled: |
1592 | - raise Unauthorized("This feature is not yet enabled.") |
1593 | - |
1594 | policy_source = getUtility(IAccessPolicySource) |
1595 | if information_types is None: |
1596 | # We delete all policy grants for the pillar. |
1597 | @@ -491,9 +475,8 @@ |
1598 | to_delete = list(ap_grant_flat.findArtifactsByGrantee( |
1599 | grantee, pillar_policies)) |
1600 | if len(to_delete) > 0: |
1601 | - accessartifact_grant_source = getUtility( |
1602 | - IAccessArtifactGrantSource) |
1603 | - accessartifact_grant_source.revokeByArtifact(to_delete, [grantee]) |
1604 | + getUtility(IAccessArtifactGrantSource).revokeByArtifact( |
1605 | + to_delete, [grantee]) |
1606 | |
1607 | # Create a job to remove subscriptions for artifacts the grantee can no |
1608 | # longer see. |
1609 | @@ -512,9 +495,6 @@ |
1610 | bugs=None): |
1611 | """See `ISharingService`.""" |
1612 | |
1613 | - if not self.write_enabled: |
1614 | - raise Unauthorized("This feature is not yet enabled.") |
1615 | - |
1616 | artifacts = [] |
1617 | if branches: |
1618 | artifacts.extend(branches) |
1619 | @@ -524,8 +504,7 @@ |
1620 | accessartifact_source = getUtility(IAccessArtifactSource) |
1621 | artifacts_to_delete = accessartifact_source.find(artifacts) |
1622 | # Revoke access to bugs/branches for the specified grantee. |
1623 | - accessartifact_grant_source = getUtility(IAccessArtifactGrantSource) |
1624 | - accessartifact_grant_source.revokeByArtifact( |
1625 | + getUtility(IAccessArtifactGrantSource).revokeByArtifact( |
1626 | artifacts_to_delete, [grantee]) |
1627 | |
1628 | # Create a job to remove subscriptions for artifacts the grantee can no |
1629 | @@ -537,9 +516,6 @@ |
1630 | ignore_permissions=False): |
1631 | """See `ISharingService`.""" |
1632 | |
1633 | - if not ignore_permissions and not self.write_enabled: |
1634 | - raise Unauthorized("This feature is not yet enabled.") |
1635 | - |
1636 | artifacts = [] |
1637 | if branches: |
1638 | artifacts.extend(branches) |
1639 | |
1640 | === modified file 'lib/lp/registry/services/tests/test_sharingservice.py' |
1641 | --- lib/lp/registry/services/tests/test_sharingservice.py 2012-08-16 06:06:36 +0000 |
1642 | +++ lib/lp/registry/services/tests/test_sharingservice.py 2012-08-29 05:40:35 +0000 |
1643 | @@ -56,13 +56,6 @@ |
1644 | from lp.testing.pages import LaunchpadWebServiceCaller |
1645 | |
1646 | |
1647 | -WRITE_FLAG = { |
1648 | - 'disclosure.enhanced_sharing.writable': 'true', |
1649 | - 'disclosure.enhanced_sharing_details.enabled': 'true', |
1650 | - 'jobs.celery.enabled_classes': 'RemoveArtifactSubscriptionsJob'} |
1651 | -DETAILS_FLAG = {'disclosure.enhanced_sharing_details.enabled': 'true'} |
1652 | - |
1653 | - |
1654 | class TestSharingService(TestCaseWithFactory): |
1655 | """Tests for the SharingService.""" |
1656 | |
1657 | @@ -71,6 +64,9 @@ |
1658 | def setUp(self): |
1659 | super(TestSharingService, self).setUp() |
1660 | self.service = getUtility(IService, 'sharing') |
1661 | + self.useFixture(FeatureFixture({ |
1662 | + 'jobs.celery.enabled_classes': 'RemoveArtifactSubscriptionsJob', |
1663 | + })) |
1664 | |
1665 | def _makeGranteeData(self, grantee, policy_permissions, |
1666 | shared_artifact_types): |
1667 | @@ -234,12 +230,11 @@ |
1668 | [policy1, policy2] = getUtility(IAccessPolicySource).findByPillar( |
1669 | [product]) |
1670 | grantee = self.factory.makePerson() |
1671 | - with FeatureFixture(DETAILS_FLAG): |
1672 | - grantees = self.service.jsonGranteeData( |
1673 | - [(grantee, { |
1674 | - policy1: SharingPermission.ALL, |
1675 | - policy2: SharingPermission.SOME}, |
1676 | - [policy1.type, policy2.type])]) |
1677 | + grantees = self.service.jsonGranteeData( |
1678 | + [(grantee, { |
1679 | + policy1: SharingPermission.ALL, |
1680 | + policy2: SharingPermission.SOME}, |
1681 | + [policy1.type, policy2.type])]) |
1682 | expected_data = self._makeGranteeData( |
1683 | grantee, |
1684 | [(policy1.type, SharingPermission.ALL), |
1685 | @@ -247,24 +242,6 @@ |
1686 | [policy1.type, policy2.type]) |
1687 | self.assertContentEqual([expected_data], grantees) |
1688 | |
1689 | - def test_jsonGranteeData_with_Some_without_flag(self): |
1690 | - # jsonGranteeData returns the expected data for a grantee with |
1691 | - # permissions which include SOME and the feature flag not set. |
1692 | - product = self.factory.makeProduct() |
1693 | - [policy1, policy2] = getUtility(IAccessPolicySource).findByPillar( |
1694 | - [product]) |
1695 | - grantee = self.factory.makePerson() |
1696 | - grantees = self.service.jsonGranteeData( |
1697 | - [(grantee, { |
1698 | - policy1: SharingPermission.ALL, |
1699 | - policy2: SharingPermission.SOME}, [policy2.type])]) |
1700 | - expected_data = self._makeGranteeData( |
1701 | - grantee, |
1702 | - [(policy1.type, SharingPermission.ALL), |
1703 | - (policy2.type, SharingPermission.SOME)], [policy2.type]) |
1704 | - expected_data['shared_items_exist'] = False |
1705 | - self.assertContentEqual([expected_data], grantees) |
1706 | - |
1707 | def test_jsonGranteeData_without_Some(self): |
1708 | # jsonGranteeData returns the expected data for a grantee with only ALL |
1709 | # permissions. |
1710 | @@ -272,10 +249,8 @@ |
1711 | [policy1, policy2] = getUtility(IAccessPolicySource).findByPillar( |
1712 | [product]) |
1713 | grantee = self.factory.makePerson() |
1714 | - with FeatureFixture(DETAILS_FLAG): |
1715 | - grantees = self.service.jsonGranteeData( |
1716 | - [(grantee, { |
1717 | - policy1: SharingPermission.ALL}, [])]) |
1718 | + grantees = self.service.jsonGranteeData( |
1719 | + [(grantee, {policy1: SharingPermission.ALL}, [])]) |
1720 | expected_data = self._makeGranteeData( |
1721 | grantee, |
1722 | [(policy1.type, SharingPermission.ALL)], []) |
1723 | @@ -290,10 +265,8 @@ |
1724 | icon = self.factory.makeLibraryFileAlias( |
1725 | filename='smurf.png', content_type='image/png') |
1726 | grantee = self.factory.makeTeam(icon=icon) |
1727 | - with FeatureFixture(DETAILS_FLAG): |
1728 | - grantees = self.service.jsonGranteeData( |
1729 | - [(grantee, { |
1730 | - policy1: SharingPermission.ALL}, [])]) |
1731 | + grantees = self.service.jsonGranteeData( |
1732 | + [(grantee, {policy1: SharingPermission.ALL}, [])]) |
1733 | expected_data = self._makeGranteeData( |
1734 | grantee, |
1735 | [(policy1.type, SharingPermission.ALL)], []) |
1736 | @@ -312,8 +285,7 @@ |
1737 | self.factory.makeAccessPolicyArtifact( |
1738 | artifact=artifact_grant.abstract_artifact, policy=access_policy) |
1739 | |
1740 | - with FeatureFixture(DETAILS_FLAG): |
1741 | - grantees = self.service.getPillarGranteeData(pillar) |
1742 | + grantees = self.service.getPillarGranteeData(pillar) |
1743 | expected_grantees = [ |
1744 | self._makeGranteeData( |
1745 | grantee, |
1746 | @@ -540,9 +512,8 @@ |
1747 | InformationType.PRIVATESECURITY: SharingPermission.ALL, |
1748 | InformationType.USERDATA: SharingPermission.SOME, |
1749 | InformationType.PROPRIETARY: SharingPermission.NOTHING} |
1750 | - with FeatureFixture(WRITE_FLAG): |
1751 | - grantee_data = self.service.sharePillarInformation( |
1752 | - pillar, grantee, grantor, permissions) |
1753 | + grantee_data = self.service.sharePillarInformation( |
1754 | + pillar, grantee, grantor, permissions) |
1755 | policies = getUtility(IAccessPolicySource).findByPillar([pillar]) |
1756 | policy_grant_source = getUtility(IAccessPolicyGrantSource) |
1757 | grants = policy_grant_source.findByPolicy(policies) |
1758 | @@ -619,9 +590,8 @@ |
1759 | |
1760 | permissions = { |
1761 | grant.policy.type: SharingPermission.SOME} |
1762 | - with FeatureFixture(WRITE_FLAG): |
1763 | - grantee_data = self.service.sharePillarInformation( |
1764 | - pillar, grantee, self.factory.makePerson(), permissions) |
1765 | + grantee_data = self.service.sharePillarInformation( |
1766 | + pillar, grantee, self.factory.makePerson(), permissions) |
1767 | self.assertIsNone(grantee_data['grantee_entry']) |
1768 | |
1769 | def test_granteePillarInformationInvisibleInformationTypes(self): |
1770 | @@ -629,13 +599,12 @@ |
1771 | # information types. |
1772 | product = self.factory.makeProduct() |
1773 | grantee = self.factory.makePerson() |
1774 | - with FeatureFixture(WRITE_FLAG): |
1775 | - with admin_logged_in(): |
1776 | - self.service.deletePillarGrantee( |
1777 | - product, product.owner, product.owner) |
1778 | - result_data = self.service.sharePillarInformation( |
1779 | - product, grantee, product.owner, |
1780 | - {InformationType.USERDATA: SharingPermission.ALL}) |
1781 | + with admin_logged_in(): |
1782 | + self.service.deletePillarGrantee( |
1783 | + product, product.owner, product.owner) |
1784 | + result_data = self.service.sharePillarInformation( |
1785 | + product, grantee, product.owner, |
1786 | + {InformationType.USERDATA: SharingPermission.ALL}) |
1787 | # The owner is granted access on product creation. So we need to allow |
1788 | # for that in the check below. |
1789 | self.assertContentEqual( |
1790 | @@ -645,42 +614,27 @@ |
1791 | def _assert_sharePillarInformationUnauthorized(self, pillar): |
1792 | # sharePillarInformation raises an Unauthorized exception if the user |
1793 | # is not permitted to do so. |
1794 | - with FeatureFixture(WRITE_FLAG): |
1795 | - grantee = self.factory.makePerson() |
1796 | - user = self.factory.makePerson() |
1797 | - self.assertRaises( |
1798 | - Unauthorized, self.service.sharePillarInformation, |
1799 | - pillar, grantee, user, |
1800 | - {InformationType.USERDATA: SharingPermission.ALL}) |
1801 | + grantee = self.factory.makePerson() |
1802 | + user = self.factory.makePerson() |
1803 | + self.assertRaises( |
1804 | + Unauthorized, self.service.sharePillarInformation, |
1805 | + pillar, grantee, user, |
1806 | + {InformationType.USERDATA: SharingPermission.ALL}) |
1807 | |
1808 | def test_sharePillarInformationAnonymous(self): |
1809 | # Anonymous users are not allowed. |
1810 | - with FeatureFixture(WRITE_FLAG): |
1811 | - product = self.factory.makeProduct() |
1812 | - login(ANONYMOUS) |
1813 | - self._assert_sharePillarInformationUnauthorized(product) |
1814 | + product = self.factory.makeProduct() |
1815 | + login(ANONYMOUS) |
1816 | + self._assert_sharePillarInformationUnauthorized(product) |
1817 | |
1818 | def test_sharePillarInformationAnyone(self): |
1819 | # Unauthorized users are not allowed. |
1820 | - with FeatureFixture(WRITE_FLAG): |
1821 | - product = self.factory.makeProduct() |
1822 | - login_person(self.factory.makePerson()) |
1823 | - self._assert_sharePillarInformationUnauthorized(product) |
1824 | - |
1825 | - def test_sharePillarInformation_without_flag(self): |
1826 | - # The feature flag needs to be enabled. |
1827 | - owner = self.factory.makePerson() |
1828 | - product = self.factory.makeProduct(owner=owner) |
1829 | - login_person(owner) |
1830 | - grantee = self.factory.makePerson() |
1831 | - user = self.factory.makePerson() |
1832 | - self.assertRaises( |
1833 | - Unauthorized, self.service.sharePillarInformation, |
1834 | - product, grantee, user, |
1835 | - {InformationType.USERDATA: SharingPermission.ALL}) |
1836 | - |
1837 | - def _assert_deletePillarGrantee( |
1838 | - self, pillar, types_to_delete=None, pillar_type=None): |
1839 | + product = self.factory.makeProduct() |
1840 | + login_person(self.factory.makePerson()) |
1841 | + self._assert_sharePillarInformationUnauthorized(product) |
1842 | + |
1843 | + def _assert_deletePillarGrantee(self, pillar, types_to_delete=None, |
1844 | + pillar_type=None): |
1845 | access_policies = getUtility(IAccessPolicySource).findByPillar( |
1846 | (pillar,)) |
1847 | information_types = [ap.type for ap in access_policies] |
1848 | @@ -701,9 +655,8 @@ |
1849 | self.factory.makeAccessPolicyArtifact( |
1850 | artifact=artifact, policy=access_policy) |
1851 | # Delete data for a specific information type. |
1852 | - with FeatureFixture(WRITE_FLAG): |
1853 | - self.service.deletePillarGrantee( |
1854 | - pillar, grantee, pillar.owner, types_to_delete) |
1855 | + self.service.deletePillarGrantee( |
1856 | + pillar, grantee, pillar.owner, types_to_delete) |
1857 | # Assemble the expected data for the remaining access grants for |
1858 | # grantee. |
1859 | expected_data = [] |
1860 | @@ -768,43 +721,30 @@ |
1861 | def test_deletePillarGranteeInvisibleInformationTypes(self): |
1862 | # Deleting a pillar grantee returns the resulting invisible info types. |
1863 | product = self.factory.makeProduct() |
1864 | - with FeatureFixture(WRITE_FLAG): |
1865 | - with admin_logged_in(): |
1866 | - invisible_information_types = self.service.deletePillarGrantee( |
1867 | - product, product.owner, product.owner) |
1868 | + with admin_logged_in(): |
1869 | + invisible_information_types = self.service.deletePillarGrantee( |
1870 | + product, product.owner, product.owner) |
1871 | self.assertContentEqual( |
1872 | ['Private', 'Private Security'], invisible_information_types) |
1873 | |
1874 | def _assert_deletePillarGranteeUnauthorized(self, pillar): |
1875 | # deletePillarGrantee raises an Unauthorized exception if the user |
1876 | # is not permitted to do so. |
1877 | - with FeatureFixture(WRITE_FLAG): |
1878 | - self.assertRaises( |
1879 | - Unauthorized, self.service.deletePillarGrantee, |
1880 | - pillar, pillar.owner, pillar.owner, [InformationType.USERDATA]) |
1881 | + self.assertRaises( |
1882 | + Unauthorized, self.service.deletePillarGrantee, |
1883 | + pillar, pillar.owner, pillar.owner, [InformationType.USERDATA]) |
1884 | |
1885 | def test_deletePillarGranteeAnonymous(self): |
1886 | # Anonymous users are not allowed. |
1887 | - with FeatureFixture(WRITE_FLAG): |
1888 | - product = self.factory.makeProduct() |
1889 | - login(ANONYMOUS) |
1890 | - self._assert_deletePillarGranteeUnauthorized(product) |
1891 | + product = self.factory.makeProduct() |
1892 | + login(ANONYMOUS) |
1893 | + self._assert_deletePillarGranteeUnauthorized(product) |
1894 | |
1895 | def test_deletePillarGranteeAnyone(self): |
1896 | # Unauthorized users are not allowed. |
1897 | - with FeatureFixture(WRITE_FLAG): |
1898 | - product = self.factory.makeProduct() |
1899 | - login_person(self.factory.makePerson()) |
1900 | - self._assert_deletePillarGranteeUnauthorized(product) |
1901 | - |
1902 | - def test_deletePillarGrantee_without_flag(self): |
1903 | - # The feature flag needs to be enabled. |
1904 | - owner = self.factory.makePerson() |
1905 | - product = self.factory.makeProduct(owner=owner) |
1906 | - login_person(owner) |
1907 | - self.assertRaises( |
1908 | - Unauthorized, self.service.deletePillarGrantee, |
1909 | - product, product.owner, product.owner, [InformationType.USERDATA]) |
1910 | + product = self.factory.makeProduct() |
1911 | + login_person(self.factory.makePerson()) |
1912 | + self._assert_deletePillarGranteeUnauthorized(product) |
1913 | |
1914 | def _assert_deleteGranteeRemoveSubscriptions(self, |
1915 | types_to_delete=None): |
1916 | @@ -839,9 +779,8 @@ |
1917 | bug.subscribe(person, product.owner) |
1918 | |
1919 | # Delete data for specified information types or all. |
1920 | - with FeatureFixture(WRITE_FLAG): |
1921 | - self.service.deletePillarGrantee( |
1922 | - product, grantee, product.owner, types_to_delete) |
1923 | + self.service.deletePillarGrantee( |
1924 | + product, grantee, product.owner, types_to_delete) |
1925 | with block_on_job(self): |
1926 | transaction.commit() |
1927 | |
1928 | @@ -907,9 +846,8 @@ |
1929 | apgfs = getUtility(IAccessPolicyGrantFlatSource) |
1930 | self.assertEqual(1, grants.count()) |
1931 | |
1932 | - with FeatureFixture(WRITE_FLAG): |
1933 | - self.service.revokeAccessGrants( |
1934 | - pillar, grantee, pillar.owner, bugs=bugs, branches=branches) |
1935 | + self.service.revokeAccessGrants( |
1936 | + pillar, grantee, pillar.owner, bugs=bugs, branches=branches) |
1937 | with block_on_job(self): |
1938 | transaction.commit() |
1939 | |
1940 | @@ -993,10 +931,8 @@ |
1941 | for bug in bugs or []: |
1942 | self.assertIn(person, bug.getDirectSubscribers()) |
1943 | |
1944 | - with FeatureFixture(WRITE_FLAG): |
1945 | - self.service.revokeAccessGrants( |
1946 | - pillar, team_grantee, pillar.owner, |
1947 | - bugs=bugs, branches=branches) |
1948 | + self.service.revokeAccessGrants( |
1949 | + pillar, team_grantee, pillar.owner, bugs=bugs, branches=branches) |
1950 | with block_on_job(self): |
1951 | transaction.commit() |
1952 | |
1953 | @@ -1042,43 +978,27 @@ |
1954 | bug = self.factory.makeBug( |
1955 | target=product, information_type=InformationType.USERDATA) |
1956 | grantee = self.factory.makePerson() |
1957 | - with FeatureFixture(WRITE_FLAG): |
1958 | - self.assertRaises( |
1959 | - Unauthorized, self.service.revokeAccessGrants, |
1960 | - product, grantee, product.owner, bugs=[bug]) |
1961 | + self.assertRaises( |
1962 | + Unauthorized, self.service.revokeAccessGrants, |
1963 | + product, grantee, product.owner, bugs=[bug]) |
1964 | |
1965 | def test_revokeAccessGrantsAnonymous(self): |
1966 | # Anonymous users are not allowed. |
1967 | - with FeatureFixture(WRITE_FLAG): |
1968 | - login(ANONYMOUS) |
1969 | - self._assert_revokeAccessGrantsUnauthorized() |
1970 | + login(ANONYMOUS) |
1971 | + self._assert_revokeAccessGrantsUnauthorized() |
1972 | |
1973 | def test_revokeAccessGrantsAnyone(self): |
1974 | # Unauthorized users are not allowed. |
1975 | - with FeatureFixture(WRITE_FLAG): |
1976 | - login_person(self.factory.makePerson()) |
1977 | - self._assert_revokeAccessGrantsUnauthorized() |
1978 | - |
1979 | - def test_revokeAccessGrants_without_flag(self): |
1980 | - # The feature flag needs to be enabled. |
1981 | - owner = self.factory.makePerson() |
1982 | - product = self.factory.makeProduct(owner=owner) |
1983 | - bug = self.factory.makeBug( |
1984 | - target=product, information_type=InformationType.USERDATA) |
1985 | - grantee = self.factory.makePerson() |
1986 | - login_person(owner) |
1987 | - self.assertRaises( |
1988 | - Unauthorized, self.service.revokeAccessGrants, |
1989 | - product, grantee, product.owner, bugs=[bug]) |
1990 | + login_person(self.factory.makePerson()) |
1991 | + self._assert_revokeAccessGrantsUnauthorized() |
1992 | |
1993 | def _assert_ensureAccessGrants(self, user, bugs, branches, |
1994 | grantee=None): |
1995 | # Creating access grants works as expected. |
1996 | if not grantee: |
1997 | grantee = self.factory.makePerson() |
1998 | - with FeatureFixture(WRITE_FLAG): |
1999 | - self.service.ensureAccessGrants( |
2000 | - [grantee], user, bugs=bugs, branches=branches) |
2001 | + self.service.ensureAccessGrants( |
2002 | + [grantee], user, bugs=bugs, branches=branches) |
2003 | |
2004 | # Check that grantee has expected access grants. |
2005 | shared_bugs = [] |
2006 | @@ -1133,8 +1053,7 @@ |
2007 | information_type=InformationType.USERDATA) |
2008 | # Create an existing access grant. |
2009 | grantee = self.factory.makePerson() |
2010 | - with FeatureFixture(WRITE_FLAG): |
2011 | - self.service.ensureAccessGrants([grantee], owner, bugs=[bug]) |
2012 | + self.service.ensureAccessGrants([grantee], owner, bugs=[bug]) |
2013 | # Test with a new bug as well as the one for which access is already |
2014 | # granted. |
2015 | self._assert_ensureAccessGrants(owner, [bug, bug2], None, grantee) |
2016 | @@ -1146,35 +1065,20 @@ |
2017 | bug = self.factory.makeBug( |
2018 | target=product, information_type=InformationType.USERDATA) |
2019 | grantee = self.factory.makePerson() |
2020 | - with FeatureFixture(WRITE_FLAG): |
2021 | - self.assertRaises( |
2022 | - Unauthorized, self.service.ensureAccessGrants, |
2023 | - [grantee], user, bugs=[bug]) |
2024 | + self.assertRaises( |
2025 | + Unauthorized, self.service.ensureAccessGrants, [grantee], user, |
2026 | + bugs=[bug]) |
2027 | |
2028 | def test_ensureAccessGrantsAnonymous(self): |
2029 | # Anonymous users are not allowed. |
2030 | - with FeatureFixture(WRITE_FLAG): |
2031 | - login(ANONYMOUS) |
2032 | - self._assert_ensureAccessGrantsUnauthorized(ANONYMOUS) |
2033 | + login(ANONYMOUS) |
2034 | + self._assert_ensureAccessGrantsUnauthorized(ANONYMOUS) |
2035 | |
2036 | def test_ensureAccessGrantsAnyone(self): |
2037 | # Unauthorized users are not allowed. |
2038 | - with FeatureFixture(WRITE_FLAG): |
2039 | - anyone = self.factory.makePerson() |
2040 | - login_person(anyone) |
2041 | - self._assert_ensureAccessGrantsUnauthorized(anyone) |
2042 | - |
2043 | - def test_ensureAccessGrants_without_flag(self): |
2044 | - # The feature flag needs to be enabled. |
2045 | - owner = self.factory.makePerson() |
2046 | - product = self.factory.makeProduct(owner=owner) |
2047 | - bug = self.factory.makeBug( |
2048 | - target=product, information_type=InformationType.USERDATA) |
2049 | - grantee = self.factory.makePerson() |
2050 | - login_person(owner) |
2051 | - self.assertRaises( |
2052 | - Unauthorized, self.service.ensureAccessGrants, |
2053 | - [grantee], product.owner, bugs=[bug]) |
2054 | + anyone = self.factory.makePerson() |
2055 | + login_person(anyone) |
2056 | + self._assert_ensureAccessGrantsUnauthorized(anyone) |
2057 | |
2058 | def test_getSharedArtifacts(self): |
2059 | # Test the getSharedArtifacts method. |
2060 | @@ -1385,14 +1289,13 @@ |
2061 | right_person = self.factory.makePerson() |
2062 | right_team = self.factory.makeTeam(members=[right_person]) |
2063 | wrong_person = self.factory.makePerson() |
2064 | - with FeatureFixture(WRITE_FLAG): |
2065 | - with admin_logged_in(): |
2066 | - self.service.sharePillarInformation( |
2067 | - product, right_team, product.owner, |
2068 | - {InformationType.USERDATA: SharingPermission.ALL}) |
2069 | - self.service.sharePillarInformation( |
2070 | - product, wrong_person, product.owner, |
2071 | - {InformationType.PRIVATESECURITY: SharingPermission.ALL}) |
2072 | + with admin_logged_in(): |
2073 | + self.service.sharePillarInformation( |
2074 | + product, right_team, product.owner, |
2075 | + {InformationType.USERDATA: SharingPermission.ALL}) |
2076 | + self.service.sharePillarInformation( |
2077 | + product, wrong_person, product.owner, |
2078 | + {InformationType.PRIVATESECURITY: SharingPermission.ALL}) |
2079 | self.assertEqual( |
2080 | False, |
2081 | self.service.checkPillarAccess( |
2082 | @@ -1415,11 +1318,10 @@ |
2083 | # an information type. |
2084 | product = self.factory.makeProduct() |
2085 | grantee = self.factory.makePerson() |
2086 | - with FeatureFixture(WRITE_FLAG): |
2087 | - with admin_logged_in(): |
2088 | - self.service.sharePillarInformation( |
2089 | - product, grantee, product.owner, |
2090 | - {InformationType.USERDATA: SharingPermission.ALL}) |
2091 | + with admin_logged_in(): |
2092 | + self.service.sharePillarInformation( |
2093 | + product, grantee, product.owner, |
2094 | + {InformationType.USERDATA: SharingPermission.ALL}) |
2095 | # The owner is granted access on product creation. So we need to allow |
2096 | # for that in the check below. |
2097 | self.assertContentEqual( |
2098 | @@ -1431,10 +1333,9 @@ |
2099 | # checkPillarAccess checks whether the user has full access to |
2100 | # an information type. |
2101 | product = self.factory.makeProduct() |
2102 | - with FeatureFixture(WRITE_FLAG): |
2103 | - with admin_logged_in(): |
2104 | - self.service.deletePillarGrantee( |
2105 | - product, product.owner, product.owner) |
2106 | + with admin_logged_in(): |
2107 | + self.service.deletePillarGrantee( |
2108 | + product, product.owner, product.owner) |
2109 | self.assertContentEqual( |
2110 | [(InformationType.PRIVATESECURITY, 0), |
2111 | (InformationType.USERDATA, 0)], |
2112 | @@ -1499,14 +1400,13 @@ |
2113 | |
2114 | def _sharePillarInformation(self): |
2115 | pillar_uri = canonical_url(self.pillar, force_local_path=True) |
2116 | - with FeatureFixture(WRITE_FLAG): |
2117 | - return self._named_post( |
2118 | - 'sharePillarInformation', pillar=pillar_uri, |
2119 | - grantee=self.grantee_uri, |
2120 | - user=self.grantor_uri, |
2121 | - permissions={ |
2122 | - InformationType.USERDATA.title: |
2123 | - SharingPermission.ALL.title}) |
2124 | + return self._named_post( |
2125 | + 'sharePillarInformation', pillar=pillar_uri, |
2126 | + grantee=self.grantee_uri, |
2127 | + user=self.grantor_uri, |
2128 | + permissions={ |
2129 | + InformationType.USERDATA.title: |
2130 | + SharingPermission.ALL.title}) |
2131 | |
2132 | |
2133 | class TestLaunchpadlib(ApiTestMixin, TestCaseWithFactory): |
2134 | @@ -1518,9 +1418,6 @@ |
2135 | super(TestLaunchpadlib, self).setUp() |
2136 | self.launchpad = self.factory.makeLaunchpadService(person=self.owner) |
2137 | self.service = self.launchpad.load('+services/sharing') |
2138 | - flag = FeatureFixture(WRITE_FLAG) |
2139 | - flag.setUp() |
2140 | - self.addCleanup(flag.cleanUp) |
2141 | transaction.commit() |
2142 | self._sharePillarInformation() |
2143 | |
2144 | |
2145 | === modified file 'lib/lp/registry/tests/test_accesspolicy.py' |
2146 | --- lib/lp/registry/tests/test_accesspolicy.py 2012-08-08 07:22:51 +0000 |
2147 | +++ lib/lp/registry/tests/test_accesspolicy.py 2012-08-29 05:40:35 +0000 |
2148 | @@ -725,3 +725,20 @@ |
2149 | reconcile_access_for_artifact( |
2150 | bug, InformationType.USERDATA, [product]) |
2151 | self.assertPoliciesForBug([(product, InformationType.USERDATA)], bug) |
2152 | + |
2153 | + def test_raises_exception_on_missing_policies(self): |
2154 | + # reconcile_access_for_artifact raises an exception if a pillar is |
2155 | + # missing an AccessPolicy. |
2156 | + product = self.factory.makeProduct() |
2157 | + # Creating a product will have created two APs, delete them. |
2158 | + aps = getUtility(IAccessPolicySource).findByPillar([product]) |
2159 | + getUtility(IAccessPolicyGrantSource).revokeByPolicy(aps) |
2160 | + for ap in aps: |
2161 | + IStore(ap).remove(ap) |
2162 | + bug = self.factory.makeBug(target=product) |
2163 | + expected = ( |
2164 | + "Pillar(s) %s require an access policy for information type " |
2165 | + "Private.") % product.name |
2166 | + self.assertRaisesWithContent( |
2167 | + AssertionError, expected, reconcile_access_for_artifact, bug, |
2168 | + InformationType.USERDATA, [product]) |
2169 | |
2170 | === modified file 'lib/lp/registry/tests/test_teammembership.py' |
2171 | --- lib/lp/registry/tests/test_teammembership.py 2012-08-14 23:27:07 +0000 |
2172 | +++ lib/lp/registry/tests/test_teammembership.py 2012-08-29 05:40:35 +0000 |
2173 | @@ -504,7 +504,7 @@ |
2174 | The number of db queries should be constant not O(depth). |
2175 | """ |
2176 | self.assertStatementCount( |
2177 | - 7, |
2178 | + 9, |
2179 | self.team5.setMembershipData, self.no_priv, |
2180 | TeamMembershipStatus.DEACTIVATED, self.team5.teamowner) |
2181 | |
2182 | @@ -998,7 +998,6 @@ |
2183 | |
2184 | def setUp(self): |
2185 | self.useFixture(FeatureFixture({ |
2186 | - 'disclosure.unsubscribe_jobs.enabled': 'true', |
2187 | 'jobs.celery.enabled_classes': 'RemoveArtifactSubscriptionsJob', |
2188 | })) |
2189 | super(TestTeamMembershipJobs, self).setUp() |
2190 | |
2191 | === modified file 'lib/lp/services/features/flags.py' |
2192 | --- lib/lp/services/features/flags.py 2012-08-14 18:51:43 +0000 |
2193 | +++ lib/lp/services/features/flags.py 2012-08-29 05:40:35 +0000 |
2194 | @@ -197,12 +197,6 @@ |
2195 | '', |
2196 | '', |
2197 | ''), |
2198 | - ('disclosure.add-team-person-picker.enabled', |
2199 | - 'boolean', |
2200 | - 'Allows users to add a new team directly from the person picker.', |
2201 | - '', |
2202 | - '', |
2203 | - ''), |
2204 | ('bugs.autoconfirm.enabled_distribution_names', |
2205 | 'space delimited', |
2206 | ('Enables auto-confirming bugtasks for distributions (and their ' |
2207 | @@ -233,34 +227,6 @@ |
2208 | '', |
2209 | '', |
2210 | ''), |
2211 | - ('disclosure.enhanced_sharing.enabled', |
2212 | - 'boolean', |
2213 | - ('If true, will allow the use of the new sharing view and apis used ' |
2214 | - 'for the new disclosure data model to view but not write data.'), |
2215 | - '', |
2216 | - 'Sharing overview', |
2217 | - ''), |
2218 | - ('disclosure.enhanced_sharing_details.enabled', |
2219 | - 'boolean', |
2220 | - ('If true, enables the details page for viewing the `Some` things that' |
2221 | - 'shared with a user or team.'), |
2222 | - '', |
2223 | - '', |
2224 | - ''), |
2225 | - ('disclosure.enhanced_sharing.writable', |
2226 | - 'boolean', |
2227 | - ('If true, will allow the use of the new sharing view and apis used ' |
2228 | - 'to edit the new disclosure data model.'), |
2229 | - '', |
2230 | - 'Sharing management', |
2231 | - ''), |
2232 | - ('disclosure.unsubscribe_jobs.enabled', |
2233 | - 'boolean', |
2234 | - ('If true, the jobs to unsubscribe users who lose access to bugs' |
2235 | - 'and branches are run.'), |
2236 | - '', |
2237 | - '', |
2238 | - ''), |
2239 | ('registry.upcoming_work_view.enabled', |
2240 | 'boolean', |
2241 | ('If true, the new upcoming work view of teams is available.'), |
2242 | |
2243 | === modified file 'lib/lp/testing/factory.py' |
2244 | --- lib/lp/testing/factory.py 2012-08-21 04:28:11 +0000 |
2245 | +++ lib/lp/testing/factory.py 2012-08-29 05:40:35 +0000 |
2246 | @@ -1688,6 +1688,8 @@ |
2247 | target = series.pillar |
2248 | else: |
2249 | target = self.makeProduct() |
2250 | + if information_type == InformationType.PROPRIETARY: |
2251 | + self.makeAccessPolicy(pillar=target) |
2252 | if IDistributionSourcePackage.providedBy(target): |
2253 | self.makeSourcePackagePublishingHistory( |
2254 | distroseries=target.distribution.currentseries, |
2255 | |
2256 | === modified file 'versions.cfg' |
2257 | --- versions.cfg 2012-08-23 02:40:39 +0000 |
2258 | +++ versions.cfg 2012-08-29 05:40:35 +0000 |
2259 | @@ -10,7 +10,7 @@ |
2260 | argparse = 1.2.1 |
2261 | auditor = 0.0.3 |
2262 | auditorclient = 0.0.2 |
2263 | -auditorfixture = 0.0.4 |
2264 | +auditorfixture = 0.0.5 |
2265 | BeautifulSoup = 3.1.0.1 |
2266 | bson = 0.3.2 |
2267 | # The source for this version of bzr is at lp:~benji/bzr/bug-998040 |