1
2 import urlparse, urllib, time
3 from zope.interface import Interface
4 from twisted.web import html, resource
5 from buildbot.status import builder
6 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION
7 from buildbot import version, util
8
10 """I represent a box in the top row of the waterfall display: the one
11 which shows the status of the last build for each builder."""
13 """Return a Box instance, which can produce a <td> cell.
14 """
15
17 """I represent the 'current activity' box, just above the builder name."""
19 """Return a Box instance, which can produce a <td> cell.
20 """
21
22 -class IBox(Interface):
23 """I represent a box in the waterfall display."""
25 """Return a Box instance, which wraps an Event and can produce a <td>
26 cell.
27 """
28
31
32 css_classes = {SUCCESS: "success",
33 WARNINGS: "warnings",
34 FAILURE: "failure",
35 SKIPPED: "skipped",
36 EXCEPTION: "exception",
37 }
38
39 ROW_TEMPLATE = '''
40 <div class="row">
41 <span class="label">%(label)s</span>
42 <span class="field">%(field)s</span>
43 </div>
44 '''
45
47 """Create a name/value row for the HTML.
48
49 `label` is plain text; it will be HTML-encoded.
50
51 `field` is a bit of HTML structure; it will not be encoded in
52 any way.
53 """
54 label = html.escape(label)
55 return ROW_TEMPLATE % {"label": label, "field": field}
56
71
86
105
106 -def td(text="", parms={}, **props):
107 data = ""
108 data += " "
109
110
111 props.update(parms)
112 comment = props.get("comment", None)
113 if comment:
114 data += "<!-- %s -->" % comment
115 data += "<td"
116 class_ = props.get('class_', None)
117 if class_:
118 props["class"] = class_
119 for prop in ("align", "colspan", "rowspan", "border",
120 "valign", "halign", "class"):
121 p = props.get(prop, None)
122 if p != None:
123 data += " %s=\"%s\"" % (prop, p)
124 data += ">"
125 if not text:
126 text = " "
127 if isinstance(text, list):
128 data += "<br />".join(text)
129 else:
130 data += text
131 data += "</td>\n"
132 return data
133
135 """
136 Return the class to use for a finished build or buildstep,
137 based on the result.
138 """
139
140 result = b.getResults()
141
142 if isinstance(b, builder.BuildStatus):
143 result = b.getResults()
144 elif isinstance(b, builder.BuildStepStatus):
145 result = b.getResults()[0]
146
147 if isinstance(result, tuple):
148 result = result[0]
149 else:
150 raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b
151
152 if result == None:
153
154 return "running"
155 return builder.Results[result]
156
158
159
160
161
162 if request.prepath:
163 segs = len(request.prepath) - 1
164 else:
165 segs = 0
166 root = "../" * segs
167 return root
168
170 return (path_to_root(request) +
171 "builders/" +
172 urllib.quote(builderstatus.getName(), safe=''))
173
177
181
186
190
192
193
194
195 spacer = False
196 - def __init__(self, text=[], class_=None, urlbase=None,
197 **parms):
198 self.text = text
199 self.class_ = class_
200 self.urlbase = urlbase
201 self.show_idle = 0
202 if parms.has_key('show_idle'):
203 del parms['show_idle']
204 self.show_idle = 1
205
206 self.parms = parms
207
208
209
210 - def td(self, **props):
216
217
219
220 contentType = "text/html; charset=UTF-8"
221 title = "Buildbot"
222 addSlash = False
223
225 if self.addSlash and path == "" and len(request.postpath) == 0:
226 return self
227 return resource.Resource.getChild(self, path, request)
228
230
231
232
233
234
235 if hasattr(request, "channel"):
236
237 request.site.buildbot_service.registerChannel(request.channel)
238
239
240
241
242
243 if False and self.addSlash and request.prepath[-1] != '':
244
245
246
247
248
249
250
251
252
253
254
255 url = request.prePathURL()
256 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
257 new_url = request.prepath[-1] + "/"
258 if query:
259 new_url += "?" + query
260 request.redirect(new_url)
261 return ''
262
263 data = self.content(request)
264 if isinstance(data, unicode):
265 data = data.encode("utf-8")
266 request.setHeader("content-type", self.contentType)
267 if request.method == "HEAD":
268 request.setHeader("content-length", len(data))
269 return ''
270 return data
271
273 return request.site.buildbot_service.getStatus()
274
276 return request.site.buildbot_service.getControl()
277
280
282 user = request.args.get("username", ["<unknown>"])[0]
283 passwd = request.args.get("passwd", ["<no-password>"])[0]
284 if user == "<unknown>" or passwd == "<no-password>":
285 return False
286 return request.site.buildbot_service.authUser(user, passwd)
287
290
293
321
324
326 s = request.site.buildbot_service
327 values = s.template_values.copy()
328 values['root'] = self.path_to_root(request)
329
330
331 values['title'] = self.getTitle(request)
332 return template % values
333
334 - def content(self, request):
335 s = request.site.buildbot_service
336 data = ""
337 data += self.fillTemplate(s.header, request)
338 data += "<head>\n"
339 for he in s.head_elements:
340 data += " " + self.fillTemplate(he, request) + "\n"
341 data += self.head(request)
342 data += "</head>\n\n"
343
344 data += '<body %s>\n' % " ".join(['%s="%s"' % (k,v)
345 for (k,v) in s.body_attrs.items()])
346 data += self.body(request)
347 data += "</body>\n"
348 data += self.fillTemplate(s.footer, request)
349 return data
350
351 - def head(self, request):
353
354 - def body(self, request):
356
362 - def body(self, request):
364
365 MINUTE = 60
366 HOUR = 60*MINUTE
367 DAY = 24*HOUR
368 WEEK = 7*DAY
369 MONTH = 30*DAY
370
372 if int(num) == 1:
373 return "%d %s" % (num, word)
374 else:
375 return "%d %s" % (num, words)
376
378 if age <= 90:
379 return "%s ago" % plural("second", "seconds", age)
380 if age < 90*MINUTE:
381 return "about %s ago" % plural("minute", "minutes", age / MINUTE)
382 if age < DAY:
383 return "about %s ago" % plural("hour", "hours", age / HOUR)
384 if age < 2*WEEK:
385 return "about %s ago" % plural("day", "days", age / DAY)
386 if age < 2*MONTH:
387 return "about %s ago" % plural("week", "weeks", age / WEEK)
388 return "a long time ago"
389
390
392 LINE_TIME_FORMAT = "%b %d %H:%M"
393
395 '''
396 Collect the data needed for each line display
397 '''
398 builder_name = build.getBuilder().getName()
399 results = build.getResults()
400 text = build.getText()
401 try:
402 rev = build.getProperty("got_revision")
403 if rev is None:
404 rev = "??"
405 except KeyError:
406 rev = "??"
407 rev = str(rev)
408 if len(rev) > 40:
409 rev = "version is too-long"
410 root = self.path_to_root(req)
411 css_class = css_classes.get(results, "")
412 values = {'class': css_class,
413 'builder_name': builder_name,
414 'buildnum': build.getNumber(),
415 'results': css_class,
416 'text': " ".join(build.getText()),
417 'buildurl': path_to_build(req, build),
418 'builderurl': path_to_builder(req, build.getBuilder()),
419 'rev': rev,
420 'time': time.strftime(self.LINE_TIME_FORMAT,
421 time.localtime(build.getTimes()[0])),
422 }
423 return values
424
425 - def make_line(self, req, build, include_builder=True):
426 '''
427 Format and render a single line into HTML
428 '''
429 values = self.get_line_values(req, build)
430 fmt_pieces = ['<font size="-1">(%(time)s)</font>',
431 'rev=[%(rev)s]',
432 '<span class="%(class)s">%(results)s</span>',
433 ]
434 if include_builder:
435 fmt_pieces.append('<a href="%(builderurl)s">%(builder_name)s</a>')
436 fmt_pieces.append('<a href="%(buildurl)s">#%(buildnum)d</a>:')
437 fmt_pieces.append('%(text)s')
438 data = " ".join(fmt_pieces) % values
439 return data
440
442
443
444
445
446
447 if "trunk" in branches:
448 return branches + [None]
449 return branches
450