1
2 import urlparse, urllib, time, re
3 from zope.interface import Interface
4 from twisted.python import log
5 from twisted.web import html, resource
6 from buildbot.status import builder
7 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION
8 from buildbot import version, util
9 from buildbot.process.properties import Properties
10
12 """I represent a box in the top row of the waterfall display: the one
13 which shows the status of the last build for each builder."""
15 """Return a Box instance, which can produce a <td> cell.
16 """
17
19 """I represent the 'current activity' box, just above the builder name."""
21 """Return a Box instance, which can produce a <td> cell.
22 """
23
24 -class IBox(Interface):
25 """I represent a box in the waterfall display."""
27 """Return a Box instance, which wraps an Event and can produce a <td>
28 cell.
29 """
30
33
34 css_classes = {SUCCESS: "success",
35 WARNINGS: "warnings",
36 FAILURE: "failure",
37 SKIPPED: "skipped",
38 EXCEPTION: "exception",
39 None: "",
40 }
41
42 ROW_TEMPLATE = '''
43 <div class="row">
44 <span class="label">%(label)s</span>
45 <span class="field">%(field)s</span>
46 </div>
47 '''
48
50 """Create a name/value row for the HTML.
51
52 `label` is plain text; it will be HTML-encoded.
53
54 `field` is a bit of HTML structure; it will not be encoded in
55 any way.
56 """
57 label = html.escape(label)
58 return ROW_TEMPLATE % {"label": label, "field": field}
59
74
89
91 """helper function to create the html for adding extra build
92 properties to a forced (or resubmitted) build. "N" is an integer
93 inserted into the form names so that more than one property can be
94 used in the form.
95 """
96 prop_html = '''
97 <div class="row">Property %(N)i
98 <span class="label">Name:</span>
99 <span class="field"><input type="text" name="property%(N)iname" /></span>
100 <span class="label">Value:</span>
101 <span class="field"><input type="text" name="property%(N)ivalue" /></span>
102 </div>
103 ''' % {"N": N}
104 return prop_html
105
127
129 """
130 Fetch custom build properties from the HTTP request of a "Force build" or
131 "Resubmit build" HTML form.
132 Check the names for valid strings, and return None if a problem is found.
133 Return a new Properties object containing each property found in req.
134 """
135 properties = Properties()
136 for i in (1,2,3):
137 pname = req.args.get("property%dname" % i, [""])[0]
138 pvalue = req.args.get("property%dvalue" % i, [""])[0]
139 if pname and pvalue:
140 if not re.match(r'^[\w\.\-\/\~:]*$', pname) \
141 or not re.match(r'^[\w\.\-\/\~:]*$', pvalue):
142 log.msg("bad property name='%s', value='%s'" % (pname, pvalue))
143 return None
144 properties.setProperty(pname, pvalue, "Force Build Form")
145 return properties
146
147 -def td(text="", parms={}, **props):
148 data = ""
149 data += " "
150
151
152 props.update(parms)
153 comment = props.get("comment", None)
154 if comment:
155 data += "<!-- %s -->" % comment
156 data += "<td"
157 class_ = props.get('class_', None)
158 if class_:
159 props["class"] = class_
160 for prop in ("align", "colspan", "rowspan", "border",
161 "valign", "halign", "class"):
162 p = props.get(prop, None)
163 if p != None:
164 data += " %s=\"%s\"" % (prop, p)
165 data += ">"
166 if not text:
167 text = " "
168 if isinstance(text, list):
169 data += "<br />".join(text)
170 else:
171 data += text
172 data += "</td>\n"
173 return data
174
176 """
177 Return the class to use for a finished build or buildstep,
178 based on the result.
179 """
180
181 result = b.getResults()
182
183 if isinstance(b, builder.BuildStatus):
184 result = b.getResults()
185 elif isinstance(b, builder.BuildStepStatus):
186 result = b.getResults()[0]
187
188 if isinstance(result, tuple):
189 result = result[0]
190 else:
191 raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b
192
193 if result == None:
194
195 return "running"
196 return builder.Results[result]
197
199
200
201
202
203 if request.prepath:
204 segs = len(request.prepath) - 1
205 else:
206 segs = 0
207 root = "../" * segs
208 return root
209
211 return (path_to_root(request) +
212 "builders/" +
213 urllib.quote(builderstatus.getName(), safe=''))
214
218
222
227
231
233
234
235
236 spacer = False
237 - def __init__(self, text=[], class_=None, urlbase=None,
238 **parms):
239 self.text = text
240 self.class_ = class_
241 self.urlbase = urlbase
242 self.show_idle = 0
243 if parms.has_key('show_idle'):
244 del parms['show_idle']
245 self.show_idle = 1
246
247 self.parms = parms
248
249
250
251 - def td(self, **props):
257
258
260
261 contentType = "text/html; charset=UTF-8"
262 title = "Buildbot"
263 addSlash = False
264
266 if self.addSlash and path == "" and len(request.postpath) == 0:
267 return self
268 return resource.Resource.getChild(self, path, request)
269
271
272
273
274
275
276 if hasattr(request, "channel"):
277
278 request.site.buildbot_service.registerChannel(request.channel)
279
280
281
282
283
284 if False and self.addSlash and request.prepath[-1] != '':
285
286
287
288
289
290
291
292
293
294
295
296 url = request.prePathURL()
297 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
298 new_url = request.prepath[-1] + "/"
299 if query:
300 new_url += "?" + query
301 request.redirect(new_url)
302 return ''
303
304 data = self.content(request)
305 if isinstance(data, unicode):
306 data = data.encode("utf-8")
307 request.setHeader("content-type", self.contentType)
308 if request.method == "HEAD":
309 request.setHeader("content-length", len(data))
310 return ''
311 return data
312
314 return request.site.buildbot_service.getStatus()
315
317 return request.site.buildbot_service.getControl()
318
321
323 user = request.args.get("username", ["<unknown>"])[0]
324 passwd = request.args.get("passwd", ["<no-password>"])[0]
325 if user == "<unknown>" or passwd == "<no-password>":
326 return False
327 return request.site.buildbot_service.authUser(user, passwd)
328
331
334
362
365
367 s = request.site.buildbot_service
368 values = s.template_values.copy()
369 values['root'] = self.path_to_root(request)
370
371
372 values['title'] = self.getTitle(request)
373 return template % values
374
375 - def content(self, request):
376 s = request.site.buildbot_service
377 data = ""
378 data += self.fillTemplate(s.header, request)
379 data += "<head>\n"
380 for he in s.head_elements:
381 data += " " + self.fillTemplate(he, request) + "\n"
382 data += self.head(request)
383 data += "</head>\n\n"
384
385 data += '<body %s>\n' % " ".join(['%s="%s"' % (k,v)
386 for (k,v) in s.body_attrs.items()])
387 data += self.body(request)
388 data += "</body>\n"
389 data += self.fillTemplate(s.footer, request)
390 return data
391
392 - def head(self, request):
394
395 - def body(self, request):
397
403 - def body(self, request):
405
406 MINUTE = 60
407 HOUR = 60*MINUTE
408 DAY = 24*HOUR
409 WEEK = 7*DAY
410 MONTH = 30*DAY
411
413 if int(num) == 1:
414 return "%d %s" % (num, word)
415 else:
416 return "%d %s" % (num, words)
417
419 if age <= 90:
420 return "%s ago" % plural("second", "seconds", age)
421 if age < 90*MINUTE:
422 return "about %s ago" % plural("minute", "minutes", age / MINUTE)
423 if age < DAY:
424 return "about %s ago" % plural("hour", "hours", age / HOUR)
425 if age < 2*WEEK:
426 return "about %s ago" % plural("day", "days", age / DAY)
427 if age < 2*MONTH:
428 return "about %s ago" % plural("week", "weeks", age / WEEK)
429 return "a long time ago"
430
431
433 LINE_TIME_FORMAT = "%b %d %H:%M"
434
436 '''
437 Collect the data needed for each line display
438 '''
439 builder_name = build.getBuilder().getName()
440 results = build.getResults()
441 text = build.getText()
442 try:
443 rev = build.getProperty("got_revision")
444 if rev is None:
445 rev = "??"
446 except KeyError:
447 rev = "??"
448 rev = str(rev)
449 if len(rev) > 40:
450 rev = "version is too-long"
451 root = self.path_to_root(req)
452 css_class = css_classes.get(results, "")
453 values = {'class': css_class,
454 'builder_name': builder_name,
455 'buildnum': build.getNumber(),
456 'results': css_class,
457 'text': " ".join(build.getText()),
458 'buildurl': path_to_build(req, build),
459 'builderurl': path_to_builder(req, build.getBuilder()),
460 'rev': rev,
461 'time': time.strftime(self.LINE_TIME_FORMAT,
462 time.localtime(build.getTimes()[0])),
463 }
464 return values
465
466 - def make_line(self, req, build, include_builder=True):
467 '''
468 Format and render a single line into HTML
469 '''
470 values = self.get_line_values(req, build)
471 fmt_pieces = ['<font size="-1">(%(time)s)</font>',
472 'rev=[%(rev)s]',
473 '<span class="%(class)s">%(results)s</span>',
474 ]
475 if include_builder:
476 fmt_pieces.append('<a href="%(builderurl)s">%(builder_name)s</a>')
477 fmt_pieces.append('<a href="%(buildurl)s">#%(buildnum)d</a>:')
478 fmt_pieces.append('%(text)s')
479 data = " ".join(fmt_pieces) % values
480 return data
481
483
484
485
486
487
488 if "trunk" in branches:
489 return branches + [None]
490 return branches
491