Merge lp:~twom/launchpad/add-rescan-link-to-merge-proposals into lp:launchpad

Proposed by Tom Wardill
Status: Merged
Merged at revision: 18929
Proposed branch: lp:~twom/launchpad/add-rescan-link-to-merge-proposals
Merge into: lp:launchpad
Diff against target: 281 lines (+182/-0)
9 files modified
lib/lp/code/browser/branchmergeproposal.py (+27/-0)
lib/lp/code/browser/configure.zcml (+6/-0)
lib/lp/code/browser/tests/test_branchmergeproposal.py (+91/-0)
lib/lp/code/interfaces/branch.py (+8/-0)
lib/lp/code/interfaces/gitref.py (+6/-0)
lib/lp/code/model/branch.py (+4/-0)
lib/lp/code/model/gitref.py (+8/-0)
lib/lp/code/templates/branchmergeproposal-index.pt (+15/-0)
lib/lp/code/templates/branchmergeproposal-rescan.pt (+17/-0)
To merge this branch: bzr merge lp:~twom/launchpad/add-rescan-link-to-merge-proposals
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+366060@code.launchpad.net

Commit message

Add a rescan button to branch merge proposals for failed branch or repository scans

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) wrote :

This is definitely an improvement, thanks! Just a bit more to go ...

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 2019-01-31 14:45:32 +0000
3+++ lib/lp/code/browser/branchmergeproposal.py 2019-04-17 13:05:36 +0000
4@@ -807,6 +807,33 @@
5 return False
6 return latest_preview.job.status == JobStatus.FAILED
7
8+ @property
9+ def show_rescan_link(self):
10+ source_job = self.context.merge_source.getLatestScanJob()
11+ target_job = self.context.merge_target.getLatestScanJob()
12+ if source_job and source_job.job.status == JobStatus.FAILED:
13+ return True
14+ if target_job and target_job.job.status == JobStatus.FAILED:
15+ return True
16+ return False
17+
18+
19+class BranchMergeProposalRescanView(LaunchpadEditFormView):
20+ schema = Interface
21+
22+ field_names = []
23+
24+ @action('Rescan', name='rescan')
25+ def rescan(self, action, data):
26+ source_job = self.context.merge_source.getLatestScanJob()
27+ target_job = self.context.merge_target.getLatestScanJob()
28+ if source_job and source_job.job.status == JobStatus.FAILED:
29+ self.context.merge_source.rescan()
30+ if target_job and target_job.job.status == JobStatus.FAILED:
31+ self.context.merge_target.rescan()
32+ self.request.response.addNotification("Rescan scheduled")
33+ self.next_url = canonical_url(self.context)
34+
35
36 @delegate_to(ICodeReviewVoteReference)
37 class DecoratedCodeReviewVoteReference:
38
39=== modified file 'lib/lp/code/browser/configure.zcml'
40--- lib/lp/code/browser/configure.zcml 2019-02-07 15:56:34 +0000
41+++ lib/lp/code/browser/configure.zcml 2019-04-17 13:05:36 +0000
42@@ -197,6 +197,12 @@
43 name="++bug-links"
44 template="../templates/branchmergeproposal-bug-links.pt"/>
45 </browser:pages>
46+ <browser:page
47+ name="+rescan"
48+ for="lp.code.interfaces.branchmergeproposal.IBranchMergeProposal"
49+ class="lp.code.browser.branchmergeproposal.BranchMergeProposalRescanView"
50+ permission="launchpad.Edit"
51+ template="../templates/branchmergeproposal-rescan.pt"/>
52 <browser:pages
53 for="lp.code.interfaces.branchmergeproposal.IBranchMergeProposal"
54 class="lp.code.browser.branchmergeproposal.BranchMergeProposalVoteView"
55
56=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
57--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2019-01-31 14:21:09 +0000
58+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2019-04-17 13:05:36 +0000
59@@ -74,6 +74,8 @@
60 IMergeProposalNeedsReviewEmailJobSource,
61 IMergeProposalUpdatedEmailJobSource,
62 )
63+from lp.code.interfaces.branchjob import IBranchScanJobSource
64+from lp.code.interfaces.gitjob import IGitRefScanJobSource
65 from lp.code.model.branchmergeproposaljob import UpdatePreviewDiffJob
66 from lp.code.model.diff import PreviewDiff
67 from lp.code.tests.helpers import (
68@@ -2177,6 +2179,95 @@
69 result = view.show_diff_update_link
70 self.assertTrue(result)
71
72+ def test_show_rescan_link_git(self):
73+ bmp = self.factory.makeBranchMergeProposalForGit()
74+ target_job = getUtility(IGitRefScanJobSource).create(
75+ bmp.target_git_repository)
76+ removeSecurityProxy(target_job).job._status = JobStatus.FAILED
77+ view = create_initialized_view(bmp, '+index')
78+ self.assertTrue(view.show_rescan_link)
79+
80+ def test_show_rescan_link_bzr(self):
81+ bmp = self.factory.makeBranchMergeProposal()
82+ target_job = getUtility(IBranchScanJobSource).create(
83+ bmp.target_branch)
84+ removeSecurityProxy(target_job).job._status = JobStatus.FAILED
85+ view = create_initialized_view(bmp, '+index')
86+ self.assertTrue(view.show_rescan_link)
87+
88+ def test_show_rescan_link_both_failed(self):
89+ bmp = self.factory.makeBranchMergeProposalForGit()
90+ target_job = getUtility(IGitRefScanJobSource).create(
91+ bmp.target_git_repository)
92+ removeSecurityProxy(target_job).job._status = JobStatus.FAILED
93+ source_job = getUtility(IGitRefScanJobSource).create(
94+ bmp.source_git_repository)
95+ removeSecurityProxy(source_job).job._status = JobStatus.FAILED
96+ view = create_initialized_view(bmp, '+index')
97+ self.assertTrue(view.show_rescan_link)
98+
99+ def test_show_rescan_link_latest_didnt_fail(self):
100+ bmp = self.factory.makeBranchMergeProposalForGit()
101+ target_job = getUtility(IGitRefScanJobSource).create(
102+ bmp.target_git_repository)
103+ removeSecurityProxy(target_job).job._status = JobStatus.COMPLETED
104+ source_job = getUtility(IGitRefScanJobSource).create(
105+ bmp.source_git_repository)
106+ removeSecurityProxy(source_job).job._status = JobStatus.COMPLETED
107+ view = create_initialized_view(bmp, '+index')
108+ self.assertFalse(view.show_rescan_link)
109+
110+
111+class TestBranchMergeProposalRescanView(BrowserTestCase):
112+
113+ layer = LaunchpadFunctionalLayer
114+
115+ def test_rescan_with_git(self):
116+ bmp = self.factory.makeBranchMergeProposalForGit()
117+ source_job = getUtility(IGitRefScanJobSource).create(
118+ bmp.source_git_repository)
119+ removeSecurityProxy(source_job).job._status = JobStatus.FAILED
120+
121+ with person_logged_in(bmp.merge_source.owner):
122+ request = LaunchpadTestRequest(
123+ method='POST',
124+ form={
125+ 'field.actions.rescan': 'Rescan',
126+ })
127+ request.setPrincipal(bmp.merge_source.owner)
128+ view = create_initialized_view(
129+ bmp,
130+ name='+rescan',
131+ request=request)
132+
133+ self.assertEqual(
134+ 'Rescan scheduled',
135+ view.request.response.notifications[0].message
136+ )
137+
138+ def test_rescan_with_bzr(self):
139+ bmp = self.factory.makeBranchMergeProposal()
140+ source_job = getUtility(IBranchScanJobSource).create(
141+ bmp.source_branch)
142+ removeSecurityProxy(source_job).job._status = JobStatus.FAILED
143+
144+ with person_logged_in(bmp.merge_source.owner):
145+ request = LaunchpadTestRequest(
146+ method='POST',
147+ form={
148+ 'field.actions.rescan': 'Rescan',
149+ })
150+ request.setPrincipal(bmp.merge_source.owner)
151+ view = create_initialized_view(
152+ bmp,
153+ name='+rescan',
154+ request=request)
155+
156+ self.assertEqual(
157+ 'Rescan scheduled',
158+ view.request.response.notifications[0].message
159+ )
160+
161
162 class TestLatestProposalsForEachBranchMixin:
163 """Confirm that the latest branch is returned."""
164
165=== modified file 'lib/lp/code/interfaces/branch.py'
166--- lib/lp/code/interfaces/branch.py 2019-04-01 10:09:31 +0000
167+++ lib/lp/code/interfaces/branch.py 2019-04-17 13:05:36 +0000
168@@ -1120,6 +1120,14 @@
169 def unscan(rescan=True):
170 """Reset this branch's scan data and optionally request a scan."""
171
172+ @export_write_operation()
173+ @operation_for_version('devel')
174+ def rescan():
175+ """Reset this branch's scan data and request a rescan.
176+
177+ A convenience function wrapper around unscan().
178+ """
179+
180
181 class IBranchEditableAttributes(Interface):
182 """IBranch attributes that can be edited.
183
184=== modified file 'lib/lp/code/interfaces/gitref.py'
185--- lib/lp/code/interfaces/gitref.py 2018-12-20 11:13:27 +0000
186+++ lib/lp/code/interfaces/gitref.py 2019-04-17 13:05:36 +0000
187@@ -394,6 +394,12 @@
188 :return: A binary string with the blob content.
189 """
190
191+ def getLatestScanJob():
192+ """Return the last IGitRefScanJobSource for the repository"""
193+
194+ def rescan():
195+ """Force a rescan of the repository"""
196+
197
198 class IGitRefEdit(Interface):
199 """IGitRef methods that require launchpad.Edit permission."""
200
201=== modified file 'lib/lp/code/model/branch.py'
202--- lib/lp/code/model/branch.py 2019-04-02 08:12:27 +0000
203+++ lib/lp/code/model/branch.py 2019-04-17 13:05:36 +0000
204@@ -1301,6 +1301,10 @@
205 job.celeryRunOnCommit()
206 return (self.last_mirrored_id, old_scanned_id)
207
208+ def rescan(self):
209+ """See `IBranchModerate`."""
210+ self.unscan(rescan=True)
211+
212 def getLatestScanJob(self):
213 from lp.code.model.branchjob import BranchJob, BranchScanJob
214 latest_job = IStore(BranchJob).find(
215
216=== modified file 'lib/lp/code/model/gitref.py'
217--- lib/lp/code/model/gitref.py 2018-12-20 11:24:27 +0000
218+++ lib/lp/code/model/gitref.py 2019-04-17 13:05:36 +0000
219@@ -475,6 +475,14 @@
220 self.repository.checkRefPermissions(
221 person, [self.path])[self.path])
222
223+ def getLatestScanJob(self):
224+ """See `IGitRef`."""
225+ return self.repository.getLatestScanJob()
226+
227+ def rescan(self):
228+ """See `IGitRef`."""
229+ return self.repository.rescan()
230+
231
232 @implementer(IGitRef)
233 class GitRef(StormBase, GitRefMixin):
234
235=== modified file 'lib/lp/code/templates/branchmergeproposal-index.pt'
236--- lib/lp/code/templates/branchmergeproposal-index.pt 2019-01-31 13:48:34 +0000
237+++ lib/lp/code/templates/branchmergeproposal-index.pt 2019-04-17 13:05:36 +0000
238@@ -208,6 +208,21 @@
239 </p>
240 </div>
241 </div>
242+ <div class="yui-g pending-update" id="diff-pending-update"
243+ tal:condition="view/show_rescan_link">
244+ <h3>Update scan failed</h3>
245+ <p>
246+ At least one of the branches involved have failed to scan.
247+ You can manually schedule a rescan if required.
248+ </p>
249+ <p>
250+ <form action="+rescan" name="launchpadform" method="post"
251+ enctype="multipart/form-data" accept-charset="UTF-8">
252+ <input class="button" type="submit"
253+ name="field.actions.rescan" value="Rescan" />
254+ </form>
255+ </p>
256+ </div>
257 <div id="review-diff" tal:condition="view/preview_diff">
258 <h2>Preview Diff </h2>
259
260
261=== added file 'lib/lp/code/templates/branchmergeproposal-rescan.pt'
262--- lib/lp/code/templates/branchmergeproposal-rescan.pt 1970-01-01 00:00:00 +0000
263+++ lib/lp/code/templates/branchmergeproposal-rescan.pt 2019-04-17 13:05:36 +0000
264@@ -0,0 +1,17 @@
265+<html
266+ xmlns="http://www.w3.org/1999/xhtml"
267+ xmlns:tal="http://xml.zope.org/namespaces/tal"
268+ xmlns:metal="http://xml.zope.org/namespaces/metal"
269+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
270+ metal:use-macro="view/macro:page/main_only"
271+ i18n:domain="launchpad">
272+ <body>
273+ <div metal:fill-slot="main">
274+ <p>
275+ You can schedule a rescan for the branches involved in this merge proposal
276+ if it appears that one or more of them are out of date.
277+ </p>
278+ <div metal:use-macro="context/@@launchpad_form/form" />
279+ </div>
280+ </body>
281+</html>