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
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2011-02-23 19:22:41 +0000
+++ lib/lp/bugs/model/bugtask.py 2011-02-25 14:55:14 +0000
@@ -1923,7 +1923,9 @@
1923 ssub_match_milestone)1923 ssub_match_milestone)
19241924
1925 join_tables.append(1925 join_tables.append(
1926 (Product, LeftJoin(Product, BugTask.productID == Product.id)))1926 (Product, LeftJoin(Product, And(
1927 BugTask.productID == Product.id,
1928 Product.active))))
1927 join_tables.append(1929 join_tables.append(
1928 (StructuralSubscription,1930 (StructuralSubscription,
1929 Join(StructuralSubscription, join_clause)))1931 Join(StructuralSubscription, join_clause)))
@@ -1932,6 +1934,28 @@
1932 % sqlvalues(params.structural_subscriber))1934 % sqlvalues(params.structural_subscriber))
1933 has_duplicate_results = True1935 has_duplicate_results = True
19341936
1937
1938 # Remove bugtasks from deactivated products, if necessary.
1939 # We don't have to do this if
1940 # 1) We're searching on bugtasks for a specific product
1941 # 2) We're searching on bugtasks for a specific productseries
1942 # 3) We're searching on bugtasks for a distribution
1943 # 4) We're searching for bugtasks for a distroseries
1944 # because in those instances we don't have arbitrary products which
1945 # may be deactivated showing up in our search.
1946 if (params.product is None and
1947 params.distribution is None and
1948 params.productseries is None and
1949 params.distroseries is None):
1950 # Prevent circular import problems.
1951 from lp.registry.model.product import Product
1952 extra_clauses.append(
1953 "(Bugtask.product IS NULL OR Product.active = TRUE)")
1954 join_tables.append(
1955 (Product, LeftJoin(Product, And(
1956 BugTask.productID == Product.id,
1957 Product.active))))
1958
1935 if params.component:1959 if params.component:
1936 clauseTables += [SourcePackagePublishingHistory,1960 clauseTables += [SourcePackagePublishingHistory,
1937 SourcePackageRelease]1961 SourcePackageRelease]
@@ -2099,7 +2123,6 @@
2099 if not decorators:2123 if not decorators:
2100 decorator = lambda x: x2124 decorator = lambda x: x
2101 else:2125 else:
2102
2103 def decorator(obj):2126 def decorator(obj):
2104 for decor in decorators:2127 for decor in decorators:
2105 obj = decor(obj)2128 obj = decor(obj)
@@ -2236,7 +2259,8 @@
2236 AND MessageChunk.fti @@ ftq(%s))""" % searchtext_quoted2259 AND MessageChunk.fti @@ ftq(%s))""" % searchtext_quoted
2237 text_search_clauses = [2260 text_search_clauses = [
2238 "Bug.fti @@ ftq(%s)" % searchtext_quoted,2261 "Bug.fti @@ ftq(%s)" % searchtext_quoted,
2239 "BugTask.fti @@ ftq(%s)" % searchtext_quoted]2262 "BugTask.fti @@ ftq(%s)" % searchtext_quoted,
2263 ]
2240 no_targetnamesearch = bool(features.getFeatureFlag(2264 no_targetnamesearch = bool(features.getFeatureFlag(
2241 'malone.disable_targetnamesearch'))2265 'malone.disable_targetnamesearch'))
2242 if not no_targetnamesearch:2266 if not no_targetnamesearch:
@@ -2374,8 +2398,9 @@
2374 origin = [BugTask]2398 origin = [BugTask]
2375 already_joined = set(origin)2399 already_joined = set(origin)
2376 for table, join in join_tables:2400 for table, join in join_tables:
2377 origin.append(join)2401 if table not in already_joined:
2378 already_joined.add(table)2402 origin.append(join)
2403 already_joined.add(table)
2379 for table, join in prejoin_tables:2404 for table, join in prejoin_tables:
2380 if table not in already_joined:2405 if table not in already_joined:
2381 origin.append(join)2406 origin.append(join)
23822407
=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py 2011-02-17 03:47:28 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py 2011-02-25 14:55:14 +0000
@@ -14,8 +14,6 @@
14from storm.store import Store14from storm.store import Store
15from testtools.matchers import (15from testtools.matchers import (
16 Equals,16 Equals,
17 LessThan,
18 Not,
19 )17 )
20from zope.component import getUtility18from zope.component import getUtility
2119
@@ -24,7 +22,10 @@
24 any,22 any,
25 greater_than,23 greater_than,
26 )24 )
27from canonical.testing.layers import LaunchpadFunctionalLayer25from canonical.testing.layers import (
26 LaunchpadFunctionalLayer,
27 DatabaseFunctionalLayer,
28 )
28from lp.bugs.interfaces.bugattachment import BugAttachmentType29from lp.bugs.interfaces.bugattachment import BugAttachmentType
29from lp.bugs.interfaces.bugtask import (30from lp.bugs.interfaces.bugtask import (
30 BugBlueprintSearch,31 BugBlueprintSearch,
@@ -532,6 +533,41 @@
532 self.assertSearchFinds(params, self.bugtasks[:1])533 self.assertSearchFinds(params, self.bugtasks[:1])
533534
534535
536class DeactivatedProductBugTaskTestCase(TestCaseWithFactory):
537
538 layer = DatabaseFunctionalLayer
539
540 def setUp(self):
541 super(DeactivatedProductBugTaskTestCase, self).setUp()
542 self.person = self.factory.makePerson()
543 self.active_product = self.factory.makeProduct()
544 self.inactive_product = self.factory.makeProduct()
545 bug = self.factory.makeBug(
546 product=self.active_product,
547 description="Monkeys are bad.")
548 self.active_bugtask = self.factory.makeBugTask(
549 bug=bug,
550 target=self.active_product)
551 self.inactive_bugtask = self.factory.makeBugTask(
552 bug=bug,
553 target=self.inactive_product)
554 with person_logged_in(self.person):
555 self.active_bugtask.transitionToAssignee(self.person)
556 self.inactive_bugtask.transitionToAssignee(self.person)
557 admin = getUtility(IPersonSet).getByEmail('admin@canonical.com')
558 with person_logged_in(admin):
559 self.inactive_product.active = False
560
561 def test_deactivated_listings_not_seen(self):
562 # Someone without permission to see deactiveated projects does
563 # not see bugtasks for deactivated projects.
564 nopriv = getUtility(IPersonSet).getByEmail('no-priv@canonical.com')
565 bugtask_set = getUtility(IBugTaskSet)
566 param = BugTaskSearchParams(user=None, fast_searchtext='Monkeys')
567 results = bugtask_set.search(param, _noprejoins=True)
568 self.assertEqual([self.active_bugtask], list(results))
569
570
535class ProductAndDistributionTests:571class ProductAndDistributionTests:
536 """Tests which are useful for distributions and products."""572 """Tests which are useful for distributions and products."""
537573