Merge lp:~twom/launchpad/precache-gitrepository-branch-queries into lp:launchpad

Proposed by Tom Wardill
Status: Merged
Merged at revision: 18769
Proposed branch: lp:~twom/launchpad/precache-gitrepository-branch-queries
Merge into: lp:launchpad
Diff against target: 255 lines (+105/-22)
4 files modified
lib/lp/code/browser/branchmergeproposal.py (+10/-3)
lib/lp/code/browser/tests/test_gitrepository.py (+71/-13)
lib/lp/code/model/branchmergeproposal.py (+20/-5)
lib/lp/code/model/gitrepository.py (+4/-1)
To merge this branch: bzr merge lp:~twom/launchpad/precache-gitrepository-branch-queries
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+354347@code.launchpad.net

Commit message

Precache gitrepository voting summary data

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Needs Fixing
Revision history for this message
Colin Watson (cjwatson) :
review: Needs Fixing
Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
2--- lib/lp/code/browser/branchmergeproposal.py 2018-07-16 00:49:00 +0000
3+++ lib/lp/code/browser/branchmergeproposal.py 2018-09-06 16:01:23 +0000
4@@ -266,7 +266,7 @@
5 if (self.context.queue_status ==
6 BranchMergeProposalStatus.NEEDS_REVIEW):
7 enabled = True
8- if (self.context.votes.count()) > 0:
9+ if len(self.context.votes) > 0:
10 text = 'Request another review'
11 return Link('+request-review', text, icon='add', enabled=enabled)
12
13@@ -890,8 +890,15 @@
14 @cachedproperty
15 def reviews(self):
16 """Return the decorated votes for the proposal."""
17- users_vote = self.context.getUsersVoteReference(self.user)
18- return [DecoratedCodeReviewVoteReference(vote, self.user, users_vote)
19+
20+ # This would use getUsersVoteReference, but we need to
21+ # be able to cache the property. We don't need to normalize
22+ # the review types.
23+ users_vote = sorted((uv for uv in self.context.votes
24+ if uv.reviewer == self.user),
25+ key=operator.attrgetter('date_created'))
26+ final_vote = users_vote[0] if users_vote else None
27+ return [DecoratedCodeReviewVoteReference(vote, self.user, final_vote)
28 for vote in self.context.votes
29 if check_permission('launchpad.LimitedView', vote.reviewer)]
30
31
32=== modified file 'lib/lp/code/browser/tests/test_gitrepository.py'
33--- lib/lp/code/browser/tests/test_gitrepository.py 2018-08-31 11:35:52 +0000
34+++ lib/lp/code/browser/tests/test_gitrepository.py 2018-09-06 16:01:23 +0000
35@@ -50,7 +50,10 @@
36 record_two_runs,
37 TestCaseWithFactory,
38 )
39-from lp.testing.layers import DatabaseFunctionalLayer
40+from lp.testing.layers import (
41+ DatabaseFunctionalLayer,
42+ LaunchpadFunctionalLayer,
43+ )
44 from lp.testing.matchers import (
45 Contains,
46 HasQueryCount,
47@@ -96,7 +99,7 @@
48
49 class TestGitRepositoryView(BrowserTestCase):
50
51- layer = DatabaseFunctionalLayer
52+ layer = LaunchpadFunctionalLayer
53
54 def test_clone_instructions(self):
55 repository = self.factory.makeGitRepository()
56@@ -267,7 +270,7 @@
57 self.assertIsNotNone(
58 find_tag_by_id(browser.contents, 'landing-candidates'))
59
60- def test_landing_candidate_count(self):
61+ def test_landing_candidates_count(self):
62 source_repository = self.factory.makeGitRepository()
63 view = create_initialized_view(source_repository, '+index')
64
65@@ -275,19 +278,44 @@
66 self.assertEqual('1 branch', view._getBranchCountText(1))
67 self.assertEqual('2 branches', view._getBranchCountText(2))
68
69+ def test_landing_candidates_query_count(self):
70+ repository = self.factory.makeGitRepository()
71+ git_refs = self.factory.makeGitRefs(
72+ repository,
73+ paths=["refs/heads/master", "refs/heads/1.0", "refs/tags/1.1"])
74+
75+ def login_and_view():
76+ with FeatureFixture({"code.git.show_repository_mps": "on"}):
77+ with person_logged_in(repository.owner):
78+ browser = self.getViewBrowser(repository)
79+ self.assertIsNotNone(
80+ find_tag_by_id(browser.contents, 'landing-candidates'))
81+
82+ def create_merge_proposal():
83+ bmp = self.factory.makeBranchMergeProposalForGit(
84+ target_ref=git_refs[0],
85+ set_state=BranchMergeProposalStatus.NEEDS_REVIEW)
86+ self.factory.makePreviewDiff(merge_proposal=bmp)
87+
88+ recorder1, recorder2 = record_two_runs(
89+ login_and_view,
90+ create_merge_proposal,
91+ 2)
92+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
93+
94 def test_view_with_landing_targets(self):
95 product = self.factory.makeProduct(name="foo", vcs=VCSType.GIT)
96 target_repository = self.factory.makeGitRepository(target=product)
97 source_repository = self.factory.makeGitRepository(target=product)
98- target_git_refs = self.factory.makeGitRefs(
99+ [target_git_ref] = self.factory.makeGitRefs(
100 target_repository,
101- paths=["refs/heads/master", "refs/heads/1.0", "refs/tags/1.1"])
102- source_git_refs = self.factory.makeGitRefs(
103+ paths=["refs/heads/master"])
104+ [source_git_ref] = self.factory.makeGitRefs(
105 source_repository,
106 paths=["refs/heads/master"])
107 self.factory.makeBranchMergeProposalForGit(
108- target_ref=target_git_refs[0],
109- source_ref=source_git_refs[0],
110+ target_ref=target_git_ref,
111+ source_ref=source_git_ref,
112 set_state=BranchMergeProposalStatus.NEEDS_REVIEW)
113 with FeatureFixture({"code.git.show_repository_mps": "on"}):
114 with person_logged_in(target_repository.owner):
115@@ -296,19 +324,49 @@
116 self.assertIsNotNone(
117 find_tag_by_id(browser.contents, 'landing-targets'))
118
119+ def test_landing_targets_query_count(self):
120+ product = self.factory.makeProduct(name="foo", vcs=VCSType.GIT)
121+ target_repository = self.factory.makeGitRepository(target=product)
122+ source_repository = self.factory.makeGitRepository(target=product)
123+
124+ def create_merge_proposal():
125+ [target_git_ref] = self.factory.makeGitRefs(
126+ target_repository)
127+ [source_git_ref] = self.factory.makeGitRefs(
128+ source_repository)
129+ bmp = self.factory.makeBranchMergeProposalForGit(
130+ target_ref=target_git_ref,
131+ source_ref=source_git_ref,
132+ set_state=BranchMergeProposalStatus.NEEDS_REVIEW)
133+ self.factory.makePreviewDiff(merge_proposal=bmp)
134+
135+ def login_and_view():
136+ with FeatureFixture({"code.git.show_repository_mps": "on"}):
137+ with person_logged_in(target_repository.owner):
138+ browser = self.getViewBrowser(
139+ source_repository, user=source_repository.owner)
140+ self.assertIsNotNone(
141+ find_tag_by_id(browser.contents, 'landing-targets'))
142+
143+ recorder1, recorder2 = record_two_runs(
144+ login_and_view,
145+ create_merge_proposal,
146+ 2)
147+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
148+
149 def test_view_with_inactive_landing_targets(self):
150 product = self.factory.makeProduct(name="foo", vcs=VCSType.GIT)
151 target_repository = self.factory.makeGitRepository(target=product)
152 source_repository = self.factory.makeGitRepository(target=product)
153- target_git_refs = self.factory.makeGitRefs(
154+ [target_git_ref] = self.factory.makeGitRefs(
155 target_repository,
156- paths=["refs/heads/master", "refs/heads/1.0", "refs/tags/1.1"])
157- source_git_refs = self.factory.makeGitRefs(
158+ paths=["refs/heads/master"])
159+ [source_git_ref] = self.factory.makeGitRefs(
160 source_repository,
161 paths=["refs/heads/master"])
162 self.factory.makeBranchMergeProposalForGit(
163- target_ref=target_git_refs[0],
164- source_ref=source_git_refs[0],
165+ target_ref=target_git_ref,
166+ source_ref=source_git_ref,
167 set_state=BranchMergeProposalStatus.MERGED)
168 with FeatureFixture({"code.git.show_repository_mps": "on"}):
169 with person_logged_in(target_repository.owner):
170
171=== modified file 'lib/lp/code/model/branchmergeproposal.py'
172--- lib/lp/code/model/branchmergeproposal.py 2018-04-25 12:01:33 +0000
173+++ lib/lp/code/model/branchmergeproposal.py 2018-09-06 16:01:23 +0000
174@@ -21,7 +21,6 @@
175 from sqlobject import (
176 ForeignKey,
177 IntCol,
178- SQLMultipleJoin,
179 StringCol,
180 )
181 from storm.expr import (
182@@ -123,6 +122,7 @@
183 from lp.services.helpers import shortlist
184 from lp.services.job.interfaces.job import JobStatus
185 from lp.services.job.model.job import Job
186+from lp.services.librarian.model import LibraryFileAlias
187 from lp.services.mail.sendmail import validate_message
188 from lp.services.propertycache import (
189 cachedproperty,
190@@ -610,8 +610,11 @@
191 def preview_diff(self):
192 return self._preview_diffs.last()
193
194- votes = SQLMultipleJoin(
195- 'CodeReviewVoteReference', joinColumn='branch_merge_proposal')
196+ @cachedproperty
197+ def votes(self):
198+ return list(Store.of(self).find(
199+ CodeReviewVoteReference,
200+ CodeReviewVoteReference.branch_merge_proposal == self))
201
202 def getNotificationRecipients(self, min_level):
203 """See IBranchMergeProposal.getNotificationRecipients"""
204@@ -1275,7 +1278,7 @@
205 return [range_ for range_, diff in zip(ranges, diffs) if diff is None]
206
207 @staticmethod
208- def preloadDataForBMPs(branch_merge_proposals, user):
209+ def preloadDataForBMPs(branch_merge_proposals, user, include_votes=True):
210 # Utility to load the data related to a list of bmps.
211 # Circular imports.
212 from lp.code.model.branch import Branch
213@@ -1330,7 +1333,7 @@
214 PreviewDiff.branch_merge_proposal_id,
215 Desc(PreviewDiff.date_created)).config(
216 distinct=[PreviewDiff.branch_merge_proposal_id])
217- load_related(Diff, preview_diffs, ['diff_id'])
218+ diffs = load_related(Diff, preview_diffs, ['diff_id'])
219 preview_diff_map = {}
220 for previewdiff in preview_diffs:
221 preview_diff_map[previewdiff.branch_merge_proposal_id] = (
222@@ -1367,6 +1370,18 @@
223 if repositories:
224 GenericGitCollection.preloadDataForRepositories(repositories)
225
226+ # if we need to include a vote summary, we should precache
227+ # that data too
228+ if include_votes:
229+ vote_list = list(load_referencing(
230+ CodeReviewVoteReference, branch_merge_proposals,
231+ ['branch_merge_proposalID']))
232+ for mp in branch_merge_proposals:
233+ get_property_cache(mp).votes = vote_list
234+
235+ # we also provide a summary of diffs, so load them
236+ load_related(LibraryFileAlias, diffs, ['diff_textID'])
237+
238
239 @implementer(IBranchMergeProposalGetter)
240 class BranchMergeProposalGetter:
241
242=== modified file 'lib/lp/code/model/gitrepository.py'
243--- lib/lp/code/model/gitrepository.py 2018-08-31 13:14:39 +0000
244+++ lib/lp/code/model/gitrepository.py 2018-09-06 16:01:23 +0000
245@@ -922,7 +922,10 @@
246
247 def getPrecachedLandingCandidates(self, user):
248 """See `IGitRepository`."""
249- loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user)
250+ loader = partial(
251+ BranchMergeProposal.preloadDataForBMPs,
252+ user=user,
253+ include_votes=True)
254 return DecoratedResultSet(
255 self.landing_candidates, pre_iter_hook=loader)
256