Merge lp:~jml/launchpad/searchTasks-modified-since-590535 into lp:launchpad

Proposed by Jonathan Lange
Status: Merged
Approved by: Deryck Hodge
Approved revision: no longer in the source branch.
Merged at revision: 11055
Proposed branch: lp:~jml/launchpad/searchTasks-modified-since-590535
Merge into: lp:launchpad
Diff against target: 302 lines (+114/-22)
6 files modified
lib/lp/bugs/interfaces/bugtarget.py (+9/-3)
lib/lp/bugs/interfaces/bugtask.py (+5/-3)
lib/lp/bugs/model/bugtarget.py (+2/-1)
lib/lp/bugs/model/bugtask.py (+5/-0)
lib/lp/bugs/stories/webservice/xx-bug.txt (+9/-0)
lib/lp/bugs/tests/test_bugtask.py (+84/-15)
To merge this branch: bzr merge lp:~jml/launchpad/searchTasks-modified-since-590535
Reviewer Review Type Date Requested Status
Deryck Hodge (community) code Approve
Review via email: mp+28344@code.launchpad.net

Description of the change

This branch fixes bug 590535 by adding a new parameter to searchTasks to allow people to search for bugs that have been modified since a certain date.

I put the tests in test_bugtask.py, since it seemed the most sensible place. I may have duplicated tests elsewhere, since I wanted to be extra confident with these, since I didn't know what I was doing.

The test_suite method change simply makes the test runner find all of the tests in the module, without them having to be picked out and named individually.

The change to xx-bug.txt is kind of a cheat, in that it doesn't test much of the behaviour. However, since I know the method works, all I'm really testing is that passing in modified_since actually has an effect on the results. I think the test works for that.

I didn't really know whether the query should be for date_last_updated >= modified_since or date_last_updated > modified_since. I didn't think hard about it, but I couldn't really see the difference, so I opted for the one needing the fewest key presses.

I'm beside myself with scrumptious anticipation as I wait dotingly on your review.

jml

To post a comment you must log in.
Revision history for this message
Deryck Hodge (deryck) wrote :

Hi, Jono.

This branch looks good to me. I was going to suggest changing the test name to be less generic about search and more about the specific case you were covering, but realizing we have no unit test coverage for searchTasks, I'm fine with this now, even if there is some tiny duplication. I would like to see us eventually move tests out of doc/bugtask-search.txt and into this class, where appropriate. Given that, keeping the name seems appropriate.

Also, limiting to tasks after but not including modified_since feels right to me, given the naming. The way you've done it here is what I would expect when using the parameter.

Finally, I agree with your choice to simply demonstrate the webservice and rely on unit test coverage. I have taken this approach myself recently.

Thanks for the work!

