[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

Brian Warner warner at users.sourceforge.net
Thu May 22 22:11:27 UTC 2008


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 = ('<div class="title"><a href="%s">%s</a></div>\n'
                 % (self.path_to_root(req), projectName))
@@ -136,7 +138,6 @@
             data += "</ol>\n"
             #data += html.PRE(b.changesText()) # TODO
 
-
         if b.isFinished() and self.builder_control is not None:
             data += "<h3>Resubmit Build:</h3>\n"
             # can we rebuild it exactly?
@@ -163,6 +164,29 @@
             data += '<input type="submit" value="Rebuild" />\n'
             data += '</form>\n'
 
+        # TODO: this stuff should be generated by a template of some sort
+        data += '<hr /><div class="footer">\n'
+
+        welcomeurl = self.path_to_root(req) + "index.html"
+        data += '[<a href="%s">welcome</a>]\n' % welcomeurl
+        data += "<br />\n"
+
+        data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>'
+        data += "-%s " % version
+        if projectName:
+            data += "working for the "
+            if projectURL:
+                data += "<a href=\"%s\">%s</a> project." % (projectURL,
+                                                            projectName)
+            else:
+                data += "%s project." % projectName
+        data += "<br />\n"
+        data += ("Page built: " +
+                 time.strftime("%a %d %b %Y %H:%M:%S",
+                               time.localtime(util.now()))
+                 + "\n")
+        data += '</div>\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 @@
             </form>
             """ % pingURL
 
+        # TODO: this stuff should be generated by a template of some sort
+        projectURL = status.getProjectURL()
+        projectName = status.getProjectName()
+        data += '<hr /><div class="footer">\n'
+
+        welcomeurl = self.path_to_root(req) + "index.html"
+        data += '[<a href="%s">welcome</a>]\n' % welcomeurl
+        data += "<br />\n"
+
+        data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>'
+        data += "-%s " % version
+        if projectName:
+            data += "working for the "
+            if projectURL:
+                data += "<a href=\"%s\">%s</a> project." % (projectURL,
+                                                            projectName)
+            else:
+                data += "%s project." % projectName
+        data += "<br />\n"
+        data += ("Page built: " +
+                 time.strftime("%a %d %b %Y %H:%M:%S",
+                               time.localtime(util.now()))
+                 + "\n")
+        data += '</div>\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 += "</ol>\n"
 
+        # TODO: this stuff should be generated by a template of some sort
+        projectURL = s.getProjectURL()
+        projectName = s.getProjectName()
+        data += '<hr /><div class="footer">\n'
+
+        welcomeurl = self.path_to_root(req) + "index.html"
+        data += '[<a href="%s">welcome</a>]\n' % welcomeurl
+        data += "<br />\n"
+
+        data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>'
+        data += "-%s " % version
+        if projectName:
+            data += "working for the "
+            if projectURL:
+                data += "<a href=\"%s\">%s</a> project." % (projectURL,
+                                                            projectName)
+            else:
+                data += "%s project." % projectName
+        data += "<br />\n"
+        data += ("Page built: " +
+                 time.strftime("%a %d %b %Y %H:%M:%S",
+                               time.localtime(util.now()))
+                 + "\n")
+        data += '</div>\n'
+
+        # TODO: this stuff should be generated by a template of some sort
+        projectURL = s.getProjectURL()
+        projectName = s.getProjectName()
+        data += '<hr /><div class="footer">\n'
+
+        welcomeurl = self.path_to_root(req) + "index.html"
+        data += '[<a href="%s">welcome</a>]\n' % welcomeurl
+        data += "<br />\n"
+
+        data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>'
+        data += "-%s " % version
+        if projectName:
+            data += "working for the "
+            if projectURL:
+                data += "<a href=\"%s\">%s</a> project." % (projectURL,
+                                                            projectName)
+            else:
+                data += "%s project." % projectName
+        data += "<br />\n"
+        data += ("Page built: " +
+                 time.strftime("%a %d %b %Y %H:%M:%S",
+                               time.localtime(util.now()))
+                 + "\n")
+        data += '</div>\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 += '<meta http-equiv="refresh" content="%d">\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 '<td class="build"> </td>\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] = '<a href="%s">%s</a>' % (url, text[0])
        text = '<br />\n'.join(text)

        return '<td class="build" bgcolor="%s">%s</td>\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 = '<a href="%s">%s</a>' % (url, builder.getName())
        pbs = builder.getPendingBuilds()
        if state != 'idle' or pbs:
            if pbs:
                text += "<br />(%s with %d pending)" % (state, len(pbs))
            else:
                text += "<br />(%s)" % state

        return  '<td valign="center" bgcolor="%s" class="builder">%s</td>\n' % \
            (color, text)

    def stamp_td(self, stamp):
        text = stamp.getText()
        return '<td valign="bottom" class="sourcestamp">%s</td>\n' % \
            "<br />".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 = '<table class="Grid" border="0" cellspacing="0">\n'
        data += '<tr>\n'
        data += '<td class="title"><a href="%s">%s</a>' % (projectURL, projectName)
        if categories:
            if len(categories) > 1:
                data += '\n<br /><b>Categories:</b><br/>%s' % ('<br/>'.join(categories))
            else:
                data += '\n<br /><b>Category:</b> %s' % categories[0]
        if branch != ANYBRANCH:
            data += '\n<br /><b>Branch:</b> %s' % (branch or 'trunk')
        data += '</td>\n'
        for stamp in stamps:
            data += self.stamp_td(stamp)
        data += '</tr>\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 += '<tr>\n'
            data += self.builder_td(request, builder)
            for build in builds:
                data += self.build_td(request, build)
            data += '</tr>\n'

        data += '</table>\n'

        # TODO: this stuff should be generated by a template of some sort
        data += '<hr /><div class="footer">\n'

        welcomeurl = self.path_to_root(request) + "index.html"
        data += '[<a href="%s">welcome</a>]\n' % welcomeurl
        data += "<br />\n"

        data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>'
        data += "-%s " % version
        if projectName:
            data += "working for the "
            if projectURL:
                data += "<a href=\"%s\">%s</a> project." % (projectURL,
                                                            projectName)
            else:
                data += "%s project." % projectName
        data += "<br />\n"
        data += ("Page built: " +
                 time.strftime("%a %d %b %Y %H:%M:%S",
                               time.localtime(util.now()))
                 + "\n")
        data += '</div>\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 @@
   <li>the <a href="waterfall">Waterfall Display</a> will give you a
   time-oriented summary of recent buildbot activity.</li>
 
+  <li>the <a href="grid">Grid Display</a> will give you a
+  developer-oriented summary of recent buildbot activity.</li>
+
   <li>The <a href="one_box_per_builder">Latest Build</a> for each builder is
   here.</li>
 

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 += "</table>\n"
 
-        data += "<hr />\n"
+        data += '<hr /><div class="footer"\n'
 
         def with_args(req, remove_args=[], new_args=[], new_path=None):
             # sigh, nevow makes this sort of manipulation easier
@@ -591,6 +591,7 @@
                  time.strftime("%a %d %b %Y %H:%M:%S",
                                time.localtime(util.now()))
                  + "\n")
+        data += '</div>\n'
         return data
 
     def body0(self, request, builders):





More information about the Commits mailing list