2 from zope
.interface
import Interface
3 from twisted
.web
import html
, resource
4 from buildbot
.status
import builder
5 from buildbot
.status
.builder
import SUCCESS
, WARNINGS
, FAILURE
, EXCEPTION
8 class ITopBox(Interface
):
9 """I represent a box in the top row of the waterfall display: the one
10 which shows the status of the last build for each builder."""
13 class ICurrentBox(Interface
):
14 """I represent the 'current activity' box, just above the builder name."""
17 class IBox(Interface
):
18 """I represent a box in the waterfall display."""
21 class IHTMLLog(Interface
):
24 css_classes
= {SUCCESS
: "success",
27 EXCEPTION
: "exception",
32 <span class="label">%(label)s</span>
33 <span class="field">%(field)s</span>
37 def make_row(label
, field
):
38 """Create a name/value row for the HTML.
40 `label` is plain text; it will be HTML-encoded.
42 `field` is a bit of HTML structure; it will not be encoded in
45 label
= html
.escape(label
)
46 return ROW_TEMPLATE
% {"label": label
, "field": field
}
51 def td(text
="", parms
={}, **props
):
54 #if not props.has_key("border"):
57 if props
.has_key("bgcolor"):
58 props
["bgcolor"] = colormap
.get(props
["bgcolor"], props
["bgcolor"])
59 comment
= props
.get("comment", None)
61 data
+= "<!-- %s -->" % comment
63 class_
= props
.get('class_', None)
65 props
["class"] = class_
66 for prop
in ("align", "bgcolor", "colspan", "rowspan", "border",
67 "valign", "halign", "class"):
68 p
= props
.get(prop
, None)
70 data
+= " %s=\"%s\"" % (prop
, p
)
74 if isinstance(text
, list):
75 data
+= "<br />".join(text
)
81 def build_get_class(b
):
83 Return the class to use for a finished build or buildstep,
86 # FIXME: this getResults duplicity might need to be fixed
87 result
= b
.getResults()
88 #print "THOMAS: result for b %r: %r" % (b, result)
89 if isinstance(b
, builder
.BuildStatus
):
90 result
= b
.getResults()
91 elif isinstance(b
, builder
.BuildStepStatus
):
92 result
= b
.getResults()[0]
93 # after forcing a build, b.getResults() returns ((None, []), []), ugh
94 if isinstance(result
, tuple):
97 raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b
100 # FIXME: this happens when a buildstep is running ?
102 return builder
.Results
[result
]
105 # a Box wraps an Event. The Box has HTML <td> parameters that Events
106 # lack, and it has a base URL to which each File's name is relative.
107 # Events don't know about HTML.
109 def __init__(self
, text
=[], color
=None, class_
=None, urlbase
=None,
114 self
.urlbase
= urlbase
116 if parms
.has_key('show_idle'):
117 del parms
['show_idle']
121 # parms is a dict of HTML parameters for the <td> element that will
122 # represent this Event in the waterfall display.
124 def td(self
, **props
):
125 props
.update(self
.parms
)
127 if not text
and self
.show_idle
:
129 return td(text
, props
, bgcolor
=self
.color
, class_
=self
.class_
)
132 class HtmlResource(resource
.Resource
):
133 # this is a cheap sort of template thingy
134 contentType
= "text/html; charset=UTF-8"
136 addSlash
= False # adapted from Nevow
138 def getChild(self
, path
, request
):
139 if self
.addSlash
and path
== "" and len(request
.postpath
) == 0:
141 return resource
.Resource
.getChild(self
, path
, request
)
143 def render(self
, request
):
144 if self
.addSlash
and request
.prepath
[-1] != '':
145 request
.redirect(request
.URLPath().child(''))
148 data
= self
.content(request
)
149 if isinstance(data
, unicode):
150 data
= data
.encode("utf-8")
151 request
.setHeader("content-type", self
.contentType
)
152 if request
.method
== "HEAD":
153 request
.setHeader("content-length", len(data
))
157 def getStatus(self
, request
):
158 return request
.site
.buildbot_service
.getStatus()
159 def getControl(self
, request
):
160 return request
.site
.buildbot_service
.getControl()
162 def getChangemaster(self
, request
):
163 return request
.site
.buildbot_service
.parent
.change_svc
165 def path_to_root(self
, request
):
166 # /waterfall : ['waterfall'] -> ''
167 # /somewhere/lower : ['somewhere', 'lower'] -> '../'
168 # /somewhere/indexy/ : ['somewhere', 'indexy', ''] -> '../../'
171 segs
= len(request
.prepath
) - 1
177 def getTitle(self
, request
):
180 def fillTemplate(self
, template
, request
):
181 s
= request
.site
.buildbot_service
182 values
= s
.template_values
.copy()
183 values
['root'] = self
.path_to_root(request
)
184 # e.g. to reference the top-level 'buildbot.css' page, use
185 # "%(root)sbuildbot.css"
186 values
['title'] = self
.getTitle(request
)
187 return template
% values
189 def content(self
, request
):
190 s
= request
.site
.buildbot_service
192 data
+= self
.fillTemplate(s
.header
, request
)
194 for he
in s
.head_elements
:
195 data
+= " " + self
.fillTemplate(he
, request
) + "\n"
196 data
+= self
.head(request
)
197 data
+= "</head>\n\n"
199 data
+= '<body %s>\n' % " ".join(['%s="%s"' % (k
,v
)
200 for (k
,v
) in s
.body_attrs
.items()])
201 data
+= self
.body(request
)
203 data
+= self
.fillTemplate(s
.footer
, request
)
206 def head(self
, request
):
209 def body(self
, request
):
212 class StaticHTML(HtmlResource
):
213 def __init__(self
, body
, title
):
214 HtmlResource
.__init
__(self
)
217 def body(self
, request
):
226 def plural(word
, words
, num
):
228 return "%d %s" % (num
, word
)
230 return "%d %s" % (num
, words
)
232 def abbreviate_age(age
):
234 return "%s ago" % plural("second", "seconds", age
)
236 return "about %s ago" % plural("minute", "minutes", age
/ MINUTE
)
238 return "about %s ago" % plural("hour", "hours", age
/ HOUR
)
240 return "about %s ago" % plural("day", "days", age
/ DAY
)
242 return "about %s ago" % plural("week", "weeks", age
/ WEEK
)
243 return "a long time ago"