launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02443
[Merge] lp:~allenap/launchpad/bugs-with-blueprints-bug-707103 into lp:launchpad
Gavin Panella has proposed merging lp:~allenap/launchpad/bugs-with-blueprints-bug-707103 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#707103 Unable to search for bugs that are attached to blueprints
https://bugs.launchpad.net/bugs/707103
For more details, see:
https://code.launchpad.net/~allenap/launchpad/bugs-with-blueprints-bug-707103/+merge/47866
Make it possible to search for bugs with or without linked Blueprints. I will update the advanced search in a subsequent branch; this branch just deals with the internal and webservice API.
--
https://code.launchpad.net/~allenap/launchpad/bugs-with-blueprints-bug-707103/+merge/47866
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/bugs-with-blueprints-bug-707103 into lp:launchpad.
=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py 2010-12-02 16:14:30 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py 2011-01-28 21:51:34 +0000
@@ -35,7 +35,6 @@
)
from lazr.restful.fields import Reference
from lazr.restful.interface import copy_field
-
from zope.interface import (
Attribute,
Interface,
@@ -52,6 +51,7 @@
from canonical.launchpad import _
from lp.bugs.interfaces.bugtask import (
+ BugBlueprintSearch,
BugBranchSearch,
BugTagsSearchCombinator,
IBugTask,
@@ -59,7 +59,8 @@
)
from lp.services.fields import Tag
-search_tasks_params_for_api_1_0 = {
+
+search_tasks_params_common = {
"order_by": List(
title=_('List of fields by which the results are ordered.'),
value_type=Text(),
@@ -80,7 +81,6 @@
"tags": copy_field(IBugTaskSearch['tag']),
"tags_combinator": copy_field(IBugTaskSearch['tags_combinator']),
"omit_duplicates": copy_field(IBugTaskSearch['omit_dupes']),
- "omit_targeted": copy_field(IBugTaskSearch['omit_targeted']),
"status_upstream": copy_field(IBugTaskSearch['status_upstream']),
"milestone_assignment": copy_field(
IBugTaskSearch['milestone_assignment']),
@@ -172,9 +172,21 @@
"date."),
required=False),
}
-search_tasks_params_for_api_devel = search_tasks_params_for_api_1_0.copy()
-search_tasks_params_for_api_devel["omit_targeted"] = copy_field(
- IBugTaskSearch['omit_targeted'], default=False)
+
+search_tasks_params_for_api_default = dict(
+ search_tasks_params_common,
+ omit_targeted=copy_field(
+ IBugTaskSearch['omit_targeted']))
+
+search_tasks_params_for_api_devel = dict(
+ search_tasks_params_common,
+ omit_targeted=copy_field(
+ IBugTaskSearch['omit_targeted'], default=False),
+ linked_blueprints=Choice(
+ title=_(
+ u"Search for bugs that are linked to blueprints or for "
+ u"bugs that are not linked to branches."),
+ vocabulary=BugBlueprintSearch, required=False))
class IHasBugs(Interface):
@@ -203,16 +215,21 @@
has_bugtasks = Attribute(
"True if a BugTask has ever been reported for this target.")
+ # searchTasks devel API declaration.
@call_with(search_params=None, user=REQUEST_USER)
@operation_parameters(**search_tasks_params_for_api_devel)
@operation_returns_collection_of(IBugTask)
@export_read_operation()
+
+ # Pop the *default* version (decorators are run last to first).
@operation_removed_in_version('devel')
+ # searchTasks default API declaration.
@call_with(search_params=None, user=REQUEST_USER)
- @operation_parameters(**search_tasks_params_for_api_1_0)
+ @operation_parameters(**search_tasks_params_for_api_default)
@operation_returns_collection_of(IBugTask)
@export_read_operation()
+
def searchTasks(search_params, user=None,
order_by=None, search_text=None,
status=None, importance=None,
@@ -232,8 +249,8 @@
hardware_owner_is_affected_by_bug=False,
hardware_owner_is_subscribed_to_bug=False,
hardware_is_linked_to_bug=False, linked_branches=None,
- structural_subscriber=None, modified_since=None,
- created_since=None, prejoins=[]):
+ linked_blueprints=None, structural_subscriber=None,
+ modified_since=None, created_since=None, prejoins=[]):
"""Search the IBugTasks reported on this entity.
:search_params: a BugTaskSearchParams object
=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py 2010-12-17 04:26:11 +0000
+++ lib/lp/bugs/interfaces/bugtask.py 2011-01-28 21:51:34 +0000
@@ -9,6 +9,7 @@
__all__ = [
'BUG_SUPERVISOR_BUGTASK_STATUSES',
+ 'BugBlueprintSearch',
'BugBranchSearch',
'BugTagsSearchCombinator',
'BugTaskImportance',
@@ -28,8 +29,6 @@
'IDistroBugTask',
'IDistroSeriesBugTask',
'IFrontPageBugTaskSearch',
- 'IllegalRelatedBugTasksParams',
- 'IllegalTarget',
'INominationsReviewTableBatchNavigator',
'INullBugTask',
'IPersonBugTaskSearch',
@@ -37,13 +36,16 @@
'IRemoveQuestionFromBugTaskForm',
'IUpstreamBugTask',
'IUpstreamProductBugTaskSearch',
+ 'IllegalRelatedBugTasksParams',
+ 'IllegalTarget',
'RESOLVED_BUGTASK_STATUSES',
'UNRESOLVED_BUGTASK_STATUSES',
'UserCannotEditBugTaskAssignee',
'UserCannotEditBugTaskImportance',
'UserCannotEditBugTaskMilestone',
'UserCannotEditBugTaskStatus',
- 'valid_remote_bug_url']
+ 'valid_remote_bug_url',
+ ]
from lazr.enum import (
DBEnumeratedType,
@@ -340,7 +342,7 @@
"""Bug branch search option.
The possible values to search for bugs having branches attached
- or not having branches attched.
+ or not having branches attached.
"""
ALL = Item("Show all bugs")
@@ -350,6 +352,20 @@
BUGS_WITHOUT_BRANCHES = Item("Show only Bugs without linked Branches")
+class BugBlueprintSearch(EnumeratedType):
+ """Bug blueprint search option.
+
+ The possible values to search for bugs having blueprints attached
+ or not having blueprints attached.
+ """
+
+ ALL = Item("Show all bugs")
+
+ BUGS_WITH_BLUEPRINTS = Item("Show only Bugs with linked Blueprints")
+
+ BUGS_WITHOUT_BLUEPRINTS = Item("Show only Bugs without linked Blueprints")
+
+
# XXX: Brad Bollenbach 2005-12-02 bugs=5320:
# In theory, INCOMPLETE belongs in UNRESOLVED_BUGTASK_STATUSES, but the
# semantics of our current reports would break if it were added to the
@@ -1144,9 +1160,9 @@
hardware_owner_is_affected_by_bug=False,
hardware_owner_is_subscribed_to_bug=False,
hardware_is_linked_to_bug=False,
- linked_branches=None, structural_subscriber=None,
- modified_since=None, created_since=None,
- exclude_conjoined_tasks=False):
+ linked_branches=None, linked_blueprints=None,
+ structural_subscriber=None, modified_since=None,
+ created_since=None, exclude_conjoined_tasks=False):
self.bug = bug
self.searchtext = searchtext
@@ -1189,6 +1205,7 @@
hardware_owner_is_subscribed_to_bug)
self.hardware_is_linked_to_bug = hardware_is_linked_to_bug
self.linked_branches = linked_branches
+ self.linked_blueprints = linked_blueprints
self.structural_subscriber = structural_subscriber
self.modified_since = modified_since
self.created_since = created_since
@@ -1265,8 +1282,8 @@
hardware_owner_is_affected_by_bug=False,
hardware_owner_is_subscribed_to_bug=False,
hardware_is_linked_to_bug=False, linked_branches=None,
- structural_subscriber=None, modified_since=None,
- created_since=None):
+ linked_blueprints=None, structural_subscriber=None,
+ modified_since=None, created_since=None):
"""Create and return a new instance using the parameter list."""
search_params = cls(user=user, orderby=order_by)
@@ -1332,7 +1349,8 @@
hardware_owner_is_subscribed_to_bug)
search_params.hardware_is_linked_to_bug = (
hardware_is_linked_to_bug)
- search_params.linked_branches=linked_branches
+ search_params.linked_branches = linked_branches
+ search_params.linked_blueprints = linked_blueprints
search_params.structural_subscriber = structural_subscriber
search_params.modified_since = modified_since
search_params.created_since = created_since
=== modified file 'lib/lp/bugs/model/bugtarget.py'
--- lib/lp/bugs/model/bugtarget.py 2010-11-30 21:39:25 +0000
+++ lib/lp/bugs/model/bugtarget.py 2011-01-28 21:51:34 +0000
@@ -94,7 +94,8 @@
hardware_owner_is_affected_by_bug=False,
hardware_owner_is_subscribed_to_bug=False,
hardware_is_linked_to_bug=False, linked_branches=None,
- modified_since=None, created_since=None, prejoins=[]):
+ linked_blueprints=None, modified_since=None,
+ created_since=None, prejoins=[]):
"""See `IHasBugs`."""
if status is None:
# If no statuses are supplied, default to the
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2011-01-20 04:50:35 +0000
+++ lib/lp/bugs/model/bugtask.py 2011-01-28 21:51:34 +0000
@@ -103,6 +103,7 @@
from lp.bugs.interfaces.bugtask import (
BUG_SUPERVISOR_BUGTASK_STATUSES,
BugBranchSearch,
+ BugBlueprintSearch,
BugTaskImportance,
BugTaskSearchParams,
BugTaskStatus,
@@ -2066,6 +2067,10 @@
# we don't need to add any clause.
pass
+ linked_blueprints_clause = self._buildBlueprintRelatedClause(params)
+ if linked_blueprints_clause is not None:
+ extra_clauses.append(linked_blueprints_clause)
+
if params.modified_since:
extra_clauses.append(
"Bug.date_last_updated > %s" % (
@@ -2318,6 +2323,21 @@
', '.join(tables), ' AND '.join(clauses))
return clause
+ def _buildBlueprintRelatedClause(self, params):
+ """Find bugs related to Blueprints, or not."""
+ if params.linked_blueprints == (
+ BugBlueprintSearch.BUGS_WITH_BLUEPRINTS):
+ return "EXISTS (%s)" % (
+ "SELECT 1 FROM SpecificationBug"
+ " WHERE SpecificationBug.bug = Bug.id")
+ elif params.linked_blueprints == (
+ BugBlueprintSearch.BUGS_WITHOUT_BLUEPRINTS):
+ return "NOT EXISTS (%s)" % (
+ "SELECT 1 FROM SpecificationBug"
+ " WHERE SpecificationBug.bug = Bug.id")
+ else:
+ return None
+
def buildOrigin(self, join_tables, prejoin_tables, clauseTables):
"""Build the parameter list for Store.using().
=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py 2011-01-13 17:00:41 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py 2011-01-28 21:51:34 +0000
@@ -31,6 +31,7 @@
from lp.bugs.interfaces.bugattachment import BugAttachmentType
from lp.bugs.interfaces.bugtask import (
BugBranchSearch,
+ BugBlueprintSearch,
BugTaskImportance,
BugTaskSearchParams,
BugTaskStatus,
@@ -445,6 +446,23 @@
user=None, linked_branches=BugBranchSearch.BUGS_WITHOUT_BRANCHES)
self.assertSearchFinds(params, self.bugtasks[1:])
+ def test_blueprints_linked(self):
+ # Search results can be limited to bugs with or without linked
+ # blueprints.
+ with person_logged_in(self.owner):
+ blueprint = self.factory.makeSpecification()
+ blueprint.linkBug(self.bugtasks[0].bug)
+ params = self.getBugTaskSearchParams(
+ user=None, linked_blueprints=(
+ BugBlueprintSearch.BUGS_WITH_BLUEPRINTS))
+ self.assertSearchFinds(params, self.bugtasks[:1])
+ params = self.getBugTaskSearchParams(
+ user=None, linked_blueprints=(
+ BugBlueprintSearch.BUGS_WITHOUT_BLUEPRINTS))
+ self.assertSearchFinds(params, self.bugtasks[1:])
+ params = self.getBugTaskSearchParams(user=None)
+ self.assertSearchFinds(params, self.bugtasks)
+
def test_limit_search_to_one_bug(self):
# Search results can be limited to a given bug.
params = self.getBugTaskSearchParams(
=== modified file 'lib/lp/bugs/tests/test_searchtasks_webservice.py'
--- lib/lp/bugs/tests/test_searchtasks_webservice.py 2010-10-04 19:50:45 +0000
+++ lib/lp/bugs/tests/test_searchtasks_webservice.py 2011-01-28 21:51:34 +0000
@@ -6,25 +6,28 @@
__metaclass__ = type
from canonical.launchpad.ftests import login
-from lp.testing import TestCaseWithFactory
from canonical.launchpad.testing.pages import LaunchpadWebServiceCaller
from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.testing import (
+ person_logged_in,
+ TestCaseWithFactory,
+ )
class TestOmitTargetedParameter(TestCaseWithFactory):
"""Test all values for the omit_targeted search parameter."""
+
layer = DatabaseFunctionalLayer
def setUp(self):
TestCaseWithFactory.setUp(self)
login('foo.bar@xxxxxxxxxxxxx')
self.distro = self.factory.makeDistribution(name='mebuntu')
- self.release = self.factory.makeDistroRelease(name='inkanyamba',
- distribution=self.distro)
+ self.release = self.factory.makeDistroRelease(
+ name='inkanyamba', distribution=self.distro)
self.bug = self.factory.makeBugTask(target=self.release)
-
- self.webservice = LaunchpadWebServiceCaller('launchpad-library',
- 'salgado-change-anything')
+ self.webservice = LaunchpadWebServiceCaller(
+ 'launchpad-library', 'salgado-change-anything')
def test_omit_targeted_old_default_true(self):
response = self.webservice.named_get('/mebuntu/inkanyamba',
@@ -35,3 +38,50 @@
response = self.webservice.named_get('/mebuntu/inkanyamba',
'searchTasks', api_version='devel').jsonBody()
self.assertEqual(response['total_size'], 1)
+
+ def test_linked_blueprints_in_devel(self):
+ response = self.webservice.named_get('/mebuntu/inkanyamba',
+ 'searchTasks', api_version='devel').jsonBody()
+ self.assertEqual(response['total_size'], 1)
+
+
+class TestLinkedBlueprintsParameter(TestCaseWithFactory):
+ """Tests for the linked_blueprints parameter."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestLinkedBlueprintsParameter, self).setUp()
+ TestCaseWithFactory.setUp(self)
+ self.owner = self.factory.makePerson()
+ with person_logged_in(self.owner):
+ self.product = self.factory.makeProduct()
+ self.bug = self.factory.makeBugTask(target=self.product)
+ self.webservice = LaunchpadWebServiceCaller(
+ 'launchpad-library', 'salgado-change-anything')
+
+ def search(self, api_version, **kwargs):
+ return self.webservice.named_get(
+ '/%s' % self.product.name, 'searchTasks',
+ api_version=api_version, **kwargs).jsonBody()
+
+ def test_linked_blueprints_in_devel(self):
+ # Searching for linked Blueprints works in the devel API.
+ self.search("devel", linked_blueprints="Show all bugs")
+ # If linked_blueprints is not a member of BugBlueprintSearch an
+ # error is returned.
+ self.assertRaises(
+ ValueError, self.search, "devel",
+ linked_blueprints="Teabags!")
+
+ def test_linked_blueprints_not_in_1_0(self):
+ # Searching for linked Blueprints does not work in the 1.0 API. No
+ # validation is performed for the linked_blueprints parameter, and
+ # thus no error is returned when we pass rubbish.
+ self.search("1.0", linked_blueprints="Teabags!")
+
+ def test_linked_blueprints_not_in_beta(self):
+ # Searching for linked Blueprints does not work in the beta API. No
+ # validation is performed for the linked_blueprints parameter, and
+ # thus no error is returned when we pass rubbish.
+ self.search("beta", linked_blueprints="Teabags!")