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

Proposed by Tom Wardill
Status: Merged
Merged at revision: 18865
Proposed branch: lp:~twom/launchpad/manually-rescan-git-link
Merge into: lp:launchpad
Diff against target: 306 lines (+157/-4)
10 files modified
lib/lp/code/browser/configure.zcml (+6/-0)
lib/lp/code/browser/gitrepository.py (+24/-0)
lib/lp/code/browser/tests/test_gitrepository.py (+35/-0)
lib/lp/code/interfaces/gitrepository.py (+3/-0)
lib/lp/code/model/branch.py (+0/-1)
lib/lp/code/model/gitrepository.py (+10/-0)
lib/lp/code/model/tests/test_branch.py (+2/-1)
lib/lp/code/model/tests/test_gitrepository.py (+45/-1)
lib/lp/code/templates/gitrepository-index.pt (+18/-1)
lib/lp/code/templates/gitrepository-rescan.pt (+14/-0)
To merge this branch: bzr merge lp:~twom/launchpad/manually-rescan-git-link
Reviewer Review Type Date Requested Status
Colin Watson Approve
Review via email: mp+362341@code.launchpad.net

Commit message

Add 'rescan' button if a GitRefScanJob has failed for a gitrepository

To post a comment you must log in.
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/configure.zcml'
2--- lib/lp/code/browser/configure.zcml 2019-01-23 13:20:48 +0000
3+++ lib/lp/code/browser/configure.zcml 2019-01-28 18:09:39 +0000
4@@ -867,6 +867,12 @@
5 template="../templates/gitrepository-edit.pt"/>
6 <browser:page
7 for="lp.code.interfaces.gitrepository.IGitRepository"
8+ class="lp.code.browser.gitrepository.GitRepositoryRescanView"
9+ permission="launchpad.Edit"
10+ name="+rescan"
11+ template="../templates/gitrepository-rescan.pt"/>
12+ <browser:page
13+ for="lp.code.interfaces.gitrepository.IGitRepository"
14 class="lp.code.browser.codeimport.CodeImportEditView"
15 permission="launchpad.Edit"
16 name="+edit-import"
17
18=== modified file 'lib/lp/code/browser/gitrepository.py'
19--- lib/lp/code/browser/gitrepository.py 2019-01-09 18:06:41 +0000
20+++ lib/lp/code/browser/gitrepository.py 2019-01-28 18:09:39 +0000
21@@ -108,6 +108,7 @@
22 from lp.services.database.constants import UTC_NOW
23 from lp.services.features import getFeatureFlag
24 from lp.services.fields import UniqueField
25+from lp.services.job.interfaces.job import JobStatus
26 from lp.services.propertycache import cachedproperty
27 from lp.services.webapp import (
28 canonical_url,
29@@ -441,6 +442,29 @@
30 self.user, only_active=True)
31 return latest_proposals_for_each_branch(targets)
32
33+ @property
34+ def show_rescan_link(self):
35+ """Only show the rescan button if the latest scan has failed"""
36+ scan_job = self.context.getLatestScanJob()
37+ # If there are no jobs, we failed to create one for some reason,
38+ # so we should allow a rescan
39+ if not scan_job:
40+ return True
41+ return scan_job.job.status == JobStatus.FAILED
42+
43+
44+class GitRepositoryRescanView(LaunchpadEditFormView):
45+
46+ schema = Interface
47+
48+ field_names = []
49+
50+ @action('Rescan', name='rescan')
51+ def rescan(self, action, data):
52+ self.context.rescan()
53+ self.request.response.addNotification("Repository scan scheduled")
54+ self.next_url = canonical_url(self.context)
55+
56
57 class GitRepositoryEditFormView(LaunchpadEditFormView):
58 """Base class for forms that edit a Git repository."""
59
60=== modified file 'lib/lp/code/browser/tests/test_gitrepository.py'
61--- lib/lp/code/browser/tests/test_gitrepository.py 2019-01-10 10:23:56 +0000
62+++ lib/lp/code/browser/tests/test_gitrepository.py 2019-01-28 18:09:39 +0000
63@@ -50,6 +50,7 @@
64 GitRepositoryType,
65 )
66 from lp.code.interfaces.revision import IRevisionSet
67+from lp.code.model.gitjob import GitRefScanJob
68 from lp.code.tests.helpers import GitHostingFixture
69 from lp.registry.enums import (
70 BranchSharingPolicy,
71@@ -63,6 +64,7 @@
72 from lp.services.beautifulsoup import BeautifulSoup
73 from lp.services.database.constants import UTC_NOW
74 from lp.services.features.testing import FeatureFixture
75+from lp.services.job.interfaces.job import JobStatus
76 from lp.services.webapp.publisher import canonical_url
77 from lp.services.webapp.servers import LaunchpadTestRequest
78 from lp.testing import (
79@@ -422,6 +424,39 @@
80 view.render()
81 self.assertThat(recorder, HasQueryCount(Equals(6)))
82
83+ def test_show_rescan_link(self):
84+ repository = self.factory.makeGitRepository()
85+ job = GitRefScanJob.create(repository)
86+ job.job._status = JobStatus.FAILED
87+ view = create_initialized_view(repository, '+index')
88+ result = view.show_rescan_link
89+ self.assertTrue(result)
90+
91+ def test_show_rescan_link_no_failures(self):
92+ repository = self.factory.makeGitRepository()
93+ job = GitRefScanJob.create(repository)
94+ job.job._status = JobStatus.COMPLETED
95+ job.job.date_finished = UTC_NOW
96+ view = create_initialized_view(repository, '+index')
97+ result = view.show_rescan_link
98+ self.assertFalse(result)
99+
100+ def test_show_rescan_link_no_scan_jobs(self):
101+ repository = self.factory.makeGitRepository()
102+ view = create_initialized_view(repository, '+index')
103+ result = view.show_rescan_link
104+ self.assertTrue(result)
105+
106+ def test_show_rescan_link_latest_didnt_fail(self):
107+ repository = self.factory.makeGitRepository()
108+ job = GitRefScanJob.create(repository)
109+ job.job._status = JobStatus.FAILED
110+ job = GitRefScanJob.create(repository)
111+ job.job._status = JobStatus.COMPLETED
112+ view = create_initialized_view(repository, '+index')
113+ result = view.show_rescan_link
114+ self.assertTrue(result)
115+
116
117 class TestGitRepositoryViewPrivateArtifacts(BrowserTestCase):
118 """Tests that Git repositories with private team artifacts can be viewed.
119
120=== modified file 'lib/lp/code/interfaces/gitrepository.py'
121--- lib/lp/code/interfaces/gitrepository.py 2019-01-09 10:50:40 +0000
122+++ lib/lp/code/interfaces/gitrepository.py 2019-01-28 18:09:39 +0000
123@@ -366,6 +366,9 @@
124 def getCodebrowseUrlForRevision(commit):
125 """The URL to the commit of the merge to the target branch"""
126
127+ def getLatestScanJob():
128+ """Return the last IGitRefScanJobSource for this repository"""
129+
130 def visibleByUser(user):
131 """Can the specified user see this repository?"""
132
133
134=== modified file 'lib/lp/code/model/branch.py'
135--- lib/lp/code/model/branch.py 2019-01-28 13:30:18 +0000
136+++ lib/lp/code/model/branch.py 2019-01-28 18:09:39 +0000
137@@ -1303,7 +1303,6 @@
138 latest_job = IStore(BranchJob).find(
139 BranchJob,
140 BranchJob.branch == self,
141- Job.date_finished != None,
142 BranchJob.job_type == BranchScanJob.class_job_type).order_by(
143 Desc(Job.date_finished)).first()
144 return latest_job
145
146=== modified file 'lib/lp/code/model/gitrepository.py'
147--- lib/lp/code/model/gitrepository.py 2019-01-09 13:07:24 +0000
148+++ lib/lp/code/model/gitrepository.py 2019-01-28 18:09:39 +0000
149@@ -746,6 +746,16 @@
150 """
151 return set()
152
153+ def getLatestScanJob(self):
154+ """See `IGitRepository`."""
155+ from lp.code.model.gitjob import GitJob, GitRefScanJob
156+ latest_job = IStore(GitJob).find(
157+ GitJob,
158+ GitJob.repository == self,
159+ GitJob.job_type == GitRefScanJob.class_job_type).order_by(
160+ Desc(Job.date_finished)).first()
161+ return latest_job
162+
163 def visibleByUser(self, user):
164 """See `IGitRepository`."""
165 if self.information_type in PUBLIC_INFORMATION_TYPES:
166
167=== modified file 'lib/lp/code/model/tests/test_branch.py'
168--- lib/lp/code/model/tests/test_branch.py 2019-01-23 17:46:04 +0000
169+++ lib/lp/code/model/tests/test_branch.py 2019-01-28 18:09:39 +0000
170@@ -3540,7 +3540,8 @@
171 failed_job = BranchScanJob.create(branch)
172 failed_job.job._status = JobStatus.FAILED
173 result = branch.getLatestScanJob()
174- self.assertFalse(result)
175+ self.assertTrue(result)
176+ self.assertIsNone(result.job.date_finished)
177
178
179 class TestWebservice(TestCaseWithFactory):
180
181=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
182--- lib/lp/code/model/tests/test_gitrepository.py 2019-01-09 13:07:24 +0000
183+++ lib/lp/code/model/tests/test_gitrepository.py 2019-01-28 18:09:39 +0000
184@@ -7,7 +7,10 @@
185
186 __metaclass__ = type
187
188-from datetime import datetime
189+from datetime import (
190+ datetime,
191+ timedelta,
192+ )
193 import email
194 from functools import partial
195 import hashlib
196@@ -96,6 +99,7 @@
197 from lp.code.model.gitjob import (
198 GitJob,
199 GitJobType,
200+ GitRefScanJob,
201 ReclaimGitRepositorySpaceJob,
202 )
203 from lp.code.model.gitrepository import (
204@@ -2193,6 +2197,46 @@
205 [job] = list(job_source.iterReady())
206 self.assertEqual(repository, job.repository)
207
208+ def test_getLatestScanJob(self):
209+ complete_date = datetime.now(pytz.UTC)
210+
211+ repository = self.factory.makeGitRepository()
212+ failed_job = GitRefScanJob.create(repository)
213+ failed_job.job._status = JobStatus.FAILED
214+ failed_job.job.date_finished = complete_date
215+ completed_job = GitRefScanJob.create(repository)
216+ completed_job.job._status = JobStatus.COMPLETED
217+ completed_job.job.date_finished = complete_date - timedelta(seconds=10)
218+ result = removeSecurityProxy(repository.getLatestScanJob())
219+ self.assertEqual(failed_job.job_id, result.job_id)
220+
221+ def test_getLatestScanJob_no_scans(self):
222+ repository = self.factory.makeGitRepository()
223+ result = repository.getLatestScanJob()
224+ self.assertIsNone(result)
225+
226+ def test_getLatestScanJob_correct_branch(self):
227+ complete_date = datetime.now(pytz.UTC)
228+
229+ main_repository = self.factory.makeGitRepository()
230+ second_repository = self.factory.makeGitRepository()
231+ failed_job = GitRefScanJob.create(second_repository)
232+ failed_job.job._status = JobStatus.FAILED
233+ failed_job.job.date_finished = complete_date
234+ completed_job = GitRefScanJob.create(main_repository)
235+ completed_job.job._status = JobStatus.COMPLETED
236+ completed_job.job.date_finished = complete_date - timedelta(seconds=10)
237+ result = removeSecurityProxy(main_repository.getLatestScanJob())
238+ self.assertEqual(completed_job.job_id, result.job_id)
239+
240+ def test_getLatestScanJob_without_completion_date(self):
241+ repository = self.factory.makeGitRepository()
242+ failed_job = GitRefScanJob.create(repository)
243+ failed_job.job._status = JobStatus.FAILED
244+ result = repository.getLatestScanJob()
245+ self.assertTrue(result)
246+ self.assertIsNone(result.job.date_finished)
247+
248
249 class TestGitRepositoryUpdateMergeCommitIDs(TestCaseWithFactory):
250
251
252=== modified file 'lib/lp/code/templates/gitrepository-index.pt'
253--- lib/lp/code/templates/gitrepository-index.pt 2018-08-31 06:29:29 +0000
254+++ lib/lp/code/templates/gitrepository-index.pt 2019-01-28 18:09:39 +0000
255@@ -71,7 +71,7 @@
256 </div>
257 </div>
258
259- <div class="yui-g" tal:condition="context/pending_updates">
260+ <div class="yui-g" tal:condition="python: not view.show_rescan_link and context.pending_updates">
261 <div class="portlet">
262 <div id="repository-pending-updates" class="pending-update">
263 <h3>Updating repository...</h3>
264@@ -83,6 +83,23 @@
265 </div>
266 </div>
267
268+ <div class="yui-g" tal:condition="view/show_rescan_link">
269+ <div class="portlet">
270+ <div id="branch-scan-failed" class="pending-update">
271+ <h3>Repository scan failed</h3>
272+ <p>
273+ Scanning this repository for changes failed. You can manually rescan if required.
274+ </p>
275+ <p>
276+ <form action="+rescan" name="launchpadform" method="post" enctype="multipart/form-data" accept-charset="UTF-8">
277+ <input id="field.actions.rescan" class="button" type="submit"
278+ name="field.actions.rescan" value="Rescan" />
279+ </form>
280+ </p>
281+ </div>
282+ </div>
283+ </div>
284+
285 <div class="yui-g">
286 <div id="repository-branches" class="portlet"
287 tal:define="branches view/branches">
288
289=== added file 'lib/lp/code/templates/gitrepository-rescan.pt'
290--- lib/lp/code/templates/gitrepository-rescan.pt 1970-01-01 00:00:00 +0000
291+++ lib/lp/code/templates/gitrepository-rescan.pt 2019-01-28 18:09:39 +0000
292@@ -0,0 +1,14 @@
293+<html
294+ xmlns="http://www.w3.org/1999/xhtml"
295+ xmlns:tal="http://xml.zope.org/namespaces/tal"
296+ xmlns:metal="http://xml.zope.org/namespaces/metal"
297+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
298+ metal:use-macro="view/macro:page/main_only"
299+ i18n:domain="launchpad">
300+ <body>
301+ <div metal:fill-slot="main">
302+ <p>You can schedule a rescan for this repository if it appears the repository is out of date.</p>
303+ <div metal:use-macro="context/@@launchpad_form/form" />
304+ </div>
305+ </body>
306+</html>