Cheers,
deryck

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py 2010-06-15 19:36:24 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py 2010-06-24 14:20:50 +0000
@@ -21,7 +21,7 @@
21 ]21 ]
2222
23from zope.interface import Interface, Attribute23from zope.interface import Interface, Attribute
24from zope.schema import Bool, Choice, List, Object, Text, TextLine24from zope.schema import Bool, Choice, Datetime, List, Object, Text, TextLine
2525
26from canonical.launchpad import _26from canonical.launchpad import _
27from canonical.launchpad.fields import Tag27from canonical.launchpad.fields import Tag
@@ -164,7 +164,13 @@
164 title=(164 title=(
165 u"Search for bugs that are linked to branches or for bugs "165 u"Search for bugs that are linked to branches or for bugs "
166 "that are not linked to branches."),166 "that are not linked to branches."),
167 vocabulary=BugBranchSearch, required=False))167 vocabulary=BugBranchSearch, required=False),
168 modified_since=Datetime(
169 title=(
170 u"Search for bugs that have been modified since the given "
171 "date."),
172 required=False),
173 )
168 @operation_returns_collection_of(IBugTask)174 @operation_returns_collection_of(IBugTask)
169 @export_read_operation()175 @export_read_operation()
170 def searchTasks(search_params, user=None,176 def searchTasks(search_params, user=None,
@@ -186,7 +192,7 @@
186 hardware_owner_is_affected_by_bug=False,192 hardware_owner_is_affected_by_bug=False,
187 hardware_owner_is_subscribed_to_bug=False,193 hardware_owner_is_subscribed_to_bug=False,
188 hardware_is_linked_to_bug=False, linked_branches=None,194 hardware_is_linked_to_bug=False, linked_branches=None,
189 structural_subscriber=None):195 structural_subscriber=None, modified_since=None):
190 """Search the IBugTasks reported on this entity.196 """Search the IBugTasks reported on this entity.
191197
192 :search_params: a BugTaskSearchParams object198 :search_params: a BugTaskSearchParams object
193199
=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py 2010-06-07 19:48:29 +0000
+++ lib/lp/bugs/interfaces/bugtask.py 2010-06-24 14:20:50 +0000
@@ -1075,8 +1075,8 @@
1075 hardware_owner_is_affected_by_bug=False,1075 hardware_owner_is_affected_by_bug=False,
1076 hardware_owner_is_subscribed_to_bug=False,1076 hardware_owner_is_subscribed_to_bug=False,
1077 hardware_is_linked_to_bug=False,1077 hardware_is_linked_to_bug=False,
1078 linked_branches=None, structural_subscriber=None1078 linked_branches=None, structural_subscriber=None,
1079 ):1079 modified_since=None):
10801080
1081 self.bug = bug1081 self.bug = bug
1082 self.searchtext = searchtext1082 self.searchtext = searchtext
@@ -1121,6 +1121,7 @@
1121 self.hardware_is_linked_to_bug = hardware_is_linked_to_bug1121 self.hardware_is_linked_to_bug = hardware_is_linked_to_bug
1122 self.linked_branches = linked_branches1122 self.linked_branches = linked_branches
1123 self.structural_subscriber = structural_subscriber1123 self.structural_subscriber = structural_subscriber
1124 self.modified_since = None
11241125
1125 def setProduct(self, product):1126 def setProduct(self, product):
1126 """Set the upstream context on which to filter the search."""1127 """Set the upstream context on which to filter the search."""
@@ -1194,7 +1195,7 @@
1194 hardware_owner_is_affected_by_bug=False,1195 hardware_owner_is_affected_by_bug=False,
1195 hardware_owner_is_subscribed_to_bug=False,1196 hardware_owner_is_subscribed_to_bug=False,
1196 hardware_is_linked_to_bug=False, linked_branches=None,1197 hardware_is_linked_to_bug=False, linked_branches=None,
1197 structural_subscriber=None):1198 structural_subscriber=None, modified_since=None):
1198 """Create and return a new instance using the parameter list."""1199 """Create and return a new instance using the parameter list."""
1199 search_params = cls(user=user, orderby=order_by)1200 search_params = cls(user=user, orderby=order_by)
12001201
@@ -1263,6 +1264,7 @@
1263 hardware_is_linked_to_bug)1264 hardware_is_linked_to_bug)
1264 search_params.linked_branches=linked_branches1265 search_params.linked_branches=linked_branches
1265 search_params.structural_subscriber = structural_subscriber1266 search_params.structural_subscriber = structural_subscriber
1267 search_params.modified_since = modified_since
12661268
1267 return search_params1269 return search_params
12681270
12691271
=== modified file 'lib/lp/bugs/model/bugtarget.py'
--- lib/lp/bugs/model/bugtarget.py 2010-05-21 04:38:42 +0000
+++ lib/lp/bugs/model/bugtarget.py 2010-06-24 14:20:50 +0000
@@ -65,7 +65,8 @@
65 hardware_owner_is_bug_reporter=None,65 hardware_owner_is_bug_reporter=None,
66 hardware_owner_is_affected_by_bug=False,66 hardware_owner_is_affected_by_bug=False,
67 hardware_owner_is_subscribed_to_bug=False,67 hardware_owner_is_subscribed_to_bug=False,
68 hardware_is_linked_to_bug=False, linked_branches=None):68 hardware_is_linked_to_bug=False, linked_branches=None,
69 modified_since=None):
69 """See `IHasBugs`."""70 """See `IHasBugs`."""
70 if status is None:71 if status is None:
71 # If no statuses are supplied, default to the72 # If no statuses are supplied, default to the
7273
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2010-06-22 17:08:23 +0000
+++ lib/lp/bugs/model/bugtask.py 2010-06-24 14:20:50 +0000
@@ -1833,6 +1833,11 @@
1833 # we don't need to add any clause.1833 # we don't need to add any clause.
1834 pass1834 pass
18351835
1836 if params.modified_since:
1837 extra_clauses.append(
1838 "Bug.date_last_updated > %s" % (
1839 sqlvalues(params.modified_since,)))
1840
1836 orderby_arg = self._processOrderBy(params)1841 orderby_arg = self._processOrderBy(params)
18371842
1838 query = " AND ".join(extra_clauses)1843 query = " AND ".join(extra_clauses)
18391844
=== modified file 'lib/lp/bugs/stories/webservice/xx-bug.txt'
--- lib/lp/bugs/stories/webservice/xx-bug.txt 2010-06-10 18:55:22 +0000
+++ lib/lp/bugs/stories/webservice/xx-bug.txt 2010-06-24 14:20:50 +0000
@@ -1436,6 +1436,15 @@
1436 total_size: 01436 total_size: 0
1437 ---1437 ---
14381438
1439It can also be used to find bugs modified since a certain date.
1440
1441 >>> pprint_collection(webservice.named_get(
1442 ... '/ubuntu', 'searchTasks',
1443 ... modified_since=u'2011-01-01T00:00:00+00:00').jsonBody())
1444 start: None
1445 total_size: 0
1446 ---
1447
1439It is possible to search for bugs targeted to a milestone within a1448It is possible to search for bugs targeted to a milestone within a
1440project group.1449project group.
14411450
14421451
=== modified file 'lib/lp/bugs/tests/test_bugtask.py'
--- lib/lp/bugs/tests/test_bugtask.py 2010-06-21 18:09:47 +0000
+++ lib/lp/bugs/tests/test_bugtask.py 2010-06-24 14:20:50 +0000
@@ -3,6 +3,7 @@
33
4__metaclass__ = type4__metaclass__ = type
55
6from datetime import timedelta
6import unittest7import unittest
78
8from zope.component import getUtility9from zope.component import getUtility
@@ -14,8 +15,10 @@
14from lp.hardwaredb.interfaces.hwdb import HWBus, IHWDeviceSet15from lp.hardwaredb.interfaces.hwdb import HWBus, IHWDeviceSet
15from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities16from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
16from canonical.launchpad.searchbuilder import all, any17from canonical.launchpad.searchbuilder import all, any
17from canonical.testing import LaunchpadFunctionalLayer, LaunchpadZopelessLayer18from canonical.testing import (
19 DatabaseFunctionalLayer, LaunchpadFunctionalLayer, LaunchpadZopelessLayer)
1820
21from lp.bugs.interfaces.bugtarget import IBugTarget
19from lp.bugs.interfaces.bugtask import (22from lp.bugs.interfaces.bugtask import (
20 BugTaskImportance, BugTaskSearchParams, BugTaskStatus)23 BugTaskImportance, BugTaskSearchParams, BugTaskStatus)
21from lp.bugs.model.bugtask import build_tag_search_clause24from lp.bugs.model.bugtask import build_tag_search_clause
@@ -28,7 +31,7 @@
2831
29class TestBugTaskDelta(TestCaseWithFactory):32class TestBugTaskDelta(TestCaseWithFactory):
3033
31 layer = LaunchpadFunctionalLayer34 layer = DatabaseFunctionalLayer
3235
33 def setUp(self):36 def setUp(self):
34 super(TestBugTaskDelta, self).setUp()37 super(TestBugTaskDelta, self).setUp()
@@ -66,7 +69,6 @@
6669
67 def test_get_bugwatch_delta(self):70 def test_get_bugwatch_delta(self):
68 # Exercise getDelta() with a change to bugwatch.71 # Exercise getDelta() with a change to bugwatch.
69 user = self.factory.makePerson()
70 bug_task = self.factory.makeBugTask()72 bug_task = self.factory.makeBugTask()
71 bug_task_before_modification = Snapshot(73 bug_task_before_modification = Snapshot(
72 bug_task, providing=providedBy(bug_task))74 bug_task, providing=providedBy(bug_task))
@@ -501,9 +503,9 @@
501 [bugtask.bug.id for bugtask in bugtasks])503 [bugtask.bug.id for bugtask in bugtasks])
502504
503505
504class TestBugTaskPermissionsToSetAssigneeBase(TestCaseWithFactory):506class TestBugTaskPermissionsToSetAssigneeMixin:
505507
506 layer = LaunchpadFunctionalLayer508 layer = DatabaseFunctionalLayer
507509
508 def setUp(self):510 def setUp(self):
509 """Create the test setup.511 """Create the test setup.
@@ -516,7 +518,7 @@
516 owners, bug supervisors, drivers518 owners, bug supervisors, drivers
517 - bug tasks for the targets519 - bug tasks for the targets
518 """520 """
519 super(TestBugTaskPermissionsToSetAssigneeBase, self).setUp()521 super(TestBugTaskPermissionsToSetAssigneeMixin, self).setUp()
520 self.target_owner_member = self.factory.makePerson()522 self.target_owner_member = self.factory.makePerson()
521 self.target_owner_team = self.factory.makeTeam(523 self.target_owner_team = self.factory.makeTeam(
522 owner=self.target_owner_member)524 owner=self.target_owner_member)
@@ -556,6 +558,14 @@
556 self.target_bugtask.transitionToAssignee(self.regular_user)558 self.target_bugtask.transitionToAssignee(self.regular_user)
557 logout()559 logout()
558560
561 def makeTarget(self):
562 """Create a target and a series.
563
564 The target and series must be assigned as attributes of self:
565 'self.target' and 'self.series'.
566 """
567 raise NotImplementedError(self.makeTarget)
568
559 def test_userCanSetAnyAssignee_anonymous_user(self):569 def test_userCanSetAnyAssignee_anonymous_user(self):
560 # Anonymous users cannot set anybody as an assignee.570 # Anonymous users cannot set anybody as an assignee.
561 login(ANONYMOUS)571 login(ANONYMOUS)
@@ -700,7 +710,7 @@
700710
701711
702class TestProductBugTaskPermissionsToSetAssignee(712class TestProductBugTaskPermissionsToSetAssignee(
703 TestBugTaskPermissionsToSetAssigneeBase):713 TestBugTaskPermissionsToSetAssigneeMixin, TestCaseWithFactory):
704714
705 def makeTarget(self):715 def makeTarget(self):
706 """Create a product and a product series."""716 """Create a product and a product series."""
@@ -709,7 +719,7 @@
709719
710720
711class TestDistributionBugTaskPermissionsToSetAssignee(721class TestDistributionBugTaskPermissionsToSetAssignee(
712 TestBugTaskPermissionsToSetAssigneeBase):722 TestBugTaskPermissionsToSetAssigneeMixin, TestCaseWithFactory):
713723
714 def makeTarget(self):724 def makeTarget(self):
715 """Create a distribution and a distroseries."""725 """Create a distribution and a distroseries."""
@@ -718,14 +728,73 @@
718 self.series = self.factory.makeDistroSeries(self.target)728 self.series = self.factory.makeDistroSeries(self.target)
719729
720730
731class TestBugTaskSearch(TestCaseWithFactory):
732
733 layer = DatabaseFunctionalLayer
734
735 def login(self):
736 # Log in as an arbitrary person.
737 person = self.factory.makePerson()
738 login_person(person)
739 self.addCleanup(logout)
740 return person
741
742 def makeBugTarget(self):
743 """Make an arbitrary bug target with no tasks on it."""
744 return IBugTarget(self.factory.makeProduct())
745
746 def test_no_tasks(self):
747 # A brand new bug target has no tasks.
748 target = self.makeBugTarget()
749 self.assertEqual([], list(target.searchTasks(None)))
750
751 def test_new_task_shows_up(self):
752 # When we create a new bugtask on the target, it shows up in
753 # searchTasks.
754 target = self.makeBugTarget()
755 self.login()
756 task = self.factory.makeBugTask(target=target)
757 self.assertEqual([task], list(target.searchTasks(None)))
758
759 def test_modified_since_excludes_earlier_bugtasks(self):
760 # When we search for bug tasks that have been modified since a certain
761 # time, tasks for bugs that have not been modified since then are
762 # excluded.
763 target = self.makeBugTarget()
764 self.login()
765 task = self.factory.makeBugTask(target=target)
766 date = task.bug.date_last_updated + timedelta(days=1)
767 result = target.searchTasks(None, modified_since=date)
768 self.assertEqual([], list(result))
769
770 def test_modified_since_includes_later_bugtasks(self):
771 # When we search for bug tasks that have been modified since a certain
772 # time, tasks for bugs that have been modified since then are
773 # included.
774 target = self.makeBugTarget()
775 self.login()
776 task = self.factory.makeBugTask(target=target)
777 date = task.bug.date_last_updated - timedelta(days=1)
778 result = target.searchTasks(None, modified_since=date)
779 self.assertEqual([task], list(result))
780
781 def test_modified_since_includes_later_bugtasks_excludes_earlier(self):
782 # When we search for bugs that have been modified since a certain
783 # time, tasks for bugs that have been modified since then are
784 # included, tasks that have not are excluded.
785 target = self.makeBugTarget()
786 self.login()
787 task1 = self.factory.makeBugTask(target=target)
788 date = task1.bug.date_last_updated
789 task1.bug.date_last_updated -= timedelta(days=1)
790 task2 = self.factory.makeBugTask(target=target)
791 task2.bug.date_last_updated += timedelta(days=1)
792 result = target.searchTasks(None, modified_since=date)
793 self.assertEqual([task2], list(result))
794
795
721def test_suite():796def test_suite():
722 suite = unittest.TestSuite()797 suite = unittest.TestSuite()
723 suite.addTest(unittest.makeSuite(TestBugTaskDelta))798 suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
724 suite.addTest(unittest.makeSuite(TestBugTaskTagSearchClauses))
725 suite.addTest(unittest.makeSuite(TestBugTaskHardwareSearch))
726 suite.addTest(unittest.makeSuite(
727 TestProductBugTaskPermissionsToSetAssignee))
728 suite.addTest(unittest.makeSuite(
729 TestDistributionBugTaskPermissionsToSetAssignee))
730 suite.addTest(DocTestSuite('lp.bugs.model.bugtask'))799 suite.addTest(DocTestSuite('lp.bugs.model.bugtask'))
731 return suite800 return suite