Merge lp:~twom/launchpad/manually-rescan-link into lp:launchpad

Proposed by Tom Wardill
Status: Merged
Merged at revision: 18863
Proposed branch: lp:~twom/launchpad/manually-rescan-link
Merge into: lp:launchpad
Diff against target: 296 lines (+156/-3)
8 files modified
lib/lp/code/browser/branch.py (+24/-0)
lib/lp/code/browser/configure.zcml (+6/-0)
lib/lp/code/browser/tests/test_branch.py (+36/-1)
lib/lp/code/interfaces/branch.py (+3/-0)
lib/lp/code/model/branch.py (+14/-1)
lib/lp/code/model/tests/test_branch.py (+40/-0)
lib/lp/code/templates/branch-index.pt (+19/-1)
lib/lp/code/templates/branch-rescan.pt (+14/-0)
To merge this branch: bzr merge lp:~twom/launchpad/manually-rescan-link
Reviewer Review Type Date Requested Status
Colin Watson Approve
Review via email: mp+362139@code.launchpad.net

Commit message

Add 'rescan' button when a scan of a branch has failed.

Description of the change

Adds a button to the Branch view, only when the latest scan has failed or there are no scan jobs for a branch.

Also add a view at +rescan so a rescan can be manually triggered otherwise.

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

