Example #1
0
    def body(self, request):
        "This method builds the main waterfall display."

        status = self.getStatus(request)
        data = ''

        projectName = status.getProjectName()
        projectURL = status.getProjectURL()

        phase = request.args.get("phase", ["2"])
        phase = int(phase[0])

        # we start with all Builders available to this Waterfall: this is
        # limited by the config-file -time categories= argument, and defaults
        # to all defined Builders.
        allBuilderNames = status.getBuilderNames(categories=self.categories)
        builders = [status.getBuilder(name) for name in allBuilderNames]

        # but if the URL has one or more builder= arguments (or the old show=
        # argument, which is still accepted for backwards compatibility), we
        # use that set of builders instead. We still don't show anything
        # outside the config-file time set limited by categories=.
        showBuilders = request.args.get("show", [])
        showBuilders.extend(request.args.get("builder", []))
        if showBuilders:
            builders = [b for b in builders if b.name in showBuilders]

        # now, if the URL has one or category= arguments, use them as a
        # filter: only show those builders which belong to one of the given
        # categories.
        showCategories = request.args.get("category", [])
        if showCategories:
            builders = [b for b in builders if b.category in showCategories]

        builderNames = [b.name for b in builders]

        if phase == -1:
            return self.body0(request, builders)
        (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = \
                      self.buildGrid(request, builders)
        if phase == 0:
            return self.phase0(request, (changeNames + builderNames),
                               timestamps, eventGrid)
        # start the table: top-header material
        data += '<table border="0" cellspacing="0">\n'

        if projectName and projectURL:
            # TODO: this is going to look really ugly
            topleft = '<a href="%s">%s</a><br />last build' % \
                      (projectURL, projectName)
        else:
            topleft = "last build"
        data += ' <tr class="LastBuild">\n'
        data += td(topleft, align="right", colspan=2, class_="Project")
        for b in builders:
            box = ITopBox(b).getBox(request)
            data += box.td(align="center")
        data += " </tr>\n"

        data += ' <tr class="Activity">\n'
        data += td('current activity', align='right', colspan=2)
        for b in builders:
            box = ICurrentBox(b).getBox(status)
            data += box.td(align="center")
        data += " </tr>\n"

        data += " <tr>\n"
        TZ = time.tzname[time.localtime()[-1]]
        data += td("time (%s)" % TZ, align="center", class_="Time")
        data += td('<a href="%s">changes</a>' %
                   request.childLink("../changes"),
                   align="center",
                   class_="Change")
        for name in builderNames:
            safename = urllib.quote(name, safe='')
            data += td('<a href="%s">%s</a>' %
                       (request.childLink("../builders/%s" % safename), name),
                       align="center",
                       class_="Builder")
        data += " </tr>\n"

        if phase == 1:
            f = self.phase1
        else:
            f = self.phase2
        data += f(request, changeNames + builderNames, timestamps, eventGrid,
                  sourceEvents)

        data += "</table>\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
            newargs = req.args.copy()
            for argname in remove_args:
                newargs[argname] = []
            if "branch" in newargs:
                newargs["branch"] = [b for b in newargs["branch"] if b]
            for k, v in new_args:
                if k in newargs:
                    newargs[k].append(v)
                else:
                    newargs[k] = [v]
            newquery = "&".join(
                ["%s=%s" % (k, v) for k in newargs for v in newargs[k]])
            if new_path:
                new_url = new_path
            elif req.prepath:
                new_url = req.prepath[-1]
            else:
                new_url = ''
            if newquery:
                new_url += "?" + newquery
            return new_url

        if timestamps:
            bottom = timestamps[-1]
            nextpage = with_args(request, ["last_time"],
                                 [("last_time", str(int(bottom)))])
            data += '[<a href="%s">next page</a>]\n' % nextpage

        helpurl = self.path_to_root(request) + "waterfall/help"
        helppage = with_args(request, new_path=helpurl)
        data += '[<a href="%s">help</a>]\n' % helppage

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

        if self.get_reload_time(request) is not None:
            no_reload_page = with_args(request, remove_args=["reload"])
            data += '[<a href="%s">Stop Reloading</a>]\n' % no_reload_page

        data += "<br />\n"

        bburl = "http://buildbot.net/?bb-ver=%s" % urllib.quote(version)
        data += '<a href="%s">Buildbot-%s</a> ' % (bburl, 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"
        # TODO: push this to the right edge, if possible
        data += ("Page built: " + time.strftime(
            "%a %d %b %Y %H:%M:%S", time.localtime(util.now())) + "\n")
        data += '</div>\n'
        return "%s %s" % (self.GetAnnounce(), data)
Example #2
0
    def body(self, request):
        "This method builds the main waterfall display."

        status = self.getStatus(request)
        data = ''

        projectName = status.getProjectName()
        projectURL = status.getProjectURL()

        phase = request.args.get("phase",["2"])
        phase = int(phase[0])

        # we start with all Builders available to this Waterfall: this is
        # limited by the config-file -time categories= argument, and defaults
        # to all defined Builders.
        allBuilderNames = status.getBuilderNames(categories=self.categories)
        builders = [status.getBuilder(name) for name in allBuilderNames]

        # but if the URL has one or more builder= arguments (or the old show=
        # argument, which is still accepted for backwards compatibility), we
        # use that set of builders instead. We still don't show anything
        # outside the config-file time set limited by categories=.
        showBuilders = request.args.get("show", [])
        showBuilders.extend(request.args.get("builder", []))
        if showBuilders:
            builders = [b for b in builders if b.name in showBuilders]

        # now, if the URL has one or category= arguments, use them as a
        # filter: only show those builders which belong to one of the given
        # categories.
        showCategories = request.args.get("category", [])
        if showCategories:
            builders = [b for b in builders if b.category in showCategories]

        # If the URL has the failures_only=true argument, we remove all the
        # builders that are not currently red or won't be turning red at the end
        # of their current run.
        failuresOnly = request.args.get("failures_only", ["false"])[0]
        if failuresOnly.lower() == "true":
            builders = [b for b in builders if not self.isSuccess(b)]

        builderNames = [b.name for b in builders]

        if phase == -1:
            return self.body0(request, builders)
        (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = \
                      self.buildGrid(request, builders)
        if phase == 0:
            return self.phase0(request, (changeNames + builderNames),
                               timestamps, eventGrid)
        # start the table: top-header material
        data += '<table border="0" cellspacing="0">\n'

        if projectName and projectURL:
            # TODO: this is going to look really ugly
            topleft = '<a href="%s">%s</a><br />last build' % \
                      (projectURL, projectName)
        else:
            topleft = "last build"
        data += ' <tr class="LastBuild">\n'
        data += td(topleft, align="right", colspan=2, class_="Project")
        for b in builders:
            box = ITopBox(b).getBox(request)
            data += box.td(align="center")
        data += " </tr>\n"

        data += ' <tr class="Activity">\n'
        data += td('current activity', align='right', colspan=2)
        for b in builders:
            box = ICurrentBox(b).getBox(status)
            data += box.td(align="center")
        data += " </tr>\n"
        
        data += " <tr>\n"
        TZ = time.tzname[time.localtime()[-1]]
        data += td("time (%s)" % TZ, align="center", class_="Time")
        data += td('<a href="%s">changes</a>' % request.childLink("../changes"),
                   align="center", class_="Change")
        for name in builderNames:
            safename = urllib.quote(name, safe='')
            data += td('<a href="%s">%s</a>' %
                       (request.childLink("../builders/%s" % safename), name),
                       align="center", class_="Builder")
        data += " </tr>\n"

        if phase == 1:
            f = self.phase1
        else:
            f = self.phase2
        data += f(request, changeNames + builderNames, timestamps, eventGrid,
                  sourceEvents)

        data += "</table>\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
            newargs = req.args.copy()
            for argname in remove_args:
                newargs[argname] = []
            if "branch" in newargs:
                newargs["branch"] = [b for b in newargs["branch"] if b]
            for k,v in new_args:
                if k in newargs:
                    newargs[k].append(v)
                else:
                    newargs[k] = [v]
            newquery = "&".join(["%s=%s" % (k, v)
                                 for k in newargs
                                 for v in newargs[k]
                                 ])
            if new_path:
                new_url = new_path
            elif req.prepath:
                new_url = req.prepath[-1]
            else:
                new_url = ''
            if newquery:
                new_url += "?" + newquery
            return new_url

        if timestamps:
            bottom = timestamps[-1]
            nextpage = with_args(request, ["last_time"],
                                 [("last_time", str(int(bottom)))])
            data += '[<a href="%s">next page</a>]\n' % nextpage

        helpurl = self.path_to_root(request) + "waterfall/help"
        helppage = with_args(request, new_path=helpurl)
        data += '[<a href="%s">help</a>]\n' % helppage

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

        if self.get_reload_time(request) is not None:
            no_reload_page = with_args(request, remove_args=["reload"])
            data += '[<a href="%s">Stop Reloading</a>]\n' % no_reload_page

        data += "<br />\n"


        bburl = "http://buildbot.net/?bb-ver=%s" % urllib.quote(version)
        data += '<a href="%s">Buildbot-%s</a> ' % (bburl, 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"
        # TODO: push this to the right edge, if possible
        data += ("Page built: " +
                 time.strftime("%a %d %b %Y %H:%M:%S",
                               time.localtime(util.now()))
                 + "\n")
        data += '</div>\n'
        return data
Example #3
0
    def content_with_db_data(self, changes, brcounts, request, ctx):
        status = self.getStatus(request)
        ctx['refresh'] = self.get_reload_time(request)

        # we start with all Builders available to this Waterfall: this is
        # limited by the config-file -time categories= argument, and defaults
        # to all defined Builders.
        allBuilderNames = status.getBuilderNames(categories=self.categories)
        builders = [status.getBuilder(name) for name in allBuilderNames]

        # but if the URL has one or more builder= arguments (or the old show=
        # argument, which is still accepted for backwards compatibility), we
        # use that set of builders instead. We still don't show anything
        # outside the config-file time set limited by categories=.
        showBuilders = request.args.get("show", [])
        showBuilders.extend(request.args.get("builder", []))
        if showBuilders:
            builders = [b for b in builders if b.name in showBuilders]

        # now, if the URL has one or category= arguments, use them as a
        # filter: only show those builders which belong to one of the given
        # categories.
        showCategories = request.args.get("category", [])
        if showCategories:
            builders = [b for b in builders if b.category in showCategories]

        # If the URL has the failures_only=true argument, we remove all the
        # builders that are not currently red or won't be turning red at the end
        # of their current run.
        failuresOnly = request.args.get("failures_only", ["false"])[0]
        if failuresOnly.lower() == "true":
            builders = [b for b in builders if not self.isSuccess(b)]
        
        (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = \
                      self.buildGrid(request, builders, changes)
            
        # start the table: top-header material
        locale_enc = locale.getdefaultlocale()[1]
        if locale_enc is not None:
            locale_tz = unicode(time.tzname[time.localtime()[-1]], locale_enc)
        else:
            locale_tz = unicode(time.tzname[time.localtime()[-1]])
        ctx['tz'] = locale_tz
        ctx['changes_url'] = request.childLink("../changes")
        
        bn = ctx['builders'] = []
                
        for name in builderNames:
            builder = status.getBuilder(name)
            top_box = ITopBox(builder).getBox(request)
            current_box = ICurrentBox(builder).getBox(status, brcounts)
            bn.append({'name': name,
                       'url': request.childLink("../builders/%s" % urllib.quote(name, safe='')), 
                       'top': top_box.text, 
                       'top_class': top_box.class_,
                       'status': current_box.text,
                       'status_class': current_box.class_,                       
                        })

        ctx.update(self.phase2(request, changeNames + builderNames, timestamps, eventGrid,
                  sourceEvents))

        def with_args(req, remove_args=[], new_args=[], new_path=None):
            # sigh, nevow makes this sort of manipulation easier
            newargs = req.args.copy()
            for argname in remove_args:
                newargs[argname] = []
            if "branch" in newargs:
                newargs["branch"] = [b for b in newargs["branch"] if b]
            for k,v in new_args:
                if k in newargs:
                    newargs[k].append(v)
                else:
                    newargs[k] = [v]
            newquery = "&amp;".join(["%s=%s" % (urllib.quote(k), urllib.quote(v))
                                 for k in newargs
                                 for v in newargs[k]
                                 ])
            if new_path:
                new_url = new_path
            elif req.prepath:
                new_url = req.prepath[-1]
            else:
                new_url = ''
            if newquery:
                new_url += "?" + newquery
            return new_url

        if timestamps:
            bottom = timestamps[-1]
            ctx['nextpage'] = with_args(request, ["last_time"],
                                 [("last_time", str(int(bottom)))])


        helpurl = path_to_root(request) + "waterfall/help"
        ctx['help_url'] = with_args(request, new_path=helpurl)

        if self.get_reload_time(request) is not None:
            ctx['no_reload_page'] = with_args(request, remove_args=["reload"])

        template = request.site.buildbot_service.templates.get_template("waterfall.html")
        data = template.render(**ctx)
        return data
Example #4
0
    def body(self, request):
        # summary of changes between this method and the overriden one:
        #  - more structural markup and CSS
        #  - removal of the phase stuff, only keep one
        "This method builds the main waterfall display."

        status = self.getStatus(request)
        data = ''

        projectName = status.getProjectName()
        projectURL = status.getProjectURL()

        # we start with all Builders available to this Waterfall: this is
        # limited by the config-file -time categories= argument, and defaults
        # to all defined Builders.
        allBuilderNames = status.getBuilderNames(categories=self.categories)
        builders = [status.getBuilder(name) for name in allBuilderNames]

        # but if the URL has one or more builder= arguments (or the old show=
        # argument, which is still accepted for backwards compatibility), we
        # use that set of builders instead. We still don't show anything
        # outside the config-file time set limited by categories=.
        showBuilders = request.args.get("show", [])
        showBuilders.extend(request.args.get("builder", []))
        if showBuilders:
            builders = [b for b in builders if b.name in showBuilders]

        # now, if the URL has one or category= arguments, use them as a
        # filter: only show those builders which belong to one of the given
        # categories.
        showCategories = request.args.get("category", [])
        if showCategories:
            builders = [b for b in builders if b.category in showCategories]

        builderNames = [b.name for b in builders]

        (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = \
                      self.buildGrid(request, builders)

        # start the table: top-header material
        data += '<table class="waterfall">\n'
        data += '<thead>\n'
        data += '<tr>\n'
        data += '<td colspan="2"></td>'
        for b in builders:
            state, builds = b.getState()
            builder_name = b.name[len(self.module_name) + 1:]
            data += '<th class="%s" title="%s"><a href="%s">%s</a></th>' % (
                state, state,
                request.childLink('../builders/%s' %
                                  urllib.quote(b.name, safe='')), builder_name)
        data += '</tr>\n'

        data += '<tr>'
        data += '<th>time<br/>(%s)</th>' % time.tzname[time.localtime()[-1]]
        data += '<th class="Change">changes</th>'

        for b in builders:
            box = ITopBox(b).getBox(request)
            data += box.td(align="center")
        data += '</tr>'

        data += '</thead>'

        data += '<tbody>'

        data += self.phase2(request, changeNames + builderNames, timestamps,
                            eventGrid, sourceEvents)

        data += '</tbody>\n'

        data += '<tfoot>\n'

        def with_args(req, remove_args=[], new_args=[], new_path=None):
            # sigh, nevow makes this sort of manipulation easier
            newargs = req.args.copy()
            for argname in remove_args:
                newargs[argname] = []
            if "branch" in newargs:
                newargs["branch"] = [b for b in newargs["branch"] if b]
            for k, v in new_args:
                if k in newargs:
                    newargs[k].append(v)
                else:
                    newargs[k] = [v]
            newquery = "&".join(
                ["%s=%s" % (k, v) for k in newargs for v in newargs[k]])
            if new_path:
                new_url = new_path
            elif req.prepath:
                new_url = req.prepath[-1]
            else:
                new_url = ''
            if newquery:
                new_url += "?" + newquery
            return new_url

        if timestamps:
            data += '<tr>'
            bottom = timestamps[-1]
            nextpage = with_args(request, ["last_time"],
                                 [("last_time", str(int(bottom)))])
            data += '<td class="Time"><a href="%s">next page</a></td>\n' % nextpage
            data += '</tr>'

        data += '</tfoot>\n'
        data += "</table>\n"

        return data
Example #5
0
    def body(self, request):
        parent = request.site.buildbot_service
        status = self.getStatus(request)

        result = ''
        result += '<table class="ProjectSummary">\n'

        # Headers
        slave_status = {}
        for slave in parent.slaves:
            for module in parent.modules:
                builder = status.getBuilder("%s-%s" % (module, slave))
                state, builds = builder.getState()
                if state == 'offline':
                    slave_status[slave] = ('offline', [])
                    break
                elif state == 'building':
                    if slave in slave_status:
                        modules = slave_status[slave][1] or []
                        slave_status[slave] = (state, modules + [module])
                    else:
                        slave_status[slave] = (state, [module])
            else:
                if not slave in slave_status:
                    slave_status[slave] = ('idle', None)

        if type(parent.moduleset) is list:
            moduleset = ', '.join(parent.moduleset)
        else:
            moduleset = parent.moduleset
        result += '<thead><tr><td>&nbsp;</td><th>' + moduleset + '</td>'
        for name in parent.slaves:
            if len(name) > 25:
                name = name[:25] + '(...)'
            klass, modules = slave_status.get(name)
            if klass == 'building':
                title = 'Building %s' % ', '.join(modules)
            else:
                title = klass
            result += '<th class="%s" title="%s"><a href="bots/%s">%s</a></th>' % (
                klass, title, name, name)
        result += '</tr>'
        thead = result
        # stop it here as a row with totals will be added here once every rows
        # have been handled

        # Contents
        result = '<tbody>'

        slave_results = {}
        for slave in parent.slaves:
            slave_results[slave] = [0, 0, 0]

        for module in parent.modules:
            result += '<tr>'
            result += '<td class="feed"><a href="%s/atom">' % module
            result += '<img src="/feed.png" alt="Atom"></a></td>\n'
            result += '<th><a href="%s">%s</a></td>' % (module, module)

            for slave in parent.slaves:
                builder = status.getBuilder("%s-%s" % (module, slave))
                box = ITopBox(builder).getBox(request)
                lastbuild = ''
                for bt in box.text:
                    if bt == 'successful' or bt == 'failed':
                        lastbuild = bt

                if lastbuild == 'successful':
                    last_build = builder.getLastFinishedBuild()
                    if last_build:
                        class_ = build_get_class(last_build)
                    else:
                        class_ = 'success'
                    lastbuild_label = 'Success'
                    if last_build and class_:
                        # use a different class/label if make check failed
                        steps = last_build.getSteps()
                        for step in reversed(steps):
                            if step.name.split()[-1] == 'check':
                                if step.results == WARNINGS:
                                    # make check failed
                                    class_ = 'failedchecks'
                                    lastbuild_label = 'Failed Checks'
                                break
                elif lastbuild == 'failed':
                    lastbuild_label = 'Failed'
                    last_build = builder.getLastFinishedBuild()
                    if last_build:
                        class_ = build_get_class(last_build)
                    else:
                        class_ = 'failure'
                else:
                    class_ = ''
                    lastbuild_label = lastbuild
                state, builds = builder.getState()
                if state == 'building':
                    result += '<td class="%s">%s</td>' % (state, state)
                else:
                    result += '<td class="%s">%s</td>' % (class_,
                                                          lastbuild_label)

                if lastbuild in ('failed', 'successful'):
                    slave_results[slave][2] += 1
                    if class_ == 'failedchecks':
                        slave_results[slave][1] += 1
                    elif lastbuild == 'successful':
                        slave_results[slave][0] += 1
                        slave_results[slave][1] += 1

            result += '</tr>\n'
        result += '</tbody>\n'
        result += '<tfoot><tr class="totals"><td colspan="2"></td>'
        thead += '<tr class="totals"><td colspan="2"></td>'
        for slave in parent.slaves:
            td = '<td><span title="Successful builds">%s</span> '\
                      '<span title="(ignoring test suites failures)">(%s)</span> / '\
                      '<span title="Total">%s</span></td>' % tuple(slave_results[slave])
            thead += td
            result += td
        thead += '</tr>\n</thead>\n'
        result += '</tr></tfoot>\n'
        result += '</table>'

        return thead + result
Example #6
0
    def body(self, request):
        # summary of changes between this method and the overriden one:
        #  - more structural markup and CSS
        #  - removal of the phase stuff, only keep one
        "This method builds the main waterfall display."

        status = self.getStatus(request)
        data = ""

        projectName = status.getProjectName()
        projectURL = status.getProjectURL()

        # we start with all Builders available to this Waterfall: this is
        # limited by the config-file -time categories= argument, and defaults
        # to all defined Builders.
        allBuilderNames = status.getBuilderNames(categories=self.categories)
        builders = [status.getBuilder(name) for name in allBuilderNames]

        # but if the URL has one or more builder= arguments (or the old show=
        # argument, which is still accepted for backwards compatibility), we
        # use that set of builders instead. We still don't show anything
        # outside the config-file time set limited by categories=.
        showBuilders = request.args.get("show", [])
        showBuilders.extend(request.args.get("builder", []))
        if showBuilders:
            builders = [b for b in builders if b.name in showBuilders]

        # now, if the URL has one or category= arguments, use them as a
        # filter: only show those builders which belong to one of the given
        # categories.
        showCategories = request.args.get("category", [])
        if showCategories:
            builders = [b for b in builders if b.category in showCategories]

        builderNames = [b.name for b in builders]

        (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = self.buildGrid(request, builders)

        # start the table: top-header material
        data += '<table class="waterfall">\n'
        data += "<thead>\n"
        data += "<tr>\n"
        data += '<td colspan="2"></td>'
        for b in builders:
            state, builds = b.getState()
            builder_name = b.name[len(self.module_name) + 1 :]
            data += '<th class="%s" title="%s"><a href="%s">%s</a></th>' % (
                state,
                state,
                request.childLink("../builders/%s" % urllib.quote(b.name, safe="")),
                builder_name,
            )
        data += "</tr>\n"

        data += "<tr>"
        data += "<th>time<br/>(%s)</th>" % time.tzname[time.localtime()[-1]]
        data += '<th class="Change">changes</th>'

        for b in builders:
            box = ITopBox(b).getBox(request)
            data += box.td(align="center")
        data += "</tr>"

        data += "</thead>"

        data += "<tbody>"

        data += self.phase2(request, changeNames + builderNames, timestamps, eventGrid, sourceEvents)

        data += "</tbody>\n"

        data += "<tfoot>\n"

        def with_args(req, remove_args=[], new_args=[], new_path=None):
            # sigh, nevow makes this sort of manipulation easier
            newargs = req.args.copy()
            for argname in remove_args:
                newargs[argname] = []
            if "branch" in newargs:
                newargs["branch"] = [b for b in newargs["branch"] if b]
            for k, v in new_args:
                if k in newargs:
                    newargs[k].append(v)
                else:
                    newargs[k] = [v]
            newquery = "&".join(["%s=%s" % (k, v) for k in newargs for v in newargs[k]])
            if new_path:
                new_url = new_path
            elif req.prepath:
                new_url = req.prepath[-1]
            else:
                new_url = ""
            if newquery:
                new_url += "?" + newquery
            return new_url

        if timestamps:
            data += "<tr>"
            bottom = timestamps[-1]
            nextpage = with_args(request, ["last_time"], [("last_time", str(int(bottom)))])
            data += '<td class="Time"><a href="%s">next page</a></td>\n' % nextpage
            data += "</tr>"

        data += "</tfoot>\n"
        data += "</table>\n"

        return data