Merge lp:~cjwatson/launchpad/preload-git-landing-targets-candidates into lp:launchpad
- preload-git-landing-targets-candidates
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18285 |
Proposed branch: | lp:~cjwatson/launchpad/preload-git-landing-targets-candidates |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/preload-branch-landing-targets |
Diff against target: |
541 lines (+268/-48) 9 files modified
lib/lp/_schema_circular_imports.py (+6/-4) lib/lp/code/browser/gitref.py (+4/-2) lib/lp/code/browser/tests/test_gitref.py (+36/-0) lib/lp/code/interfaces/gitref.py (+40/-16) lib/lp/code/interfaces/gitrepository.py (+40/-16) lib/lp/code/model/gitref.py (+20/-10) lib/lp/code/model/gitrepository.py (+20/-0) lib/lp/code/model/tests/test_gitref.py (+51/-0) lib/lp/code/model/tests/test_gitrepository.py (+51/-0) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/preload-git-landing-targets-candidates |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+310655@code.launchpad.net |
Commit message
Preload {GitRepository,
Description of the change
This is in line with similar handling in Branch.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
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/_schema_circular_imports.py' | |||
2 | --- lib/lp/_schema_circular_imports.py 2016-11-11 15:10:55 +0000 | |||
3 | +++ lib/lp/_schema_circular_imports.py 2016-11-11 15:10:56 +0000 | |||
4 | @@ -566,8 +566,10 @@ | |||
5 | 566 | IGitRef, 'createMergeProposal', 'merge_target', IGitRef) | 566 | IGitRef, 'createMergeProposal', 'merge_target', IGitRef) |
6 | 567 | patch_plain_parameter_type( | 567 | patch_plain_parameter_type( |
7 | 568 | IGitRef, 'createMergeProposal', 'merge_prerequisite', IGitRef) | 568 | IGitRef, 'createMergeProposal', 'merge_prerequisite', IGitRef) |
10 | 569 | patch_collection_property(IGitRef, 'landing_targets', IBranchMergeProposal) | 569 | patch_collection_property( |
11 | 570 | patch_collection_property(IGitRef, 'landing_candidates', IBranchMergeProposal) | 570 | IGitRef, '_api_landing_targets', IBranchMergeProposal) |
12 | 571 | patch_collection_property( | ||
13 | 572 | IGitRef, '_api_landing_candidates', IBranchMergeProposal) | ||
14 | 571 | patch_collection_property(IGitRef, 'dependent_landings', IBranchMergeProposal) | 573 | patch_collection_property(IGitRef, 'dependent_landings', IBranchMergeProposal) |
15 | 572 | patch_entry_return_type(IGitRef, 'createMergeProposal', IBranchMergeProposal) | 574 | patch_entry_return_type(IGitRef, 'createMergeProposal', IBranchMergeProposal) |
16 | 573 | patch_collection_return_type( | 575 | patch_collection_return_type( |
17 | @@ -581,9 +583,9 @@ | |||
18 | 581 | patch_entry_return_type(IGitRepository, 'getSubscription', IGitSubscription) | 583 | patch_entry_return_type(IGitRepository, 'getSubscription', IGitSubscription) |
19 | 582 | patch_reference_property(IGitRepository, 'code_import', ICodeImport) | 584 | patch_reference_property(IGitRepository, 'code_import', ICodeImport) |
20 | 583 | patch_collection_property( | 585 | patch_collection_property( |
22 | 584 | IGitRepository, 'landing_targets', IBranchMergeProposal) | 586 | IGitRepository, '_api_landing_targets', IBranchMergeProposal) |
23 | 585 | patch_collection_property( | 587 | patch_collection_property( |
25 | 586 | IGitRepository, 'landing_candidates', IBranchMergeProposal) | 588 | IGitRepository, '_api_landing_candidates', IBranchMergeProposal) |
26 | 587 | patch_collection_property( | 589 | patch_collection_property( |
27 | 588 | IGitRepository, 'dependent_landings', IBranchMergeProposal) | 590 | IGitRepository, 'dependent_landings', IBranchMergeProposal) |
28 | 589 | 591 | ||
29 | 590 | 592 | ||
30 | === modified file 'lib/lp/code/browser/gitref.py' | |||
31 | --- lib/lp/code/browser/gitref.py 2016-10-14 16:16:18 +0000 | |||
32 | +++ lib/lp/code/browser/gitref.py 2016-11-11 15:10:56 +0000 | |||
33 | @@ -133,12 +133,14 @@ | |||
34 | 133 | @cachedproperty | 133 | @cachedproperty |
35 | 134 | def landing_targets(self): | 134 | def landing_targets(self): |
36 | 135 | """Return a filtered list of landing targets.""" | 135 | """Return a filtered list of landing targets.""" |
38 | 136 | return latest_proposals_for_each_branch(self.context.landing_targets) | 136 | targets = self.context.getPrecachedLandingTargets(self.user) |
39 | 137 | return latest_proposals_for_each_branch(targets) | ||
40 | 137 | 138 | ||
41 | 138 | @cachedproperty | 139 | @cachedproperty |
42 | 139 | def landing_candidates(self): | 140 | def landing_candidates(self): |
43 | 140 | """Return a decorated list of landing candidates.""" | 141 | """Return a decorated list of landing candidates.""" |
45 | 141 | return [proposal for proposal in self.context.landing_candidates | 142 | candidates = self.context.getPrecachedLandingCandidates(self.user) |
46 | 143 | return [proposal for proposal in candidates | ||
47 | 142 | if check_permission("launchpad.View", proposal)] | 144 | if check_permission("launchpad.View", proposal)] |
48 | 143 | 145 | ||
49 | 144 | def _getBranchCountText(self, count): | 146 | def _getBranchCountText(self, count): |
50 | 145 | 147 | ||
51 | === modified file 'lib/lp/code/browser/tests/test_gitref.py' | |||
52 | --- lib/lp/code/browser/tests/test_gitref.py 2016-09-07 11:12:58 +0000 | |||
53 | +++ lib/lp/code/browser/tests/test_gitref.py 2016-11-11 15:10:56 +0000 | |||
54 | @@ -12,6 +12,8 @@ | |||
55 | 12 | from BeautifulSoup import BeautifulSoup | 12 | from BeautifulSoup import BeautifulSoup |
56 | 13 | import pytz | 13 | import pytz |
57 | 14 | import soupmatchers | 14 | import soupmatchers |
58 | 15 | from storm.store import Store | ||
59 | 16 | from testtools.matchers import Equals | ||
60 | 15 | from zope.component import getUtility | 17 | from zope.component import getUtility |
61 | 16 | from zope.security.proxy import removeSecurityProxy | 18 | from zope.security.proxy import removeSecurityProxy |
62 | 17 | 19 | ||
63 | @@ -23,9 +25,11 @@ | |||
64 | 23 | from lp.testing import ( | 25 | from lp.testing import ( |
65 | 24 | admin_logged_in, | 26 | admin_logged_in, |
66 | 25 | BrowserTestCase, | 27 | BrowserTestCase, |
67 | 28 | StormStatementRecorder, | ||
68 | 26 | ) | 29 | ) |
69 | 27 | from lp.testing.dbuser import dbuser | 30 | from lp.testing.dbuser import dbuser |
70 | 28 | from lp.testing.layers import LaunchpadFunctionalLayer | 31 | from lp.testing.layers import LaunchpadFunctionalLayer |
71 | 32 | from lp.testing.matchers import HasQueryCount | ||
72 | 29 | from lp.testing.pages import ( | 33 | from lp.testing.pages import ( |
73 | 30 | extract_text, | 34 | extract_text, |
74 | 31 | find_tags_by_class, | 35 | find_tags_by_class, |
75 | @@ -220,3 +224,35 @@ | |||
76 | 220 | soupmatchers.Tag( | 224 | soupmatchers.Tag( |
77 | 221 | 'all commits link', 'a', text='All commits', | 225 | 'all commits link', 'a', text='All commits', |
78 | 222 | attrs={'href': expected_url})))) | 226 | attrs={'href': expected_url})))) |
79 | 227 | |||
80 | 228 | def test_query_count_landing_candidates(self): | ||
81 | 229 | project = self.factory.makeProduct() | ||
82 | 230 | [ref] = self.factory.makeGitRefs(target=project) | ||
83 | 231 | for i in range(10): | ||
84 | 232 | self.factory.makeBranchMergeProposalForGit(target_ref=ref) | ||
85 | 233 | [source] = self.factory.makeGitRefs(target=project) | ||
86 | 234 | [prereq] = self.factory.makeGitRefs(target=project) | ||
87 | 235 | self.factory.makeBranchMergeProposalForGit( | ||
88 | 236 | source_ref=source, target_ref=ref, prerequisite_ref=prereq) | ||
89 | 237 | Store.of(ref).flush() | ||
90 | 238 | Store.of(ref).invalidate() | ||
91 | 239 | view = create_view(ref, '+index') | ||
92 | 240 | with StormStatementRecorder() as recorder: | ||
93 | 241 | view.landing_candidates | ||
94 | 242 | self.assertThat(recorder, HasQueryCount(Equals(11))) | ||
95 | 243 | |||
96 | 244 | def test_query_count_landing_targets(self): | ||
97 | 245 | project = self.factory.makeProduct() | ||
98 | 246 | [ref] = self.factory.makeGitRefs(target=project) | ||
99 | 247 | for i in range(10): | ||
100 | 248 | self.factory.makeBranchMergeProposalForGit(source_ref=ref) | ||
101 | 249 | [target] = self.factory.makeGitRefs(target=project) | ||
102 | 250 | [prereq] = self.factory.makeGitRefs(target=project) | ||
103 | 251 | self.factory.makeBranchMergeProposalForGit( | ||
104 | 252 | source_ref=ref, target_ref=target, prerequisite_ref=prereq) | ||
105 | 253 | Store.of(ref).flush() | ||
106 | 254 | Store.of(ref).invalidate() | ||
107 | 255 | view = create_view(ref, '+index') | ||
108 | 256 | with StormStatementRecorder() as recorder: | ||
109 | 257 | view.landing_targets | ||
110 | 258 | self.assertThat(recorder, HasQueryCount(Equals(11))) | ||
111 | 223 | 259 | ||
112 | === modified file 'lib/lp/code/interfaces/gitref.py' | |||
113 | --- lib/lp/code/interfaces/gitref.py 2016-11-09 17:18:21 +0000 | |||
114 | +++ lib/lp/code/interfaces/gitref.py 2016-11-11 15:10:56 +0000 | |||
115 | @@ -221,22 +221,34 @@ | |||
116 | 221 | and their subscriptions. | 221 | and their subscriptions. |
117 | 222 | """ | 222 | """ |
118 | 223 | 223 | ||
135 | 224 | landing_targets = exported(CollectionField( | 224 | landing_targets = Attribute( |
136 | 225 | title=_("Landing targets"), | 225 | "A collection of the merge proposals where this reference is " |
137 | 226 | description=_( | 226 | "the source.") |
138 | 227 | "A collection of the merge proposals where this reference is the " | 227 | _api_landing_targets = exported( |
139 | 228 | "source."), | 228 | CollectionField( |
140 | 229 | readonly=True, | 229 | title=_("Landing targets"), |
141 | 230 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. | 230 | description=_( |
142 | 231 | value_type=Reference(Interface))) | 231 | "A collection of the merge proposals where this reference is " |
143 | 232 | landing_candidates = exported(CollectionField( | 232 | "the source."), |
144 | 233 | title=_("Landing candidates"), | 233 | readonly=True, |
145 | 234 | description=_( | 234 | # Really IBranchMergeProposal, patched in |
146 | 235 | "A collection of the merge proposals where this reference is the " | 235 | # _schema_circular_imports.py. |
147 | 236 | "target."), | 236 | value_type=Reference(Interface)), |
148 | 237 | readonly=True, | 237 | exported_as="landing_targets") |
149 | 238 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. | 238 | landing_candidates = Attribute( |
150 | 239 | value_type=Reference(Interface))) | 239 | "A collection of the merge proposals where this reference is " |
151 | 240 | "the target.") | ||
152 | 241 | _api_landing_candidates = exported( | ||
153 | 242 | CollectionField( | ||
154 | 243 | title=_("Landing candidates"), | ||
155 | 244 | description=_( | ||
156 | 245 | "A collection of the merge proposals where this reference is " | ||
157 | 246 | "the target."), | ||
158 | 247 | readonly=True, | ||
159 | 248 | # Really IBranchMergeProposal, patched in | ||
160 | 249 | # _schema_circular_imports.py. | ||
161 | 250 | value_type=Reference(Interface)), | ||
162 | 251 | exported_as="landing_candidates") | ||
163 | 240 | dependent_landings = exported(CollectionField( | 252 | dependent_landings = exported(CollectionField( |
164 | 241 | title=_("Dependent landings"), | 253 | title=_("Dependent landings"), |
165 | 242 | description=_( | 254 | description=_( |
166 | @@ -246,6 +258,18 @@ | |||
167 | 246 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. | 258 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. |
168 | 247 | value_type=Reference(Interface))) | 259 | value_type=Reference(Interface))) |
169 | 248 | 260 | ||
170 | 261 | def getPrecachedLandingTargets(user): | ||
171 | 262 | """Return precached landing targets. | ||
172 | 263 | |||
173 | 264 | Target and prerequisite repositories are preloaded. | ||
174 | 265 | """ | ||
175 | 266 | |||
176 | 267 | def getPrecachedLandingCandidates(user): | ||
177 | 268 | """Return precached landing candidates. | ||
178 | 269 | |||
179 | 270 | Source and prerequisite repositories are preloaded. | ||
180 | 271 | """ | ||
181 | 272 | |||
182 | 249 | # XXX cjwatson 2015-04-16: Rename in line with landing_targets above | 273 | # XXX cjwatson 2015-04-16: Rename in line with landing_targets above |
183 | 250 | # once we have a better name. | 274 | # once we have a better name. |
184 | 251 | def addLandingTarget(registrant, merge_target, merge_prerequisite=None, | 275 | def addLandingTarget(registrant, merge_target, merge_prerequisite=None, |
185 | 252 | 276 | ||
186 | === modified file 'lib/lp/code/interfaces/gitrepository.py' | |||
187 | --- lib/lp/code/interfaces/gitrepository.py 2016-10-14 15:08:36 +0000 | |||
188 | +++ lib/lp/code/interfaces/gitrepository.py 2016-11-11 15:10:56 +0000 | |||
189 | @@ -476,22 +476,34 @@ | |||
190 | 476 | and their subscriptions. | 476 | and their subscriptions. |
191 | 477 | """ | 477 | """ |
192 | 478 | 478 | ||
209 | 479 | landing_targets = exported(CollectionField( | 479 | landing_targets = Attribute( |
210 | 480 | title=_("Landing targets"), | 480 | "A collection of the merge proposals where this repository is " |
211 | 481 | description=_( | 481 | "the source.") |
212 | 482 | "A collection of the merge proposals where this repository is the " | 482 | _api_landing_targets = exported( |
213 | 483 | "source."), | 483 | CollectionField( |
214 | 484 | readonly=True, | 484 | title=_("Landing targets"), |
215 | 485 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. | 485 | description=_( |
216 | 486 | value_type=Reference(Interface))) | 486 | "A collection of the merge proposals where this repository is " |
217 | 487 | landing_candidates = exported(CollectionField( | 487 | "the source."), |
218 | 488 | title=_("Landing candidates"), | 488 | readonly=True, |
219 | 489 | description=_( | 489 | # Really IBranchMergeProposal, patched in |
220 | 490 | "A collection of the merge proposals where this repository is the " | 490 | # _schema_circular_imports.py. |
221 | 491 | "target."), | 491 | value_type=Reference(Interface)), |
222 | 492 | readonly=True, | 492 | exported_as="landing_targets") |
223 | 493 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. | 493 | landing_candidates = Attribute( |
224 | 494 | value_type=Reference(Interface))) | 494 | "A collection of the merge proposals where this repository is " |
225 | 495 | "the target.") | ||
226 | 496 | _api_landing_candidates = exported( | ||
227 | 497 | CollectionField( | ||
228 | 498 | title=_("Landing candidates"), | ||
229 | 499 | description=_( | ||
230 | 500 | "A collection of the merge proposals where this repository is " | ||
231 | 501 | "the target."), | ||
232 | 502 | readonly=True, | ||
233 | 503 | # Really IBranchMergeProposal, patched in | ||
234 | 504 | # _schema_circular_imports.py. | ||
235 | 505 | value_type=Reference(Interface)), | ||
236 | 506 | exported_as="landing_candidates") | ||
237 | 495 | dependent_landings = exported(CollectionField( | 507 | dependent_landings = exported(CollectionField( |
238 | 496 | title=_("Dependent landings"), | 508 | title=_("Dependent landings"), |
239 | 497 | description=_( | 509 | description=_( |
240 | @@ -501,6 +513,18 @@ | |||
241 | 501 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. | 513 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. |
242 | 502 | value_type=Reference(Interface))) | 514 | value_type=Reference(Interface))) |
243 | 503 | 515 | ||
244 | 516 | def getPrecachedLandingTargets(user): | ||
245 | 517 | """Return precached landing targets. | ||
246 | 518 | |||
247 | 519 | Target and prerequisite repositories are preloaded. | ||
248 | 520 | """ | ||
249 | 521 | |||
250 | 522 | def getPrecachedLandingCandidates(user): | ||
251 | 523 | """Return precached landing candidates. | ||
252 | 524 | |||
253 | 525 | Source and prerequisite repositories are preloaded. | ||
254 | 526 | """ | ||
255 | 527 | |||
256 | 504 | def getMergeProposalByID(id): | 528 | def getMergeProposalByID(id): |
257 | 505 | """Return this repository's merge proposal with this id, or None.""" | 529 | """Return this repository's merge proposal with this id, or None.""" |
258 | 506 | 530 | ||
259 | 507 | 531 | ||
260 | === modified file 'lib/lp/code/model/gitref.py' | |||
261 | --- lib/lp/code/model/gitref.py 2016-11-09 17:18:21 +0000 | |||
262 | +++ lib/lp/code/model/gitref.py 2016-11-11 15:10:56 +0000 | |||
263 | @@ -8,6 +8,7 @@ | |||
264 | 8 | ] | 8 | ] |
265 | 9 | 9 | ||
266 | 10 | from datetime import datetime | 10 | from datetime import datetime |
267 | 11 | from functools import partial | ||
268 | 11 | import json | 12 | import json |
269 | 12 | from urllib import quote_plus | 13 | from urllib import quote_plus |
270 | 13 | from urlparse import urlsplit | 14 | from urlparse import urlsplit |
271 | @@ -51,7 +52,6 @@ | |||
272 | 51 | BranchMergeProposalGetter, | 52 | BranchMergeProposalGetter, |
273 | 52 | ) | 53 | ) |
274 | 53 | from lp.services.config import config | 54 | from lp.services.config import config |
275 | 54 | from lp.services.database.bulk import load_related | ||
276 | 55 | from lp.services.database.constants import UTC_NOW | 55 | from lp.services.database.constants import UTC_NOW |
277 | 56 | from lp.services.database.decoratedresultset import DecoratedResultSet | 56 | from lp.services.database.decoratedresultset import DecoratedResultSet |
278 | 57 | from lp.services.database.enumcol import EnumCol | 57 | from lp.services.database.enumcol import EnumCol |
279 | @@ -59,6 +59,7 @@ | |||
280 | 59 | from lp.services.database.stormbase import StormBase | 59 | from lp.services.database.stormbase import StormBase |
281 | 60 | from lp.services.features import getFeatureFlag | 60 | from lp.services.features import getFeatureFlag |
282 | 61 | from lp.services.memcache.interfaces import IMemcacheClient | 61 | from lp.services.memcache.interfaces import IMemcacheClient |
283 | 62 | from lp.services.webapp.interfaces import ILaunchBag | ||
284 | 62 | 63 | ||
285 | 63 | 64 | ||
286 | 64 | class GitRefMixin: | 65 | class GitRefMixin: |
287 | @@ -194,25 +195,34 @@ | |||
288 | 194 | BranchMergeProposal.source_git_repository == self.repository, | 195 | BranchMergeProposal.source_git_repository == self.repository, |
289 | 195 | BranchMergeProposal.source_git_path == self.path) | 196 | BranchMergeProposal.source_git_path == self.path) |
290 | 196 | 197 | ||
291 | 198 | def getPrecachedLandingTargets(self, user): | ||
292 | 199 | """See `IGitRef`.""" | ||
293 | 200 | loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) | ||
294 | 201 | return DecoratedResultSet(self.landing_targets, pre_iter_hook=loader) | ||
295 | 202 | |||
296 | 203 | @property | ||
297 | 204 | def _api_landing_targets(self): | ||
298 | 205 | return self.getPrecachedLandingTargets(getUtility(ILaunchBag).user) | ||
299 | 206 | |||
300 | 197 | @property | 207 | @property |
301 | 198 | def landing_candidates(self): | 208 | def landing_candidates(self): |
302 | 199 | """See `IGitRef`.""" | 209 | """See `IGitRef`.""" |
307 | 200 | # Circular import. | 210 | return Store.of(self).find( |
304 | 201 | from lp.code.model.gitrepository import GitRepository | ||
305 | 202 | |||
306 | 203 | result = Store.of(self).find( | ||
308 | 204 | BranchMergeProposal, | 211 | BranchMergeProposal, |
309 | 205 | BranchMergeProposal.target_git_repository == self.repository, | 212 | BranchMergeProposal.target_git_repository == self.repository, |
310 | 206 | BranchMergeProposal.target_git_path == self.path, | 213 | BranchMergeProposal.target_git_path == self.path, |
311 | 207 | Not(BranchMergeProposal.queue_status.is_in( | 214 | Not(BranchMergeProposal.queue_status.is_in( |
312 | 208 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) | 215 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) |
313 | 209 | 216 | ||
318 | 210 | def eager_load(rows): | 217 | def getPrecachedLandingCandidates(self, user): |
319 | 211 | load_related( | 218 | """See `IGitRef`.""" |
320 | 212 | GitRepository, rows, | 219 | loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) |
321 | 213 | ["source_git_repositoryID", "prerequisite_git_repositoryID"]) | 220 | return DecoratedResultSet( |
322 | 221 | self.landing_candidates, pre_iter_hook=loader) | ||
323 | 214 | 222 | ||
325 | 215 | return DecoratedResultSet(result, pre_iter_hook=eager_load) | 223 | @property |
326 | 224 | def _api_landing_candidates(self): | ||
327 | 225 | return self.getPrecachedLandingCandidates(getUtility(ILaunchBag).user) | ||
328 | 216 | 226 | ||
329 | 217 | @property | 227 | @property |
330 | 218 | def dependent_landings(self): | 228 | def dependent_landings(self): |
331 | 219 | 229 | ||
332 | === modified file 'lib/lp/code/model/gitrepository.py' | |||
333 | --- lib/lp/code/model/gitrepository.py 2016-10-14 15:08:58 +0000 | |||
334 | +++ lib/lp/code/model/gitrepository.py 2016-11-11 15:10:56 +0000 | |||
335 | @@ -155,6 +155,7 @@ | |||
336 | 155 | get_property_cache, | 155 | get_property_cache, |
337 | 156 | ) | 156 | ) |
338 | 157 | from lp.services.webapp.authorization import available_with_permission | 157 | from lp.services.webapp.authorization import available_with_permission |
339 | 158 | from lp.services.webapp.interfaces import ILaunchBag | ||
340 | 158 | from lp.services.webhooks.interfaces import IWebhookSet | 159 | from lp.services.webhooks.interfaces import IWebhookSet |
341 | 159 | from lp.services.webhooks.model import WebhookTargetMixin | 160 | from lp.services.webhooks.model import WebhookTargetMixin |
342 | 160 | from lp.snappy.interfaces.snap import ISnapSet | 161 | from lp.snappy.interfaces.snap import ISnapSet |
343 | @@ -887,6 +888,15 @@ | |||
344 | 887 | BranchMergeProposal, | 888 | BranchMergeProposal, |
345 | 888 | BranchMergeProposal.source_git_repository == self) | 889 | BranchMergeProposal.source_git_repository == self) |
346 | 889 | 890 | ||
347 | 891 | def getPrecachedLandingTargets(self, user): | ||
348 | 892 | """See `IGitRef`.""" | ||
349 | 893 | loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) | ||
350 | 894 | return DecoratedResultSet(self.landing_targets, pre_iter_hook=loader) | ||
351 | 895 | |||
352 | 896 | @property | ||
353 | 897 | def _api_landing_targets(self): | ||
354 | 898 | return self.getPrecachedLandingTargets(getUtility(ILaunchBag).user) | ||
355 | 899 | |||
356 | 890 | def getActiveLandingTargets(self, paths): | 900 | def getActiveLandingTargets(self, paths): |
357 | 891 | """Merge proposals not in final states where these refs are source.""" | 901 | """Merge proposals not in final states where these refs are source.""" |
358 | 892 | return Store.of(self).find( | 902 | return Store.of(self).find( |
359 | @@ -905,6 +915,16 @@ | |||
360 | 905 | Not(BranchMergeProposal.queue_status.is_in( | 915 | Not(BranchMergeProposal.queue_status.is_in( |
361 | 906 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) | 916 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) |
362 | 907 | 917 | ||
363 | 918 | def getPrecachedLandingCandidates(self, user): | ||
364 | 919 | """See `IGitRef`.""" | ||
365 | 920 | loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) | ||
366 | 921 | return DecoratedResultSet( | ||
367 | 922 | self.landing_candidates, pre_iter_hook=loader) | ||
368 | 923 | |||
369 | 924 | @property | ||
370 | 925 | def _api_landing_candidates(self): | ||
371 | 926 | return self.getPrecachedLandingCandidates(getUtility(ILaunchBag).user) | ||
372 | 927 | |||
373 | 908 | def getActiveLandingCandidates(self, paths): | 928 | def getActiveLandingCandidates(self, paths): |
374 | 909 | """Merge proposals not in final states where these refs are target.""" | 929 | """Merge proposals not in final states where these refs are target.""" |
375 | 910 | return Store.of(self).find( | 930 | return Store.of(self).find( |
376 | 911 | 931 | ||
377 | === modified file 'lib/lp/code/model/tests/test_gitref.py' | |||
378 | --- lib/lp/code/model/tests/test_gitref.py 2016-09-07 11:12:58 +0000 | |||
379 | +++ lib/lp/code/model/tests/test_gitref.py 2016-11-11 15:10:56 +0000 | |||
380 | @@ -18,6 +18,7 @@ | |||
381 | 18 | EndsWith, | 18 | EndsWith, |
382 | 19 | Equals, | 19 | Equals, |
383 | 20 | Is, | 20 | Is, |
384 | 21 | LessThan, | ||
385 | 21 | MatchesListwise, | 22 | MatchesListwise, |
386 | 22 | MatchesStructure, | 23 | MatchesStructure, |
387 | 23 | ) | 24 | ) |
388 | @@ -36,6 +37,7 @@ | |||
389 | 36 | ANONYMOUS, | 37 | ANONYMOUS, |
390 | 37 | api_url, | 38 | api_url, |
391 | 38 | person_logged_in, | 39 | person_logged_in, |
392 | 40 | record_two_runs, | ||
393 | 39 | TestCaseWithFactory, | 41 | TestCaseWithFactory, |
394 | 40 | verifyObject, | 42 | verifyObject, |
395 | 41 | ) | 43 | ) |
396 | @@ -43,6 +45,7 @@ | |||
397 | 43 | DatabaseFunctionalLayer, | 45 | DatabaseFunctionalLayer, |
398 | 44 | LaunchpadFunctionalLayer, | 46 | LaunchpadFunctionalLayer, |
399 | 45 | ) | 47 | ) |
400 | 48 | from lp.testing.matchers import HasQueryCount | ||
401 | 46 | from lp.testing.pages import webservice_for_person | 49 | from lp.testing.pages import webservice_for_person |
402 | 47 | 50 | ||
403 | 48 | 51 | ||
404 | @@ -461,6 +464,30 @@ | |||
405 | 461 | self.assertThat( | 464 | self.assertThat( |
406 | 462 | landing_candidates["entries"][0]["self_link"], EndsWith(bmp_url)) | 465 | landing_candidates["entries"][0]["self_link"], EndsWith(bmp_url)) |
407 | 463 | 466 | ||
408 | 467 | def test_landing_candidates_constant_queries(self): | ||
409 | 468 | project = self.factory.makeProduct() | ||
410 | 469 | with person_logged_in(project.owner): | ||
411 | 470 | [trunk] = self.factory.makeGitRefs(target=project) | ||
412 | 471 | trunk_url = api_url(trunk) | ||
413 | 472 | webservice = webservice_for_person( | ||
414 | 473 | project.owner, permission=OAuthPermission.WRITE_PRIVATE) | ||
415 | 474 | |||
416 | 475 | def create_mp(): | ||
417 | 476 | with admin_logged_in(): | ||
418 | 477 | [ref] = self.factory.makeGitRefs( | ||
419 | 478 | target=project, | ||
420 | 479 | information_type=InformationType.PRIVATESECURITY) | ||
421 | 480 | self.factory.makeBranchMergeProposalForGit( | ||
422 | 481 | source_ref=ref, target_ref=trunk) | ||
423 | 482 | |||
424 | 483 | def list_mps(): | ||
425 | 484 | webservice.get(trunk_url + "/landing_candidates") | ||
426 | 485 | |||
427 | 486 | list_mps() | ||
428 | 487 | recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2) | ||
429 | 488 | self.assertThat(recorder1, HasQueryCount(LessThan(30))) | ||
430 | 489 | self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) | ||
431 | 490 | |||
432 | 464 | def test_landing_targets(self): | 491 | def test_landing_targets(self): |
433 | 465 | bmp_db = self.factory.makeBranchMergeProposalForGit() | 492 | bmp_db = self.factory.makeBranchMergeProposalForGit() |
434 | 466 | with person_logged_in(bmp_db.registrant): | 493 | with person_logged_in(bmp_db.registrant): |
435 | @@ -476,6 +503,30 @@ | |||
436 | 476 | self.assertThat( | 503 | self.assertThat( |
437 | 477 | landing_targets["entries"][0]["self_link"], EndsWith(bmp_url)) | 504 | landing_targets["entries"][0]["self_link"], EndsWith(bmp_url)) |
438 | 478 | 505 | ||
439 | 506 | def test_landing_targets_constant_queries(self): | ||
440 | 507 | project = self.factory.makeProduct() | ||
441 | 508 | with person_logged_in(project.owner): | ||
442 | 509 | [source] = self.factory.makeGitRefs(target=project) | ||
443 | 510 | source_url = api_url(source) | ||
444 | 511 | webservice = webservice_for_person( | ||
445 | 512 | project.owner, permission=OAuthPermission.WRITE_PRIVATE) | ||
446 | 513 | |||
447 | 514 | def create_mp(): | ||
448 | 515 | with admin_logged_in(): | ||
449 | 516 | [ref] = self.factory.makeGitRefs( | ||
450 | 517 | target=project, | ||
451 | 518 | information_type=InformationType.PRIVATESECURITY) | ||
452 | 519 | self.factory.makeBranchMergeProposalForGit( | ||
453 | 520 | source_ref=source, target_ref=ref) | ||
454 | 521 | |||
455 | 522 | def list_mps(): | ||
456 | 523 | webservice.get(source_url + "/landing_targets") | ||
457 | 524 | |||
458 | 525 | list_mps() | ||
459 | 526 | recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2) | ||
460 | 527 | self.assertThat(recorder1, HasQueryCount(LessThan(30))) | ||
461 | 528 | self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) | ||
462 | 529 | |||
463 | 479 | def test_dependent_landings(self): | 530 | def test_dependent_landings(self): |
464 | 480 | [ref] = self.factory.makeGitRefs() | 531 | [ref] = self.factory.makeGitRefs() |
465 | 481 | bmp_db = self.factory.makeBranchMergeProposalForGit( | 532 | bmp_db = self.factory.makeBranchMergeProposalForGit( |
466 | 482 | 533 | ||
467 | === modified file 'lib/lp/code/model/tests/test_gitrepository.py' | |||
468 | --- lib/lp/code/model/tests/test_gitrepository.py 2016-10-14 16:16:18 +0000 | |||
469 | +++ lib/lp/code/model/tests/test_gitrepository.py 2016-11-11 15:10:56 +0000 | |||
470 | @@ -20,6 +20,7 @@ | |||
471 | 20 | from storm.store import Store | 20 | from storm.store import Store |
472 | 21 | from testtools.matchers import ( | 21 | from testtools.matchers import ( |
473 | 22 | EndsWith, | 22 | EndsWith, |
474 | 23 | LessThan, | ||
475 | 23 | MatchesSetwise, | 24 | MatchesSetwise, |
476 | 24 | MatchesStructure, | 25 | MatchesStructure, |
477 | 25 | ) | 26 | ) |
478 | @@ -2800,6 +2801,31 @@ | |||
479 | 2800 | self.assertThat( | 2801 | self.assertThat( |
480 | 2801 | landing_candidates["entries"][0]["self_link"], EndsWith(bmp_url)) | 2802 | landing_candidates["entries"][0]["self_link"], EndsWith(bmp_url)) |
481 | 2802 | 2803 | ||
482 | 2804 | def test_landing_candidates_constant_queries(self): | ||
483 | 2805 | project = self.factory.makeProduct() | ||
484 | 2806 | with person_logged_in(project.owner): | ||
485 | 2807 | repository = self.factory.makeGitRepository(target=project) | ||
486 | 2808 | repository_url = api_url(repository) | ||
487 | 2809 | webservice = webservice_for_person( | ||
488 | 2810 | project.owner, permission=OAuthPermission.WRITE_PRIVATE) | ||
489 | 2811 | |||
490 | 2812 | def create_mp(): | ||
491 | 2813 | with admin_logged_in(): | ||
492 | 2814 | [target] = self.factory.makeGitRefs(repository=repository) | ||
493 | 2815 | [source] = self.factory.makeGitRefs( | ||
494 | 2816 | target=project, | ||
495 | 2817 | information_type=InformationType.PRIVATESECURITY) | ||
496 | 2818 | self.factory.makeBranchMergeProposalForGit( | ||
497 | 2819 | source_ref=source, target_ref=target) | ||
498 | 2820 | |||
499 | 2821 | def list_mps(): | ||
500 | 2822 | webservice.get(repository_url + "/landing_candidates") | ||
501 | 2823 | |||
502 | 2824 | list_mps() | ||
503 | 2825 | recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2) | ||
504 | 2826 | self.assertThat(recorder1, HasQueryCount(LessThan(40))) | ||
505 | 2827 | self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) | ||
506 | 2828 | |||
507 | 2803 | def test_landing_targets(self): | 2829 | def test_landing_targets(self): |
508 | 2804 | bmp_db = self.factory.makeBranchMergeProposalForGit() | 2830 | bmp_db = self.factory.makeBranchMergeProposalForGit() |
509 | 2805 | with person_logged_in(bmp_db.registrant): | 2831 | with person_logged_in(bmp_db.registrant): |
510 | @@ -2815,6 +2841,31 @@ | |||
511 | 2815 | self.assertThat( | 2841 | self.assertThat( |
512 | 2816 | landing_targets["entries"][0]["self_link"], EndsWith(bmp_url)) | 2842 | landing_targets["entries"][0]["self_link"], EndsWith(bmp_url)) |
513 | 2817 | 2843 | ||
514 | 2844 | def test_landing_targets_constant_queries(self): | ||
515 | 2845 | project = self.factory.makeProduct() | ||
516 | 2846 | with person_logged_in(project.owner): | ||
517 | 2847 | repository = self.factory.makeGitRepository(target=project) | ||
518 | 2848 | repository_url = api_url(repository) | ||
519 | 2849 | webservice = webservice_for_person( | ||
520 | 2850 | project.owner, permission=OAuthPermission.WRITE_PRIVATE) | ||
521 | 2851 | |||
522 | 2852 | def create_mp(): | ||
523 | 2853 | with admin_logged_in(): | ||
524 | 2854 | [source] = self.factory.makeGitRefs(repository=repository) | ||
525 | 2855 | [target] = self.factory.makeGitRefs( | ||
526 | 2856 | target=project, | ||
527 | 2857 | information_type=InformationType.PRIVATESECURITY) | ||
528 | 2858 | self.factory.makeBranchMergeProposalForGit( | ||
529 | 2859 | source_ref=source, target_ref=target) | ||
530 | 2860 | |||
531 | 2861 | def list_mps(): | ||
532 | 2862 | webservice.get(repository_url + "/landing_targets") | ||
533 | 2863 | |||
534 | 2864 | list_mps() | ||
535 | 2865 | recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2) | ||
536 | 2866 | self.assertThat(recorder1, HasQueryCount(LessThan(30))) | ||
537 | 2867 | self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) | ||
538 | 2868 | |||
539 | 2818 | def test_dependent_landings(self): | 2869 | def test_dependent_landings(self): |
540 | 2819 | [ref] = self.factory.makeGitRefs() | 2870 | [ref] = self.factory.makeGitRefs() |
541 | 2820 | bmp_db = self.factory.makeBranchMergeProposalForGit( | 2871 | bmp_db = self.factory.makeBranchMergeProposalForGit( |