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
1=== modified file 'lib/lp/code/interfaces/hasbranches.py'
2--- lib/lp/code/interfaces/hasbranches.py 2015-05-12 17:30:40 +0000
3+++ lib/lp/code/interfaces/hasbranches.py 2016-09-09 12:37:03 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Interface definitions for IHas<code related bits>."""
10@@ -87,15 +87,17 @@
11 status=List(
12 title=_("A list of merge proposal statuses to filter by."),
13 value_type=Choice(vocabulary=BranchMergeProposalStatus)))
14- @call_with(visible_by_user=REQUEST_USER)
15+ @call_with(visible_by_user=REQUEST_USER, eager_load=True)
16 @operation_returns_collection_of(Interface) # Really IBranchMergeProposal.
17 @export_read_operation()
18 @operation_for_version('beta')
19- def getMergeProposals(status=None, visible_by_user=None):
20+ def getMergeProposals(status=None, visible_by_user=None, eager_load=False):
21 """Returns all merge proposals of a given status.
22
23 :param status: A list of statuses to filter with.
24 :param visible_by_user: Normally the user who is asking.
25+ :param eager_load: If True, load related objects for the whole
26+ collection.
27 :returns: A list of `IBranchMergeProposal`.
28 """
29
30
31=== modified file 'lib/lp/code/model/branchmergeproposal.py'
32--- lib/lp/code/model/branchmergeproposal.py 2016-09-02 18:21:07 +0000
33+++ lib/lp/code/model/branchmergeproposal.py 2016-09-09 12:37:03 +0000
34@@ -101,6 +101,7 @@
35 from lp.services.config import config
36 from lp.services.database.bulk import (
37 load,
38+ load_referencing,
39 load_related,
40 )
41 from lp.services.database.constants import (
42@@ -521,7 +522,11 @@
43 dbName='superseded_by', foreignKey='BranchMergeProposal',
44 notNull=False, default=None)
45
46- supersedes = Reference("<primary key>", "superseded_by", on_remote=True)
47+ _supersedes = Reference("<primary key>", "superseded_by", on_remote=True)
48+
49+ @cachedproperty
50+ def supersedes(self):
51+ return self._supersedes
52
53 date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
54 date_review_requested = UtcDateTimeCol(notNull=False, default=None)
55@@ -1288,9 +1293,21 @@
56 Desc(PreviewDiff.date_created)).config(
57 distinct=[PreviewDiff.branch_merge_proposal_id])
58 load_related(Diff, preview_diffs, ['diff_id'])
59+ preview_diff_map = {}
60 for previewdiff in preview_diffs:
61- cache = get_property_cache(previewdiff.branch_merge_proposal)
62- cache.preview_diff = previewdiff
63+ preview_diff_map[previewdiff.branch_merge_proposal_id] = (
64+ previewdiff)
65+ for mp in branch_merge_proposals:
66+ get_property_cache(mp).preview_diff = preview_diff_map.get(mp.id)
67+
68+ # Preload other merge proposals that supersede these.
69+ supersedes_map = {}
70+ for other_mp in load_referencing(
71+ BranchMergeProposal, branch_merge_proposals,
72+ ['superseded_byID']):
73+ supersedes_map[other_mp.superseded_byID] = other_mp
74+ for mp in branch_merge_proposals:
75+ get_property_cache(mp).supersedes = supersedes_map.get(mp.id)
76
77 # Add source branch/repository owners' to the list of pre-loaded
78 # persons. We need the target repository owner as well; unlike
79
80=== modified file 'lib/lp/code/model/tests/test_hasbranches.py'
81--- lib/lp/code/model/tests/test_hasbranches.py 2012-01-01 02:58:52 +0000
82+++ lib/lp/code/model/tests/test_hasbranches.py 2016-09-09 12:37:03 +0000
83@@ -1,13 +1,26 @@
84-# Copyright 2009 Canonical Ltd. This software is licensed under the
85+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
86 # GNU Affero General Public License version 3 (see the file LICENSE).
87
88 """Tests for classes that implement IHasBranches."""
89
90 __metaclass__ = type
91
92+from functools import partial
93+
94+from lp.app.enums import InformationType
95 from lp.code.interfaces.hasbranches import IHasBranches
96-from lp.testing import TestCaseWithFactory
97+from lp.registry.enums import BranchSharingPolicy
98+from lp.services.webapp.interfaces import OAuthPermission
99+from lp.testing import (
100+ api_url,
101+ login_person,
102+ logout,
103+ record_two_runs,
104+ TestCaseWithFactory,
105+ )
106 from lp.testing.layers import DatabaseFunctionalLayer
107+from lp.testing.matchers import HasQueryCount
108+from lp.testing.pages import webservice_for_person
109
110
111 class TestIHasBranches(TestCaseWithFactory):
112@@ -29,3 +42,44 @@
113 # ProjectGroups should implement IHasBranches.
114 project = self.factory.makeProject()
115 self.assertProvides(project, IHasBranches)
116+
117+
118+class TestHasMergeProposalsWebservice(TestCaseWithFactory):
119+
120+ layer = DatabaseFunctionalLayer
121+
122+ def test_constant_query_count(self):
123+ # Person.getMergeProposals has a constant query count on the
124+ # webservice.
125+
126+ # Ensure that both runs fit in a single batch, to avoid needing to
127+ # account for an extra count query.
128+ self.pushConfig("launchpad", default_batch_size=10)
129+
130+ owner = self.factory.makePerson()
131+ owner_url = api_url(owner)
132+ webservice = webservice_for_person(
133+ owner, permission=OAuthPermission.READ_PRIVATE)
134+
135+ def create_merge_proposals():
136+ policy = BranchSharingPolicy.PUBLIC_OR_PROPRIETARY
137+ target = self.factory.makeProduct(branch_sharing_policy=policy)
138+ source_branch = self.factory.makeProductBranch(
139+ owner=owner, product=target,
140+ information_type=InformationType.PROPRIETARY)
141+ self.factory.makeBranchMergeProposal(source_branch=source_branch)
142+ [source_ref] = self.factory.makeGitRefs(
143+ owner=owner, target=target,
144+ information_type=InformationType.PROPRIETARY)
145+ self.factory.makeBranchMergeProposalForGit(source_ref=source_ref)
146+
147+ def get_merge_proposals():
148+ logout()
149+ response = webservice.named_get(owner_url, "getMergeProposals")
150+ self.assertEqual(200, response.status)
151+ self.assertNotEqual(0, len(response.jsonBody()["entries"]))
152+
153+ recorder1, recorder2 = record_two_runs(
154+ get_merge_proposals, create_merge_proposals, 2,
155+ login_method=partial(login_person, owner), record_request=True)
156+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))