Merge lp:~jcsackett/launchpad/bugs-in-deactivated-pillars into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 12473
Proposed branch: lp:~jcsackett/launchpad/bugs-in-deactivated-pillars
Merge into: lp:launchpad
Diff against target: 139 lines (+69/-8)
2 files modified
lib/lp/bugs/model/bugtask.py (+30/-5)
lib/lp/bugs/tests/test_bugtask_search.py (+39/-3)
To merge this branch: bzr merge lp:~jcsackett/launchpad/bugs-in-deactivated-pillars
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Launchpad code reviewers Pending
Review via email: mp+50000@code.launchpad.net

Commit message

[r=lifeless][bug=632847] Removes deactivated product bugtasks from bug search listings.

Description of the change

Summary
=======

Per bug 632847, bugtasks for deactivated projects can show up in search results. Users clicking on links for these bugtasks then get a 404. So for those searches that can return arbitrary product related bugtasks, we strip out the deactivated products.

This is the second attempt at fixing this bug; the previous branch had to be rolled back as it introduced performance issues. (See https://code.launchpad.net/~jcsackett/launchpad/bugtasks-deacitvated-context-632847/+merge/48925)

Proposed
========

Remove the bugtasks with deactivated products when a search would return arbitrary product results (e.g. we're not searching for bugtasks on a particular product, distribution, &c).

Preimplementation
=================

Spoke with Deryck Hodge and Curtis Hovey on the intial branch.

Spoke with Robert Collins regarding performance issues in the previous branch that was rolled back.

Implementation
==============

A check in buildQuery in the bugtask search system determines if the search can return arbitrary products; if it can, it adds a Left Join to Product and a where clause to filter out bugtasks targeting a deactivated product.

Tests
=====

bin/test -t test_buglisting

Demo & QA
=========

Using the link provided in the bug (though switch to qastaging). You should no longer see the problematic bugtask in the search result, but should see the other bugtask for the bug in question.

Lint
====

make lint output:

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/browser/tests/test_buglisting.py
  lib/lp/bugs/model/bugtask.py

./lib/lp/bugs/model/bugtask.py
    2487: E301 expected 1 blank line, found 0

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

In https://bugs.launchpad.net/launchpad/+bug/421901/comments/14 we had a more complex join - we enforce the active flag in the join, which reduces the data brought into intermediary tables. Was there a particular reason for eliding that aspect here?

Revision history for this message
j.c.sackett (jcsackett) wrote :

> In https://bugs.launchpad.net/launchpad/+bug/421901/comments/14 we had a more
> complex join - we enforce the active flag in the join, which reduces the data
> brought into intermediary tables. Was there a particular reason for eliding
> that aspect here?

As I mentioned on IRC, the only reason was distraction in assembling the python code. The proper join has been pushed up.

Revision history for this message
Robert Collins (lifeless) wrote :

You can write this in storm:
       extra_clauses.append(Or(BugTask.productID==None, Product.active))

review: Approve
Revision history for this message
j.c.sackett (jcsackett) wrote :

> You can write this in storm:
> extra_clauses.append(Or(BugTask.productID==None, Product.active))

Thanks, Robert.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/model/bugtask.py'
2--- lib/lp/bugs/model/bugtask.py 2011-02-23 19:22:41 +0000
3+++ lib/lp/bugs/model/bugtask.py 2011-02-25 14:55:14 +0000
4@@ -1923,7 +1923,9 @@
5 ssub_match_milestone)
6
7 join_tables.append(
8- (Product, LeftJoin(Product, BugTask.productID == Product.id)))
9+ (Product, LeftJoin(Product, And(
10+ BugTask.productID == Product.id,
11+ Product.active))))
12 join_tables.append(
13 (StructuralSubscription,
14 Join(StructuralSubscription, join_clause)))
15@@ -1932,6 +1934,28 @@
16 % sqlvalues(params.structural_subscriber))
17 has_duplicate_results = True
18
19+
20+ # Remove bugtasks from deactivated products, if necessary.
21+ # We don't have to do this if
22+ # 1) We're searching on bugtasks for a specific product
23+ # 2) We're searching on bugtasks for a specific productseries
24+ # 3) We're searching on bugtasks for a distribution
25+ # 4) We're searching for bugtasks for a distroseries
26+ # because in those instances we don't have arbitrary products which
27+ # may be deactivated showing up in our search.
28+ if (params.product is None and
29+ params.distribution is None and
30+ params.productseries is None and
31+ params.distroseries is None):
32+ # Prevent circular import problems.
33+ from lp.registry.model.product import Product
34+ extra_clauses.append(
35+ "(Bugtask.product IS NULL OR Product.active = TRUE)")
36+ join_tables.append(
37+ (Product, LeftJoin(Product, And(
38+ BugTask.productID == Product.id,
39+ Product.active))))
40+
41 if params.component:
42 clauseTables += [SourcePackagePublishingHistory,
43 SourcePackageRelease]
44@@ -2099,7 +2123,6 @@
45 if not decorators:
46 decorator = lambda x: x
47 else:
48-
49 def decorator(obj):
50 for decor in decorators:
51 obj = decor(obj)
52@@ -2236,7 +2259,8 @@
53 AND MessageChunk.fti @@ ftq(%s))""" % searchtext_quoted
54 text_search_clauses = [
55 "Bug.fti @@ ftq(%s)" % searchtext_quoted,
56- "BugTask.fti @@ ftq(%s)" % searchtext_quoted]
57+ "BugTask.fti @@ ftq(%s)" % searchtext_quoted,
58+ ]
59 no_targetnamesearch = bool(features.getFeatureFlag(
60 'malone.disable_targetnamesearch'))
61 if not no_targetnamesearch:
62@@ -2374,8 +2398,9 @@
63 origin = [BugTask]
64 already_joined = set(origin)
65 for table, join in join_tables:
66- origin.append(join)
67- already_joined.add(table)
68+ if table not in already_joined:
69+ origin.append(join)
70+ already_joined.add(table)
71 for table, join in prejoin_tables:
72 if table not in already_joined:
73 origin.append(join)
74
75=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
76--- lib/lp/bugs/tests/test_bugtask_search.py 2011-02-17 03:47:28 +0000
77+++ lib/lp/bugs/tests/test_bugtask_search.py 2011-02-25 14:55:14 +0000
78@@ -14,8 +14,6 @@
79 from storm.store import Store
80 from testtools.matchers import (
81 Equals,
82- LessThan,
83- Not,
84 )
85 from zope.component import getUtility
86
87@@ -24,7 +22,10 @@
88 any,
89 greater_than,
90 )
91-from canonical.testing.layers import LaunchpadFunctionalLayer
92+from canonical.testing.layers import (
93+ LaunchpadFunctionalLayer,
94+ DatabaseFunctionalLayer,
95+ )
96 from lp.bugs.interfaces.bugattachment import BugAttachmentType
97 from lp.bugs.interfaces.bugtask import (
98 BugBlueprintSearch,
99@@ -532,6 +533,41 @@
100 self.assertSearchFinds(params, self.bugtasks[:1])
101
102
103+class DeactivatedProductBugTaskTestCase(TestCaseWithFactory):
104+
105+ layer = DatabaseFunctionalLayer
106+
107+ def setUp(self):
108+ super(DeactivatedProductBugTaskTestCase, self).setUp()
109+ self.person = self.factory.makePerson()
110+ self.active_product = self.factory.makeProduct()
111+ self.inactive_product = self.factory.makeProduct()
112+ bug = self.factory.makeBug(
113+ product=self.active_product,
114+ description="Monkeys are bad.")
115+ self.active_bugtask = self.factory.makeBugTask(
116+ bug=bug,
117+ target=self.active_product)
118+ self.inactive_bugtask = self.factory.makeBugTask(
119+ bug=bug,
120+ target=self.inactive_product)
121+ with person_logged_in(self.person):
122+ self.active_bugtask.transitionToAssignee(self.person)
123+ self.inactive_bugtask.transitionToAssignee(self.person)
124+ admin = getUtility(IPersonSet).getByEmail('admin@canonical.com')
125+ with person_logged_in(admin):
126+ self.inactive_product.active = False
127+
128+ def test_deactivated_listings_not_seen(self):
129+ # Someone without permission to see deactiveated projects does
130+ # not see bugtasks for deactivated projects.
131+ nopriv = getUtility(IPersonSet).getByEmail('no-priv@canonical.com')
132+ bugtask_set = getUtility(IBugTaskSet)
133+ param = BugTaskSearchParams(user=None, fast_searchtext='Monkeys')
134+ results = bugtask_set.search(param, _noprejoins=True)
135+ self.assertEqual([self.active_bugtask], list(results))
136+
137+
138 class ProductAndDistributionTests:
139 """Tests which are useful for distributions and products."""
140