web: Builder/Build: use a relative link to the welcome page
[buildbot.git] / buildbot / status / web / build.py
blob05c96d74bcb2e7c66ec60fb6e066f0147fb03b49
2 from twisted.web import html
3 from twisted.web.util import Redirect, DeferredResource
4 from twisted.internet import defer, reactor
6 import urllib, time
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):
15 addSlash = True
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()))
28 def body(self, req):
29 b = self.build_status
30 status = self.getStatus(req)
31 projectName = status.getProjectName()
32 data = ('<div class="title"><a href="../../../..">%s</a></div>\n'
33 % projectName)
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>"
41 when = b.getETA()
42 if when is not None:
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"))
49 data += """
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" />
58 </form>
59 """
61 if b.isFinished():
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],
66 text)
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"
73 data += " <ul>\n"
74 if ss.branch:
75 data += " <li>Branch: %s</li>\n" % html.escape(ss.branch)
76 if ss.revision:
77 data += " <li>Revision: %s</li>\n" % html.escape(str(ss.revision))
78 if ss.patch:
79 data += " <li>Patch: YES</li>\n" # TODO: provide link to .diff
80 if ss.changes:
81 data += " <li>Changes: see below</li>\n"
82 if (ss.branch is None and ss.revision is None and ss.patch is None
83 and not ss.changes):
84 data += " <li>build of most recent revision</li>\n"
85 got_revision = None
86 try:
87 got_revision = b.getProperty("got_revision")
88 except KeyError:
89 pass
90 if 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
94 data += " </ul>\n"
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"
102 if b.getLogs():
103 data += "<ol>\n"
104 for s in b.getSteps():
105 name = s.getName()
106 data += (" <li><a href=\"%s\">%s</a> [%s]\n"
107 % (req.childLink("steps/%s" % urllib.quote(name)),
108 name,
109 " ".join(s.getText())))
110 if s.getLogs():
111 data += " <ol>\n"
112 for logfile in s.getLogs():
113 logname = logfile.getName()
114 logurl = req.childLink("steps/%s/logs/%s" %
115 (urllib.quote(name),
116 urllib.quote(logname)))
117 data += (" <li><a href=\"%s\">%s</a></li>\n" %
118 (logurl, logfile.getName()))
119 data += " </ol>\n"
120 data += " </li>\n"
121 data += "</ol>\n"
123 data += "<h2>Blamelist:</h2>\n"
124 if list(b.getResponsibleUsers()):
125 data += " <ol>\n"
126 for who in b.getResponsibleUsers():
127 data += " <li>%s</li>\n" % html.escape(who)
128 data += " </ol>\n"
129 else:
130 data += "<div>no responsible users</div>\n"
132 if ss.changes:
133 data += "<h2>All Changes</h2>\n"
134 data += "<ol>\n"
135 for c in ss.changes:
136 data += "<li>" + c.asHTML() + "</li>\n"
137 data += "</ol>\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()
145 if exactly:
146 data += ("<p>This tree was built from a specific set of \n"
147 "source files, and can be rebuilt exactly</p>\n")
148 else:
149 data += ("<p>This tree was built from the most recent "
150 "revision")
151 if ss.branch:
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'
159 % rebuildURL)
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'
165 data += '</form>\n'
167 return data
169 def stop(self, req):
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))
178 c.stopBuild(reason)
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
185 d = defer.Deferred()
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
202 else:
203 bc.resubmitBuild(b, reason)
204 # we're at
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
214 # waterfall.
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
219 d = defer.Deferred()
220 reactor.callLater(1, d.callback, r)
221 return DeferredResource(d)
223 def getChild(self, path, req):
224 if path == "stop":
225 return self.stop(req)
226 if path == "rebuild":
227 return self.rebuild(req)
228 if path == "steps":
229 return StepsResource(self.build_status)
230 if path == "tests":
231 return TestsResource(self.build_status)
233 return HtmlResource.getChild(self, path, req)
235 class BuildsResource(HtmlResource):
236 addSlash = True
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):
244 try:
245 num = int(path)
246 except ValueError:
247 num = None
248 if num is not None:
249 build_status = self.builder_status.getBuild(num)
250 if build_status:
251 build_control = None
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)