2 from twisted
.web
import html
3 from twisted
.web
.util
import Redirect
, DeferredResource
4 from twisted
.internet
import defer
, reactor
7 from twisted
.python
import log
8 from buildbot
.status
.web
.base
import HtmlResource
, make_row
, css_classes
10 from buildbot
.status
.web
.tests
import TestsResource
11 from buildbot
.status
.web
.step
import StepsResource
13 # builders/$builder/builds/$buildnum
14 class StatusResourceBuild(HtmlResource
):
17 def __init__(self
, build_status
, build_control
, builder_control
):
18 HtmlResource
.__init
__(self
)
19 self
.build_status
= build_status
20 self
.build_control
= build_control
21 self
.builder_control
= builder_control
23 def getTitle(self
, request
):
24 return ("Buildbot: %s Build #%d" %
25 (html
.escape(self
.build_status
.getBuilder().getName()),
26 self
.build_status
.getNumber()))
30 status
= self
.getStatus(req
)
31 projectName
= status
.getProjectName()
32 data
= ('<div class="title"><a href="../../../..">%s</a></div>\n'
34 # the color in the following line gives python-mode trouble
35 builder_name
= b
.getBuilder().getName()
36 data
+= ("<h1><a href=\"../..\">Builder %s</a>: Build #%d</h1>\n"
37 % (builder_name
, b
.getNumber()))
39 if not b
.isFinished():
40 data
+= "<h2>Build In Progress</h2>"
43 when_time
= time
.strftime("%H:%M:%S",
44 time
.localtime(time
.time() + when
))
45 data
+= "<div>ETA %ds (%s)</div>\n" % (when
, when_time
)
47 if self
.build_control
is not None:
48 stopURL
= urllib
.quote(req
.childLink("stop"))
50 <form action="%s" class='command stopbuild'>
51 <p>To stop this build, fill out the following fields and
52 push the 'Stop' button</p>\n""" % stopURL
53 data
+= make_row("Your name:",
54 "<input type='text' name='username' />")
55 data
+= make_row("Reason for stopping build:",
56 "<input type='text' name='comments' />")
57 data
+= """<input type="submit" value="Stop Builder" />
62 results
= b
.getResults()
63 data
+= "<h2>Results:</h2>\n"
64 text
= " ".join(b
.getText())
65 data
+= '<span class="%s">%s</span>\n' % (css_classes
[results
],
67 if b
.getTestResults():
68 url
= req
.childLink("tests")
69 data
+= "<h3><a href=\"%s\">test results</a></h3>\n" % url
71 ss
= b
.getSourceStamp()
72 data
+= "<h2>SourceStamp:</h2>\n"
75 data
+= " <li>Branch: %s</li>\n" % html
.escape(ss
.branch
)
77 data
+= " <li>Revision: %s</li>\n" % html
.escape(str(ss
.revision
))
79 data
+= " <li>Patch: YES</li>\n" # TODO: provide link to .diff
81 data
+= " <li>Changes: see below</li>\n"
82 if (ss
.branch
is None and ss
.revision
is None and ss
.patch
is None
84 data
+= " <li>build of most recent revision</li>\n"
87 got_revision
= b
.getProperty("got_revision")
91 if len(got_revision
) > 40:
92 got_revision
= "[revision string too long]"
93 data
+= " <li>Got Revision: %s</li>\n" % got_revision
96 # TODO: turn this into a table, or some other sort of definition-list
97 # that doesn't take up quite so much vertical space
98 data
+= "<h2>Buildslave:</h2>\n %s\n" % html
.escape(b
.getSlavename())
99 data
+= "<h2>Reason:</h2>\n%s\n" % html
.escape(b
.getReason())
101 data
+= "<h2>Steps and Logfiles:</h2>\n"
104 for s
in b
.getSteps():
106 data
+= (" <li><a href=\"%s\">%s</a> [%s]\n"
107 % (req
.childLink("steps/%s" % urllib
.quote(name
)),
109 " ".join(s
.getText())))
112 for logfile
in s
.getLogs():
113 logname
= logfile
.getName()
114 logurl
= req
.childLink("steps/%s/logs/%s" %
116 urllib
.quote(logname
)))
117 data
+= (" <li><a href=\"%s\">%s</a></li>\n" %
118 (logurl
, logfile
.getName()))
123 data
+= "<h2>Blamelist:</h2>\n"
124 if list(b
.getResponsibleUsers()):
126 for who
in b
.getResponsibleUsers():
127 data
+= " <li>%s</li>\n" % html
.escape(who
)
130 data
+= "<div>no responsible users</div>\n"
133 data
+= "<h2>All Changes</h2>\n"
136 data
+= "<li>" + c
.asHTML() + "</li>\n"
138 #data += html.PRE(b.changesText()) # TODO
141 if b
.isFinished() and self
.builder_control
is not None:
142 data
+= "<h3>Resubmit Build:</h3>\n"
143 # can we rebuild it exactly?
144 exactly
= (ss
.revision
is not None) or b
.getChanges()
146 data
+= ("<p>This tree was built from a specific set of \n"
147 "source files, and can be rebuilt exactly</p>\n")
149 data
+= ("<p>This tree was built from the most recent "
152 data
+= " (along some branch)"
153 data
+= (" and thus it might not be possible to rebuild it \n"
154 "exactly. Any changes that have been committed \n"
155 "after this build was started <b>will</b> be \n"
156 "included in a rebuild.</p>\n")
157 rebuildURL
= urllib
.quote(req
.childLink("rebuild"))
158 data
+= ('<form action="%s" class="command rebuild">\n'
160 data
+= make_row("Your name:",
161 "<input type='text' name='username' />")
162 data
+= make_row("Reason for re-running build:",
163 "<input type='text' name='comments' />")
164 data
+= '<input type="submit" value="Rebuild" />\n'
170 b
= self
.build_status
171 c
= self
.build_control
172 log
.msg("web stopBuild of build %s:%s" % \
173 (b
.getBuilder().getName(), b
.getNumber()))
174 name
= req
.args
.get("username", ["<unknown>"])[0]
175 comments
= req
.args
.get("comments", ["<no reason specified>"])[0]
176 reason
= ("The web-page 'stop build' button was pressed by "
177 "'%s': %s\n" % (name
, comments
))
179 # we're at http://localhost:8080/svn-hello/builds/5/stop?[args] and
180 # we want to go to: http://localhost:8080/svn-hello/builds/5 or
181 # http://localhost:8080/
183 #return Redirect("../%d" % self.build.getNumber())
184 r
= Redirect("../../..") # TODO: no longer correct
186 reactor
.callLater(1, d
.callback
, r
)
187 return DeferredResource(d
)
189 def rebuild(self
, req
):
190 b
= self
.build_status
191 bc
= self
.builder_control
192 builder_name
= b
.getBuilder().getName()
193 log
.msg("web rebuild of build %s:%s" % (builder_name
, b
.getNumber()))
194 name
= req
.args
.get("username", ["<unknown>"])[0]
195 comments
= req
.args
.get("comments", ["<no reason specified>"])[0]
196 reason
= ("The web-page 'rebuild' button was pressed by "
197 "'%s': %s\n" % (name
, comments
))
198 if not bc
or not b
.isFinished():
199 log
.msg("could not rebuild: bc=%s, isFinished=%s"
200 % (bc
, b
.isFinished()))
201 # TODO: indicate an error
203 bc
.resubmitBuild(b
, reason
)
205 # http://localhost:8080/builders/NAME/builds/5/rebuild?[args]
206 # Where should we send them?
208 # Ideally it would be to the per-build page that they just started,
209 # but we don't know the build number for it yet (besides, it might
210 # have to wait for a current build to finish). The next-most
211 # preferred place is somewhere that the user can see tangible
212 # evidence of their build starting (or to see the reason that it
213 # didn't start). This could either be the Builder page, or the
215 #r = Redirect("../../../..") # this takes us back to the welcome page
216 #r = Redirect("../../../../waterfall") # or the Waterfall
217 #r = Redirect("../../../../waterfall?show=%s" % builder_name)
218 r
= Redirect("../..") # the Builder's page
220 reactor
.callLater(1, d
.callback
, r
)
221 return DeferredResource(d
)
223 def getChild(self
, path
, req
):
225 return self
.stop(req
)
226 if path
== "rebuild":
227 return self
.rebuild(req
)
229 return StepsResource(self
.build_status
)
231 return TestsResource(self
.build_status
)
233 return HtmlResource
.getChild(self
, path
, req
)
235 class BuildsResource(HtmlResource
):
238 def __init__(self
, builder_status
, builder_control
):
239 HtmlResource
.__init
__(self
)
240 self
.builder_status
= builder_status
241 self
.builder_control
= builder_control
243 def getChild(self
, path
, req
):
249 build_status
= self
.builder_status
.getBuild(num
)
252 if self
.builder_control
:
253 builder_control
= self
.builder_control
.getBuild(num
)
254 return StatusResourceBuild(build_status
, build_control
,
255 self
.builder_control
)
257 return HtmlResource
.getChild(self
, path
, req
)