Merge lp:~deryck/launchpad/top-level-bug-search-and-privacy-1069895 into lp:launchpad

Proposed by Deryck Hodge on 2012-11-13
Status: Merged
Approved by: Deryck Hodge on 2012-11-13
Approved revision: no longer in the source branch.
Merged at revision: 16275
Proposed branch: lp:~deryck/launchpad/top-level-bug-search-and-privacy-1069895
Merge into: lp:launchpad
Diff against target: 226 lines (+70/-18)
6 files modified
lib/lp/bugs/browser/tests/test_bugs.py (+34/-4)
lib/lp/bugs/browser/tests/test_bugtask.py (+5/-5)
lib/lp/bugs/model/bugtasksearch.py (+17/-1)
lib/lp/registry/browser/tests/test_milestone.py (+6/-6)
lib/lp/registry/browser/tests/test_pillar_sharing.py (+1/-1)
lib/lp/registry/model/product.py (+7/-1)
To merge this branch: bzr merge lp:~deryck/launchpad/top-level-bug-search-and-privacy-1069895
Reviewer Review Type Date Requested Status
Richard Harding (community) 2012-11-13 Approve on 2012-11-13
Review via email: mp+134164@code.launchpad.net

Commit Message

Ensure that bugtask search accounts for whether or not any related products are private, which will keep the top-level bugs homepage from erroring out if there are any public bugs against proprietary or embargoed projects.

Description of the Change

This branch fixes bug 1069895 by ensuring that any bugtask search results that is a mix of public bugs with those that have private projects will exclude bugs with private projects. This is kind of an odd situation to be in and we have other work in progress to prevent public bugs on proprietary projects but this fix will make sure we don't error (especially on the top-level bugs homepage) if we get into this kind of mixed data situation.

The fix is pretty straight forward. It amounts to updating the bugtask search query to account for private projects. There is also a test to ensure the specific condition we want to avoid is safe. Finally, a handful of bugtask search query count tests had to be updated. I'm honestly at a loss for how these counts could change, given that I'm adding a join to an existing query. I was worried that maybe I was returning more data than originally and some loop over the data was doing extra queries. But some of these tests here even assert that the expected number of bugtasks are returned, and that the size of the queries doesn't change as more data is added.

So I'm not sure what's up here, but it should be safe, given that it's only adding a query or two in each case.

To post a comment you must log in.
Richard Harding (rharding) wrote :

#58/59

Do you mean to be asserting that the factory is working by checking the information type/title on bug?

I thought in the stand up it was brought that the the query counts should be exact so that you can know when you changed/broke something that reduced the query count as well. This appears to continue using the 'less than' approach.

#141

For my FYI: can we have a bugtask without a product? This struck me as odd.

review: Approve
Deryck Hodge (deryck) wrote :

Thanks for the review. #58/59 is a mistake. It's a hold over from an earlier version of the test. I'll drop that line. Good catch!

