Merge lp:~cjwatson/launchpad/getMergeProposals-timeout into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18186
Proposed branch: lp:~cjwatson/launchpad/getMergeProposals-timeout
Merge into: lp:launchpad
Diff against target: 156 lines (+81/-8)
3 files modified
lib/lp/code/interfaces/hasbranches.py (+5/-3)
lib/lp/code/model/branchmergeproposal.py (+20/-3)
lib/lp/code/model/tests/test_hasbranches.py (+56/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/getMergeProposals-timeout
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+305065@code.launchpad.net

Commit message

Make Person.getMergeProposals have a constant query count on the webservice.

Description of the change

Make Person.getMergeProposals have a constant query count on the webservice.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

I'd like the new test to use a READ_PRIVATE token and include various private MPs, since the dominant queries in the OOPSes were actually permission checks.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/interfaces/hasbranches.py'
--- lib/lp/code/interfaces/hasbranches.py 2015-05-12 17:30:40 +0000
+++ lib/lp/code/interfaces/hasbranches.py 2016-09-09 12:37:03 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2015 Canonical Ltd. This software is licensed under the1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Interface definitions for IHas<code related bits>."""4"""Interface definitions for IHas<code related bits>."""
@@ -87,15 +87,17 @@
87 status=List(87 status=List(
88 title=_("A list of merge proposal statuses to filter by."),88 title=_("A list of merge proposal statuses to filter by."),
89 value_type=Choice(vocabulary=BranchMergeProposalStatus)))89 value_type=Choice(vocabulary=BranchMergeProposalStatus)))
90 @call_with(visible_by_user=REQUEST_USER)90 @call_with(visible_by_user=REQUEST_USER, eager_load=True)
91 @operation_returns_collection_of(Interface) # Really IBranchMergeProposal.91 @operation_returns_collection_of(Interface) # Really IBranchMergeProposal.
92 @export_read_operation()92 @export_read_operation()
93 @operation_for_version('beta')93 @operation_for_version('beta')
94 def getMergeProposals(status=None, visible_by_user=None):94 def getMergeProposals(status=None, visible_by_user=None, eager_load=False):
95 """Returns all merge proposals of a given status.95 """Returns all merge proposals of a given status.
9696
97 :param status: A list of statuses to filter with.97 :param status: A list of statuses to filter with.
98 :param visible_by_user: Normally the user who is asking.98 :param visible_by_user: Normally the user who is asking.
99 :param eager_load: If True, load related objects for the whole
100 collection.
99 :returns: A list of `IBranchMergeProposal`.101 :returns: A list of `IBranchMergeProposal`.
100 """102 """
101103
102104
=== modified file 'lib/lp/code/model/branchmergeproposal.py'
--- lib/lp/code/model/branchmergeproposal.py 2016-09-02 18:21:07 +0000
+++ lib/lp/code/model/branchmergeproposal.py 2016-09-09 12:37:03 +0000
@@ -101,6 +101,7 @@
101from lp.services.config import config101from lp.services.config import config
102from lp.services.database.bulk import (102from lp.services.database.bulk import (
103 load,103 load,
104 load_referencing,
104 load_related,105 load_related,
105 )106 )
106from lp.services.database.constants import (107from lp.services.database.constants import (
@@ -521,7 +522,11 @@
521 dbName='superseded_by', foreignKey='BranchMergeProposal',522 dbName='superseded_by', foreignKey='BranchMergeProposal',
522 notNull=False, default=None)523 notNull=False, default=None)
523524
524 supersedes = Reference("<primary key>", "superseded_by", on_remote=True)525 _supersedes = Reference("<primary key>", "superseded_by", on_remote=True)
526
527 @cachedproperty
528 def supersedes(self):
529 return self._supersedes
525530
526 date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)531 date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
527 date_review_requested = UtcDateTimeCol(notNull=False, default=None)532 date_review_requested = UtcDateTimeCol(notNull=False, default=None)
@@ -1288,9 +1293,21 @@
1288 Desc(PreviewDiff.date_created)).config(1293 Desc(PreviewDiff.date_created)).config(
1289 distinct=[PreviewDiff.branch_merge_proposal_id])1294 distinct=[PreviewDiff.branch_merge_proposal_id])
1290 load_related(Diff, preview_diffs, ['diff_id'])1295 load_related(Diff, preview_diffs, ['diff_id'])
1296 preview_diff_map = {}
1291 for previewdiff in preview_diffs:1297 for previewdiff in preview_diffs:
1292 cache = get_property_cache(previewdiff.branch_merge_proposal)1298 preview_diff_map[previewdiff.branch_merge_proposal_id] = (
1293 cache.preview_diff = previewdiff1299 previewdiff)
1300 for mp in branch_merge_proposals:
1301 get_property_cache(mp).preview_diff = preview_diff_map.get(mp.id)
1302
1303 # Preload other merge proposals that supersede these.
1304 supersedes_map = {}
1305 for other_mp in load_referencing(
1306 BranchMergeProposal, branch_merge_proposals,
1307 ['superseded_byID']):
1308 supersedes_map[other_mp.superseded_byID] = other_mp
1309 for mp in branch_merge_proposals:
1310 get_property_cache(mp).supersedes = supersedes_map.get(mp.id)
12941311
1295 # Add source branch/repository owners' to the list of pre-loaded1312 # Add source branch/repository owners' to the list of pre-loaded
1296 # persons. We need the target repository owner as well; unlike1313 # persons. We need the target repository owner as well; unlike
12971314
=== modified file 'lib/lp/code/model/tests/test_hasbranches.py'
--- lib/lp/code/model/tests/test_hasbranches.py 2012-01-01 02:58:52 +0000
+++ lib/lp/code/model/tests/test_hasbranches.py 2016-09-09 12:37:03 +0000
@@ -1,13 +1,26 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for classes that implement IHasBranches."""4"""Tests for classes that implement IHasBranches."""
55
6__metaclass__ = type6__metaclass__ = type
77
8from functools import partial
9
10from lp.app.enums import InformationType
8from lp.code.interfaces.hasbranches import IHasBranches11from lp.code.interfaces.hasbranches import IHasBranches
9from lp.testing import TestCaseWithFactory12from lp.registry.enums import BranchSharingPolicy
13from lp.services.webapp.interfaces import OAuthPermission
14from lp.testing import (
15 api_url,
16 login_person,
17 logout,
18 record_two_runs,
19 TestCaseWithFactory,
20 )
10from lp.testing.layers import DatabaseFunctionalLayer21from lp.testing.layers import DatabaseFunctionalLayer
22from lp.testing.matchers import HasQueryCount
23from lp.testing.pages import webservice_for_person
1124
1225
13class TestIHasBranches(TestCaseWithFactory):26class TestIHasBranches(TestCaseWithFactory):
@@ -29,3 +42,44 @@
29 # ProjectGroups should implement IHasBranches.42 # ProjectGroups should implement IHasBranches.
30 project = self.factory.makeProject()43 project = self.factory.makeProject()
31 self.assertProvides(project, IHasBranches)44 self.assertProvides(project, IHasBranches)
45
46
47class TestHasMergeProposalsWebservice(TestCaseWithFactory):
48
49 layer = DatabaseFunctionalLayer
50
51 def test_constant_query_count(self):
52 # Person.getMergeProposals has a constant query count on the
53 # webservice.
54
55 # Ensure that both runs fit in a single batch, to avoid needing to
56 # account for an extra count query.
57 self.pushConfig("launchpad", default_batch_size=10)
58
59 owner = self.factory.makePerson()
60 owner_url = api_url(owner)
61 webservice = webservice_for_person(
62 owner, permission=OAuthPermission.READ_PRIVATE)
63
64 def create_merge_proposals():
65 policy = BranchSharingPolicy.PUBLIC_OR_PROPRIETARY
66 target = self.factory.makeProduct(branch_sharing_policy=policy)
67 source_branch = self.factory.makeProductBranch(
68 owner=owner, product=target,
69 information_type=InformationType.PROPRIETARY)
70 self.factory.makeBranchMergeProposal(source_branch=source_branch)
71 [source_ref] = self.factory.makeGitRefs(
72 owner=owner, target=target,
73 information_type=InformationType.PROPRIETARY)
74 self.factory.makeBranchMergeProposalForGit(source_ref=source_ref)
75
76 def get_merge_proposals():
77 logout()
78 response = webservice.named_get(owner_url, "getMergeProposals")
79 self.assertEqual(200, response.status)
80 self.assertNotEqual(0, len(response.jsonBody()["entries"]))
81
82 recorder1, recorder2 = record_two_runs(
83 get_merge_proposals, create_merge_proposals, 2,
84 login_method=partial(login_person, owner), record_request=True)
85 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))