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
1=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
2--- lib/lp/bugs/interfaces/bugtarget.py 2010-06-15 19:36:24 +0000
3+++ lib/lp/bugs/interfaces/bugtarget.py 2010-06-24 14:20:50 +0000
4@@ -21,7 +21,7 @@
5 ]
6
7 from zope.interface import Interface, Attribute
8-from zope.schema import Bool, Choice, List, Object, Text, TextLine
9+from zope.schema import Bool, Choice, Datetime, List, Object, Text, TextLine
10
11 from canonical.launchpad import _
12 from canonical.launchpad.fields import Tag
13@@ -164,7 +164,13 @@
14 title=(
15 u"Search for bugs that are linked to branches or for bugs "
16 "that are not linked to branches."),
17- vocabulary=BugBranchSearch, required=False))
18+ vocabulary=BugBranchSearch, required=False),
19+ modified_since=Datetime(
20+ title=(
21+ u"Search for bugs that have been modified since the given "
22+ "date."),
23+ required=False),
24+ )
25 @operation_returns_collection_of(IBugTask)
26 @export_read_operation()
27 def searchTasks(search_params, user=None,
28@@ -186,7 +192,7 @@
29 hardware_owner_is_affected_by_bug=False,
30 hardware_owner_is_subscribed_to_bug=False,
31 hardware_is_linked_to_bug=False, linked_branches=None,
32- structural_subscriber=None):
33+ structural_subscriber=None, modified_since=None):
34 """Search the IBugTasks reported on this entity.
35
36 :search_params: a BugTaskSearchParams object
37
38=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
39--- lib/lp/bugs/interfaces/bugtask.py 2010-06-07 19:48:29 +0000
40+++ lib/lp/bugs/interfaces/bugtask.py 2010-06-24 14:20:50 +0000
41@@ -1075,8 +1075,8 @@
42 hardware_owner_is_affected_by_bug=False,
43 hardware_owner_is_subscribed_to_bug=False,
44 hardware_is_linked_to_bug=False,
45- linked_branches=None, structural_subscriber=None
46- ):
47+ linked_branches=None, structural_subscriber=None,
48+ modified_since=None):
49
50 self.bug = bug
51 self.searchtext = searchtext
52@@ -1121,6 +1121,7 @@
53 self.hardware_is_linked_to_bug = hardware_is_linked_to_bug
54 self.linked_branches = linked_branches
55 self.structural_subscriber = structural_subscriber
56+ self.modified_since = None
57
58 def setProduct(self, product):
59 """Set the upstream context on which to filter the search."""
60@@ -1194,7 +1195,7 @@
61 hardware_owner_is_affected_by_bug=False,
62 hardware_owner_is_subscribed_to_bug=False,
63 hardware_is_linked_to_bug=False, linked_branches=None,
64- structural_subscriber=None):
65+ structural_subscriber=None, modified_since=None):
66 """Create and return a new instance using the parameter list."""
67 search_params = cls(user=user, orderby=order_by)
68
69@@ -1263,6 +1264,7 @@
70 hardware_is_linked_to_bug)
71 search_params.linked_branches=linked_branches
72 search_params.structural_subscriber = structural_subscriber
73+ search_params.modified_since = modified_since
74
75 return search_params
76
77
78=== modified file 'lib/lp/bugs/model/bugtarget.py'
79--- lib/lp/bugs/model/bugtarget.py 2010-05-21 04:38:42 +0000
80+++ lib/lp/bugs/model/bugtarget.py 2010-06-24 14:20:50 +0000
81@@ -65,7 +65,8 @@
82 hardware_owner_is_bug_reporter=None,
83 hardware_owner_is_affected_by_bug=False,
84 hardware_owner_is_subscribed_to_bug=False,
85- hardware_is_linked_to_bug=False, linked_branches=None):
86+ hardware_is_linked_to_bug=False, linked_branches=None,
87+ modified_since=None):
88 """See `IHasBugs`."""
89 if status is None:
90 # If no statuses are supplied, default to the
91
92=== modified file 'lib/lp/bugs/model/bugtask.py'
93--- lib/lp/bugs/model/bugtask.py 2010-06-22 17:08:23 +0000
94+++ lib/lp/bugs/model/bugtask.py 2010-06-24 14:20:50 +0000
95@@ -1833,6 +1833,11 @@
96 # we don't need to add any clause.
97 pass
98
99+ if params.modified_since:
100+ extra_clauses.append(
101+ "Bug.date_last_updated > %s" % (
102+ sqlvalues(params.modified_since,)))
103+
104 orderby_arg = self._processOrderBy(params)
105
106 query = " AND ".join(extra_clauses)
107
108=== modified file 'lib/lp/bugs/stories/webservice/xx-bug.txt'
109--- lib/lp/bugs/stories/webservice/xx-bug.txt 2010-06-10 18:55:22 +0000
110+++ lib/lp/bugs/stories/webservice/xx-bug.txt 2010-06-24 14:20:50 +0000
111@@ -1436,6 +1436,15 @@
112 total_size: 0
113 ---
114
115+It can also be used to find bugs modified since a certain date.
116+
117+ >>> pprint_collection(webservice.named_get(
118+ ... '/ubuntu', 'searchTasks',
119+ ... modified_since=u'2011-01-01T00:00:00+00:00').jsonBody())
120+ start: None
121+ total_size: 0
122+ ---
123+
124 It is possible to search for bugs targeted to a milestone within a
125 project group.
126
127
128=== modified file 'lib/lp/bugs/tests/test_bugtask.py'
129--- lib/lp/bugs/tests/test_bugtask.py 2010-06-21 18:09:47 +0000
130+++ lib/lp/bugs/tests/test_bugtask.py 2010-06-24 14:20:50 +0000
131@@ -3,6 +3,7 @@
132
133 __metaclass__ = type
134
135+from datetime import timedelta
136 import unittest
137
138 from zope.component import getUtility
139@@ -14,8 +15,10 @@
140 from lp.hardwaredb.interfaces.hwdb import HWBus, IHWDeviceSet
141 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
142 from canonical.launchpad.searchbuilder import all, any
143-from canonical.testing import LaunchpadFunctionalLayer, LaunchpadZopelessLayer
144+from canonical.testing import (
145+ DatabaseFunctionalLayer, LaunchpadFunctionalLayer, LaunchpadZopelessLayer)
146
147+from lp.bugs.interfaces.bugtarget import IBugTarget
148 from lp.bugs.interfaces.bugtask import (
149 BugTaskImportance, BugTaskSearchParams, BugTaskStatus)
150 from lp.bugs.model.bugtask import build_tag_search_clause
151@@ -28,7 +31,7 @@
152
153 class TestBugTaskDelta(TestCaseWithFactory):
154
155- layer = LaunchpadFunctionalLayer
156+ layer = DatabaseFunctionalLayer
157
158 def setUp(self):
159 super(TestBugTaskDelta, self).setUp()
160@@ -66,7 +69,6 @@
161
162 def test_get_bugwatch_delta(self):
163 # Exercise getDelta() with a change to bugwatch.
164- user = self.factory.makePerson()
165 bug_task = self.factory.makeBugTask()
166 bug_task_before_modification = Snapshot(
167 bug_task, providing=providedBy(bug_task))
168@@ -501,9 +503,9 @@
169 [bugtask.bug.id for bugtask in bugtasks])
170
171
172-class TestBugTaskPermissionsToSetAssigneeBase(TestCaseWithFactory):
173+class TestBugTaskPermissionsToSetAssigneeMixin:
174
175- layer = LaunchpadFunctionalLayer
176+ layer = DatabaseFunctionalLayer
177
178 def setUp(self):
179 """Create the test setup.
180@@ -516,7 +518,7 @@
181 owners, bug supervisors, drivers
182 - bug tasks for the targets
183 """
184- super(TestBugTaskPermissionsToSetAssigneeBase, self).setUp()
185+ super(TestBugTaskPermissionsToSetAssigneeMixin, self).setUp()
186 self.target_owner_member = self.factory.makePerson()
187 self.target_owner_team = self.factory.makeTeam(
188 owner=self.target_owner_member)
189@@ -556,6 +558,14 @@
190 self.target_bugtask.transitionToAssignee(self.regular_user)
191 logout()
192
193+ def makeTarget(self):
194+ """Create a target and a series.
195+
196+ The target and series must be assigned as attributes of self:
197+ 'self.target' and 'self.series'.
198+ """
199+ raise NotImplementedError(self.makeTarget)
200+
201 def test_userCanSetAnyAssignee_anonymous_user(self):
202 # Anonymous users cannot set anybody as an assignee.
203 login(ANONYMOUS)
204@@ -700,7 +710,7 @@
205
206
207 class TestProductBugTaskPermissionsToSetAssignee(
208- TestBugTaskPermissionsToSetAssigneeBase):
209+ TestBugTaskPermissionsToSetAssigneeMixin, TestCaseWithFactory):
210
211 def makeTarget(self):
212 """Create a product and a product series."""
213@@ -709,7 +719,7 @@
214
215
216 class TestDistributionBugTaskPermissionsToSetAssignee(
217- TestBugTaskPermissionsToSetAssigneeBase):
218+ TestBugTaskPermissionsToSetAssigneeMixin, TestCaseWithFactory):
219
220 def makeTarget(self):
221 """Create a distribution and a distroseries."""
222@@ -718,14 +728,73 @@
223 self.series = self.factory.makeDistroSeries(self.target)
224
225
226+class TestBugTaskSearch(TestCaseWithFactory):
227+
228+ layer = DatabaseFunctionalLayer
229+
230+ def login(self):
231+ # Log in as an arbitrary person.
232+ person = self.factory.makePerson()
233+ login_person(person)
234+ self.addCleanup(logout)
235+ return person
236+
237+ def makeBugTarget(self):
238+ """Make an arbitrary bug target with no tasks on it."""
239+ return IBugTarget(self.factory.makeProduct())
240+
241+ def test_no_tasks(self):
242+ # A brand new bug target has no tasks.
243+ target = self.makeBugTarget()
244+ self.assertEqual([], list(target.searchTasks(None)))
245+
246+ def test_new_task_shows_up(self):
247+ # When we create a new bugtask on the target, it shows up in
248+ # searchTasks.
249+ target = self.makeBugTarget()
250+ self.login()
251+ task = self.factory.makeBugTask(target=target)
252+ self.assertEqual([task], list(target.searchTasks(None)))
253+
254+ def test_modified_since_excludes_earlier_bugtasks(self):
255+ # When we search for bug tasks that have been modified since a certain
256+ # time, tasks for bugs that have not been modified since then are
257+ # excluded.
258+ target = self.makeBugTarget()
259+ self.login()
260+ task = self.factory.makeBugTask(target=target)
261+ date = task.bug.date_last_updated + timedelta(days=1)
262+ result = target.searchTasks(None, modified_since=date)
263+ self.assertEqual([], list(result))
264+
265+ def test_modified_since_includes_later_bugtasks(self):
266+ # When we search for bug tasks that have been modified since a certain
267+ # time, tasks for bugs that have been modified since then are
268+ # included.
269+ target = self.makeBugTarget()
270+ self.login()
271+ task = self.factory.makeBugTask(target=target)
272+ date = task.bug.date_last_updated - timedelta(days=1)
273+ result = target.searchTasks(None, modified_since=date)
274+ self.assertEqual([task], list(result))
275+
276+ def test_modified_since_includes_later_bugtasks_excludes_earlier(self):
277+ # When we search for bugs that have been modified since a certain
278+ # time, tasks for bugs that have been modified since then are
279+ # included, tasks that have not are excluded.
280+ target = self.makeBugTarget()
281+ self.login()
282+ task1 = self.factory.makeBugTask(target=target)
283+ date = task1.bug.date_last_updated
284+ task1.bug.date_last_updated -= timedelta(days=1)
285+ task2 = self.factory.makeBugTask(target=target)
286+ task2.bug.date_last_updated += timedelta(days=1)
287+ result = target.searchTasks(None, modified_since=date)
288+ self.assertEqual([task2], list(result))
289+
290+
291 def test_suite():
292 suite = unittest.TestSuite()
293- suite.addTest(unittest.makeSuite(TestBugTaskDelta))
294- suite.addTest(unittest.makeSuite(TestBugTaskTagSearchClauses))
295- suite.addTest(unittest.makeSuite(TestBugTaskHardwareSearch))
296- suite.addTest(unittest.makeSuite(
297- TestProductBugTaskPermissionsToSetAssignee))
298- suite.addTest(unittest.makeSuite(
299- TestDistributionBugTaskPermissionsToSetAssignee))
300+ suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
301 suite.addTest(DocTestSuite('lp.bugs.model.bugtask'))
302 return suite