Nice feature, thanks! Just a quick review, with a couple of questions.

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/branch.py'
2--- lib/lp/code/browser/branch.py 2018-07-16 00:49:00 +0000
3+++ lib/lp/code/browser/branch.py 2019-01-28 13:31:58 +0000
4@@ -118,6 +118,7 @@
5 english_list,
6 truncate_text,
7 )
8+from lp.services.job.interfaces.job import JobStatus
9 from lp.services.propertycache import cachedproperty
10 from lp.services.webapp import (
11 canonical_url,
12@@ -587,6 +588,16 @@
13 """Only show the link if there are more than five."""
14 return len(self.landing_candidates) > 5
15
16+ @property
17+ def show_rescan_link(self):
18+ """Only show the rescan button if the latest scan has failed"""
19+ scan_job = self.context.getLatestScanJob()
20+ # If there are no jobs, we failed to create one for some reason,
21+ # so we should allow a rescan
22+ if not scan_job:
23+ return True
24+ return scan_job.job.status == JobStatus.FAILED
25+
26 @cachedproperty
27 def linked_bugtasks(self):
28 """Return a list of bugtasks linked to the branch."""
29@@ -635,6 +646,19 @@
30 return self.context.getSpecificationLinks(self.user)
31
32
33+class BranchRescanView(LaunchpadEditFormView):
34+
35+ schema = Interface
36+
37+ field_names = []
38+
39+ @action('Rescan', name='rescan')
40+ def rescan(self, action, data):
41+ self.context.unscan(rescan=True)
42+ self.request.response.addNotification("Branch scan scheduled")
43+ self.next_url = canonical_url(self.context)
44+
45+
46 class BranchEditFormView(LaunchpadEditFormView):
47 """Base class for forms that edit a branch."""
48
49
50=== modified file 'lib/lp/code/browser/configure.zcml'
51--- lib/lp/code/browser/configure.zcml 2018-11-09 22:46:32 +0000
52+++ lib/lp/code/browser/configure.zcml 2019-01-28 13:31:58 +0000
53@@ -437,6 +437,12 @@
54 permission="launchpad.Edit"
55 template="../../app/templates/generic-edit.pt"/>
56 <browser:page
57+ name="+rescan"
58+ for="lp.code.interfaces.branch.IBranch"
59+ class="lp.code.browser.branch.BranchRescanView"
60+ permission="launchpad.Edit"
61+ template="../templates/branch-rescan.pt"/>
62+ <browser:page
63 name="+edit-import"
64 for="lp.code.interfaces.branch.IBranch"
65 class="lp.code.browser.codeimport.CodeImportEditView"
66
67=== modified file 'lib/lp/code/browser/tests/test_branch.py'
68--- lib/lp/code/browser/tests/test_branch.py 2018-09-10 14:10:26 +0000
69+++ lib/lp/code/browser/tests/test_branch.py 2019-01-28 13:31:58 +0000
70@@ -34,6 +34,7 @@
71 RepositoryFormat,
72 )
73 from lp.code.enums import BranchType
74+from lp.code.model.branchjob import BranchScanJob
75 from lp.code.tests.helpers import BranchHostingFixture
76 from lp.registry.enums import BranchSharingPolicy
77 from lp.registry.interfaces.accesspolicy import IAccessPolicySource
78@@ -43,6 +44,7 @@
79 from lp.services.database.constants import UTC_NOW
80 from lp.services.features.testing import FeatureFixture
81 from lp.services.helpers import truncate_text
82+from lp.services.job.interfaces.job import JobStatus
83 from lp.services.webapp.publisher import canonical_url
84 from lp.services.webapp.servers import LaunchpadTestRequest
85 from lp.testing import (
86@@ -299,6 +301,39 @@
87 '<a href="+recipes">2 recipes</a> using this branch.',
88 view.recipes_link)
89
90+ def test_show_rescan_link(self):
91+ branch = self.factory.makeAnyBranch()
92+ job = BranchScanJob.create(branch)
93+ job.job._status = JobStatus.FAILED
94+ view = create_initialized_view(branch, '+index')
95+ result = view.show_rescan_link
96+ self.assertTrue(result)
97+
98+ def test_show_rescan_link_no_failures(self):
99+ branch = self.factory.makeAnyBranch()
100+ job = BranchScanJob.create(branch)
101+ job.job._status = JobStatus.COMPLETED
102+ job.job.date_finished = UTC_NOW
103+ view = create_initialized_view(branch, '+index')
104+ result = view.show_rescan_link
105+ self.assertFalse(result)
106+
107+ def test_show_rescan_link_no_scan_jobs(self):
108+ branch = self.factory.makeAnyBranch()
109+ view = create_initialized_view(branch, '+index')
110+ result = view.show_rescan_link
111+ self.assertTrue(result)
112+
113+ def test_show_rescan_link_latest_didnt_fail(self):
114+ branch = self.factory.makeAnyBranch()
115+ job = BranchScanJob.create(branch)
116+ job.job._status = JobStatus.FAILED
117+ job = BranchScanJob.create(branch)
118+ job.job._status = JobStatus.COMPLETED
119+ view = create_initialized_view(branch, '+index')
120+ result = view.show_rescan_link
121+ self.assertTrue(result)
122+
123 def _addBugLinks(self, branch):
124 for status in BugTaskStatus.items:
125 bug = self.factory.makeBug(status=status)
126@@ -611,7 +646,7 @@
127 logout()
128 with StormStatementRecorder() as recorder:
129 browser.open(branch_url)
130- self.assertThat(recorder, HasQueryCount(Equals(28)))
131+ self.assertThat(recorder, HasQueryCount(Equals(29)))
132
133
134 class TestBranchViewPrivateArtifacts(BrowserTestCase):
135
136=== modified file 'lib/lp/code/interfaces/branch.py'
137--- lib/lp/code/interfaces/branch.py 2018-05-17 14:10:29 +0000
138+++ lib/lp/code/interfaces/branch.py 2019-01-28 13:31:58 +0000
139@@ -1034,6 +1034,9 @@
140 detail page.
141 """
142
143+ def getLatestScanJob():
144+ """Get the latest IBranchScanJob for this branch"""
145+
146 def checkUpgrade():
147 """Check whether an upgrade should be performed, and raise if not.
148
149
150=== modified file 'lib/lp/code/model/branch.py'
151--- lib/lp/code/model/branch.py 2018-12-10 13:54:34 +0000
152+++ lib/lp/code/model/branch.py 2019-01-28 13:31:58 +0000
153@@ -167,7 +167,10 @@
154 from lp.services.database.datetimecol import UtcDateTimeCol
155 from lp.services.database.decoratedresultset import DecoratedResultSet
156 from lp.services.database.enumcol import EnumCol
157-from lp.services.database.interfaces import IMasterStore
158+from lp.services.database.interfaces import (
159+ IMasterStore,
160+ IStore,
161+ )
162 from lp.services.database.sqlbase import (
163 SQLBase,
164 sqlvalues,
165@@ -1295,6 +1298,16 @@
166 job.celeryRunOnCommit()
167 return (self.last_mirrored_id, old_scanned_id)
168
169+ def getLatestScanJob(self):
170+ from lp.code.model.branchjob import BranchJob, BranchScanJob
171+ latest_job = IStore(BranchJob).find(
172+ BranchJob,
173+ BranchJob.branch == self,
174+ Job.date_finished != None,
175+ BranchJob.job_type == BranchScanJob.class_job_type).order_by(
176+ Desc(Job.date_finished)).first()
177+ return latest_job
178+
179 def requestMirror(self):
180 """See `IBranch`."""
181 if self.branch_type in (BranchType.REMOTE, BranchType.HOSTED):
182
183=== modified file 'lib/lp/code/model/tests/test_branch.py'
184--- lib/lp/code/model/tests/test_branch.py 2018-07-09 09:27:06 +0000
185+++ lib/lp/code/model/tests/test_branch.py 2019-01-28 13:31:58 +0000
186@@ -105,6 +105,7 @@
187 from lp.code.model.branchjob import (
188 BranchJob,
189 BranchJobType,
190+ BranchScanJob,
191 ReclaimBranchSpaceJob,
192 )
193 from lp.code.model.branchmergeproposal import BranchMergeProposal
194@@ -3502,6 +3503,45 @@
195 getUtility(ILaunchpadCelebrities).commercial_admin):
196 branch.unscan()
197
198+ def test_getLatestScanJob(self):
199+ complete_date = datetime.now(UTC)
200+
201+ branch = self.factory.makeAnyBranch()
202+ failed_job = BranchScanJob.create(branch)
203+ failed_job.job._status = JobStatus.FAILED
204+ failed_job.job.date_finished = complete_date
205+ completed_job = BranchScanJob.create(branch)
206+ completed_job.job._status = JobStatus.COMPLETED
207+ completed_job.job.date_finished = complete_date - timedelta(seconds=10)
208+ result = branch.getLatestScanJob()
209+ self.assertEqual(failed_job.id, result.id)
210+
211+ def test_getLatestScanJob_no_scans(self):
212+ branch = self.factory.makeAnyBranch()
213+ result = branch.getLatestScanJob()
214+ self.assertIsNone(result)
215+
216+ def test_getLatestScanJob_correct_branch(self):
217+ complete_date = datetime.now(UTC)
218+
219+ main_branch = self.factory.makeAnyBranch()
220+ second_branch = self.factory.makeAnyBranch()
221+ failed_job = BranchScanJob.create(second_branch)
222+ failed_job.job._status = JobStatus.FAILED
223+ failed_job.job.date_finished = complete_date
224+ completed_job = BranchScanJob.create(main_branch)
225+ completed_job.job._status = JobStatus.COMPLETED
226+ completed_job.job.date_finished = complete_date - timedelta(seconds=10)
227+ result = main_branch.getLatestScanJob()
228+ self.assertEqual(completed_job.id, result.id)
229+
230+ def test_getLatestScanJob_without_completion_date(self):
231+ branch = self.factory.makeAnyBranch()
232+ failed_job = BranchScanJob.create(branch)
233+ failed_job.job._status = JobStatus.FAILED
234+ result = branch.getLatestScanJob()
235+ self.assertFalse(result)
236+
237
238 class TestWebservice(TestCaseWithFactory):
239 """Tests for the webservice."""
240
241=== modified file 'lib/lp/code/templates/branch-index.pt'
242--- lib/lp/code/templates/branch-index.pt 2017-11-06 09:32:45 +0000
243+++ lib/lp/code/templates/branch-index.pt 2019-01-28 13:31:58 +0000
244@@ -130,7 +130,7 @@
245
246 </div>
247
248- <div class="yui-g" tal:condition="view/pending_updates">
249+ <div class="yui-g" tal:condition="python: not view.show_rescan_link and view.pending_updates">
250 <div class="portlet">
251 <div id="branch-pending-updates" class="pending-update">
252 <h3>Updating branch...</h3>
253@@ -142,6 +142,24 @@
254 </div>
255 </div>
256
257+ <div class="yui-g" tal:condition="view/show_rescan_link">
258+ <div class="portlet">
259+ <div id="branch-scan-failed" class="pending-update">
260+ <h3>Branch scan failed</h3>
261+ <p>
262+ Scanning this branch for changes failed. You can manually rescan if required.
263+ </p>
264+ <p>
265+ <form action="+rescan" name="launchpadform" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
266+ <input id="field.actions.rescan" class="button" type="submit"
267+ name="field.actions.rescan" value="Rescan" />
268+ </form>
269+ </p>
270+ </div>
271+ </div>
272+ </div>
273+
274+
275 <div class="yui-g">
276 <div class="portlet" id="recent-revisions">
277 <h2>Recent revisions</h2>
278
279=== added file 'lib/lp/code/templates/branch-rescan.pt'
280--- lib/lp/code/templates/branch-rescan.pt 1970-01-01 00:00:00 +0000
281+++ lib/lp/code/templates/branch-rescan.pt 2019-01-28 13:31:58 +0000
282@@ -0,0 +1,14 @@
283+<html
284+ xmlns="http://www.w3.org/1999/xhtml"
285+ xmlns:tal="http://xml.zope.org/namespaces/tal"
286+ xmlns:metal="http://xml.zope.org/namespaces/metal"
287+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
288+ metal:use-macro="view/macro:page/main_only"
289+ i18n:domain="launchpad">
290+ <body>
291+ <div metal:fill-slot="main">
292+ <p>You can schedule a rescan for this branch if it appears the branch is out of date.</p>
293+ <div metal:use-macro="context/@@launchpad_form/form" />
294+ </div>
295+ </body>
296+</html>