1
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
1
# Copyright 2009 Canonical Ltd. This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
4
"""Browser views for builds."""
15
14
'BuildRescoringView',
18
'DistributionBuildRecordsView',
22
from itertools import groupby
23
from operator import attrgetter
25
from lazr.batchnavigator import ListRangeFactory
26
19
from lazr.delegates import delegates
27
from lazr.restful.utils import safe_hasattr
28
20
from zope.component import getUtility
29
from zope.interface import (
33
from zope.security.interfaces import Unauthorized
34
from zope.security.proxy import removeSecurityProxy
21
from zope.interface import implements
36
23
from canonical.launchpad import _
37
24
from canonical.launchpad.browser.librarian import (
51
38
from canonical.launchpad.webapp.authorization import check_permission
52
from canonical.launchpad.webapp.batching import (
39
from canonical.launchpad.webapp.batching import BatchNavigator
56
40
from canonical.launchpad.webapp.breadcrumb import Breadcrumb
57
41
from canonical.launchpad.webapp.interfaces import ICanonicalUrlData
42
from canonical.lazr.utils import safe_hasattr
58
43
from lp.app.browser.launchpadform import (
64
49
UnexpectedFormData,
66
51
from lp.buildmaster.enums import BuildStatus
67
from lp.buildmaster.interfaces.buildfarmjob import (
69
InconsistentBuildFarmJobError,
70
ISpecificBuildFarmJobSource,
72
from lp.buildmaster.interfaces.packagebuild import IPackageBuild
52
from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSet
73
53
from lp.code.interfaces.sourcepackagerecipebuild import (
74
ISourcePackageRecipeBuildSource,
54
ISourcePackageRecipeBuildSource)
76
55
from lp.services.job.interfaces.job import JobStatus
77
56
from lp.services.propertycache import cachedproperty
78
57
from lp.soyuz.enums import PackageUploadStatus
172
151
"""Overview menu for build records """
173
152
usedfor = IBinaryPackageBuild
175
links = ['ppa', 'records', 'retry', 'rescore', 'cancel']
154
links = ['ppa', 'records', 'retry', 'rescore']
178
157
def is_ppa_build(self):
205
184
'+rescore', text, icon='edit',
206
185
enabled=self.context.can_be_rescored)
208
@enabled_with_permission('launchpad.Edit')
210
"""Only enabled for pending/active virtual builds."""
211
text = 'Cancel build'
213
'+cancel', text, icon='edit',
214
enabled=self.context.can_be_cancelled)
217
188
class BuildBreadcrumb(Breadcrumb):
218
189
"""Builds a breadcrumb for an `IBinaryPackageBuild`."""
423
392
"Build rescored to %s." % score)
426
class BuildCancelView(LaunchpadFormView):
427
"""View class for build cancellation."""
429
class schema(Interface):
430
"""Schema for cancelling a build."""
432
page_title = label = "Cancel build"
435
def cancel_url(self):
436
return canonical_url(self.context)
437
next_url = cancel_url
439
@action("Cancel build", name="cancel")
440
def request_action(self, action, data):
441
"""Cancel the build."""
442
self.context.cancel()
443
if self.context.status == BuildStatus.CANCELLING:
444
self.request.response.addNotification(
445
"Build cancellation in progress.")
446
elif self.context.status == BuildStatus.CANCELLED:
447
self.request.response.addNotification("Build cancelled.")
449
self.request.response.addNotification("Unable to cancel build.")
452
395
class CompleteBuild:
453
396
"""Super object to store related IBinaryPackageBuild & IBuildQueue."""
454
397
delegates(IBinaryPackageBuild)
472
415
Return a list of built CompleteBuild instances, or empty
473
416
list if no builds were contained in the received batch.
475
builds = getSpecificJobs(
476
[build.build_farm_job if IPackageBuild.providedBy(build) else build
418
builds = [build.getSpecificJob() for build in batch]
500
441
return complete_builds
503
def getSpecificJobs(jobs):
504
"""Return the specific build jobs associated with each of the jobs
505
in the provided job list.
508
key = attrgetter('job_type.name')
509
sorted_jobs = sorted(jobs, key=key)
511
for job_type_name, grouped_jobs in groupby(sorted_jobs, key=key):
512
# Fetch the jobs in batches grouped by their job type.
514
ISpecificBuildFarmJobSource, job_type_name)
515
builds = [build for build
516
in source.getByBuildFarmJobs(list(grouped_jobs))
517
if build is not None]
518
is_binary_package_build = IBinaryPackageBuildSet.providedBy(
521
if is_binary_package_build:
522
job_builds[build.package_build.build_farm_job.id] = build
525
job_builds[build.build_farm_job.id] = build
527
# If the build farm job is private, we will get an
528
# Unauthorized exception; we only use
529
# removeSecurityProxy to get the id of build_farm_job
530
# but the corresponding build returned in the list
532
naked_build = removeSecurityProxy(build)
533
job_builds[naked_build.build_farm_job.id] = None
534
# Return the corresponding builds.
536
return [job_builds[job.id] for job in jobs]
538
raise InconsistentBuildFarmJobError(
539
"Could not find all the related specific jobs.")
542
444
class BuildRecordsView(LaunchpadView):
543
445
"""Base class used to present objects that contains build records.
586
486
builds = self.context.getBuildRecords(
587
487
build_state=self.state, name=self.text, arch_tag=self.arch_tag,
588
488
user=self.user, binary_only=binary_only)
589
self.batchnav = BatchNavigator(
590
builds, self.request, range_factory=self.range_factory(builds))
489
self.batchnav = BatchNavigator(builds, self.request)
591
490
# We perform this extra step because we don't what to issue one
592
491
# extra query to retrieve the BuildQueue for each Build (batch item)
593
492
# A more elegant approach should be extending Batching class and
741
640
def no_results(self):
742
641
return self.form_submitted and not self.complete_builds
745
class DistributionBuildRecordsView(BuildRecordsView):
746
"""See BuildRecordsView."""
748
# SQL Queries generated by the default ListRangeFactory time out
749
# for some views, like +builds?build_state=all. StormRangeFactory
751
range_factory = StormRangeFactory