Merge lp:~rockstar/launchpad/spr-admin into lp:launchpad
- spr-admin
- Merge into devel
Proposed by
Paul Hummer
Status: | Merged |
---|---|
Approved by: | Aaron Bentley |
Approved revision: | no longer in the source branch. |
Merged at revision: | 11144 |
Proposed branch: | lp:~rockstar/launchpad/spr-admin |
Merge into: | lp:launchpad |
Prerequisite: | lp:~rockstar/launchpad/bug-602333 |
Diff against target: |
514 lines (+277/-76) 10 files modified
lib/canonical/launchpad/security.py (+9/-0) lib/lp/code/browser/configure.zcml (+14/-3) lib/lp/code/browser/sourcepackagerecipe.py (+4/-69) lib/lp/code/browser/sourcepackagerecipebuild.py (+121/-0) lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+3/-3) lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py (+91/-0) lib/lp/code/interfaces/sourcepackagerecipebuild.py (+3/-0) lib/lp/code/model/sourcepackagerecipebuild.py (+12/-1) lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+10/-0) lib/lp/code/templates/sourcepackagerecipebuild-index.pt (+10/-0) |
To merge this branch: | bzr merge lp:~rockstar/launchpad/spr-admin |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Approve | ||
Review via email: mp+29780@code.launchpad.net |
Commit message
Description of the change
This branch makes a link available on source package recipe builds that allows admins and bazaar experts to delete the build. Julian isn't really thrilled about this change, but we're still in beta, we need to be better at getting rid of broken builds, so, yeah, that's why we did this. Patch is (hopefully) pretty self explanatory.
To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) wrote : | # |
Revision history for this message
Aaron Bentley (abentley) wrote : | # |
Some minor formatting stuff to fix, as discussed.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/security.py' |
2 | --- lib/canonical/launchpad/security.py 2010-07-13 10:57:31 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2010-07-16 13:31:52 +0000 |
4 | @@ -1142,6 +1142,15 @@ |
5 | usedfor = ICodeImportMachine |
6 | |
7 | |
8 | +class DeleteSourcePackageRecipeBuilds(OnlyBazaarExpertsAndAdmins): |
9 | + """Control who can delete SourcePackageRecipeBuilds. |
10 | + |
11 | + Access is restricted to members of ~bazaar-experts and Launchpad admins. |
12 | + """ |
13 | + permission = 'launchpad.Edit' |
14 | + usedfor = ISourcePackageRecipeBuild |
15 | + |
16 | + |
17 | class AdminDistributionTranslations(AuthorizationBase): |
18 | """Class for deciding who can administer distribution translations. |
19 | |
20 | |
21 | === modified file 'lib/lp/code/browser/configure.zcml' |
22 | --- lib/lp/code/browser/configure.zcml 2010-07-09 10:22:32 +0000 |
23 | +++ lib/lp/code/browser/configure.zcml 2010-07-16 13:31:52 +0000 |
24 | @@ -1083,11 +1083,16 @@ |
25 | attribute_to_parent="recipe" |
26 | path_expression="string:+build/${id}" |
27 | rootsite="code" /> |
28 | + <browser:menus |
29 | + classes="SourcePackageRecipeBuildContextMenu" |
30 | + module="lp.code.browser.sourcepackagerecipebuild"/> |
31 | |
32 | <browser:navigation |
33 | module="lp.code.browser.sourcepackagerecipe" |
34 | - classes="SourcePackageRecipeNavigation |
35 | - SourcePackageRecipeBuildNavigation" /> |
36 | + classes="SourcePackageRecipeNavigation" /> |
37 | + <browser:navigation |
38 | + module="lp.code.browser.sourcepackagerecipebuild" |
39 | + classes="SourcePackageRecipeBuildNavigation" /> |
40 | |
41 | <facet facet="branches"> |
42 | |
43 | @@ -1115,10 +1120,16 @@ |
44 | layer="canonical.launchpad.layers.CodeLayer"/> |
45 | <browser:page |
46 | for="lp.code.interfaces.sourcepackagerecipebuild.ISourcePackageRecipeBuild" |
47 | - class="lp.code.browser.sourcepackagerecipe.SourcePackageRecipeBuildView" |
48 | + class="lp.code.browser.sourcepackagerecipebuild.SourcePackageRecipeBuildView" |
49 | name="+index" |
50 | template="../templates/sourcepackagerecipebuild-index.pt" |
51 | permission="launchpad.View"/> |
52 | + <browser:page |
53 | + for="lp.code.interfaces.sourcepackagerecipebuild.ISourcePackageRecipeBuild" |
54 | + class="lp.code.browser.sourcepackagerecipebuild.SourcePackageRecipeBuildCancelView" |
55 | + name="+cancel" |
56 | + template="../../app/templates/generic-edit.pt" |
57 | + permission="launchpad.View"/> |
58 | <browser:menus |
59 | classes=" |
60 | SourcePackageRecipeNavigationMenu |
61 | |
62 | === modified file 'lib/lp/code/browser/sourcepackagerecipe.py' |
63 | --- lib/lp/code/browser/sourcepackagerecipe.py 2010-07-02 01:12:15 +0000 |
64 | +++ lib/lp/code/browser/sourcepackagerecipe.py 2010-07-16 13:31:52 +0000 |
65 | @@ -7,7 +7,6 @@ |
66 | |
67 | __all__ = [ |
68 | 'SourcePackageRecipeAddView', |
69 | - 'SourcePackageRecipeBuildView', |
70 | 'SourcePackageRecipeContextMenu', |
71 | 'SourcePackageRecipeEditView', |
72 | 'SourcePackageRecipeNavigationMenu', |
73 | @@ -29,7 +28,6 @@ |
74 | |
75 | from canonical.database.constants import UTC_NOW |
76 | from canonical.launchpad.browser.launchpad import Hierarchy |
77 | -from canonical.launchpad.browser.librarian import FileNavigationMixin |
78 | from canonical.launchpad.interfaces import ILaunchBag |
79 | from canonical.launchpad.webapp import ( |
80 | action, canonical_url, ContextMenu, custom_widget, |
81 | @@ -39,19 +37,18 @@ |
82 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
83 | from canonical.launchpad.webapp.sorting import sorted_dotted_numbers |
84 | from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget |
85 | -from lp.buildmaster.interfaces.buildbase import BuildStatus |
86 | -from lp.code.errors import BuildAlreadyPending, ForbiddenInstruction |
87 | +from lp.code.errors import ForbiddenInstruction |
88 | +from lp.code.errors import BuildAlreadyPending |
89 | from lp.code.interfaces.branch import NoSuchBranch |
90 | from lp.code.interfaces.sourcepackagerecipe import ( |
91 | ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT) |
92 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
93 | - ISourcePackageRecipeBuild, ISourcePackageRecipeBuildSource) |
94 | + ISourcePackageRecipeBuildSource) |
95 | from lp.soyuz.browser.archive import make_archive_vocabulary |
96 | from lp.soyuz.interfaces.archive import ( |
97 | IArchiveSet) |
98 | from lp.registry.interfaces.distroseries import IDistroSeriesSet |
99 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
100 | -from lp.services.job.interfaces.job import JobStatus |
101 | |
102 | RECIPE_BETA_MESSAGE = structured( |
103 | 'We\'re still working on source package recipes. ' |
104 | @@ -193,6 +190,7 @@ |
105 | terms.reverse() |
106 | return SimpleVocabulary(terms) |
107 | |
108 | + |
109 | def target_ppas_vocabulary(context): |
110 | """Return a vocabulary of ppas that the current user can target.""" |
111 | ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user) |
112 | @@ -264,69 +262,6 @@ |
113 | |
114 | |
115 | |
116 | -class SourcePackageRecipeBuildNavigation(Navigation, FileNavigationMixin): |
117 | - |
118 | - usedfor = ISourcePackageRecipeBuild |
119 | - |
120 | - |
121 | -class SourcePackageRecipeBuildView(LaunchpadView): |
122 | - """Default view of a SourcePackageRecipeBuild.""" |
123 | - |
124 | - @property |
125 | - def status(self): |
126 | - """A human-friendly status string.""" |
127 | - if (self.context.buildstate == BuildStatus.NEEDSBUILD |
128 | - and self.eta is None): |
129 | - return 'No suitable builders' |
130 | - return { |
131 | - BuildStatus.NEEDSBUILD: 'Pending build', |
132 | - BuildStatus.FULLYBUILT: 'Successful build', |
133 | - BuildStatus.MANUALDEPWAIT: ( |
134 | - 'Could not build because of missing dependencies'), |
135 | - BuildStatus.CHROOTWAIT: ( |
136 | - 'Could not build because of chroot problem'), |
137 | - BuildStatus.SUPERSEDED: ( |
138 | - 'Could not build because source package was superseded'), |
139 | - BuildStatus.FAILEDTOUPLOAD: 'Could not be uploaded correctly', |
140 | - }.get(self.context.buildstate, self.context.buildstate.title) |
141 | - |
142 | - @property |
143 | - def eta(self): |
144 | - """The datetime when the build job is estimated to complete. |
145 | - |
146 | - This is the BuildQueue.estimated_duration plus the |
147 | - Job.date_started or BuildQueue.getEstimatedJobStartTime. |
148 | - """ |
149 | - if self.context.buildqueue_record is None: |
150 | - return None |
151 | - queue_record = self.context.buildqueue_record |
152 | - if queue_record.job.status == JobStatus.WAITING: |
153 | - start_time = queue_record.getEstimatedJobStartTime() |
154 | - if start_time is None: |
155 | - return None |
156 | - else: |
157 | - start_time = queue_record.job.date_started |
158 | - duration = queue_record.estimated_duration |
159 | - return start_time + duration |
160 | - |
161 | - @property |
162 | - def date(self): |
163 | - """The date when the build completed or is estimated to complete.""" |
164 | - if self.estimate: |
165 | - return self.eta |
166 | - return self.context.datebuilt |
167 | - |
168 | - @property |
169 | - def estimate(self): |
170 | - """If true, the date value is an estimate.""" |
171 | - if self.context.datebuilt is not None: |
172 | - return False |
173 | - return self.eta is not None |
174 | - |
175 | - def binary_builds(self): |
176 | - return list(self.context.binary_builds) |
177 | - |
178 | - |
179 | class ISourcePackageAddEditSchema(Interface): |
180 | """Schema for adding or editing a recipe.""" |
181 | |
182 | |
183 | === added file 'lib/lp/code/browser/sourcepackagerecipebuild.py' |
184 | --- lib/lp/code/browser/sourcepackagerecipebuild.py 1970-01-01 00:00:00 +0000 |
185 | +++ lib/lp/code/browser/sourcepackagerecipebuild.py 2010-07-16 13:31:52 +0000 |
186 | @@ -0,0 +1,121 @@ |
187 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
188 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
189 | + |
190 | +"""SourcePackageRecipeBuild views.""" |
191 | + |
192 | +__metaclass__ = type |
193 | + |
194 | +__all__ = [ |
195 | + 'SourcePackageRecipeBuildContextMenu', |
196 | + 'SourcePackageRecipeBuildNavigation', |
197 | + 'SourcePackageRecipeBuildView', |
198 | + 'SourcePackageRecipeBuildCancelView', |
199 | + ] |
200 | + |
201 | +from zope.interface import Interface |
202 | + |
203 | +from canonical.launchpad.browser.librarian import FileNavigationMixin |
204 | +from canonical.launchpad.webapp import ( |
205 | + action, canonical_url, ContextMenu, enabled_with_permission, |
206 | + LaunchpadView, LaunchpadFormView, Link, Navigation) |
207 | + |
208 | +from lp.buildmaster.interfaces.buildbase import BuildStatus |
209 | +from lp.code.interfaces.sourcepackagerecipebuild import ( |
210 | + ISourcePackageRecipeBuild) |
211 | +from lp.services.job.interfaces.job import JobStatus |
212 | + |
213 | + |
214 | +class SourcePackageRecipeBuildNavigation(Navigation, FileNavigationMixin): |
215 | + |
216 | + usedfor = ISourcePackageRecipeBuild |
217 | + |
218 | + |
219 | +class SourcePackageRecipeBuildContextMenu(ContextMenu): |
220 | + """Navigation menu for sourcepackagerecipe build.""" |
221 | + |
222 | + usedfor = ISourcePackageRecipeBuild |
223 | + |
224 | + facet = 'branches' |
225 | + |
226 | + links = ('cancel',) |
227 | + |
228 | + @enabled_with_permission('launchpad.Edit') |
229 | + def cancel(self): |
230 | + return Link('+cancel', 'Cancel build', icon='remove') |
231 | + |
232 | + |
233 | +class SourcePackageRecipeBuildView(LaunchpadView): |
234 | + """Default view of a SourcePackageRecipeBuild.""" |
235 | + |
236 | + @property |
237 | + def status(self): |
238 | + """A human-friendly status string.""" |
239 | + if (self.context.buildstate == BuildStatus.NEEDSBUILD |
240 | + and self.eta is None): |
241 | + return 'No suitable builders' |
242 | + return { |
243 | + BuildStatus.NEEDSBUILD: 'Pending build', |
244 | + BuildStatus.FULLYBUILT: 'Successful build', |
245 | + BuildStatus.MANUALDEPWAIT: ( |
246 | + 'Could not build because of missing dependencies'), |
247 | + BuildStatus.CHROOTWAIT: ( |
248 | + 'Could not build because of chroot problem'), |
249 | + BuildStatus.SUPERSEDED: ( |
250 | + 'Could not build because source package was superseded'), |
251 | + BuildStatus.FAILEDTOUPLOAD: 'Could not be uploaded correctly', |
252 | + }.get(self.context.buildstate, self.context.buildstate.title) |
253 | + |
254 | + @property |
255 | + def eta(self): |
256 | + """The datetime when the build job is estimated to complete. |
257 | + |
258 | + This is the BuildQueue.estimated_duration plus the |
259 | + Job.date_started or BuildQueue.getEstimatedJobStartTime. |
260 | + """ |
261 | + if self.context.buildqueue_record is None: |
262 | + return None |
263 | + queue_record = self.context.buildqueue_record |
264 | + if queue_record.job.status == JobStatus.WAITING: |
265 | + start_time = queue_record.getEstimatedJobStartTime() |
266 | + if start_time is None: |
267 | + return None |
268 | + else: |
269 | + start_time = queue_record.job.date_started |
270 | + duration = queue_record.estimated_duration |
271 | + return start_time + duration |
272 | + |
273 | + @property |
274 | + def date(self): |
275 | + """The date when the build completed or is estimated to complete.""" |
276 | + if self.estimate: |
277 | + return self.eta |
278 | + return self.context.datebuilt |
279 | + |
280 | + @property |
281 | + def estimate(self): |
282 | + """If true, the date value is an estimate.""" |
283 | + if self.context.datebuilt is not None: |
284 | + return False |
285 | + return self.eta is not None |
286 | + |
287 | + def binary_builds(self): |
288 | + return list(self.context.binary_builds) |
289 | + |
290 | + |
291 | +class SourcePackageRecipeBuildCancelView(LaunchpadFormView): |
292 | + """View for cancelling a build.""" |
293 | + |
294 | + class schema(Interface): |
295 | + """Schema for cancelling a build.""" |
296 | + |
297 | + page_title = label = "Cancel build" |
298 | + |
299 | + @property |
300 | + def cancel_url(self): |
301 | + return canonical_url(self.context) |
302 | + next_url = cancel_url |
303 | + |
304 | + @action('Cancel build', name='cancel') |
305 | + def request_action(self, action, data): |
306 | + """Cancel the build.""" |
307 | + self.context.cancelBuild() |
308 | |
309 | === modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py' |
310 | --- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-02 11:11:32 +0000 |
311 | +++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-16 13:31:52 +0000 |
312 | @@ -21,9 +21,9 @@ |
313 | DatabaseFunctionalLayer, LaunchpadFunctionalLayer) |
314 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
315 | from lp.code.browser.sourcepackagerecipe import ( |
316 | - SourcePackageRecipeView, SourcePackageRecipeRequestBuildsView, |
317 | - SourcePackageRecipeBuildView |
318 | -) |
319 | + SourcePackageRecipeView, SourcePackageRecipeRequestBuildsView) |
320 | +from lp.code.browser.sourcepackagerecipebuild import ( |
321 | + SourcePackageRecipeBuildView) |
322 | from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT |
323 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
324 | from lp.soyuz.model.processor import ProcessorFamily |
325 | |
326 | === added file 'lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py' |
327 | --- lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py 1970-01-01 00:00:00 +0000 |
328 | +++ lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py 2010-07-16 13:31:52 +0000 |
329 | @@ -0,0 +1,91 @@ |
330 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
331 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
332 | +# pylint: disable-msg=F0401,E1002 |
333 | + |
334 | +"""Tests for the source package recipe view classes and templates.""" |
335 | + |
336 | +__metaclass__ = type |
337 | + |
338 | +from mechanize import LinkNotFoundError |
339 | +import transaction |
340 | +from zope.component import getUtility |
341 | + |
342 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
343 | +from canonical.launchpad.webapp import canonical_url |
344 | +from canonical.testing import DatabaseFunctionalLayer |
345 | +from lp.buildmaster.interfaces.buildbase import BuildStatus |
346 | +from lp.soyuz.model.processor import ProcessorFamily |
347 | +from lp.testing import ANONYMOUS, BrowserTestCase, login, logout |
348 | + |
349 | + |
350 | +class TestSourcePackageRecipeBuild(BrowserTestCase): |
351 | + """Create some sample data for recipe tests.""" |
352 | + |
353 | + layer = DatabaseFunctionalLayer |
354 | + |
355 | + def setUp(self): |
356 | + """Provide useful defaults.""" |
357 | + super(TestSourcePackageRecipeBuild, self).setUp() |
358 | + self.chef = self.factory.makePerson( |
359 | + displayname='Master Chef', name='chef', password='test') |
360 | + self.user = self.chef |
361 | + self.ppa = self.factory.makeArchive( |
362 | + displayname='Secret PPA', owner=self.chef, name='ppa') |
363 | + self.squirrel = self.factory.makeDistroSeries( |
364 | + displayname='Secret Squirrel', name='secret', version='100.04', |
365 | + distribution=self.ppa.distribution) |
366 | + self.squirrel.nominatedarchindep = self.squirrel.newArch( |
367 | + 'i386', ProcessorFamily.get(1), False, self.chef, |
368 | + supports_virtualized=True) |
369 | + |
370 | + def makeRecipeBuild(self): |
371 | + """Create and return a specific recipe.""" |
372 | + chocolate = self.factory.makeProduct(name='chocolate') |
373 | + cake_branch = self.factory.makeProductBranch( |
374 | + owner=self.chef, name='cake', product=chocolate) |
375 | + recipe = self.factory.makeSourcePackageRecipe( |
376 | + owner=self.chef, distroseries=self.squirrel, name=u'cake_recipe', |
377 | + description=u'This recipe builds a foo for disto bar, with my' |
378 | + ' Secret Squirrel changes.', branches=[cake_branch], |
379 | + daily_build_archive=self.ppa) |
380 | + build = self.factory.makeSourcePackageRecipeBuild( |
381 | + recipe=recipe) |
382 | + return build |
383 | + |
384 | + def test_cancel_build(self): |
385 | + """An admin can cancel a build.""" |
386 | + experts = getUtility(ILaunchpadCelebrities).bazaar_experts.teamowner |
387 | + build = self.makeRecipeBuild() |
388 | + transaction.commit() |
389 | + build_url = canonical_url(build) |
390 | + logout() |
391 | + |
392 | + browser = self.getUserBrowser(build_url, user=experts) |
393 | + browser.getLink('Cancel build').click() |
394 | + |
395 | + self.assertEqual( |
396 | + browser.getLink('Cancel').url, |
397 | + build_url) |
398 | + |
399 | + browser.getControl('Cancel build').click() |
400 | + |
401 | + self.assertEqual( |
402 | + browser.url, |
403 | + build_url) |
404 | + |
405 | + login(ANONYMOUS) |
406 | + self.assertEqual( |
407 | + BuildStatus.SUPERSEDED, |
408 | + build.status) |
409 | + |
410 | + def test_cancel_build_not_admin(self): |
411 | + """No one but admins can cancel a build.""" |
412 | + build = self.makeRecipeBuild() |
413 | + transaction.commit() |
414 | + build_url = canonical_url(build) |
415 | + logout() |
416 | + |
417 | + browser = self.getUserBrowser(build_url, user=self.chef) |
418 | + self.assertRaises( |
419 | + LinkNotFoundError, |
420 | + browser.getLink, 'Cancel build') |
421 | |
422 | === modified file 'lib/lp/code/interfaces/sourcepackagerecipebuild.py' |
423 | --- lib/lp/code/interfaces/sourcepackagerecipebuild.py 2010-06-30 02:27:10 +0000 |
424 | +++ lib/lp/code/interfaces/sourcepackagerecipebuild.py 2010-07-16 13:31:52 +0000 |
425 | @@ -76,6 +76,9 @@ |
426 | def getFileByName(filename): |
427 | """Return the file under +files with specified name.""" |
428 | |
429 | + def cancelBuild(): |
430 | + """Cancel the build.""" |
431 | + |
432 | def destroySelf(): |
433 | """Delete the build itself.""" |
434 | |
435 | |
436 | === modified file 'lib/lp/code/model/sourcepackagerecipebuild.py' |
437 | --- lib/lp/code/model/sourcepackagerecipebuild.py 2010-06-30 02:27:10 +0000 |
438 | +++ lib/lp/code/model/sourcepackagerecipebuild.py 2010-07-16 13:31:52 +0000 |
439 | @@ -240,7 +240,8 @@ |
440 | recipe.is_stale = False |
441 | return builds |
442 | |
443 | - def destroySelf(self): |
444 | + def _unqueueBuild(self): |
445 | + """Remove the build's queue and job.""" |
446 | store = Store.of(self) |
447 | if self.buildqueue_record is not None: |
448 | job = self.buildqueue_record.job |
449 | @@ -249,6 +250,15 @@ |
450 | SourcePackageRecipeBuildJob, |
451 | SourcePackageRecipeBuildJob.build == self.id).remove() |
452 | store.remove(job) |
453 | + |
454 | + def cancelBuild(self): |
455 | + """See `ISourcePackageRecipeBuild.`""" |
456 | + self._unqueueBuild() |
457 | + self.status = BuildStatus.SUPERSEDED |
458 | + |
459 | + def destroySelf(self): |
460 | + self._unqueueBuild() |
461 | + store = Store.of(self) |
462 | store.remove(self) |
463 | |
464 | @classmethod |
465 | @@ -309,6 +319,7 @@ |
466 | if build.status == BuildStatus.FULLYBUILT: |
467 | build.notify() |
468 | |
469 | + |
470 | class SourcePackageRecipeBuildJob(BuildFarmJobOldDerived, Storm): |
471 | classProvides(ISourcePackageRecipeBuildJobSource) |
472 | implements(ISourcePackageRecipeBuildJob) |
473 | |
474 | === modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py' |
475 | --- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-06-30 02:27:10 +0000 |
476 | +++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-16 13:31:52 +0000 |
477 | @@ -294,6 +294,16 @@ |
478 | build = self.factory.makeSourcePackageRecipeBuild() |
479 | build.destroySelf() |
480 | |
481 | + def test_cancelBuild(self): |
482 | + # ISourcePackageRecipeBuild should make sure to remove jobs and build |
483 | + # queue entries and then invalidate itself. |
484 | + build = self.factory.makeSourcePackageRecipeBuild() |
485 | + build.cancelBuild() |
486 | + |
487 | + self.assertEqual( |
488 | + BuildStatus.SUPERSEDED, |
489 | + build.status) |
490 | + |
491 | |
492 | class TestAsBuildmaster(TestCaseWithFactory): |
493 | |
494 | |
495 | === modified file 'lib/lp/code/templates/sourcepackagerecipebuild-index.pt' |
496 | --- lib/lp/code/templates/sourcepackagerecipebuild-index.pt 2010-05-05 19:16:24 +0000 |
497 | +++ lib/lp/code/templates/sourcepackagerecipebuild-index.pt 2010-07-16 13:31:52 +0000 |
498 | @@ -158,6 +158,16 @@ |
499 | (<span tal:replace="file/content/filesize/fmt:bytes" />) |
500 | </li> |
501 | </ul> |
502 | + |
503 | + <div |
504 | + style="margin-top: 1.5em" |
505 | + tal:define="context_menu view/context/menu:context; |
506 | + link context_menu/cancel" |
507 | + tal:condition="link/enabled" |
508 | + > |
509 | + <a tal:replace="structure link/fmt:link" /> |
510 | + </div> |
511 | + |
512 | </metal:macro> |
513 | |
514 | <metal:macro define-macro="buildlog"> |
Oh yeah, the other thing I did was to move lp.code. browser. sourcepackagere cipe stuff that was really sourcepackagere cipebuild stuff to lp.code. browser. sourcepackagere cipebuild.