From warner at users.sourceforge.net Thu May 22 22:11:27 2008 From: warner at users.sourceforge.net (Brian Warner) Date: Thu, 22 May 2008 22:11:27 +0000 Subject: [Buildbot-commits] buildbot/buildbot/status/web grid.py, NONE, 1.1 baseweb.py, 1.34, 1.35 build.py, 1.16, 1.17 builder.py, 1.17, 1.18 classic.css, 1.2, 1.3 index.html, 1.4, 1.5 waterfall.py, 1.28, 1.29 Message-ID: Update of /cvsroot/buildbot/buildbot/buildbot/status/web In directory sc8-pr-cvs3.sourceforge.net:/tmp/cvs-serv16506/buildbot/status/web Modified Files: baseweb.py build.py builder.py classic.css index.html waterfall.py Added Files: grid.py Log Message: [project @ #72:statusgrid.patch] Add a "status grid" web display which has a row for each builder and a column for each distinct sourcestamp. Original author: dustin at v.igoro.us Date: 2008-02-17 18:16:14+00:00 Index: baseweb.py =================================================================== RCS file: /cvsroot/buildbot/buildbot/buildbot/status/web/baseweb.py,v retrieving revision 1.34 retrieving revision 1.35 diff -u -d -r1.34 -r1.35 --- baseweb.py 23 Mar 2008 03:14:52 -0000 1.34 +++ baseweb.py 22 May 2008 22:11:25 -0000 1.35 @@ -14,6 +14,7 @@ build_get_class, ICurrentBox, OneLineMixin, map_branches, \ make_stop_form, make_force_build_form from buildbot.status.web.waterfall import WaterfallStatusResource +from buildbot.status.web.grid import GridStatusResource from buildbot.status.web.changes import ChangesResource from buildbot.status.web.builder import BuildersResource from buildbot.status.web.slaves import BuildSlavesResource @@ -280,6 +281,9 @@ to individual changes, builders, builds, steps, and logs. A number of query-arguments can be added to influence the display. + /grid : another summary display that shows a grid of builds, with + sourcestamps on the x axis, and builders on the y. Query + arguments similar to those for the waterfall can be added. /builders/BUILDERNAME: a page summarizing the builder. This includes references to the Schedulers that feed it, any builds currently in the queue, which @@ -431,6 +435,7 @@ def setupUsualPages(self): #self.putChild("", IndexOrWaterfallRedirection()) self.putChild("waterfall", WaterfallStatusResource()) + self.putChild("grid", GridStatusResource()) self.putChild("builders", BuildersResource()) # has builds/steps/logs self.putChild("changes", ChangesResource()) self.putChild("buildslaves", BuildSlavesResource()) Index: build.py =================================================================== RCS file: /cvsroot/buildbot/buildbot/buildbot/status/web/build.py,v retrieving revision 1.16 retrieving revision 1.17 diff -u -d -r1.16 -r1.17 --- build.py 21 Nov 2007 09:23:34 -0000 1.16 +++ build.py 22 May 2008 22:11:25 -0000 1.17 @@ -10,6 +10,7 @@ from buildbot.status.web.tests import TestsResource from buildbot.status.web.step import StepsResource +from buildbot import version, util # /builders/$builder/builds/$buildnum class StatusResourceBuild(HtmlResource): @@ -29,6 +30,7 @@ def body(self, req): b = self.build_status status = self.getStatus(req) + projectURL = status.getProjectURL() projectName = status.getProjectName() data = ('
%s
\n' % (self.path_to_root(req), projectName)) @@ -136,7 +138,6 @@ data += "\n" #data += html.PRE(b.changesText()) # TODO - if b.isFinished() and self.builder_control is not None: data += "

Resubmit Build:

\n" # can we rebuild it exactly? @@ -163,6 +164,29 @@ data += '\n' data += '\n' + # TODO: this stuff should be generated by a template of some sort + data += '
\n' + return data def stop(self, req): @@ -176,11 +200,8 @@ "'%s': %s\n" % (name, comments)) c.stopBuild(reason) # we're at http://localhost:8080/svn-hello/builds/5/stop?[args] and - # we want to go to: http://localhost:8080/svn-hello/builds/5 or - # http://localhost:8080/waterfall - # - #return Redirect("../%d" % self.build.getNumber()) - r = Redirect("../../../../waterfall") + # we want to go to: http://localhost:8080/svn-hello + return Redirect("../..") d = defer.Deferred() reactor.callLater(1, d.callback, r) return DeferredResource(d) @@ -209,11 +230,7 @@ # have to wait for a current build to finish). The next-most # preferred place is somewhere that the user can see tangible # evidence of their build starting (or to see the reason that it - # didn't start). This could either be the Builder page, or the - # waterfall. - #r = Redirect("../../../..") # this takes us back to the welcome page - #r = Redirect("../../../../waterfall") # or the Waterfall - #r = Redirect("../../../../waterfall?show=%s" % builder_name) + # didn't start). This should be the Builder page. r = Redirect("../..") # the Builder's page d = defer.Deferred() reactor.callLater(1, d.callback, r) Index: builder.py =================================================================== RCS file: /cvsroot/buildbot/buildbot/buildbot/status/web/builder.py,v retrieving revision 1.17 retrieving revision 1.18 diff -u -d -r1.17 -r1.18 --- builder.py 21 Nov 2007 09:24:18 -0000 1.17 +++ builder.py 22 May 2008 22:11:25 -0000 1.18 @@ -10,6 +10,7 @@ make_force_build_form, OneLineMixin from buildbot.process.base import BuildRequest from buildbot.sourcestamp import SourceStamp +from buildbot import version, util from buildbot.status.web.build import BuildsResource, StatusResourceBuild @@ -124,6 +125,31 @@ """ % pingURL + # TODO: this stuff should be generated by a template of some sort + projectURL = status.getProjectURL() + projectName = status.getProjectName() + data += '
\n' + return data def force(self, req): @@ -166,12 +192,14 @@ # TODO: tell the web user that their request could not be # honored pass - return Redirect("../../waterfall") + # send the user back to the builder page + return Redirect(".") def ping(self, req): log.msg("web ping of builder '%s'" % self.builder_status.getName()) self.builder_control.ping() # TODO: there ought to be an ISlaveControl - return Redirect("../../waterfall") + # send the user back to the builder page + return Redirect(".") def getChild(self, path, req): if path == "force": @@ -229,7 +257,8 @@ builder_control = c.getBuilder(bname) build = StatusResourceBuilder(builder_status, builder_control) build.force(req) - return Redirect("../../waterfall") + # back to the welcome page + return Redirect("../..") def stop(self, req): for bname in self.status.getBuilderNames(): @@ -252,7 +281,8 @@ build = StatusResourceBuild(build_status, build_control, builder_control) build.stop(req) - return Redirect("../../waterfall") + # go back to the welcome page + return Redirect("../..") # /builders @@ -275,6 +305,56 @@ urllib.quote(bname, safe=''))) data += "\n" + # TODO: this stuff should be generated by a template of some sort + projectURL = s.getProjectURL() + projectName = s.getProjectName() + data += '
\n' + + # TODO: this stuff should be generated by a template of some sort + projectURL = s.getProjectURL() + projectName = s.getProjectName() + data += '
\n' + return data def getChild(self, path, req): Index: classic.css =================================================================== RCS file: /cvsroot/buildbot/buildbot/buildbot/status/web/classic.css,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -r1.2 -r1.3 --- classic.css 30 Sep 2007 09:23:57 -0000 1.2 +++ classic.css 22 May 2008 22:11:25 -0000 1.3 @@ -41,3 +41,38 @@ .start,.running { background-color: yellow; } + +/* grid styles */ + +table.Grid { + border-collapse: collapse; +} + +table.Grid tr td { + padding: 0.2em; + margin: 0px; + text-align: center; +} + +table.Grid tr td.title { + font-size: 90%; + border-right: 1px gray solid; + border-bottom: 1px gray solid; +} + +table.Grid tr td.sourcestamp { + font-size: 90%; +} + +table.Grid tr td.builder { + text-align: right; + font-size: 90%; +} + +table.Grid tr td.build { + border: 1px gray solid; +} + +div.footer { + font-size: 80%; +} --- NEW FILE: grid.py --- from __future__ import generators import sys, string, types, time, os.path import urllib from buildbot import interfaces, util from buildbot import version from buildbot.status.web.base import HtmlResource # set grid_css to the full pathname of the css file if hasattr(sys, "frozen"): # all 'data' files are in the directory of our executable here = os.path.dirname(sys.executable) grid_css = os.path.abspath(os.path.join(here, "grid.css")) else: # running from source; look for a sibling to __file__ up = os.path.dirname grid_css = os.path.abspath(os.path.join(up(__file__), "grid.css")) class ANYBRANCH: pass # a flag value, used below class GridStatusResource(HtmlResource): # TODO: docs status = None control = None changemaster = None def __init__(self, allowForce=True, css=None): HtmlResource.__init__(self) self.allowForce = allowForce self.css = css or grid_css def getTitle(self, request): status = self.getStatus(request) p = status.getProjectName() if p: return "BuildBot: %s" % p else: return "BuildBot" def getChangemaster(self, request): # TODO: this wants to go away, access it through IStatus return request.site.buildbot_service.parent.change_svc # handle reloads through an http header # TODO: send this as a real header, rather than a tag def get_reload_time(self, request): if "reload" in request.args: try: reload_time = int(request.args["reload"][0]) return max(reload_time, 15) except ValueError: pass return None def head(self, request): head = '' reload_time = self.get_reload_time(request) if reload_time is not None: head += '\n' % reload_time return head # def setBuildmaster(self, buildmaster): # self.status = buildmaster.getStatus() # if self.allowForce: # self.control = interfaces.IControl(buildmaster) # else: # self.control = None # self.changemaster = buildmaster.change_svc # # # try to set the page title # p = self.status.getProjectName() # if p: # self.title = "BuildBot: %s" % p # def build_td(self, request, build): if not build: return ' \n' if build.isFinished(): color = build.getColor() if color == 'green': color = '#72ff75' # the "Buildbot Green" # get the text and annotate the first line with a link text = build.getText() if not text: text = [ "(no information)" ] if text == [ "build", "successful" ]: text = [ "OK" ] else: color = 'yellow' # to match the yellow of the builder text = [ 'building' ] name = build.getBuilder().getName() number = build.getNumber() url = "builders/%s/builds/%d" % (name, number) text[0] = '%s' % (url, text[0]) text = '
\n'.join(text) return '%s\n' % (color, text) def builder_td(self, request, builder): state, builds = builder.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 = builder.getName() for s in self.getStatus(request).getSchedulers(): if builderName in s.listBuilderNames(): upcoming.extend(s.getPendingBuildTimes()) if state == "idle" and upcoming: state = "waiting" if state == "building": color = "yellow" elif state == "offline": color = "red" elif state == "idle": color = "white" elif state == "waiting": color = "yellow" else: color = "white" # 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) url = 'builders/%s/' % urllib.quote(builder.getName(), safe='') text = '%s' % (url, builder.getName()) pbs = builder.getPendingBuilds() if state != 'idle' or pbs: if pbs: text += "
(%s with %d pending)" % (state, len(pbs)) else: text += "
(%s)" % state return '%s\n' % \ (color, text) def stamp_td(self, stamp): text = stamp.getText() return '%s\n' % \ "
".join(text) def body(self, request): "This method builds the main waterfall display." # get url parameters numBuilds = int(request.args.get("width", [5])[0]) categories = request.args.get("category", []) branch = request.args.get("branch", [ANYBRANCH])[0] if branch == 'trunk': branch = None # and the data we want to render status = self.getStatus(request) stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch) projectURL = status.getProjectURL() projectName = status.getProjectName() data = '\n' data += '\n' data += '\n' for stamp in stamps: data += self.stamp_td(stamp) data += '\n' for bn in status.getBuilderNames(): builds = [None] * len(stamps) builder = status.getBuilder(bn) if categories and builder.category not in categories: continue build = builder.getBuild(-1) while build and None in builds: ss = build.getSourceStamp(specific=True) for i in range(len(stamps)): if ss == stamps[i] and builds[i] is None: builds[i] = build build = build.getPreviousBuild() data += '\n' data += self.builder_td(request, builder) for build in builds: data += self.build_td(request, build) data += '\n' data += '
%s' % (projectURL, projectName) if categories: if len(categories) > 1: data += '\n
Categories:
%s' % ('
'.join(categories)) else: data += '\n
Category: %s' % categories[0] if branch != ANYBRANCH: data += '\n
Branch: %s' % (branch or 'trunk') data += '
\n' # TODO: this stuff should be generated by a template of some sort data += '
\n' return data def getRecentSourcestamps(self, status, numBuilds, categories, branch): """ get a list of the most recent NUMBUILDS SourceStamp tuples, sorted by the earliest start we've seen for them """ # TODO: use baseweb's getLastNBuilds? sourcestamps = { } # { ss-tuple : earliest time } for bn in status.getBuilderNames(): builder = status.getBuilder(bn) if categories and builder.category not in categories: continue build = builder.getBuild(-1) while build: ss = build.getSourceStamp(specific=True) start = build.getTimes()[0] build = build.getPreviousBuild() # skip un-started builds if not start: continue # skip non-matching branches if branch != ANYBRANCH and ss.branch != branch: continue sourcestamps[ss] = min(sourcestamps.get(ss, sys.maxint), start) # now sort those and take the NUMBUILDS most recent sourcestamps = sourcestamps.items() sourcestamps.sort(lambda x, y: cmp(x[1], y[1])) sourcestamps = map(lambda tup : tup[0], sourcestamps) sourcestamps = sourcestamps[-numBuilds:] return sourcestamps Index: index.html =================================================================== RCS file: /cvsroot/buildbot/buildbot/buildbot/status/web/index.html,v retrieving revision 1.4 retrieving revision 1.5 diff -u -d -r1.4 -r1.5 --- index.html 26 Sep 2007 06:32:24 -0000 1.4 +++ index.html 22 May 2008 22:11:25 -0000 1.5 @@ -12,6 +12,9 @@
  • the Waterfall Display will give you a time-oriented summary of recent buildbot activity.
  • +
  • the Grid Display will give you a + developer-oriented summary of recent buildbot activity.
  • +
  • The Latest Build for each builder is here.
  • Index: waterfall.py =================================================================== RCS file: /cvsroot/buildbot/buildbot/buildbot/status/web/waterfall.py,v retrieving revision 1.28 retrieving revision 1.29 diff -u -d -r1.28 -r1.29 --- waterfall.py 30 Mar 2008 02:27:53 -0000 1.28 +++ waterfall.py 22 May 2008 22:11:25 -0000 1.29 @@ -528,7 +528,7 @@ data += "\n" - data += "
    \n" + data += '