Merge lp:~twom/launchpad/manually-rescan-link into lp:launchpad
- manually-rescan-link
- Merge into devel
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | 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 : | # |
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 | 118 | english_list, | 118 | english_list, |
6 | 119 | truncate_text, | 119 | truncate_text, |
7 | 120 | ) | 120 | ) |
8 | 121 | from lp.services.job.interfaces.job import JobStatus | ||
9 | 121 | from lp.services.propertycache import cachedproperty | 122 | from lp.services.propertycache import cachedproperty |
10 | 122 | from lp.services.webapp import ( | 123 | from lp.services.webapp import ( |
11 | 123 | canonical_url, | 124 | canonical_url, |
12 | @@ -587,6 +588,16 @@ | |||
13 | 587 | """Only show the link if there are more than five.""" | 588 | """Only show the link if there are more than five.""" |
14 | 588 | return len(self.landing_candidates) > 5 | 589 | return len(self.landing_candidates) > 5 |
15 | 589 | 590 | ||
16 | 591 | @property | ||
17 | 592 | def show_rescan_link(self): | ||
18 | 593 | """Only show the rescan button if the latest scan has failed""" | ||
19 | 594 | scan_job = self.context.getLatestScanJob() | ||
20 | 595 | # If there are no jobs, we failed to create one for some reason, | ||
21 | 596 | # so we should allow a rescan | ||
22 | 597 | if not scan_job: | ||
23 | 598 | return True | ||
24 | 599 | return scan_job.job.status == JobStatus.FAILED | ||
25 | 600 | |||
26 | 590 | @cachedproperty | 601 | @cachedproperty |
27 | 591 | def linked_bugtasks(self): | 602 | def linked_bugtasks(self): |
28 | 592 | """Return a list of bugtasks linked to the branch.""" | 603 | """Return a list of bugtasks linked to the branch.""" |
29 | @@ -635,6 +646,19 @@ | |||
30 | 635 | return self.context.getSpecificationLinks(self.user) | 646 | return self.context.getSpecificationLinks(self.user) |
31 | 636 | 647 | ||
32 | 637 | 648 | ||
33 | 649 | class BranchRescanView(LaunchpadEditFormView): | ||
34 | 650 | |||
35 | 651 | schema = Interface | ||
36 | 652 | |||
37 | 653 | field_names = [] | ||
38 | 654 | |||
39 | 655 | @action('Rescan', name='rescan') | ||
40 | 656 | def rescan(self, action, data): | ||
41 | 657 | self.context.unscan(rescan=True) | ||
42 | 658 | self.request.response.addNotification("Branch scan scheduled") | ||
43 | 659 | self.next_url = canonical_url(self.context) | ||
44 | 660 | |||
45 | 661 | |||
46 | 638 | class BranchEditFormView(LaunchpadEditFormView): | 662 | class BranchEditFormView(LaunchpadEditFormView): |
47 | 639 | """Base class for forms that edit a branch.""" | 663 | """Base class for forms that edit a branch.""" |
48 | 640 | 664 | ||
49 | 641 | 665 | ||
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 | 437 | permission="launchpad.Edit" | 437 | permission="launchpad.Edit" |
55 | 438 | template="../../app/templates/generic-edit.pt"/> | 438 | template="../../app/templates/generic-edit.pt"/> |
56 | 439 | <browser:page | 439 | <browser:page |
57 | 440 | name="+rescan" | ||
58 | 441 | for="lp.code.interfaces.branch.IBranch" | ||
59 | 442 | class="lp.code.browser.branch.BranchRescanView" | ||
60 | 443 | permission="launchpad.Edit" | ||
61 | 444 | template="../templates/branch-rescan.pt"/> | ||
62 | 445 | <browser:page | ||
63 | 440 | name="+edit-import" | 446 | name="+edit-import" |
64 | 441 | for="lp.code.interfaces.branch.IBranch" | 447 | for="lp.code.interfaces.branch.IBranch" |
65 | 442 | class="lp.code.browser.codeimport.CodeImportEditView" | 448 | class="lp.code.browser.codeimport.CodeImportEditView" |
66 | 443 | 449 | ||
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 | 34 | RepositoryFormat, | 34 | RepositoryFormat, |
72 | 35 | ) | 35 | ) |
73 | 36 | from lp.code.enums import BranchType | 36 | from lp.code.enums import BranchType |
74 | 37 | from lp.code.model.branchjob import BranchScanJob | ||
75 | 37 | from lp.code.tests.helpers import BranchHostingFixture | 38 | from lp.code.tests.helpers import BranchHostingFixture |
76 | 38 | from lp.registry.enums import BranchSharingPolicy | 39 | from lp.registry.enums import BranchSharingPolicy |
77 | 39 | from lp.registry.interfaces.accesspolicy import IAccessPolicySource | 40 | from lp.registry.interfaces.accesspolicy import IAccessPolicySource |
78 | @@ -43,6 +44,7 @@ | |||
79 | 43 | from lp.services.database.constants import UTC_NOW | 44 | from lp.services.database.constants import UTC_NOW |
80 | 44 | from lp.services.features.testing import FeatureFixture | 45 | from lp.services.features.testing import FeatureFixture |
81 | 45 | from lp.services.helpers import truncate_text | 46 | from lp.services.helpers import truncate_text |
82 | 47 | from lp.services.job.interfaces.job import JobStatus | ||
83 | 46 | from lp.services.webapp.publisher import canonical_url | 48 | from lp.services.webapp.publisher import canonical_url |
84 | 47 | from lp.services.webapp.servers import LaunchpadTestRequest | 49 | from lp.services.webapp.servers import LaunchpadTestRequest |
85 | 48 | from lp.testing import ( | 50 | from lp.testing import ( |
86 | @@ -299,6 +301,39 @@ | |||
87 | 299 | '<a href="+recipes">2 recipes</a> using this branch.', | 301 | '<a href="+recipes">2 recipes</a> using this branch.', |
88 | 300 | view.recipes_link) | 302 | view.recipes_link) |
89 | 301 | 303 | ||
90 | 304 | def test_show_rescan_link(self): | ||
91 | 305 | branch = self.factory.makeAnyBranch() | ||
92 | 306 | job = BranchScanJob.create(branch) | ||
93 | 307 | job.job._status = JobStatus.FAILED | ||
94 | 308 | view = create_initialized_view(branch, '+index') | ||
95 | 309 | result = view.show_rescan_link | ||
96 | 310 | self.assertTrue(result) | ||
97 | 311 | |||
98 | 312 | def test_show_rescan_link_no_failures(self): | ||
99 | 313 | branch = self.factory.makeAnyBranch() | ||
100 | 314 | job = BranchScanJob.create(branch) | ||
101 | 315 | job.job._status = JobStatus.COMPLETED | ||
102 | 316 | job.job.date_finished = UTC_NOW | ||
103 | 317 | view = create_initialized_view(branch, '+index') | ||
104 | 318 | result = view.show_rescan_link | ||
105 | 319 | self.assertFalse(result) | ||
106 | 320 | |||
107 | 321 | def test_show_rescan_link_no_scan_jobs(self): | ||
108 | 322 | branch = self.factory.makeAnyBranch() | ||
109 | 323 | view = create_initialized_view(branch, '+index') | ||
110 | 324 | result = view.show_rescan_link | ||
111 | 325 | self.assertTrue(result) | ||
112 | 326 | |||
113 | 327 | def test_show_rescan_link_latest_didnt_fail(self): | ||
114 | 328 | branch = self.factory.makeAnyBranch() | ||
115 | 329 | job = BranchScanJob.create(branch) | ||
116 | 330 | job.job._status = JobStatus.FAILED | ||
117 | 331 | job = BranchScanJob.create(branch) | ||
118 | 332 | job.job._status = JobStatus.COMPLETED | ||
119 | 333 | view = create_initialized_view(branch, '+index') | ||
120 | 334 | result = view.show_rescan_link | ||
121 | 335 | self.assertTrue(result) | ||
122 | 336 | |||
123 | 302 | def _addBugLinks(self, branch): | 337 | def _addBugLinks(self, branch): |
124 | 303 | for status in BugTaskStatus.items: | 338 | for status in BugTaskStatus.items: |
125 | 304 | bug = self.factory.makeBug(status=status) | 339 | bug = self.factory.makeBug(status=status) |
126 | @@ -611,7 +646,7 @@ | |||
127 | 611 | logout() | 646 | logout() |
128 | 612 | with StormStatementRecorder() as recorder: | 647 | with StormStatementRecorder() as recorder: |
129 | 613 | browser.open(branch_url) | 648 | browser.open(branch_url) |
131 | 614 | self.assertThat(recorder, HasQueryCount(Equals(28))) | 649 | self.assertThat(recorder, HasQueryCount(Equals(29))) |
132 | 615 | 650 | ||
133 | 616 | 651 | ||
134 | 617 | class TestBranchViewPrivateArtifacts(BrowserTestCase): | 652 | class TestBranchViewPrivateArtifacts(BrowserTestCase): |
135 | 618 | 653 | ||
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 | 1034 | detail page. | 1034 | detail page. |
141 | 1035 | """ | 1035 | """ |
142 | 1036 | 1036 | ||
143 | 1037 | def getLatestScanJob(): | ||
144 | 1038 | """Get the latest IBranchScanJob for this branch""" | ||
145 | 1039 | |||
146 | 1037 | def checkUpgrade(): | 1040 | def checkUpgrade(): |
147 | 1038 | """Check whether an upgrade should be performed, and raise if not. | 1041 | """Check whether an upgrade should be performed, and raise if not. |
148 | 1039 | 1042 | ||
149 | 1040 | 1043 | ||
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 | 167 | from lp.services.database.datetimecol import UtcDateTimeCol | 167 | from lp.services.database.datetimecol import UtcDateTimeCol |
155 | 168 | from lp.services.database.decoratedresultset import DecoratedResultSet | 168 | from lp.services.database.decoratedresultset import DecoratedResultSet |
156 | 169 | from lp.services.database.enumcol import EnumCol | 169 | from lp.services.database.enumcol import EnumCol |
158 | 170 | from lp.services.database.interfaces import IMasterStore | 170 | from lp.services.database.interfaces import ( |
159 | 171 | IMasterStore, | ||
160 | 172 | IStore, | ||
161 | 173 | ) | ||
162 | 171 | from lp.services.database.sqlbase import ( | 174 | from lp.services.database.sqlbase import ( |
163 | 172 | SQLBase, | 175 | SQLBase, |
164 | 173 | sqlvalues, | 176 | sqlvalues, |
165 | @@ -1295,6 +1298,16 @@ | |||
166 | 1295 | job.celeryRunOnCommit() | 1298 | job.celeryRunOnCommit() |
167 | 1296 | return (self.last_mirrored_id, old_scanned_id) | 1299 | return (self.last_mirrored_id, old_scanned_id) |
168 | 1297 | 1300 | ||
169 | 1301 | def getLatestScanJob(self): | ||
170 | 1302 | from lp.code.model.branchjob import BranchJob, BranchScanJob | ||
171 | 1303 | latest_job = IStore(BranchJob).find( | ||
172 | 1304 | BranchJob, | ||
173 | 1305 | BranchJob.branch == self, | ||
174 | 1306 | Job.date_finished != None, | ||
175 | 1307 | BranchJob.job_type == BranchScanJob.class_job_type).order_by( | ||
176 | 1308 | Desc(Job.date_finished)).first() | ||
177 | 1309 | return latest_job | ||
178 | 1310 | |||
179 | 1298 | def requestMirror(self): | 1311 | def requestMirror(self): |
180 | 1299 | """See `IBranch`.""" | 1312 | """See `IBranch`.""" |
181 | 1300 | if self.branch_type in (BranchType.REMOTE, BranchType.HOSTED): | 1313 | if self.branch_type in (BranchType.REMOTE, BranchType.HOSTED): |
182 | 1301 | 1314 | ||
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 | 105 | from lp.code.model.branchjob import ( | 105 | from lp.code.model.branchjob import ( |
188 | 106 | BranchJob, | 106 | BranchJob, |
189 | 107 | BranchJobType, | 107 | BranchJobType, |
190 | 108 | BranchScanJob, | ||
191 | 108 | ReclaimBranchSpaceJob, | 109 | ReclaimBranchSpaceJob, |
192 | 109 | ) | 110 | ) |
193 | 110 | from lp.code.model.branchmergeproposal import BranchMergeProposal | 111 | from lp.code.model.branchmergeproposal import BranchMergeProposal |
194 | @@ -3502,6 +3503,45 @@ | |||
195 | 3502 | getUtility(ILaunchpadCelebrities).commercial_admin): | 3503 | getUtility(ILaunchpadCelebrities).commercial_admin): |
196 | 3503 | branch.unscan() | 3504 | branch.unscan() |
197 | 3504 | 3505 | ||
198 | 3506 | def test_getLatestScanJob(self): | ||
199 | 3507 | complete_date = datetime.now(UTC) | ||
200 | 3508 | |||
201 | 3509 | branch = self.factory.makeAnyBranch() | ||
202 | 3510 | failed_job = BranchScanJob.create(branch) | ||
203 | 3511 | failed_job.job._status = JobStatus.FAILED | ||
204 | 3512 | failed_job.job.date_finished = complete_date | ||
205 | 3513 | completed_job = BranchScanJob.create(branch) | ||
206 | 3514 | completed_job.job._status = JobStatus.COMPLETED | ||
207 | 3515 | completed_job.job.date_finished = complete_date - timedelta(seconds=10) | ||
208 | 3516 | result = branch.getLatestScanJob() | ||
209 | 3517 | self.assertEqual(failed_job.id, result.id) | ||
210 | 3518 | |||
211 | 3519 | def test_getLatestScanJob_no_scans(self): | ||
212 | 3520 | branch = self.factory.makeAnyBranch() | ||
213 | 3521 | result = branch.getLatestScanJob() | ||
214 | 3522 | self.assertIsNone(result) | ||
215 | 3523 | |||
216 | 3524 | def test_getLatestScanJob_correct_branch(self): | ||
217 | 3525 | complete_date = datetime.now(UTC) | ||
218 | 3526 | |||
219 | 3527 | main_branch = self.factory.makeAnyBranch() | ||
220 | 3528 | second_branch = self.factory.makeAnyBranch() | ||
221 | 3529 | failed_job = BranchScanJob.create(second_branch) | ||
222 | 3530 | failed_job.job._status = JobStatus.FAILED | ||
223 | 3531 | failed_job.job.date_finished = complete_date | ||
224 | 3532 | completed_job = BranchScanJob.create(main_branch) | ||
225 | 3533 | completed_job.job._status = JobStatus.COMPLETED | ||
226 | 3534 | completed_job.job.date_finished = complete_date - timedelta(seconds=10) | ||
227 | 3535 | result = main_branch.getLatestScanJob() | ||
228 | 3536 | self.assertEqual(completed_job.id, result.id) | ||
229 | 3537 | |||
230 | 3538 | def test_getLatestScanJob_without_completion_date(self): | ||
231 | 3539 | branch = self.factory.makeAnyBranch() | ||
232 | 3540 | failed_job = BranchScanJob.create(branch) | ||
233 | 3541 | failed_job.job._status = JobStatus.FAILED | ||
234 | 3542 | result = branch.getLatestScanJob() | ||
235 | 3543 | self.assertFalse(result) | ||
236 | 3544 | |||
237 | 3505 | 3545 | ||
238 | 3506 | class TestWebservice(TestCaseWithFactory): | 3546 | class TestWebservice(TestCaseWithFactory): |
239 | 3507 | """Tests for the webservice.""" | 3547 | """Tests for the webservice.""" |
240 | 3508 | 3548 | ||
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 | 130 | 130 | ||
246 | 131 | </div> | 131 | </div> |
247 | 132 | 132 | ||
249 | 133 | <div class="yui-g" tal:condition="view/pending_updates"> | 133 | <div class="yui-g" tal:condition="python: not view.show_rescan_link and view.pending_updates"> |
250 | 134 | <div class="portlet"> | 134 | <div class="portlet"> |
251 | 135 | <div id="branch-pending-updates" class="pending-update"> | 135 | <div id="branch-pending-updates" class="pending-update"> |
252 | 136 | <h3>Updating branch...</h3> | 136 | <h3>Updating branch...</h3> |
253 | @@ -142,6 +142,24 @@ | |||
254 | 142 | </div> | 142 | </div> |
255 | 143 | </div> | 143 | </div> |
256 | 144 | 144 | ||
257 | 145 | <div class="yui-g" tal:condition="view/show_rescan_link"> | ||
258 | 146 | <div class="portlet"> | ||
259 | 147 | <div id="branch-scan-failed" class="pending-update"> | ||
260 | 148 | <h3>Branch scan failed</h3> | ||
261 | 149 | <p> | ||
262 | 150 | Scanning this branch for changes failed. You can manually rescan if required. | ||
263 | 151 | </p> | ||
264 | 152 | <p> | ||
265 | 153 | <form action="+rescan" name="launchpadform" method="post" enctype="multipart/form-data" accept-charset="UTF-8"> | ||
266 | 154 | <input id="field.actions.rescan" class="button" type="submit" | ||
267 | 155 | name="field.actions.rescan" value="Rescan" /> | ||
268 | 156 | </form> | ||
269 | 157 | </p> | ||
270 | 158 | </div> | ||
271 | 159 | </div> | ||
272 | 160 | </div> | ||
273 | 161 | |||
274 | 162 | |||
275 | 145 | <div class="yui-g"> | 163 | <div class="yui-g"> |
276 | 146 | <div class="portlet" id="recent-revisions"> | 164 | <div class="portlet" id="recent-revisions"> |
277 | 147 | <h2>Recent revisions</h2> | 165 | <h2>Recent revisions</h2> |
278 | 148 | 166 | ||
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 | 1 | <html | ||
284 | 2 | xmlns="http://www.w3.org/1999/xhtml" | ||
285 | 3 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
286 | 4 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
287 | 5 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
288 | 6 | metal:use-macro="view/macro:page/main_only" | ||
289 | 7 | i18n:domain="launchpad"> | ||
290 | 8 | <body> | ||
291 | 9 | <div metal:fill-slot="main"> | ||
292 | 10 | <p>You can schedule a rescan for this branch if it appears the branch is out of date.</p> | ||
293 | 11 | <div metal:use-macro="context/@@launchpad_form/form" /> | ||
294 | 12 | </div> | ||
295 | 13 | </body> | ||
296 | 14 | </html> |
Nice feature, thanks! Just a quick review, with a couple of questions.