And #141, yes, you can indeed have a bugtask without a product. It can be against a distro source package, the distro itself, and so on. Any context other than a product wouldn't have a product.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/tests/test_bugs.py'
2--- lib/lp/bugs/browser/tests/test_bugs.py 2012-10-08 06:13:17 +0000
3+++ lib/lp/bugs/browser/tests/test_bugs.py 2012-11-13 19:36:22 +0000
4@@ -6,7 +6,6 @@
5 __metaclass__ = type
6
7 from zope.component import getUtility
8-from zope.security.proxy import removeSecurityProxy
9
10 from lp.app.enums import InformationType
11 from lp.bugs.interfaces.bugtask import BugTaskStatus
12@@ -16,16 +15,19 @@
13 from lp.registry.interfaces.product import License
14 from lp.services.webapp.publisher import canonical_url
15 from lp.testing import (
16+ BrowserTestCase,
17 celebrity_logged_in,
18 person_logged_in,
19- TestCaseWithFactory,
20 )
21 from lp.testing.layers import DatabaseFunctionalLayer
22-from lp.testing.pages import find_tag_by_id
23+from lp.testing.pages import (
24+ find_tag_by_id,
25+ setupBrowserForUser,
26+ )
27 from lp.testing.views import create_initialized_view
28
29
30-class TestMaloneView(TestCaseWithFactory):
31+class TestMaloneView(BrowserTestCase):
32 """Test the MaloneView for the Bugs application."""
33 layer = DatabaseFunctionalLayer
34
35@@ -104,6 +106,34 @@
36 # we should get some valid content out of this
37 self.assertIn('Search all bugs', content)
38
39+ def test_search_bugs_with_proprietary_product(self):
40+ owner = self.factory.makePerson()
41+ anon_user = self.factory.makePerson()
42+ information_type = InformationType.PROPRIETARY
43+ product = self.factory.makeProduct(
44+ owner=owner, information_type=information_type)
45+ title = 'huhdwudwyhbdwhbdwhbwdwhb'
46+ query_string = (
47+ '?field.searchtext=%s&search=Search+Bug+Reports'
48+ '&field.scope=all&field.scope.target=' % title)
49+ base_url = canonical_url(
50+ self.application, view_name='+bugs', rootsite='bugs')
51+ url = '%s%s' % (base_url, query_string)
52+ with person_logged_in(owner):
53+ product.setBugSharingPolicy(
54+ BugSharingPolicy.PROPRIETARY_OR_PUBLIC)
55+ bug = self.factory.makeBug(
56+ target=product, information_type=InformationType.PUBLIC,
57+ title=title)
58+ self.assertEqual(bug.information_type, InformationType.PUBLIC)
59+ with person_logged_in(anon_user):
60+ browser = setupBrowserForUser(user=anon_user)
61+ browser.open(url)
62+ # It doesn't matter what we assert here. The test is really
63+ # just to confirm the browser.open call above didn't raise
64+ # an Unauthorized error.
65+ self.assertIn('No results for search', browser.contents)
66+
67 def _assert_getBugData(self, related_bug=None):
68 # The getBugData method works as expected.
69 owner = self.factory.makePerson()
70
71=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
72--- lib/lp/bugs/browser/tests/test_bugtask.py 2012-10-15 09:15:29 +0000
73+++ lib/lp/bugs/browser/tests/test_bugtask.py 2012-11-13 19:36:22 +0000
74@@ -141,7 +141,7 @@
75 # "SELECT id, product, project, distribution FROM PillarName ..."
76 # query by previously browsing the task url, in which case the
77 # query count is decreased by one.
78- self.assertThat(recorder, HasQueryCount(LessThan(82)))
79+ self.assertThat(recorder, HasQueryCount(LessThan(85)))
80 count_with_no_teams = recorder.count
81 # count with many teams
82 self.invalidate_caches(task)
83@@ -157,7 +157,7 @@
84 def test_rendered_query_counts_constant_with_attachments(self):
85 with celebrity_logged_in('admin'):
86 browses_under_limit = BrowsesWithQueryLimit(
87- 85, self.factory.makePerson())
88+ 87, self.factory.makePerson())
89
90 # First test with a single attachment.
91 task = self.factory.makeBugTask()
92@@ -251,7 +251,7 @@
93 # Render the view with one activity.
94 with celebrity_logged_in('admin'):
95 browses_under_limit = BrowsesWithQueryLimit(
96- 83, self.factory.makePerson())
97+ 85, self.factory.makePerson())
98 person = self.factory.makePerson()
99 add_activity("description", person)
100
101@@ -2036,10 +2036,10 @@
102 self.invalidate_caches(bug)
103 # count with single task
104 self.getUserBrowser(url)
105- self.assertThat(recorder, HasQueryCount(LessThan(35)))
106+ self.assertThat(recorder, HasQueryCount(LessThan(37)))
107 # count with many tasks
108 self.getUserBrowser(buggy_url)
109- self.assertThat(recorder, HasQueryCount(LessThan(35)))
110+ self.assertThat(recorder, HasQueryCount(LessThan(37)))
111
112 def test_mustache_model_in_json(self):
113 """The IJSONRequestCache should contain mustache_model.
114
115=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
116--- lib/lp/bugs/model/bugtasksearch.py 2012-10-25 06:59:34 +0000
117+++ lib/lp/bugs/model/bugtasksearch.py 2012-11-13 19:36:22 +0000
118@@ -671,10 +671,26 @@
119 Milestone.dateexpected <= dateexpected_before)
120
121 if not params.ignore_privacy:
122- clause, decorator = _get_bug_privacy_filter_with_decorator(params.user)
123+ clause, decorator = _get_bug_privacy_filter_with_decorator(
124+ params.user)
125 if clause:
126 extra_clauses.append(clause)
127 decorators.append(decorator)
128+ cross_context_search = (
129+ params.product is None
130+ and params.project is None
131+ and params.distribution is None
132+ and params.productseries is None
133+ and params.distroseries is None)
134+ if params.product is not None or cross_context_search:
135+ if Product not in clauseTables:
136+ clauseTables.append(Product)
137+ extra_clauses.append(
138+ Or(
139+ BugTaskFlat.product_id == Product.id,
140+ BugTaskFlat.product_id == None))
141+ extra_clauses.append(
142+ ProductSet.getProductPrivacyFilter(params.user))
143
144 hw_clause = _build_hardware_related_clause(params)
145 if hw_clause is not None:
146
147=== modified file 'lib/lp/registry/browser/tests/test_milestone.py'
148--- lib/lp/registry/browser/tests/test_milestone.py 2012-10-26 09:01:27 +0000
149+++ lib/lp/registry/browser/tests/test_milestone.py 2012-11-13 19:36:22 +0000
150@@ -327,12 +327,12 @@
151 # 10. All related people
152 bugtask_count = 10
153 self.assert_bugtasks_query_count(
154- self.milestone, bugtask_count, query_limit=11)
155+ self.milestone, bugtask_count, query_limit=12)
156
157 def test_milestone_eager_loading(self):
158 # Verify that the number of queries does not increase with more
159 # bugs with different assignees.
160- browses_under_limit = BrowsesWithQueryLimit(36, self.owner)
161+ browses_under_limit = BrowsesWithQueryLimit(37, self.owner)
162 self.add_bug(3)
163 self.assertThat(self.milestone, browses_under_limit)
164 self.add_bug(10)
165@@ -450,7 +450,7 @@
166 # 10. All related people.
167 bugtask_count = 10
168 self.assert_bugtasks_query_count(
169- self.milestone, bugtask_count, query_limit=11)
170+ self.milestone, bugtask_count, query_limit=12)
171
172 def test_milestone_eager_loading(self):
173 # Verify that the number of queries does not increase with more
174@@ -509,12 +509,12 @@
175 # 9. Load links to milestones.
176 bugtask_count = 10
177 self.assert_bugtasks_query_count(
178- self.milestone, bugtask_count, query_limit=11)
179+ self.milestone, bugtask_count, query_limit=12)
180
181 def test_milestone_eager_loading(self):
182 # Verify that the number of queries does not increase with more
183 # bugs with different assignees.
184- browses_under_limit = BrowsesWithQueryLimit(35, self.owner)
185+ browses_under_limit = BrowsesWithQueryLimit(36, self.owner)
186 self.add_bug(4)
187 self.assertThat(self.milestone, browses_under_limit)
188 self.add_bug(10)
189@@ -588,4 +588,4 @@
190 # bugtasks retrieval.
191 bugtask_count = 10
192 self.assert_bugtasks_query_count(
193- self.milestonetag, bugtask_count, query_limit=11)
194+ self.milestonetag, bugtask_count, query_limit=12)
195
196=== modified file 'lib/lp/registry/browser/tests/test_pillar_sharing.py'
197--- lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-10-09 10:28:02 +0000
198+++ lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-11-13 19:36:22 +0000
199@@ -215,7 +215,7 @@
200 IStore(self.pillar).invalidate()
201 with StormStatementRecorder() as recorder:
202 create_initialized_view(pillarperson, '+index')
203- self.assertThat(recorder, HasQueryCount(LessThan(13)))
204+ self.assertThat(recorder, HasQueryCount(LessThan(14)))
205
206
207 class TestProductSharingDetailsView(
208
209=== modified file 'lib/lp/registry/model/product.py'
210--- lib/lp/registry/model/product.py 2012-11-12 11:21:14 +0000
211+++ lib/lp/registry/model/product.py 2012-11-13 19:36:22 +0000
212@@ -1715,7 +1715,13 @@
213 @staticmethod
214 def getProductPrivacyFilter(user):
215 if user is not None:
216- roles = IPersonRoles(user)
217+ # Generally our `user` here is a Person object,
218+ # but sometimes it's a PersonRoles object.
219+ if IPersonRoles.providedBy(user):
220+ roles = user
221+ user = user.person
222+ else:
223+ roles = IPersonRoles(user)
224 if roles.in_admin or roles.in_commercial_admin:
225 return True
226 granted_products = And(