Package buildbot :: Package status :: Package web :: Module grid
[hide private]
[frames] | no frames]

Source Code for Module buildbot.status.web.grid

  1  from __future__ import generators 
  2   
  3  import sys, time, os.path 
  4  import urllib 
  5   
  6  from buildbot import util 
  7  from buildbot import version 
  8  from buildbot.status.web.base import HtmlResource 
  9  #from buildbot.status.web.base import Box, HtmlResource, IBox, ICurrentBox, \ 
 10  #     ITopBox, td, build_get_class, path_to_build, path_to_step, map_branches 
 11  from buildbot.status.web.base import build_get_class 
 12   
 13  # set grid_css to the full pathname of the css file 
 14  if hasattr(sys, "frozen"): 
 15      # all 'data' files are in the directory of our executable 
 16      here = os.path.dirname(sys.executable) 
 17      grid_css = os.path.abspath(os.path.join(here, "grid.css")) 
 18  else: 
 19      # running from source; look for a sibling to __file__ 
 20      up = os.path.dirname 
 21      grid_css = os.path.abspath(os.path.join(up(__file__), "grid.css")) 
 22   
23 -class ANYBRANCH: pass # a flag value, used below
24
25 -class GridStatusMixin(object):
26 - def getTitle(self, request):
27 status = self.getStatus(request) 28 p = status.getProjectName() 29 if p: 30 return "BuildBot: %s" % p 31 else: 32 return "BuildBot"
33
34 - def getChangemaster(self, request):
35 # TODO: this wants to go away, access it through IStatus 36 return request.site.buildbot_service.getChangeSvc()
37 38 # handle reloads through an http header 39 # TODO: send this as a real header, rather than a tag
40 - def get_reload_time(self, request):
41 if "reload" in request.args: 42 try: 43 reload_time = int(request.args["reload"][0]) 44 return max(reload_time, 15) 45 except ValueError: 46 pass 47 return None
48
49 - def head(self, request):
50 head = '' 51 reload_time = self.get_reload_time(request) 52 if reload_time is not None: 53 head += '<meta http-equiv="refresh" content="%d">\n' % reload_time 54 return head
55 56 # def setBuildmaster(self, buildmaster): 57 # self.status = buildmaster.getStatus() 58 # if self.allowForce: 59 # self.control = interfaces.IControl(buildmaster) 60 # else: 61 # self.control = None 62 # self.changemaster = buildmaster.change_svc 63 # 64 # # try to set the page title 65 # p = self.status.getProjectName() 66 # if p: 67 # self.title = "BuildBot: %s" % p 68 #
69 - def build_td(self, request, build):
70 if not build: 71 return '<td class="build">&nbsp;</td>\n' 72 73 if build.isFinished(): 74 # get the text and annotate the first line with a link 75 text = build.getText() 76 if not text: text = [ "(no information)" ] 77 if text == [ "build", "successful" ]: text = [ "OK" ] 78 else: 79 text = [ 'building' ] 80 81 name = build.getBuilder().getName() 82 number = build.getNumber() 83 url = "builders/%s/builds/%d" % (name, number) 84 text[0] = '<a href="%s">%s</a>' % (url, text[0]) 85 text = '<br />\n'.join(text) 86 class_ = build_get_class(build) 87 88 return '<td class="build %s">%s</td>\n' % (class_, text)
89
90 - def builder_td(self, request, builder):
91 state, builds = builder.getState() 92 93 # look for upcoming builds. We say the state is "waiting" if the 94 # builder is otherwise idle and there is a scheduler which tells us a 95 # build will be performed some time in the near future. TODO: this 96 # functionality used to be in BuilderStatus.. maybe this code should 97 # be merged back into it. 98 upcoming = [] 99 builderName = builder.getName() 100 for s in self.getStatus(request).getSchedulers(): 101 if builderName in s.listBuilderNames(): 102 upcoming.extend(s.getPendingBuildTimes()) 103 if state == "idle" and upcoming: 104 state = "waiting" 105 106 # TODO: for now, this pending/upcoming stuff is in the "current 107 # activity" box, but really it should go into a "next activity" row 108 # instead. The only times it should show up in "current activity" is 109 # when the builder is otherwise idle. 110 111 # are any builds pending? (waiting for a slave to be free) 112 url = 'builders/%s/' % urllib.quote(builder.getName(), safe='') 113 text = '<a href="%s">%s</a>' % (url, builder.getName()) 114 pbs = builder.getPendingBuilds() 115 if state != 'idle' or pbs: 116 if pbs: 117 text += "<br />(%s with %d pending)" % (state, len(pbs)) 118 else: 119 text += "<br />(%s)" % state 120 121 return '<td valign="center" class="builder %s">%s</td>\n' % \ 122 (state, text)
123
124 - def stamp_td(self, stamp):
125 text = stamp.getText() 126 return '<td valign="bottom" class="sourcestamp">%s</td>\n' % \ 127 "<br />".join(text)
128
129 - def getRecentSourcestamps(self, status, numBuilds, categories, branch):
130 """ 131 get a list of the most recent NUMBUILDS SourceStamp tuples, sorted 132 by the earliest start we've seen for them 133 """ 134 # TODO: use baseweb's getLastNBuilds? 135 sourcestamps = { } # { ss-tuple : earliest time } 136 for bn in status.getBuilderNames(): 137 builder = status.getBuilder(bn) 138 if categories and builder.category not in categories: 139 continue 140 build = builder.getBuild(-1) 141 while build: 142 ss = build.getSourceStamp(absolute=True) 143 start = build.getTimes()[0] 144 build = build.getPreviousBuild() 145 146 # skip un-started builds 147 if not start: continue 148 149 # skip non-matching branches 150 if branch != ANYBRANCH and ss.branch != branch: continue 151 152 sourcestamps[ss] = min(sourcestamps.get(ss, sys.maxint), start) 153 154 # now sort those and take the NUMBUILDS most recent 155 sourcestamps = sourcestamps.items() 156 sourcestamps.sort(lambda x, y: cmp(x[1], y[1])) 157 sourcestamps = map(lambda tup : tup[0], sourcestamps) 158 sourcestamps = sourcestamps[-numBuilds:] 159 160 return sourcestamps
161
162 -class GridStatusResource(HtmlResource, GridStatusMixin):
163 # TODO: docs 164 status = None 165 control = None 166 changemaster = None 167
168 - def __init__(self, allowForce=True, css=None):
169 HtmlResource.__init__(self) 170 171 self.allowForce = allowForce 172 self.css = css or grid_css
173 174
175 - def body(self, request):
176 """This method builds the regular grid display. 177 That is, build stamps across the top, build hosts down the left side 178 """ 179 180 # get url parameters 181 numBuilds = int(request.args.get("width", [5])[0]) 182 categories = request.args.get("category", []) 183 branch = request.args.get("branch", [ANYBRANCH])[0] 184 if branch == 'trunk': branch = None 185 186 # and the data we want to render 187 status = self.getStatus(request) 188 stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) 189 190 projectURL = status.getProjectURL() 191 projectName = status.getProjectName() 192 193 data = '<table class="Grid" border="0" cellspacing="0">\n' 194 data += '<tr>\n' 195 data += '<td class="title"><a href="%s">%s</a>' % (projectURL, projectName) 196 if categories: 197 if len(categories) > 1: 198 data += '\n<br /><b>Categories:</b><br/>%s' % ('<br/>'.join(categories)) 199 else: 200 data += '\n<br /><b>Category:</b> %s' % categories[0] 201 if branch != ANYBRANCH: 202 data += '\n<br /><b>Branch:</b> %s' % (branch or 'trunk') 203 data += '</td>\n' 204 for stamp in stamps: 205 data += self.stamp_td(stamp) 206 data += '</tr>\n' 207 208 sortedBuilderNames = status.getBuilderNames()[:] 209 sortedBuilderNames.sort() 210 for bn in sortedBuilderNames: 211 builds = [None] * len(stamps) 212 213 builder = status.getBuilder(bn) 214 if categories and builder.category not in categories: 215 continue 216 217 build = builder.getBuild(-1) 218 while build and None in builds: 219 ss = build.getSourceStamp(absolute=True) 220 for i in range(len(stamps)): 221 if ss == stamps[i] and builds[i] is None: 222 builds[i] = build 223 build = build.getPreviousBuild() 224 225 data += '<tr>\n' 226 data += self.builder_td(request, builder) 227 for build in builds: 228 data += self.build_td(request, build) 229 data += '</tr>\n' 230 231 data += '</table>\n' 232 233 # TODO: this stuff should be generated by a template of some sort 234 data += '<hr /><div class="footer">\n' 235 236 welcomeurl = self.path_to_root(request) + "index.html" 237 data += '[<a href="%s">welcome</a>]\n' % welcomeurl 238 data += "<br />\n" 239 240 data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>' 241 data += "-%s " % version 242 if projectName: 243 data += "working for the " 244 if projectURL: 245 data += "<a href=\"%s\">%s</a> project." % (projectURL, 246 projectName) 247 else: 248 data += "%s project." % projectName 249 data += "<br />\n" 250 data += ("Page built: " + 251 time.strftime("%a %d %b %Y %H:%M:%S", 252 time.localtime(util.now())) 253 + "\n") 254 data += '</div>\n' 255 return data
256
257 -class TransposedGridStatusResource(HtmlResource, GridStatusMixin):
258 # TODO: docs 259 status = None 260 control = None 261 changemaster = None 262
263 - def __init__(self, allowForce=True, css=None):
264 HtmlResource.__init__(self) 265 266 self.allowForce = allowForce 267 self.css = css or grid_css
268 269
270 - def body(self, request):
271 """This method builds the transposed grid display. 272 That is, build hosts across the top, ebuild stamps down the left side 273 """ 274 275 # get url parameters 276 numBuilds = int(request.args.get("length", [5])[0]) 277 categories = request.args.get("category", []) 278 branch = request.args.get("branch", [ANYBRANCH])[0] 279 if branch == 'trunk': branch = None 280 281 # and the data we want to render 282 status = self.getStatus(request) 283 stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) 284 285 projectURL = status.getProjectURL() 286 projectName = status.getProjectName() 287 288 data = '<table class="Grid" border="0" cellspacing="0">\n' 289 data += '<tr>\n' 290 data += '<td class="title"><a href="%s">%s</a>' % (projectURL, projectName) 291 if categories: 292 if len(categories) > 1: 293 data += '\n<br /><b>Categories:</b><br/>%s' % ('<br/>'.join(categories)) 294 else: 295 data += '\n<br /><b>Category:</b> %s' % categories[0] 296 if branch != ANYBRANCH: 297 data += '\n<br /><b>Branch:</b> %s' % (branch or 'trunk') 298 data += '</td>\n' 299 300 sortedBuilderNames = status.getBuilderNames()[:] 301 sortedBuilderNames.sort() 302 303 builder_builds = {} 304 305 for bn in sortedBuilderNames: 306 builds = [None] * len(stamps) 307 308 builder = status.getBuilder(bn) 309 if categories and builder.category not in categories: 310 continue 311 312 build = builder.getBuild(-1) 313 while build and None in builds: 314 ss = build.getSourceStamp(absolute=True) 315 for i in range(len(stamps)): 316 if ss == stamps[i] and builds[i] is None: 317 builds[i] = build 318 build = build.getPreviousBuild() 319 320 data += self.builder_td(request, builder) 321 builder_builds[bn] = builds 322 323 data += '</tr>\n' 324 325 for i in range(len(stamps)): 326 data += '<tr>\n' 327 data += self.stamp_td(stamps[i]) 328 for bn in sortedBuilderNames: 329 data += self.build_td(request, builder_builds[bn][i]) 330 data += '</tr>\n' 331 332 data += '</table>\n' 333 334 # TODO: this stuff should be generated by a template of some sort 335 data += '<hr /><div class="footer">\n' 336 337 welcomeurl = self.path_to_root(request) + "index.html" 338 data += '[<a href="%s">welcome</a>]\n' % welcomeurl 339 data += "<br />\n" 340 341 data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>' 342 data += "-%s " % version 343 if projectName: 344 data += "working for the " 345 if projectURL: 346 data += "<a href=\"%s\">%s</a> project." % (projectURL, 347 projectName) 348 else: 349 data += "%s project." % projectName 350 data += "<br />\n" 351 data += ("Page built: " + 352 time.strftime("%a %d %b %Y %H:%M:%S", 353 time.localtime(util.now())) 354 + "\n") 355 data += '</div>\n' 356 return data
357