Merge lp:~thumper/launchpad/blueprint-linked-bug-tasks into lp:launchpad
- blueprint-linked-bug-tasks
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Robert Collins | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 12679 | ||||
Proposed branch: | lp:~thumper/launchpad/blueprint-linked-bug-tasks | ||||
Merge into: | lp:launchpad | ||||
Prerequisite: | lp:~thumper/launchpad/add-publishing-for-factory-distro-sourcepackage-bug-tasks | ||||
Diff against target: |
897 lines (+483/-49) 23 files modified
lib/lp/blueprints/browser/specification.py (+1/-2) lib/lp/blueprints/interfaces/specification.py (+10/-0) lib/lp/blueprints/model/specification.py (+21/-0) lib/lp/blueprints/stories/blueprints/xx-buglinks.txt (+1/-1) lib/lp/blueprints/templates/specification-index.pt (+12/-5) lib/lp/bugs/configure.zcml (+8/-0) lib/lp/bugs/interfaces/bugtarget.py (+11/-0) lib/lp/bugs/interfaces/bugtask.py (+4/-0) lib/lp/bugs/interfaces/bugtaskfilter.py (+68/-0) lib/lp/bugs/model/bugtarget.py (+5/-0) lib/lp/bugs/model/bugtask.py (+18/-9) lib/lp/bugs/model/tests/test_bugtask.py (+22/-0) lib/lp/bugs/tests/test_bugtaskfilter.py (+196/-0) lib/lp/code/interfaces/branch.py (+1/-1) lib/lp/code/model/branch.py (+2/-26) lib/lp/code/model/branchcollection.py (+3/-5) lib/lp/registry/interfaces/distroseries.py (+1/-0) lib/lp/registry/interfaces/productseries.py (+1/-0) lib/lp/registry/model/distribution.py (+17/-0) lib/lp/registry/model/distroseries.py (+20/-0) lib/lp/registry/model/product.py (+17/-0) lib/lp/registry/model/productseries.py (+19/-0) lib/lp/registry/model/sourcepackage.py (+25/-0) |
||||
To merge this branch: | bzr merge lp:~thumper/launchpad/blueprint-linked-bug-tasks | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Collins (community) | Approve | ||
Ian Booth (community) | *code | Approve | |
Review via email: mp+53734@code.launchpad.net |
Commit message
[r=lifeless,
Description of the change
This branch primarily fixes bug 487337.
A perhaps slightly over-engineered fix, but it is something that
I've been meaning to do for a while. Choosing the correct bug
task to show for links when we link to bugs not bug tasks has been
a bit of a problem for a while (IMO).
This branch adds a new method in its own module (to avoid circular
dependencies) called filter_
aims to choose the most appropriate bug task for different contexts.
It does this by creating the appropriate weighting calculator for
the context as different contexts put different weights on the different
bug tasks. These are then sorted for any particular bug, and the
bug task with the best weighting is chosen.
This method now replaces the branch specific bugtask chooser.
In order to do this without database queries, the IDs of the various
context objects are used. This meant exposing the field IDs in a few
different interfaces.
The bug task search method is extended to check for a specific blueprint
being linked rather than just any blueprint. This is then used in
the blueprint method getLinkedBugTasks. The user is passed in, and
is used by the searching code to check for visibility, so we don't have
to post process to check for visibility.
Finally, the formatted bugtask is shown on the blueprint page along
with the current status.
Robert Collins (lifeless) wrote : | # |
On Thu, Mar 17, 2011 at 4:56 PM, Ian Booth <email address hidden> wrote:
> Review: Approve *code
> This is great, especially the no sql bit, and it fits in with other work already done to optimise and improve the security around retrieving bug tasks.
>
> I'm wondering how the order of looking for contexts was determined in getLinkedBugTas
its arbitrary and the order doesn't matter - see the check constraints
on bugtask.
Tim Penhey (thumper) wrote : | # |
On Fri, 18 Mar 2011 08:17:54 Robert Collins wrote:
> On Thu, Mar 17, 2011 at 4:56 PM, Ian Booth <email address hidden> wrote:
> > Review: Approve *code
> > This is great, especially the no sql bit, and it fits in with other work
> > already done to optimise and improve the security around retrieving bug
> > tasks.
>
> > I'm wondering how the order of looking for contexts was determined in
getLinkedBugTas
> its arbitrary and the order doesn't matter - see the check constraints
> on bugtask.
It isn't entirely arbitrary. Blueprints use the associated productseries or
distroseries to indicate goals. If a particular bug had a bug task for the
goal series, it made more sense to return that one than to return the more
generic distro or product task.
Robert Collins (lifeless) wrote : | # |
On Fri, Mar 18, 2011 at 9:39 AM, Tim Penhey <email address hidden> wrote:
> On Fri, 18 Mar 2011 08:17:54 Robert Collins wrote:
>> On Thu, Mar 17, 2011 at 4:56 PM, Ian Booth <email address hidden> wrote:
>> > Review: Approve *code
>> > This is great, especially the no sql bit, and it fits in with other work
>> > already done to optimise and improve the security around retrieving bug
>> > tasks.
>>
>> > I'm wondering how the order of looking for contexts was determined in
> getLinkedBugTas
>> its arbitrary and the order doesn't matter - see the check constraints
>> on bugtask.
>
> It isn't entirely arbitrary. Blueprints use the associated productseries or
> distroseries to indicate goals. If a particular bug had a bug task for the
> goal series, it made more sense to return that one than to return the more
> generic distro or product task.
Bugtasks are constrainted to have only one target: one of (product
series, product, etc etc). So the code that was changed which is
examining only one task, could be in any order and have the same
outcome.
Robert Collins (lifeless) wrote : | # |
Ok, so I like what this achieves.
I think the code could be a little leaner: two specific suggestions.
Rather than classes with __call__, use closures.
Secondly, rather than type inspection, extend the contract for IHasBugs to include getBugTaskWeigh
Preview Diff
1 | === modified file 'lib/lp/blueprints/browser/specification.py' |
2 | --- lib/lp/blueprints/browser/specification.py 2011-03-10 01:25:13 +0000 |
3 | +++ lib/lp/blueprints/browser/specification.py 2011-03-28 00:03:43 +0000 |
4 | @@ -557,8 +557,7 @@ |
5 | |
6 | @cachedproperty |
7 | def bug_links(self): |
8 | - return [bug_link for bug_link in self.context.bug_links |
9 | - if check_permission('launchpad.View', bug_link.bug)] |
10 | + return self.context.getLinkedBugTasks(self.user) |
11 | |
12 | |
13 | class SpecificationView(SpecificationSimpleView): |
14 | |
15 | === modified file 'lib/lp/blueprints/interfaces/specification.py' |
16 | --- lib/lp/blueprints/interfaces/specification.py 2011-03-24 12:54:40 +0000 |
17 | +++ lib/lp/blueprints/interfaces/specification.py 2011-03-28 00:03:43 +0000 |
18 | @@ -510,6 +510,16 @@ |
19 | def getBranchLink(branch): |
20 | """Return the SpecificationBranch link for the branch, or None.""" |
21 | |
22 | + def getLinkedBugTasks(user): |
23 | + """Return the bug tasks that are relevant to this blueprint. |
24 | + |
25 | + When multiple tasks are on a bug, if one of the tasks is for the |
26 | + target, then only that task is returned. Otherwise the default |
27 | + bug task is returned. |
28 | + |
29 | + :param user: The user doing the search. |
30 | + """ |
31 | + |
32 | |
33 | class ISpecificationEditRestricted(Interface): |
34 | """Specification's attributes and methods protected with launchpad.Edit. |
35 | |
36 | === modified file 'lib/lp/blueprints/model/specification.py' |
37 | --- lib/lp/blueprints/model/specification.py 2011-03-14 22:14:13 +0000 |
38 | +++ lib/lp/blueprints/model/specification.py 2011-03-28 00:03:43 +0000 |
39 | @@ -30,6 +30,7 @@ |
40 | SQL, |
41 | ) |
42 | from storm.store import Store |
43 | +from zope.component import getUtility |
44 | from zope.event import notify |
45 | from zope.interface import implements |
46 | |
47 | @@ -77,6 +78,11 @@ |
48 | SpecificationSubscription, |
49 | ) |
50 | from lp.bugs.interfaces.buglink import IBugLinkTarget |
51 | +from lp.bugs.interfaces.bugtask import ( |
52 | + BugTaskSearchParams, |
53 | + IBugTaskSet, |
54 | + ) |
55 | +from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context |
56 | from lp.bugs.model.buglinktarget import BugLinkTargetMixin |
57 | from lp.registry.interfaces.distribution import IDistribution |
58 | from lp.registry.interfaces.distroseries import IDistroSeries |
59 | @@ -669,11 +675,26 @@ |
60 | spec_branch = self.getBranchLink(branch) |
61 | spec_branch.destroySelf() |
62 | |
63 | + def getLinkedBugTasks(self, user): |
64 | + """See `ISpecification`.""" |
65 | + params = BugTaskSearchParams(user=user, linked_blueprints=self.id) |
66 | + tasks = getUtility(IBugTaskSet).search(params) |
67 | + if self.distroseries is not None: |
68 | + context = self.distroseries |
69 | + elif self.distribution is not None: |
70 | + context = self.distribution |
71 | + elif self.productseries is not None: |
72 | + context = self.productseries |
73 | + else: |
74 | + context = self.product |
75 | + return filter_bugtasks_by_context(context, tasks) |
76 | + |
77 | def __repr__(self): |
78 | return '<Specification %s %r for %r>' % ( |
79 | self.id, self.name, self.target.name) |
80 | |
81 | |
82 | + |
83 | class HasSpecificationsMixin: |
84 | """A mixin class that implements many of the common shortcut properties |
85 | for other classes that have specifications. |
86 | |
87 | === modified file 'lib/lp/blueprints/stories/blueprints/xx-buglinks.txt' |
88 | --- lib/lp/blueprints/stories/blueprints/xx-buglinks.txt 2010-08-26 02:30:06 +0000 |
89 | +++ lib/lp/blueprints/stories/blueprints/xx-buglinks.txt 2011-03-28 00:03:43 +0000 |
90 | @@ -13,7 +13,7 @@ |
91 | ... 'http://launchpad.dev/firefox/+spec/svg-support') |
92 | >>> print extract_text(find_tag_by_id(anon_browser.contents, 'bug_links')) |
93 | Related bugs |
94 | - Bug #1: Firefox does not support SVG |
95 | + Bug #1: Firefox does not support SVG New |
96 | |
97 | |
98 | == Adding Links == |
99 | |
100 | === modified file 'lib/lp/blueprints/templates/specification-index.pt' |
101 | --- lib/lp/blueprints/templates/specification-index.pt 2011-03-10 01:44:17 +0000 |
102 | +++ lib/lp/blueprints/templates/specification-index.pt 2011-03-28 00:03:43 +0000 |
103 | @@ -208,11 +208,18 @@ |
104 | <div id="bug_links"> |
105 | <h3>Related bugs</h3> |
106 | |
107 | - <ul tal:condition="view/bug_links"> |
108 | - <li tal:repeat="link view/bug_links"> |
109 | - <tal:link replace="structure link/bug/fmt:link" /> |
110 | - </li> |
111 | - </ul> |
112 | + <table tal:condition="view/bug_links"> |
113 | + <tr tal:repeat="bugtask view/bug_links"> |
114 | + <td> |
115 | + <tal:link replace="structure bugtask/fmt:link" /> |
116 | + </td> |
117 | + <td> |
118 | + <span tal:content="bugtask/status/title" |
119 | + tal:attributes="class string:status${bugtask/status/name}" |
120 | + >Triaged</span> |
121 | + </td> |
122 | + </tr> |
123 | + </table> |
124 | |
125 | <ul class="horizontal"> |
126 | <li tal:define="link context_menu/linkbug" |
127 | |
128 | === modified file 'lib/lp/bugs/configure.zcml' |
129 | --- lib/lp/bugs/configure.zcml 2011-03-24 14:13:45 +0000 |
130 | +++ lib/lp/bugs/configure.zcml 2011-03-28 00:03:43 +0000 |
131 | @@ -168,6 +168,10 @@ |
132 | <facet |
133 | facet="bugs"> |
134 | |
135 | + <class class="lp.bugs.interfaces.bugtaskfilter.OrderedBugTask"> |
136 | + <allow attributes="rank id task"/> |
137 | + </class> |
138 | + |
139 | <!-- IBugTask --> |
140 | |
141 | <class |
142 | @@ -190,8 +194,12 @@ |
143 | date_fix_released |
144 | date_left_closed |
145 | date_closed |
146 | + distributionID |
147 | + distroseriesID |
148 | milestoneID |
149 | + productID |
150 | productseriesID |
151 | + sourcepackagenameID |
152 | task_age |
153 | bug_subscribers |
154 | is_complete |
155 | |
156 | === modified file 'lib/lp/bugs/interfaces/bugtarget.py' |
157 | --- lib/lp/bugs/interfaces/bugtarget.py 2011-03-23 13:51:39 +0000 |
158 | +++ lib/lp/bugs/interfaces/bugtarget.py 2011-03-28 00:03:43 +0000 |
159 | @@ -20,6 +20,7 @@ |
160 | 'IOfficialBugTagTargetRestricted', |
161 | ] |
162 | |
163 | + |
164 | from lazr.enum import DBEnumeratedType |
165 | from lazr.restful.declarations import ( |
166 | call_with, |
167 | @@ -279,6 +280,16 @@ |
168 | None, all statuses will be included. |
169 | """ |
170 | |
171 | + def getBugTaskWeightFunction(): |
172 | + """Return a function that is used to weight the bug tasks. |
173 | + |
174 | + The function should take a bug task as a parameter and return |
175 | + an OrderedBugTask. |
176 | + |
177 | + The ordered bug tasks are used to choose the most relevant bug task |
178 | + for any particular context. |
179 | + """ |
180 | + |
181 | |
182 | class IBugTarget(IHasBugs): |
183 | """An entity on which a bug can be reported. |
184 | |
185 | === modified file 'lib/lp/bugs/interfaces/bugtask.py' |
186 | --- lib/lp/bugs/interfaces/bugtask.py 2011-03-23 16:28:51 +0000 |
187 | +++ lib/lp/bugs/interfaces/bugtask.py 2011-03-28 00:03:43 +0000 |
188 | @@ -462,17 +462,21 @@ |
189 | BugField(title=_("Bug"), readonly=True)) |
190 | product = Choice( |
191 | title=_('Project'), required=False, vocabulary='Product') |
192 | + productID = Attribute('The product ID') |
193 | productseries = Choice( |
194 | title=_('Series'), required=False, vocabulary='ProductSeries') |
195 | productseriesID = Attribute('The product series ID') |
196 | sourcepackagename = Choice( |
197 | title=_("Package"), required=False, |
198 | vocabulary='SourcePackageName') |
199 | + sourcepackagenameID = Attribute('The sourcepackagename ID') |
200 | distribution = Choice( |
201 | title=_("Distribution"), required=False, vocabulary='Distribution') |
202 | + distributionID = Attribute('The distribution ID') |
203 | distroseries = Choice( |
204 | title=_("Series"), required=False, |
205 | vocabulary='DistroSeries') |
206 | + distroseriesID = Attribute('The distroseries ID') |
207 | milestone = exported(ReferenceChoice( |
208 | title=_('Milestone'), |
209 | required=False, |
210 | |
211 | === added file 'lib/lp/bugs/interfaces/bugtaskfilter.py' |
212 | --- lib/lp/bugs/interfaces/bugtaskfilter.py 1970-01-01 00:00:00 +0000 |
213 | +++ lib/lp/bugs/interfaces/bugtaskfilter.py 2011-03-28 00:03:43 +0000 |
214 | @@ -0,0 +1,68 @@ |
215 | +# Copyright 2011 Canonical Ltd. This software is licensed under the |
216 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
217 | + |
218 | +"""Fiter bugtasks based on context.""" |
219 | + |
220 | +__metaclass__ = type |
221 | +__all__ = [ |
222 | + 'filter_bugtasks_by_context', |
223 | + 'OrderedBugTask', |
224 | + 'simple_weight_calculator', |
225 | + ] |
226 | + |
227 | + |
228 | +from collections import defaultdict, namedtuple |
229 | +from operator import attrgetter |
230 | + |
231 | +from lp.bugs.interfaces.bugtarget import IHasBugs |
232 | + |
233 | + |
234 | +OrderedBugTask = namedtuple('OrderedBugTask', 'rank id task') |
235 | + |
236 | + |
237 | +def simple_weight_calculator(bugtask): |
238 | + """All tasks have the same weighting.""" |
239 | + return OrderedBugTask(1, bugtask.id, bugtask) |
240 | + |
241 | + |
242 | +def filter_bugtasks_by_context(context, bugtasks): |
243 | + """Return the bugtasks filtered so there is only one bug task per bug. |
244 | + |
245 | + The context is used to return the most relevent bugtask for that context. |
246 | + |
247 | + An initial constraint is to not require any database queries from this |
248 | + method. |
249 | + |
250 | + Current contexts that impact selection: |
251 | + IProduct |
252 | + IProductSeries |
253 | + IDistribution |
254 | + IDistroSeries |
255 | + ISourcePackage |
256 | + Others: |
257 | + get the first bugtask for any particular bug |
258 | + |
259 | + If the context is a Product, then return the product bug task if there is |
260 | + one. If the context is a ProductSeries, then return the productseries |
261 | + task if there is one, and if there isn't, look for the product task. A |
262 | + similar approach is taked for Distribution and distroseries. |
263 | + |
264 | + For source packages, we look for the source package task, followed by the |
265 | + distro source package, then the distroseries task, and lastly the distro |
266 | + task. |
267 | + |
268 | + If there is no specific matching task, we return the first task (the one |
269 | + with the smallest database id). |
270 | + """ |
271 | + has_bugs = IHasBugs(context, None) |
272 | + if has_bugs is None: |
273 | + weight_calculator = simple_weight_calculator |
274 | + else: |
275 | + weight_calculator = has_bugs.getBugTaskWeightFunction() |
276 | + |
277 | + bug_mapping = defaultdict(list) |
278 | + for task in bugtasks: |
279 | + bug_mapping[task.bugID].append(weight_calculator(task)) |
280 | + |
281 | + filtered = [sorted(tasks)[0].task for tasks in bug_mapping.itervalues()] |
282 | + return sorted(filtered, key=attrgetter('bugID')) |
283 | |
284 | === modified file 'lib/lp/bugs/model/bugtarget.py' |
285 | --- lib/lp/bugs/model/bugtarget.py 2011-02-17 04:00:06 +0000 |
286 | +++ lib/lp/bugs/model/bugtarget.py 2011-03-28 00:03:43 +0000 |
287 | @@ -51,6 +51,7 @@ |
288 | RESOLVED_BUGTASK_STATUSES, |
289 | UNRESOLVED_BUGTASK_STATUSES, |
290 | ) |
291 | +from lp.bugs.interfaces.bugtaskfilter import simple_weight_calculator |
292 | from lp.bugs.model.bugtask import ( |
293 | BugTaskSet, |
294 | get_bug_privacy_filter, |
295 | @@ -233,6 +234,10 @@ |
296 | counts = cur.fetchone() |
297 | return dict(zip(statuses, counts)) |
298 | |
299 | + def getBugTaskWeightFunction(self): |
300 | + """Default weight function is the simple one.""" |
301 | + return simple_weight_calculator |
302 | + |
303 | |
304 | class BugTargetBase(HasBugsBase): |
305 | """Standard functionality for IBugTargets. |
306 | |
307 | === modified file 'lib/lp/bugs/model/bugtask.py' |
308 | --- lib/lp/bugs/model/bugtask.py 2011-03-28 00:03:41 +0000 |
309 | +++ lib/lp/bugs/model/bugtask.py 2011-03-28 00:03:43 +0000 |
310 | @@ -2323,16 +2323,25 @@ |
311 | def _buildBlueprintRelatedClause(self, params): |
312 | """Find bugs related to Blueprints, or not.""" |
313 | linked_blueprints = params.linked_blueprints |
314 | - if linked_blueprints == BugBlueprintSearch.BUGS_WITH_BLUEPRINTS: |
315 | - return "EXISTS (%s)" % ( |
316 | - "SELECT 1 FROM SpecificationBug" |
317 | - " WHERE SpecificationBug.bug = Bug.id") |
318 | - elif linked_blueprints == BugBlueprintSearch.BUGS_WITHOUT_BLUEPRINTS: |
319 | - return "NOT EXISTS (%s)" % ( |
320 | - "SELECT 1 FROM SpecificationBug" |
321 | - " WHERE SpecificationBug.bug = Bug.id") |
322 | + if linked_blueprints is None: |
323 | + return None |
324 | + elif zope_isinstance(linked_blueprints, BaseItem): |
325 | + if linked_blueprints == BugBlueprintSearch.BUGS_WITH_BLUEPRINTS: |
326 | + return "EXISTS (%s)" % ( |
327 | + "SELECT 1 FROM SpecificationBug" |
328 | + " WHERE SpecificationBug.bug = Bug.id") |
329 | + elif (linked_blueprints == |
330 | + BugBlueprintSearch.BUGS_WITHOUT_BLUEPRINTS): |
331 | + return "NOT EXISTS (%s)" % ( |
332 | + "SELECT 1 FROM SpecificationBug" |
333 | + " WHERE SpecificationBug.bug = Bug.id") |
334 | else: |
335 | - return None |
336 | + # A specific search term has been supplied. |
337 | + return """EXISTS ( |
338 | + SELECT TRUE FROM SpecificationBug |
339 | + WHERE SpecificationBug.bug=Bug.id AND |
340 | + SpecificationBug.specification %s) |
341 | + """ % search_value_to_where_condition(linked_blueprints) |
342 | |
343 | def buildOrigin(self, join_tables, prejoin_tables, clauseTables): |
344 | """Build the parameter list for Store.using(). |
345 | |
346 | === modified file 'lib/lp/bugs/model/tests/test_bugtask.py' |
347 | --- lib/lp/bugs/model/tests/test_bugtask.py 2011-03-22 01:33:16 +0000 |
348 | +++ lib/lp/bugs/model/tests/test_bugtask.py 2011-03-28 00:03:43 +0000 |
349 | @@ -58,6 +58,7 @@ |
350 | login_person, |
351 | logout, |
352 | normalize_whitespace, |
353 | + person_logged_in, |
354 | StormStatementRecorder, |
355 | TestCase, |
356 | TestCaseWithFactory, |
357 | @@ -1010,6 +1011,27 @@ |
358 | self.assertEqual([task2], list(result)) |
359 | |
360 | |
361 | +class BugTaskSetSearchTest(TestCaseWithFactory): |
362 | + |
363 | + layer = DatabaseFunctionalLayer |
364 | + |
365 | + def test_explicit_blueprint_specified(self): |
366 | + # If the linked_blueprints is an integer id, then only bugtasks for |
367 | + # bugs that are linked to that blueprint are returned. |
368 | + bug1 = self.factory.makeBug() |
369 | + blueprint1 = self.factory.makeBlueprint() |
370 | + with person_logged_in(blueprint1.owner): |
371 | + blueprint1.linkBug(bug1) |
372 | + bug2 = self.factory.makeBug() |
373 | + blueprint2 = self.factory.makeBlueprint() |
374 | + with person_logged_in(blueprint2.owner): |
375 | + blueprint2.linkBug(bug2) |
376 | + self.factory.makeBug() |
377 | + params = BugTaskSearchParams(user=None, linked_blueprints=blueprint1.id) |
378 | + tasks = set(getUtility(IBugTaskSet).search(params)) |
379 | + self.assertThat(set(bug1.bugtasks), Equals(tasks)) |
380 | + |
381 | + |
382 | class BugTaskSearchBugsElsewhereTest(unittest.TestCase): |
383 | """Tests for searching bugs filtering on related bug tasks. |
384 | |
385 | |
386 | === added file 'lib/lp/bugs/tests/test_bugtaskfilter.py' |
387 | --- lib/lp/bugs/tests/test_bugtaskfilter.py 1970-01-01 00:00:00 +0000 |
388 | +++ lib/lp/bugs/tests/test_bugtaskfilter.py 2011-03-28 00:03:43 +0000 |
389 | @@ -0,0 +1,196 @@ |
390 | +# Copyright 2011 Canonical Ltd. This software is licensed under the |
391 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
392 | + |
393 | +"""Tests for lp.bugs.interfaces.bugtaskfilter.""" |
394 | + |
395 | +__metaclass__ = type |
396 | + |
397 | +from testtools.matchers import Equals |
398 | + |
399 | +from canonical.testing.layers import DatabaseFunctionalLayer |
400 | +from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context |
401 | +from lp.testing import ( |
402 | + StormStatementRecorder, |
403 | + TestCaseWithFactory, |
404 | + ) |
405 | +from lp.testing.matchers import HasQueryCount |
406 | + |
407 | + |
408 | +class TestFilterBugTasksByContext(TestCaseWithFactory): |
409 | + |
410 | + layer = DatabaseFunctionalLayer |
411 | + |
412 | + def test_simple_case(self): |
413 | + bug = self.factory.makeBug() |
414 | + tasks = list(bug.bugtasks) |
415 | + self.assertThat( |
416 | + filter_bugtasks_by_context(None, tasks), |
417 | + Equals(tasks)) |
418 | + |
419 | + def test_multiple_bugs(self): |
420 | + bug1 = self.factory.makeBug() |
421 | + bug2 = self.factory.makeBug() |
422 | + bug3 = self.factory.makeBug() |
423 | + tasks = list(bug1.bugtasks) |
424 | + tasks.extend(bug2.bugtasks) |
425 | + tasks.extend(bug3.bugtasks) |
426 | + with StormStatementRecorder() as recorder: |
427 | + filtered = filter_bugtasks_by_context(None, tasks) |
428 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
429 | + self.assertThat(len(filtered), Equals(3)) |
430 | + self.assertThat(filtered, Equals(tasks)) |
431 | + |
432 | + def test_two_product_tasks_case_no_context(self): |
433 | + widget = self.factory.makeProduct() |
434 | + bug = self.factory.makeBug(product=widget) |
435 | + cogs = self.factory.makeProduct() |
436 | + self.factory.makeBugTask(bug=bug, target=cogs) |
437 | + tasks = list(bug.bugtasks) |
438 | + with StormStatementRecorder() as recorder: |
439 | + filtered = filter_bugtasks_by_context(None, tasks) |
440 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
441 | + self.assertThat(filtered, Equals([bug.getBugTask(widget)])) |
442 | + |
443 | + def test_two_product_tasks_case(self): |
444 | + widget = self.factory.makeProduct() |
445 | + bug = self.factory.makeBug(product=widget) |
446 | + cogs = self.factory.makeProduct() |
447 | + task = self.factory.makeBugTask(bug=bug, target=cogs) |
448 | + tasks = list(bug.bugtasks) |
449 | + with StormStatementRecorder() as recorder: |
450 | + filtered = filter_bugtasks_by_context(cogs, tasks) |
451 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
452 | + self.assertThat(filtered, Equals([task])) |
453 | + |
454 | + def test_product_context_with_series_task(self): |
455 | + bug = self.factory.makeBug() |
456 | + widget = self.factory.makeProduct() |
457 | + task = self.factory.makeBugTask(bug=bug, target=widget) |
458 | + self.factory.makeBugTask(bug=bug, target=widget.development_focus) |
459 | + tasks = list(bug.bugtasks) |
460 | + with StormStatementRecorder() as recorder: |
461 | + filtered = filter_bugtasks_by_context(widget, tasks) |
462 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
463 | + self.assertThat(filtered, Equals([task])) |
464 | + |
465 | + def test_productseries_context_with_series_task(self): |
466 | + bug = self.factory.makeBug() |
467 | + widget = self.factory.makeProduct() |
468 | + self.factory.makeBugTask(bug=bug, target=widget) |
469 | + series = widget.development_focus |
470 | + task = self.factory.makeBugTask(bug=bug, target=series) |
471 | + tasks = list(bug.bugtasks) |
472 | + with StormStatementRecorder() as recorder: |
473 | + filtered = filter_bugtasks_by_context(series, tasks) |
474 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
475 | + self.assertThat(filtered, Equals([task])) |
476 | + |
477 | + def test_productseries_context_with_only_product_task(self): |
478 | + bug = self.factory.makeBug() |
479 | + widget = self.factory.makeProduct() |
480 | + task = self.factory.makeBugTask(bug=bug, target=widget) |
481 | + series = widget.development_focus |
482 | + tasks = list(bug.bugtasks) |
483 | + with StormStatementRecorder() as recorder: |
484 | + filtered = filter_bugtasks_by_context(series, tasks) |
485 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
486 | + self.assertThat(filtered, Equals([task])) |
487 | + |
488 | + def test_distro_context(self): |
489 | + bug = self.factory.makeBug() |
490 | + mint = self.factory.makeDistribution() |
491 | + task = self.factory.makeBugTask(bug=bug, target=mint) |
492 | + tasks = list(bug.bugtasks) |
493 | + with StormStatementRecorder() as recorder: |
494 | + filtered = filter_bugtasks_by_context(mint, tasks) |
495 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
496 | + self.assertThat(filtered, Equals([task])) |
497 | + |
498 | + def test_distro_context_with_series_task(self): |
499 | + bug = self.factory.makeBug() |
500 | + mint = self.factory.makeDistribution() |
501 | + task = self.factory.makeBugTask(bug=bug, target=mint) |
502 | + devel = self.factory.makeDistroSeries(mint) |
503 | + self.factory.makeBugTask(bug=bug, target=devel) |
504 | + tasks = list(bug.bugtasks) |
505 | + with StormStatementRecorder() as recorder: |
506 | + filtered = filter_bugtasks_by_context(mint, tasks) |
507 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
508 | + self.assertThat(filtered, Equals([task])) |
509 | + |
510 | + def test_distroseries_context_with_series_task(self): |
511 | + bug = self.factory.makeBug() |
512 | + mint = self.factory.makeDistribution() |
513 | + self.factory.makeBugTask(bug=bug, target=mint) |
514 | + devel = self.factory.makeDistroSeries(mint) |
515 | + task = self.factory.makeBugTask(bug=bug, target=devel) |
516 | + tasks = list(bug.bugtasks) |
517 | + with StormStatementRecorder() as recorder: |
518 | + filtered = filter_bugtasks_by_context(devel, tasks) |
519 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
520 | + self.assertThat(filtered, Equals([task])) |
521 | + |
522 | + def test_distroseries_context_with_no_series_task(self): |
523 | + bug = self.factory.makeBug() |
524 | + mint = self.factory.makeDistribution() |
525 | + task = self.factory.makeBugTask(bug=bug, target=mint) |
526 | + devel = self.factory.makeDistroSeries(mint) |
527 | + tasks = list(bug.bugtasks) |
528 | + with StormStatementRecorder() as recorder: |
529 | + filtered = filter_bugtasks_by_context(devel, tasks) |
530 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
531 | + self.assertThat(filtered, Equals([task])) |
532 | + |
533 | + def test_sourcepackage_context_with_sourcepackage_task(self): |
534 | + bug = self.factory.makeBug() |
535 | + sp = self.factory.makeSourcePackage() |
536 | + task = self.factory.makeBugTask(bug=bug, target=sp) |
537 | + tasks = list(bug.bugtasks) |
538 | + with StormStatementRecorder() as recorder: |
539 | + filtered = filter_bugtasks_by_context(sp, tasks) |
540 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
541 | + self.assertThat(filtered, Equals([task])) |
542 | + |
543 | + def test_sourcepackage_context_with_distrosourcepackage_task(self): |
544 | + bug = self.factory.makeBug() |
545 | + sp = self.factory.makeSourcePackage() |
546 | + dsp = sp.distribution_sourcepackage |
547 | + task = self.factory.makeBugTask(bug=bug, target=dsp) |
548 | + tasks = list(bug.bugtasks) |
549 | + with StormStatementRecorder() as recorder: |
550 | + filtered = filter_bugtasks_by_context(sp, tasks) |
551 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
552 | + self.assertThat(filtered, Equals([task])) |
553 | + |
554 | + def test_sourcepackage_context_series_task(self): |
555 | + bug = self.factory.makeBug() |
556 | + sp = self.factory.makeSourcePackage() |
557 | + task = self.factory.makeBugTask(bug=bug, target=sp.distroseries) |
558 | + tasks = list(bug.bugtasks) |
559 | + with StormStatementRecorder() as recorder: |
560 | + filtered = filter_bugtasks_by_context(sp, tasks) |
561 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
562 | + self.assertThat(filtered, Equals([task])) |
563 | + |
564 | + def test_sourcepackage_context_distro_task(self): |
565 | + bug = self.factory.makeBug() |
566 | + sp = self.factory.makeSourcePackage() |
567 | + task = self.factory.makeBugTask(bug=bug, target=sp.distribution) |
568 | + tasks = list(bug.bugtasks) |
569 | + with StormStatementRecorder() as recorder: |
570 | + filtered = filter_bugtasks_by_context(sp, tasks) |
571 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
572 | + self.assertThat(filtered, Equals([task])) |
573 | + |
574 | + def test_sourcepackage_context_distro_task_with_other_distro_package(self): |
575 | + bug = self.factory.makeBug() |
576 | + sp = self.factory.makeSourcePackage() |
577 | + task = self.factory.makeBugTask(bug=bug, target=sp.distribution) |
578 | + other_sp = self.factory.makeSourcePackage( |
579 | + sourcepackagename=sp.sourcepackagename) |
580 | + self.factory.makeBugTask(bug=bug, target=other_sp) |
581 | + tasks = list(bug.bugtasks) |
582 | + with StormStatementRecorder() as recorder: |
583 | + filtered = filter_bugtasks_by_context(sp, tasks) |
584 | + self.assertThat(recorder, HasQueryCount(Equals(0))) |
585 | + self.assertThat(filtered, Equals([task])) |
586 | |
587 | === modified file 'lib/lp/code/interfaces/branch.py' |
588 | --- lib/lp/code/interfaces/branch.py 2011-03-24 12:03:02 +0000 |
589 | +++ lib/lp/code/interfaces/branch.py 2011-03-28 00:03:43 +0000 |
590 | @@ -418,7 +418,7 @@ |
591 | When multiple tasks are on a bug, if one of the tasks is for the |
592 | branch.target, then only that task is returned. Otherwise the default |
593 | bug task is returned. |
594 | - |
595 | + |
596 | :param user: The user doing the search. |
597 | :param status_filter: Passed onto the bug search as a constraint. |
598 | """ |
599 | |
600 | === modified file 'lib/lp/code/model/branch.py' |
601 | --- lib/lp/code/model/branch.py 2011-03-23 16:28:51 +0000 |
602 | +++ lib/lp/code/model/branch.py 2011-03-28 00:03:43 +0000 |
603 | @@ -7,7 +7,6 @@ |
604 | __all__ = [ |
605 | 'Branch', |
606 | 'BranchSet', |
607 | - 'filter_one_task_per_bug', |
608 | ] |
609 | |
610 | from datetime import datetime |
611 | @@ -75,6 +74,7 @@ |
612 | BugTaskSearchParams, |
613 | IBugTaskSet, |
614 | ) |
615 | +from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context |
616 | from lp.buildmaster.model.buildqueue import BuildQueue |
617 | from lp.code.bzr import ( |
618 | BranchFormat, |
619 | @@ -316,7 +316,7 @@ |
620 | tasks = shortlist(getUtility(IBugTaskSet).search(params), 1000) |
621 | # Post process to discard irrelevant tasks: we only return one task per |
622 | # bug, and cannot easily express this in sql (yet). |
623 | - return filter_one_task_per_bug(self, tasks) |
624 | + return filter_bugtasks_by_context(self.target.context, tasks) |
625 | |
626 | def linkBug(self, bug, registrant): |
627 | """See `IBranch`.""" |
628 | @@ -1390,27 +1390,3 @@ |
629 | """ |
630 | update_trigger_modified_fields(branch) |
631 | send_branch_modified_notifications(branch, event) |
632 | - |
633 | - |
634 | -def filter_one_task_per_bug(branch, tasks): |
635 | - """Given bug tasks for a branch, discard irrelevant ones. |
636 | - |
637 | - Cannot easily be expressed in SQL yet, so we need this helper method. |
638 | - """ |
639 | - order = {} |
640 | - bugtarget = branch.target.context |
641 | - # First pass calculates the order and selects the bugtasks that match |
642 | - # our target. |
643 | - # Second pass selects the earliest bugtask where the bug has no task on |
644 | - # our target. |
645 | - for pos, task in enumerate(tasks): |
646 | - bug = task.bug |
647 | - if bug not in order: |
648 | - order[bug] = [pos, None] |
649 | - if task.target == bugtarget: |
650 | - order[bug][1] = task |
651 | - for task in tasks: |
652 | - index = order[task.bug] |
653 | - if index[1] is None: |
654 | - index[1] = task |
655 | - return [task for pos, task in sorted(order.values())] |
656 | |
657 | === modified file 'lib/lp/code/model/branchcollection.py' |
658 | --- lib/lp/code/model/branchcollection.py 2011-03-26 19:33:54 +0000 |
659 | +++ lib/lp/code/model/branchcollection.py 2011-03-28 00:03:43 +0000 |
660 | @@ -41,6 +41,7 @@ |
661 | IBugTaskSet, |
662 | BugTaskSearchParams, |
663 | ) |
664 | +from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context |
665 | from lp.bugs.model.bugbranch import BugBranch |
666 | from lp.bugs.model.bugtask import BugTask |
667 | from lp.code.interfaces.branch import user_has_special_branch_access |
668 | @@ -54,10 +55,7 @@ |
669 | from lp.code.enums import BranchMergeProposalStatus |
670 | from lp.code.interfaces.branchlookup import IBranchLookup |
671 | from lp.code.interfaces.codehosting import LAUNCHPAD_SERVICES |
672 | -from lp.code.model.branch import ( |
673 | - Branch, |
674 | - filter_one_task_per_bug, |
675 | - ) |
676 | +from lp.code.model.branch import Branch |
677 | from lp.code.model.branchmergeproposal import BranchMergeProposal |
678 | from lp.code.model.branchsubscription import BranchSubscription |
679 | from lp.code.model.codeimport import CodeImport |
680 | @@ -342,7 +340,7 @@ |
681 | # Now filter those down to one bugtask per branch |
682 | for branch, tasks in bugtasks_for_branch.iteritems(): |
683 | linked_bugtasks[branch.id].extend( |
684 | - filter_one_task_per_bug(branch, tasks)) |
685 | + filter_bugtasks_by_context(branch.target.context, tasks)) |
686 | |
687 | return [make_rev_info( |
688 | rev, merge_proposal_revs, linked_bugtasks) |
689 | |
690 | === modified file 'lib/lp/registry/interfaces/distroseries.py' |
691 | --- lib/lp/registry/interfaces/distroseries.py 2011-03-10 14:05:51 +0000 |
692 | +++ lib/lp/registry/interfaces/distroseries.py 2011-03-28 00:03:43 +0000 |
693 | @@ -223,6 +223,7 @@ |
694 | Interface, # Really IDistribution, see circular import fix below. |
695 | title=_("Distribution"), required=True, |
696 | description=_("The distribution for which this is a series."))) |
697 | + distributionID = Attribute('The distribution ID.') |
698 | named_version = Attribute('The combined display name and version.') |
699 | parent = Attribute('The structural parent of this series - the distro') |
700 | components = Attribute("The series components.") |
701 | |
702 | === modified file 'lib/lp/registry/interfaces/productseries.py' |
703 | --- lib/lp/registry/interfaces/productseries.py 2011-03-24 14:13:45 +0000 |
704 | +++ lib/lp/registry/interfaces/productseries.py 2011-03-28 00:03:43 +0000 |
705 | @@ -137,6 +137,7 @@ |
706 | ReferenceChoice(title=_('Project'), required=True, |
707 | vocabulary='Product', schema=Interface), # really IProduct |
708 | exported_as='project') |
709 | + productID = Attribute('The product ID.') |
710 | |
711 | status = exported( |
712 | Choice( |
713 | |
714 | === modified file 'lib/lp/registry/model/distribution.py' |
715 | --- lib/lp/registry/model/distribution.py 2011-03-24 11:21:19 +0000 |
716 | +++ lib/lp/registry/model/distribution.py 2011-03-28 00:03:43 +0000 |
717 | @@ -107,6 +107,7 @@ |
718 | BugTaskStatus, |
719 | UNRESOLVED_BUGTASK_STATUSES, |
720 | ) |
721 | +from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask |
722 | from lp.bugs.model.bug import ( |
723 | BugSet, |
724 | get_bug_tags, |
725 | @@ -1840,6 +1841,22 @@ |
726 | productseries = sourcepackage.productseries |
727 | return productseries.product.invitesTranslationEdits(person, language) |
728 | |
729 | + def getBugTaskWeightFunction(self): |
730 | + """Provide a weight function to determine optimal bug task. |
731 | + |
732 | + Full weight is given to tasks for this distribution. |
733 | + |
734 | + Given that there must be a distribution task for a series of that |
735 | + distribution to have a task, we give no more weighting to a |
736 | + distroseries task than any other. |
737 | + """ |
738 | + distributionID = self.id |
739 | + def weight_function(bugtask): |
740 | + if bugtask.distributionID == distributionID: |
741 | + return OrderedBugTask(1, bugtask.id, bugtask) |
742 | + return OrderedBugTask(2, bugtask.id, bugtask) |
743 | + return weight_function |
744 | + |
745 | |
746 | class DistributionSet: |
747 | """This class is to deal with Distribution related stuff""" |
748 | |
749 | === modified file 'lib/lp/registry/model/distroseries.py' |
750 | --- lib/lp/registry/model/distroseries.py 2011-03-24 11:46:45 +0000 |
751 | +++ lib/lp/registry/model/distroseries.py 2011-03-28 00:03:43 +0000 |
752 | @@ -83,6 +83,7 @@ |
753 | Specification, |
754 | ) |
755 | from lp.bugs.interfaces.bugtarget import IHasBugHeat |
756 | +from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask |
757 | from lp.bugs.model.bug import ( |
758 | get_bug_tags, |
759 | get_bug_tags_open_count, |
760 | @@ -1982,6 +1983,25 @@ |
761 | return Store.of(self).find( |
762 | DistroSeries, DistroSeries.parent_series == self) |
763 | |
764 | + def getBugTaskWeightFunction(self): |
765 | + """Provide a weight function to determine optimal bug task. |
766 | + |
767 | + Full weight is given to tasks for this distro series. |
768 | + |
769 | + If the series isn't found, the distribution task is better than |
770 | + others. |
771 | + """ |
772 | + seriesID = self.id |
773 | + distributionID = self.distributionID |
774 | + def weight_function(bugtask): |
775 | + if bugtask.distroseriesID == seriesID: |
776 | + return OrderedBugTask(1, bugtask.id, bugtask) |
777 | + elif bugtask.distributionID == distributionID: |
778 | + return OrderedBugTask(2, bugtask.id, bugtask) |
779 | + else: |
780 | + return OrderedBugTask(3, bugtask.id, bugtask) |
781 | + return weight_function |
782 | + |
783 | |
784 | class DistroSeriesSet: |
785 | implements(IDistroSeriesSet) |
786 | |
787 | === modified file 'lib/lp/registry/model/product.py' |
788 | --- lib/lp/registry/model/product.py 2011-03-24 20:48:35 +0000 |
789 | +++ lib/lp/registry/model/product.py 2011-03-28 00:03:43 +0000 |
790 | @@ -113,6 +113,7 @@ |
791 | from lp.blueprints.model.sprint import HasSprintsMixin |
792 | from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor |
793 | from lp.bugs.interfaces.bugtarget import IHasBugHeat |
794 | +from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask |
795 | from lp.bugs.model.bug import ( |
796 | BugSet, |
797 | get_bug_tags, |
798 | @@ -1331,6 +1332,22 @@ |
799 | SourcePackageRecipeData.base_branch == Branch.id, |
800 | Branch.product == self) |
801 | |
802 | + def getBugTaskWeightFunction(self): |
803 | + """Provide a weight function to determine optimal bug task. |
804 | + |
805 | + Full weight is given to tasks for this product. |
806 | + |
807 | + Given that there must be a product task for a series of that product |
808 | + to have a task, we give no more weighting to a productseries task than |
809 | + any other. |
810 | + """ |
811 | + productID = self.id |
812 | + def weight_function(bugtask): |
813 | + if bugtask.productID == productID: |
814 | + return OrderedBugTask(1, bugtask.id, bugtask) |
815 | + return OrderedBugTask(2, bugtask.id, bugtask) |
816 | + return weight_function |
817 | + |
818 | |
819 | class ProductSet: |
820 | implements(IProductSet) |
821 | |
822 | === modified file 'lib/lp/registry/model/productseries.py' |
823 | --- lib/lp/registry/model/productseries.py 2011-03-24 11:01:45 +0000 |
824 | +++ lib/lp/registry/model/productseries.py 2011-03-28 00:03:43 +0000 |
825 | @@ -59,6 +59,7 @@ |
826 | Specification, |
827 | ) |
828 | from lp.bugs.interfaces.bugtarget import IHasBugHeat |
829 | +from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask |
830 | from lp.bugs.model.bug import ( |
831 | get_bug_tags, |
832 | get_bug_tags_open_count, |
833 | @@ -661,6 +662,24 @@ |
834 | landmarks=landmarks, |
835 | product=self.product) |
836 | |
837 | + def getBugTaskWeightFunction(self): |
838 | + """Provide a weight function to determine optimal bug task. |
839 | + |
840 | + Full weight is given to tasks for this product series. |
841 | + |
842 | + If the series isn't found, the product task is better than others. |
843 | + """ |
844 | + seriesID = self.id |
845 | + productID = self.productID |
846 | + def weight_function(bugtask): |
847 | + if bugtask.productseriesID == seriesID: |
848 | + return OrderedBugTask(1, bugtask.id, bugtask) |
849 | + elif bugtask.productID == productID: |
850 | + return OrderedBugTask(2, bugtask.id, bugtask) |
851 | + else: |
852 | + return OrderedBugTask(3, bugtask.id, bugtask) |
853 | + return weight_function |
854 | + |
855 | |
856 | class TimelineProductSeries: |
857 | """See `ITimelineProductSeries`.""" |
858 | |
859 | === modified file 'lib/lp/registry/model/sourcepackage.py' |
860 | --- lib/lp/registry/model/sourcepackage.py 2011-03-24 11:01:45 +0000 |
861 | +++ lib/lp/registry/model/sourcepackage.py 2011-03-28 00:03:43 +0000 |
862 | @@ -41,6 +41,7 @@ |
863 | QuestionTargetSearch, |
864 | ) |
865 | from lp.bugs.interfaces.bugtarget import IHasBugHeat |
866 | +from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask |
867 | from lp.bugs.model.bug import get_bug_tags_open_count |
868 | from lp.bugs.model.bugtarget import ( |
869 | BugTargetBase, |
870 | @@ -756,3 +757,27 @@ |
871 | def linkedBranches(self): |
872 | """See `ISourcePackage`.""" |
873 | return dict((p.name, b) for (p, b) in self.linked_branches) |
874 | + |
875 | + def getBugTaskWeightFunction(self): |
876 | + """Provide a weight function to determine optimal bug task. |
877 | + |
878 | + We look for the source package task, followed by the distro source |
879 | + package, then the distroseries task, and lastly the distro task. |
880 | + """ |
881 | + sourcepackagenameID = self.sourcepackagename.id |
882 | + seriesID = self.distroseries.id |
883 | + distributionID = self.distroseries.distributionID |
884 | + def weight_function(bugtask): |
885 | + if bugtask.sourcepackagenameID == sourcepackagenameID: |
886 | + if bugtask.distroseriesID == seriesID: |
887 | + return OrderedBugTask(1, bugtask.id, bugtask) |
888 | + elif bugtask.distributionID == distributionID: |
889 | + return OrderedBugTask(2, bugtask.id, bugtask) |
890 | + elif bugtask.distroseriesID == seriesID: |
891 | + return OrderedBugTask(3, bugtask.id, bugtask) |
892 | + elif bugtask.distributionID == distributionID: |
893 | + return OrderedBugTask(4, bugtask.id, bugtask) |
894 | + # Catch the default case, and where there is a task for the same |
895 | + # sourcepackage on a different distro. |
896 | + return OrderedBugTask(5, bugtask.id, bugtask) |
897 | + return weight_function |
This is great, especially the no sql bit, and it fits in with other work already done to optimise and improve the security around retrieving bug tasks.
I'm wondering how the order of looking for contexts was determined in getLinkedBugTas ks():
67 + if self.distroseries is not None:
68 + context = self.distroseries
69 + elif self.distribution is not None:
70 + context = self.distribution
71 + elif self.productseries is not None:
72 + context = self.productseries
73 + else:
74 + context = self.product
distroseries and productseries are set as goals i think? So one of the other is set. And product and distribution as targets. What makes distroseries more important than productseries for example? Perhaps a comment of two to explain would be good. Since this is only used here, there's no need for a helper method unless it may be something useful to abstract out.