def body(self, req): status = self.getStatus(req) control = self.getControl(req) builders = req.args.get("builder", status.getBuilderNames()) branches = [b for b in req.args.get("branch", []) if b] data = "" data += "<h2>Latest builds: %s</h2>\n" % ", ".join(branches) data += "<table>\n" building = False online = 0 base_builders_url = self.path_to_root(req) + "builders/" for bn in builders: base_builder_url = base_builders_url + urllib.quote(bn, safe='') builder = status.getBuilder(bn) data += "<tr>\n" data += '<td class="box"><a href="%s">%s</a></td>\n' \ % (base_builder_url, html.escape(bn)) builds = list(builder.generateFinishedBuilds(map_branches(branches), num_builds=1)) if builds: b = builds[0] url = (base_builder_url + "/builds/%d" % b.getNumber()) try: label = b.getProperty("got_revision") except KeyError: label = None if not label or len(str(label)) > 20: label = "#%d" % b.getNumber() text = ['<a href="%s">%s</a>' % (url, label)] text.extend(b.getText()) box = Box(text, b.getColor(), class_="LastBuild box %s" % build_get_class(b)) data += box.td(align="center") else: data += '<td class="LastBuild box" >no build</td>\n' current_box = ICurrentBox(builder).getBox(status) data += current_box.td(align="center") builder_status = builder.getState()[0] if builder_status == "building": building = True online += 1 elif builder_status != "offline": online += 1 data += "</table>\n" if control is not None: if building: stopURL = "builders/_all/stop" data += make_stop_form(stopURL, True, "Builds") if online: forceURL = "builders/_all/force" data += make_force_build_form(forceURL, True) return data
def getBox(self, req): assert interfaces.IBuilderStatus(self.original) branches = [b for b in req.args.get("branch", []) if b] builder = self.original builds = list(builder.generateFinishedBuilds(map_branches(branches), num_builds=1)) if not builds: return Box(["none"], class_="LastBuild") b = builds[0] url = path_to_build(req, b) text = b.getText() tests_failed = b.getSummaryStatistic('tests-failed', operator.add, 0) if tests_failed: text.extend(["Failed tests: %d" % tests_failed]) # TODO: maybe add logs? class_ = build_get_class(b) return Box(text, urlbase=url, class_="LastBuild %s" % class_)
def getBox(self, req): urlbase = path_to_step(req, self.original) text = self.original.getText() if text is None: log.msg("getText() gave None", urlbase) text = [] text = text[:] def native_link_fn(l): if l.hasContents(): url = urlbase + "/logs/%s" % urllib.quote(l.getName()) else: url = None return url logs, urls, _ = getStepLogsURLsAndAliases( self.original, False, native_link_fn, ) cxt = dict(text=text, logs=logs, urls=urls) template = req.site.buildbot_service.templates.get_template( "box_macros.html") text = template.module.step_box(**unicodify(cxt)) class_ = "BuildStep " + build_get_class(self.original) return Box(text, class_=class_, buildNumber=self.original.build_number, builder=self.original.builder.getName(), stepName=self.original.getName())
def getBox(self, req): url = req.childLink("../changes/%d" % self.original.number) template = req.site.buildbot_service.templates.get_template("change_macros.html") text = template.module.box_contents(url=url, who=self.original.getShortAuthor(), pageTitle=self.original.comments) return Box([text], class_="Change")
def getBox(self, req): text = self.original.getText() color = self.original.getColor() class_ = "Event" if color: class_ += " " + color return Box(text, color, class_=class_)
def getBox(self, req): urlbase = path_to_step(req, self.original) text = self.original.getText() if text is None: log.msg("getText() gave None", urlbase) text = [] text = text[:] logs = self.original.getLogs() cxt = dict(text=text, logs=[], urls=[]) for num in range(len(logs)): name = logs[num].getName() if logs[num].hasContents(): url = urlbase + "/logs/%s" % urllib.quote(name) else: url = None cxt['logs'].append(dict(name=name, url=url)) for name, target in self.original.getURLs().items(): cxt['urls'].append(dict(link=target,name=name)) template = req.site.buildbot_service.templates.get_template("box_macros.html") text = template.module.step_box(**cxt) class_ = "BuildStep " + build_get_class(self.original) return Box(text, class_=class_)
def getBox(self, req): assert interfaces.IBuilderStatus(self.original) branches = [b for b in req.args.get("branch", []) if b] builder = self.original builds = list( builder.generateFinishedBuilds(map_branches(branches), num_builds=1)) if not builds: return Box(["none"], "white", class_="LastBuild") b = builds[0] name = b.getBuilder().getName() number = b.getNumber() url = path_to_build(req, b) text = b.getText() # TODO: maybe add logs? # TODO: add link to the per-build page at 'url' c = b.getColor() class_ = build_get_class(b) return Box(text, c, class_="LastBuild %s" % class_)
def getBox(self, status, brcounts): # getState() returns offline, idle, or building state, builds = self.original.getState() # look for upcoming builds. We say the state is "waiting" if the # builder is otherwise idle and there is a scheduler which tells us a # build will be performed some time in the near future. TODO: this # functionality used to be in BuilderStatus.. maybe this code should # be merged back into it. upcoming = [] builderName = self.original.getName() for s in status.getSchedulers(): if builderName in s.listBuilderNames(): upcoming.extend(s.getPendingBuildTimes()) if state == "idle" and upcoming: state = "waiting" if state == "building": text = ["building"] if builds: for b in builds: eta = b.getETA() if len(builds) <= 2: # Show more information if there's only one or two # builds. started = time.localtime(b.getTimes()[0]) text.extend( ["start:", time.strftime("%H:%M", started)]) text.extend(self.formatETA("ETA in", eta)) elif state == "offline": text = ["offline"] elif state == "idle": text = ["idle"] elif state == "waiting": text = ["waiting"] else: # just in case I add a state and forget to update this text = [state] # TODO: for now, this pending/upcoming stuff is in the "current # activity" box, but really it should go into a "next activity" row # instead. The only times it should show up in "current activity" is # when the builder is otherwise idle. # are any builds pending? (waiting for a slave to be free) brcount = brcounts[builderName] if brcount: text.append("%d pending" % brcount) for t in upcoming: if t is not None: eta = t - util.now() text.extend(self.formatETA("next in", eta)) return Box(text, class_="Activity " + state)
def getBox(self, req): b = self.original number = b.getNumber() url = path_to_build(req, b) reason = b.getReason() template = req.site.buildbot_service.templates.get_template("box_macros.html") text = template.module.build_box(reason=reason,url=url,number=number) class_ = "start" if b.isFinished() and not b.getSteps(): # the steps have been pruned, so there won't be any indication # of whether it succeeded or failed. class_ = build_get_class(b) return Box([text], class_="BuildStep " + class_)
def getBox(self, req): b = self.original number = b.getNumber() url = path_to_build(req, b) reason = b.getReason() text = ('<a title="Reason: %s" href="%s">Build %d</a>' % (html.escape(reason), url, number)) class_ = "start" if b.isFinished() and not b.getSteps(): # the steps have been pruned, so there won't be any indication # of whether it succeeded or failed. class_ = build_get_class(b) return Box([text], class_="BuildStep " + class_)
def getBox(self, status): # getState() returns offline, idle, or building state, builds = self.original.getState() # look for upcoming builds. We say the state is "waiting" if the # builder is otherwise idle and there is a scheduler which tells us a # build will be performed some time in the near future. TODO: this # functionality used to be in BuilderStatus.. maybe this code should # be merged back into it. upcoming = [] builderName = self.original.getName() for s in status.getSchedulers(): if builderName in s.listBuilderNames(): upcoming.extend(s.getPendingBuildTimes()) if state == "idle" and upcoming: state = "waiting" if state == "building": color = "yellow" text = ["building"] if builds: for b in builds: eta = b.getETA() text.extend(self.formatETA("ETA in", eta)) elif state == "offline": color = "red" text = ["offline"] elif state == "idle": color = "white" text = ["idle"] elif state == "waiting": color = "yellow" text = ["waiting"] else: # just in case I add a state and forget to update this color = "white" text = [state] # TODO: for now, this pending/upcoming stuff is in the "current # activity" box, but really it should go into a "next activity" row # instead. The only times it should show up in "current activity" is # when the builder is otherwise idle. # are any builds pending? (waiting for a slave to be free) pbs = self.original.getPendingBuilds() if pbs: text.append("%d pending" % len(pbs)) for t in upcoming: eta = t - util.now() text.extend(self.formatETA("next in", eta)) return Box(text, color=color, class_="Activity " + state)
def getBox(self, req): urlbase = path_to_step(req, self.original) text = self.original.getText() if text is None: log.msg("getText() gave None", urlbase) text = [] text = text[:] logs = self.original.getLogs() for num in range(len(logs)): name = logs[num].getName() if logs[num].hasContents(): url = urlbase + "/logs/%s" % urllib.quote(name) text.append("<a href=\"%s\">%s</a>" % (url, html.escape(name))) else: text.append(html.escape(name)) urls = self.original.getURLs() ex_url_class = "BuildStep external" for name, target in urls.items(): text.append('[<a href="%s" class="%s">%s</a>]' % (target, ex_url_class, html.escape(name))) class_ = "BuildStep " + build_get_class(self.original) return Box(text, class_=class_)
def getBox(self, req): b = self.original number = b.getNumber() url = path_to_build(req, b) reason = b.getReason() # Update the buildbot BuildStatus so that it displays the hg revision number as the build number changedesc = '(%d)' % number revision = b.getSourceStamp().revision if (len(b.changes) > 0): changedesc = '%s (%d)' % (b.changes[0].revision, number) if (revision): changedesc = "%s (%d)" % (revision, number) text = ('<a title="Reason: %s" href="%s">Build %s</a>' % (html.escape(reason), url, changedesc)) class_ = "start" if b.isFinished() and not b.getSteps(): # the steps have been pruned, so there won't be any indication # of whether it succeeded or failed. class_ = build_get_class(b) return Box([text], class_="BuildStep " + class_)
def body(self, req): status = self.getStatus(req) control = self.getControl(req) builders = req.args.get("builder", status.getBuilderNames()) branches = [b for b in req.args.get("branch", []) if b] data = "" data += "<h2>Latest builds: %s</h2>\n" % ", ".join(branches) data += "<table>\n" building = False online = 0 base_builders_url = self.path_to_root(req) + "builders/" for bn in builders: base_builder_url = base_builders_url + urllib.quote(bn, safe='') builder = status.getBuilder(bn) data += "<tr>\n" data += '<td class="box"><a href="%s">%s</a></td>\n' \ % (base_builder_url, html.escape(bn)) builds = list( builder.generateFinishedBuilds(map_branches(branches), num_builds=1)) if builds: b = builds[0] url = (base_builder_url + "/builds/%d" % b.getNumber()) try: label = b.getProperty("got_revision") except KeyError: label = None if not label or len(str(label)) > 20: label = "#%d" % b.getNumber() text = ['<a href="%s">%s</a>' % (url, label)] text.extend(b.getText()) box = Box(text, b.getColor(), class_="LastBuild box %s" % build_get_class(b)) data += box.td(align="center") else: data += '<td class="LastBuild box" >no build</td>\n' current_box = ICurrentBox(builder).getBox(status) data += current_box.td(align="center") builder_status = builder.getState()[0] if builder_status == "building": building = True online += 1 elif builder_status != "offline": online += 1 data += "</table>\n" if control is not None: if building: stopURL = "builders/_all/stop" data += make_stop_form(stopURL, True, "Builds") if online: forceURL = "builders/_all/force" data += make_force_build_form(forceURL, True) return data
def phase2(self, request, sourceNames, timestamps, eventGrid, sourceEvents): data = "" if not timestamps: return data # first pass: figure out the height of the chunks, populate grid grid = [] for i in range(1 + len(sourceNames)): grid.append([]) # grid is a list of columns, one for the timestamps, and one per # event source. Each column is exactly the same height. Each element # of the list is a single <td> box. lastDate = time.strftime("<b>%d %b %Y</b>", time.localtime(util.now())) for r in range(0, len(timestamps)): chunkstrip = eventGrid[r] # chunkstrip is a horizontal strip of event blocks. Each block # is a vertical list of events, all for the same source. assert (len(chunkstrip) == len(sourceNames)) maxRows = reduce(lambda x, y: max(x, y), map(lambda x: len(x), chunkstrip)) for i in range(maxRows): if i != maxRows - 1: grid[0].append(None) else: # timestamp goes at the bottom of the chunk stuff = [] # add the date at the beginning (if it is not the same as # today's date), and each time it changes todayday = time.strftime("<b>%a</b>", time.localtime(timestamps[r])) today = time.strftime("<b>%d %b %Y</b>", time.localtime(timestamps[r])) if today != lastDate: stuff.append(todayday) stuff.append(today) lastDate = today stuff.append( time.strftime("%H:%M:%S", time.localtime(timestamps[r]))) grid[0].append( Box(text=stuff, class_="Time", valign="bottom", align="center")) # at this point the timestamp column has been populated with # maxRows boxes, most None but the last one has the time string for c in range(0, len(chunkstrip)): block = chunkstrip[c] assert (block != None) # should be [] instead for i in range(maxRows - len(block)): # fill top of chunk with blank space grid[c + 1].append(None) for i in range(len(block)): # so the events are bottom-justified b = IBox(block[i]).getBox(request) b.parms['valign'] = "top" b.parms['align'] = "center" grid[c + 1].append(b) # now all the other columns have maxRows new boxes too # populate the last row, if empty gridlen = len(grid[0]) for i in range(len(grid)): strip = grid[i] assert (len(strip) == gridlen) if strip[-1] == None: if sourceEvents[i - 1]: filler = IBox(sourceEvents[i - 1]).getBox(request) else: # this can happen if you delete part of the build history filler = Box(text=["?"], align="center") strip[-1] = filler strip[-1].parms['rowspan'] = 1 # second pass: bubble the events upwards to un-occupied locations # Every square of the grid that has a None in it needs to have # something else take its place. noBubble = request.args.get("nobubble", ['0']) noBubble = int(noBubble[0]) if not noBubble: for col in range(len(grid)): strip = grid[col] if col == 1: # changes are handled differently for i in range(2, len(strip) + 1): # only merge empty boxes. Don't bubble commit boxes. if strip[-i] == None: next = strip[-i + 1] assert (next) if next: #if not next.event: if next.spacer: # bubble the empty box up strip[-i] = next strip[-i].parms['rowspan'] += 1 strip[-i + 1] = None else: # we are above a commit box. Leave it # be, and turn the current box into an # empty one strip[-i] = Box([], rowspan=1, comment="commit bubble") strip[-i].spacer = True else: # we are above another empty box, which # somehow wasn't already converted. # Shouldn't happen pass else: for i in range(2, len(strip) + 1): # strip[-i] will go from next-to-last back to first if strip[-i] == None: # bubble previous item up assert (strip[-i + 1] != None) strip[-i] = strip[-i + 1] strip[-i].parms['rowspan'] += 1 strip[-i + 1] = None else: strip[-i].parms['rowspan'] = 1 # third pass: render the HTML table for i in range(gridlen): data += " <tr>\n" for strip in grid: b = strip[i] if b: data += b.td() else: if noBubble: data += td([]) # Nones are left empty, rowspan should make it all fit data += " </tr>\n" return data
def getBox(self, req): #b = Box(["spacer"], "white") b = Box([]) b.spacer = True return b
def getBox(self, req): text = self.original.getText() class_ = "Event" return Box(text, class_=class_)
def phase2(self, request, sourceNames, timestamps, eventGrid, sourceEvents): data = "" if not timestamps: return data # first pass: figure out the height of the chunks, populate grid grid = [] for i in range(1+len(sourceNames)): grid.append([]) # grid is a list of columns, one for the timestamps, and one per # event source. Each column is exactly the same height. Each element # of the list is a single <td> box. lastDate = time.strftime("<b>%d %b %Y</b>", time.localtime(util.now())) for r in range(0, len(timestamps)): chunkstrip = eventGrid[r] # chunkstrip is a horizontal strip of event blocks. Each block # is a vertical list of events, all for the same source. assert(len(chunkstrip) == len(sourceNames)) maxRows = reduce(lambda x,y: max(x,y), map(lambda x: len(x), chunkstrip)) for i in range(maxRows): if i != maxRows-1: grid[0].append(None) else: # timestamp goes at the bottom of the chunk stuff = [] # add the date at the beginning (if it is not the same as # today's date), and each time it changes todayday = time.strftime("<b>%a</b>", time.localtime(timestamps[r])) today = time.strftime("<b>%d %b %Y</b>", time.localtime(timestamps[r])) if today != lastDate: stuff.append(todayday) stuff.append(today) lastDate = today stuff.append( time.strftime("%H:%M:%S", time.localtime(timestamps[r]))) grid[0].append(Box(text=stuff, class_="Time", valign="bottom", align="center")) # at this point the timestamp column has been populated with # maxRows boxes, most None but the last one has the time string for c in range(0, len(chunkstrip)): block = chunkstrip[c] assert(block != None) # should be [] instead for i in range(maxRows - len(block)): # fill top of chunk with blank space grid[c+1].append(None) for i in range(len(block)): # so the events are bottom-justified b = IBox(block[i]).getBox(request) b.parms['valign'] = "top" b.parms['align'] = "center" grid[c+1].append(b) # now all the other columns have maxRows new boxes too # populate the last row, if empty gridlen = len(grid[0]) for i in range(len(grid)): strip = grid[i] assert(len(strip) == gridlen) if strip[-1] == None: if sourceEvents[i-1]: filler = IBox(sourceEvents[i-1]).getBox(request) else: # this can happen if you delete part of the build history filler = Box(text=["?"], align="center") strip[-1] = filler strip[-1].parms['rowspan'] = 1 # second pass: bubble the events upwards to un-occupied locations # Every square of the grid that has a None in it needs to have # something else take its place. noBubble = request.args.get("nobubble",['0']) noBubble = int(noBubble[0]) if not noBubble: for col in range(len(grid)): strip = grid[col] if col == 1: # changes are handled differently for i in range(2, len(strip)+1): # only merge empty boxes. Don't bubble commit boxes. if strip[-i] == None: next = strip[-i+1] assert(next) if next: #if not next.event: if next.spacer: # bubble the empty box up strip[-i] = next strip[-i].parms['rowspan'] += 1 strip[-i+1] = None else: # we are above a commit box. Leave it # be, and turn the current box into an # empty one strip[-i] = Box([], rowspan=1, comment="commit bubble") strip[-i].spacer = True else: # we are above another empty box, which # somehow wasn't already converted. # Shouldn't happen pass else: for i in range(2, len(strip)+1): # strip[-i] will go from next-to-last back to first if strip[-i] == None: # bubble previous item up assert(strip[-i+1] != None) strip[-i] = strip[-i+1] strip[-i].parms['rowspan'] += 1 strip[-i+1] = None else: strip[-i].parms['rowspan'] = 1 # third pass: render the HTML table for i in range(gridlen): data += " <tr>\n"; for strip in grid: b = strip[i] if b: # convert data to a unicode string, whacking any non-ASCII characters it might contain s = b.td() if isinstance(s, unicode): s = s.encode("utf-8", "replace") data += s else: if noBubble: data += td([]) # Nones are left empty, rowspan should make it all fit data += " </tr>\n" return data
def body(self, req): from twisted.web import html status = self.getStatus(req) control = self.getControl(req) builders = req.args.get("builder", status.getBuilderNames()) branches = [b for b in req.args.get("branch", []) if b] building = False online = 0 base_builders_url = self.path_to_root(req) + "builders/" all_builders = [html.escape(bn) for bn in builders] trunk_builders = [bn for bn in all_builders if bn.startswith('trunk')] stable_builders = [bn for bn in all_builders if bn.startswith('stable')] trunk_builders_link = 'waterfall?builder='+'&builder='.join(trunk_builders) stable_builders_link = 'waterfall?builder=' + '&builder='.join(stable_builders) tr_b = False st_b = False data = "" data += "<table class='grid' id='latest_builds'>" for bn in all_builders: if (bn.startswith('stable')) and (not st_b): st_b = True data += "<table class='grid' id='stable_builds'>\n" data += "<tr class='grid-header'><td class='grid-cell'><span>Latest Stable</span></td><td class='grid-cell'><a href=%s>Stable Tests</a></td><td class='grid-cell'><a href='Changelog/5.0'>Changelog</a></td></tr><\n>"[:-3]%(stable_builders_link) if (bn.startswith('trunk')) and (not tr_b): tr_b = True data += "<tr id='trunk_builds'><td colspan='3'></td>" data += "<tr class='grid-header'><td class='grid-cell'><span>Latest Trunk</span></td><td class='grid-cell'><a href='%s'>Trunk Tests</a></td><td class='grid-cell'><a href='Changelog/trunk'>Changelog</a></td></tr><\n>"[:-3]%(trunk_builders_link) base_builder_url = base_builders_url + urllib.quote(bn, safe='') builder = status.getBuilder(bn) data += "<tr class='grid-row'>\n" data += '<td class="grid-cell"><a href="%s">%s</a></td>\n' \ % (base_builder_url, html.escape(bn)) builds = list(builder.generateFinishedBuilds(map_branches(branches), num_builds=1)) if builds: b = builds[0] url = (base_builder_url + "/builds/%d" % b.getNumber()) try: label = b.getProperty("got_revision") except KeyError: label = None if not label or len(str(label)) > 20: label = "#%d" % b.getNumber() text = ['<a href="%s">%s</a>' % (url, label)] text.append(' '.join(b.getText())) box = Box(text, b.getColor(), class_="LastBuild box %s" % build_get_class(b)) data += box.td(class_="grid-cell",align="center") else: data += '<td class="grid-cell" align="center">no build</td>\n' current_box = ICurrentBox(builder).getBox(status) data += current_box.td(class_="grid-cell",align="center") data+='</tr>' builder_status = builder.getState()[0] if builder_status == "building": building = True online += 1 elif builder_status != "offline": online += 1 data += "</table></table>\n" if control is not None: if building: stopURL = "builders/_all/stop" data += make_stop_form(stopURL, True, "Builds") if online: forceURL = "builders/_all/force" data += make_force_build_form(forceURL, True) return data
def getBox(self, req): url = req.childLink("../changes/%d" % self.original.number) text = self.original.get_HTML_box(url) return Box([text], color="white", class_="Change")