diff bitten/web_ui.py @ 503:a7c795920c4a

Merging trac-0.11 branch to trunk. This revision is equivalent to [571].
author wbell
date Mon, 09 Mar 2009 00:46:14 +0000
parents f10da0985227
children e2eef154f1af
line wrap: on
line diff
--- a/bitten/web_ui.py
+++ b/bitten/web_ui.py
@@ -16,65 +16,51 @@
 from StringIO import StringIO
 
 import pkg_resources
+from genshi.builder import tag
 from trac.core import *
-try:
-    from trac.timeline import ITimelineEventProvider
-    have_trac_011 = True
-except ImportError:
-    from trac.Timeline import ITimelineEventProvider
-    have_trac_011 = False
+from trac.timeline import ITimelineEventProvider
 from trac.util import escape, pretty_timedelta, format_datetime, shorten_line, \
                       Markup
 from trac.util.html import html
 from trac.web import IRequestHandler
 from trac.web.chrome import INavigationContributor, ITemplateProvider, \
-                            add_link, add_stylesheet
-from trac.wiki import wiki_to_html
-from trac.wiki import wiki_to_oneliner as wiki_to_oneliner_
+                            add_link, add_stylesheet, add_ctxtnav, \
+                            prevnext_nav, add_script
+from trac.wiki import wiki_to_html, wiki_to_oneliner
 from bitten.api import ILogFormatter, IReportChartGenerator, IReportSummarizer
 from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \
                          BuildLog, Report
 from bitten.queue import collect_changes
 
-def wiki_to_oneliner(wikitext, env, db=None, shorten=False, absurls=False,
-                      req=None):
-    if have_trac_011:
-        return wiki_to_oneliner_(wikitext, env, db=db, shorten=shorten,
-                                 absurls=absurls, req=req)
-    else:
-        return wiki_to_oneliner_(wikitext, env, db=db, shorten=shorten,
-                                 absurls=absurls)
-
-
 _status_label = {Build.PENDING: 'pending',
                  Build.IN_PROGRESS: 'in progress',
                  Build.SUCCESS: 'completed',
                  Build.FAILURE: 'failed'}
 
-def _build_to_hdf(env, req, build):
-    hdf = {'id': build.id, 'name': build.slave, 'rev': build.rev,
-           'status': _status_label[build.status],
-           'cls': _status_label[build.status].replace(' ', '-'),
-           'href': req.href.build(build.config, build.id),
-           'chgset_href': req.href.changeset(build.rev)}
+def _get_build_data(env, req, build):
+    data = {'id': build.id, 'name': build.slave, 'rev': build.rev,
+            'status': _status_label[build.status],
+            'cls': _status_label[build.status].replace(' ', '-'),
+            'href': req.href.build(build.config, build.id),
+            'chgset_href': req.href.changeset(build.rev)}
     if build.started:
-        hdf['started'] = format_datetime(build.started)
-        hdf['started_delta'] = pretty_timedelta(build.started)
-        hdf['duration'] = pretty_timedelta(build.started)
+        data['started'] = format_datetime(build.started)
+        data['started_delta'] = pretty_timedelta(build.started)
+        data['duration'] = pretty_timedelta(build.started)
     if build.stopped:
-        hdf['stopped'] = format_datetime(build.stopped)
-        hdf['stopped_delta'] = pretty_timedelta(build.stopped)
-        hdf['duration'] = pretty_timedelta(build.stopped, build.started)
-    hdf['slave'] = {
+        data['stopped'] = format_datetime(build.stopped)
+        data['stopped_delta'] = pretty_timedelta(build.stopped)
+        data['duration'] = pretty_timedelta(build.stopped, build.started)
+    data['slave'] = {
         'name': build.slave,
         'ipnr': build.slave_info.get(Build.IP_ADDRESS),
-        'os.name': build.slave_info.get(Build.OS_NAME),
-        'os.family': build.slave_info.get(Build.OS_FAMILY),
-        'os.version': build.slave_info.get(Build.OS_VERSION),
+        'os_name': build.slave_info.get(Build.OS_NAME),
+        'os_family': build.slave_info.get(Build.OS_FAMILY),
+        'os_version': build.slave_info.get(Build.OS_VERSION),
         'machine': build.slave_info.get(Build.MACHINE),
         'processor': build.slave_info.get(Build.PROCESSOR)
     }
-    return hdf
+    return data
 
 
 class BittenChrome(Component):
@@ -95,11 +81,9 @@
     def get_navigation_items(self, req):
         """Return the navigation item for access the build status overview from
         the Trac navigation bar."""
-        if not req.perm.has_permission('BUILD_VIEW'):
-            return
-        yield ('mainnav', 'build', \
-               Markup('<a href="%s" accesskey="5">Build Status</a>') %
-                      req.href.build())
+        if 'BUILD_VIEW' in req.perm:
+            yield ('mainnav', 'build',
+                   tag.a('Builds Status', href=req.href.build(), accesskey=5))
 
     # ITemplatesProvider methods
 
@@ -127,43 +111,44 @@
             return True
 
     def process_request(self, req):
-        req.perm.assert_permission('BUILD_VIEW')
+        req.perm.require('BUILD_VIEW')
 
         action = req.args.get('action')
         view = req.args.get('view')
         config = req.args.get('config')
 
         if config:
-            self._render_config(req, config)
+            data = self._render_config(req, config)
         elif view == 'inprogress':
-            self._render_inprogress(req)
+            data = self._render_inprogress(req)
         else:
-            self._render_overview(req)
+            data = self._render_overview(req)
 
         add_stylesheet(req, 'bitten/bitten.css')
-        return 'bitten_config.cs', None
+        return 'bitten_config.html', data, None
 
     # Internal methods
 
     def _render_overview(self, req):
-        req.hdf['title'] = 'Build Status'
+        data = {'title': 'Build Status'}
         show_all = False
         if req.args.get('show') == 'all':
             show_all = True
-        req.hdf['config.show_all'] = show_all
+        data['show_all'] = show_all
 
-        configs = BuildConfig.select(self.env, include_inactive=show_all)
-        for idx, config in enumerate(configs):
-            prefix = 'configs.%d' % idx
+        configs = []
+        for config in BuildConfig.select(self.env, include_inactive=show_all):
             description = config.description
             if description:
                 description = wiki_to_html(description, self.env, req)
-            req.hdf[prefix] = {
+            config_data = {
                 'name': config.name, 'label': config.label or config.name,
                 'active': config.active, 'path': config.path,
                 'description': description,
                 'href': req.href.build(config.name),
+                'builds': []
             }
+            configs.append(config_data)
             if not config.active:
                 continue
 
@@ -176,69 +161,74 @@
                 if rev != prev_rev:
                     if prev_rev is None:
                         chgset = repos.get_changeset(rev)
-                        req.hdf[prefix + '.youngest_rev'] = {
+                        config_data['youngest_rev'] = {
                             'id': rev, 'href': req.href.changeset(rev),
                             'author': chgset.author or 'anonymous',
                             'date': format_datetime(chgset.date),
                             'message': wiki_to_oneliner(
-                                shorten_line(chgset.message), self.env, req=req
-                            )
+                                shorten_line(chgset.message), self.env, req=req)
                         }
                     else:
                         break
                     prev_rev = rev
                 if build:
-                    build_hdf = _build_to_hdf(self.env, req, build)
-                    build_hdf['platform'] = platform.name
-                    req.hdf[prefix + '.builds.%d' % platform.id] = build_hdf
+                    build_data = _get_build_data(self.env, req, build)
+                    build_data['platform'] = platform.name
+                    config_data['builds'].append(build_data)
                 else:
-                    req.hdf[prefix + '.builds.%d' % platform.id] = {
+                    config_data['builds'].append({
                         'platform': platform.name, 'status': 'pending'
-                    }
+                    })
 
-        req.hdf['page.mode'] = 'overview'
+        data['configs'] = configs
+        data['page_mode'] = 'overview'
         add_link(req, 'views', req.href.build(view='inprogress'),
                  'In Progress Builds')
+        add_ctxtnav(req, 'In Progress Builds',
+                    req.href.build(view='inprogress'))
+        return data
 
     def _render_inprogress(self, req):
-        req.hdf['title'] = 'In Progress Builds'
+        data = {'title': 'In Progress Builds',
+                'page_mode': 'view-inprogress'}
 
         db = self.env.get_db_cnx()
 
-        configs = BuildConfig.select(self.env, include_inactive=False)
-        for idx, config in enumerate(configs):
+        configs = []
+        for config in BuildConfig.select(self.env, include_inactive=False):
+            self.log.debug(config.name)
             if not config.active:
                 continue
 
             in_progress_builds = Build.select(self.env, config=config.name,
                                               status=Build.IN_PROGRESS, db=db)
 
-            # sort correctly by revision.
-            builds = list(in_progress_builds)
-            builds.sort(lambda x, y: int(y.rev) - int(x.rev))
-            prefix = 'configs.%d' % idx
-
             current_builds = 0
-            for idx2, build in enumerate(builds):
-                prefix2 = '%s.builds.%d' % (prefix, idx2)
+            builds = []
+            # sort correctly by revision.
+            for build in sorted(in_progress_builds,
+                                cmp=lambda x, y: int(y.rev) - int(x.rev)):
                 rev = build.rev
-                req.hdf[prefix2] = _build_to_hdf(self.env, req, build)
-                req.hdf[prefix2 + '.rev'] = rev
-                req.hdf[prefix2 + '.rev_href'] = req.href.changeset(rev)
+                build_data = _get_build_data(self.env, req, build)
+                build_data['rev'] = rev
+                build_data['rev_href'] = req.href.changeset(rev)
                 platform = TargetPlatform.fetch(self.env, build.platform)
-                req.hdf[prefix2 + '.platform'] = platform.name
+                build_data['platform'] = platform.name
+                build_data['steps'] = []
 
                 for step in BuildStep.select(self.env, build=build.id, db=db):
-                    req.hdf['%s.steps.%s' % (prefix2, step.name)] = {
-                         'description': step.description,
-                         'duration': datetime.fromtimestamp(step.stopped) - \
-                                     datetime.fromtimestamp(step.started),
-                         'failed': not step.successful,
-                         'errors': step.errors,
-                         'href': req.hdf[prefix2 + '.href'] + '#step_' + step.name
-                    }
+                    build_data['steps'].append({
+                        'name': step.name,
+                        'description': step.description,
+                        'duration': datetime.fromtimestamp(step.stopped) - \
+                                    datetime.fromtimestamp(step.started),
+                        'failed': not step.successful,
+                        'errors': step.errors,
+                        'href': build_data['href'] + '#step_' + step.name
+                    })
 
-                current_builds = current_builds + 1
+                builds.append(build_data)
+                current_builds += 1
 
             if current_builds == 0: 
                 continue
@@ -246,26 +236,29 @@
             description = config.description
             if description:
                 description = wiki_to_html(description, self.env, req)
-            req.hdf[prefix] = {
+            configs.append({
                 'name': config.name, 'label': config.label or config.name,
                 'active': config.active, 'path': config.path,
                 'description': description,
                 'href': req.href.build(config.name),
-            }
+                'builds': builds
+            })
 
-        req.hdf['page.mode'] = 'view_inprogress'
+        data['configs'] = configs
+        return data
 
     def _render_config(self, req, config_name):
         db = self.env.get_db_cnx()
 
         config = BuildConfig.fetch(self.env, config_name, db=db)
-        req.hdf['title'] = 'Build Configuration "%s"' \
-                           % config.label or config.name
+        data = {'title': 'Build Configuration "%s"' \
+                          % config.label or config.name,
+                'page_mode': 'view_config'}
         add_link(req, 'up', req.href.build(), 'Build Status')
         description = config.description
         if description:
             description = wiki_to_html(description, self.env, req)
-        req.hdf['config'] = {
+        data['config'] = {
             'name': config.name, 'label': config.label, 'path': config.path,
             'min_rev': config.min_rev,
             'min_rev_href': req.href.changeset(config.min_rev),
@@ -274,12 +267,12 @@
             'active': config.active, 'description': description,
             'browser_href': req.href.browser(config.path)
         }
-        req.hdf['page.mode'] = 'view_config'
 
         platforms = list(TargetPlatform.select(self.env, config=config_name,
                                                db=db))
-        req.hdf['config.platforms'] = [
-            {'name': platform.name, 'id': platform.id} for platform in platforms
+        data['config']['platforms'] = [
+            {'name': platform.name, 'id': platform.id}
+            for platform in platforms
         ]
 
         has_reports = False
@@ -294,14 +287,14 @@
                     chart_generators.append({
                         'href': req.href.build(config.name, 'chart/' + category) 
                     })
-            req.hdf['config.charts'] = chart_generators 
+            data['config']['charts'] = chart_generators 
             charts_license = self.config.get('bitten', 'charts_license')
             if charts_license:
-                req.hdf['config.charts_license'] = charts_license
+                data['config']['charts_license'] = charts_license
 
         page = max(1, int(req.args.get('page', 1)))
         more = False
-        req.hdf['page.number'] = page
+        data['page_number'] = page
 
         repos = self.env.get_repository(req.authname)
         if hasattr(repos, 'sync'):
@@ -309,28 +302,31 @@
 
         builds_per_page = 12 * len(platforms)
         idx = 0
+        builds = {}
         for platform, rev, build in collect_changes(repos, config):
             if idx >= page * builds_per_page:
                 more = True
                 break
             elif idx >= (page - 1) * builds_per_page:
-                prefix = 'config.builds.%s' % rev
-                req.hdf[prefix + '.href'] = req.href.changeset(rev)
+                builds.setdefault(rev, {})
+                builds[rev].setdefault('href', req.href.changeset(rev))
                 if build and build.status != Build.PENDING:
-                    build_hdf = _build_to_hdf(self.env, req, build)
-                    req.hdf['%s.%s' % (prefix, platform.id)] = build_hdf
+                    build_data = _get_build_data(self.env, req, build)
+                    build_data['steps'] = []
                     for step in BuildStep.select(self.env, build=build.id,
                                                  db=db):
-                        req.hdf['%s.%s.steps.%s' % (prefix, platform.id,
-                                                    step.name)] = {
+                        build_data['steps'].append({
+                            'name': step.name,
                             'description': step.description,
                             'duration': datetime.fromtimestamp(step.stopped) - \
                                         datetime.fromtimestamp(step.started),
                             'failed': not step.successful,
                             'errors': step.errors,
-                            'href': build_hdf['href'] + '#step_' + step.name,
-                        }
+                            'href': build_data['href'] + '#step_' + step.name
+                        })
+                    builds[rev][platform.id] = build_data
             idx += 1
+        data['config']['builds'] = builds
 
         if page > 1:
             if page == 2:
@@ -341,6 +337,8 @@
         if more:
             next_href = req.href.build(config.name, page=page + 1)
             add_link(req, 'next', next_href, 'Next Page')
+        prevnext_nav(req, 'Page')
+        return data
 
 
 class BuildController(Component):
@@ -370,7 +368,7 @@
             return True
 
     def process_request(self, req):
-        req.perm.assert_permission('BUILD_VIEW')
+        req.perm.require('BUILD_VIEW')
 
         db = self.env.get_db_cnx()
         build_id = int(req.args.get('id'))
@@ -386,11 +384,12 @@
                  'Build Configuration')
         status2title = {Build.SUCCESS: 'Success', Build.FAILURE: 'Failure',
                         Build.IN_PROGRESS: 'In Progress'}
-        req.hdf['title'] = 'Build %s - %s' % (build_id,
-                                              status2title[build.status])
-        req.hdf['page.mode'] = 'view_build'
+        data = {'title': 'Build %s - %s' % (build_id,
+                                            status2title[build.status]),
+                'page_mode': 'view_build',
+                'build': {}}
         config = BuildConfig.fetch(self.env, build.config, db=db)
-        req.hdf['build.config'] = {
+        data['build']['config'] = {
             'name': config.label,
             'href': req.href.build(config.name)
         }
@@ -404,7 +403,7 @@
             categories = summarizer.get_supported_categories()
             summarizers.update(dict([(cat, summarizer) for cat in categories]))
 
-        req.hdf['build'] = _build_to_hdf(self.env, req, build)
+        data['build'].update(_get_build_data(self.env, req, build))
         steps = []
         for step in BuildStep.select(self.env, build=build.id, db=db):
             steps.append({
@@ -416,20 +415,21 @@
                 'reports': self._render_reports(req, config, build, summarizers,
                                                 step)
             })
-        req.hdf['build.steps'] = steps
-        req.hdf['build.can_delete'] = req.perm.has_permission('BUILD_DELETE')
+        data['build']['steps'] = steps
+        data['build']['can_delete'] = ('BUILD_DELETE' in req.perm)
 
         repos = self.env.get_repository(req.authname)
         chgset = repos.get_changeset(build.rev)
-        req.hdf['build.chgset_author'] = chgset.author
+        data['build']['chgset_author'] = chgset.author
 
+        add_script(req, 'bitten/tabset.js')
         add_stylesheet(req, 'bitten/bitten.css')
-        return 'bitten_build.cs', None
+        return 'bitten_build.html', data, None
 
     # ITimelineEventProvider methods
 
     def get_timeline_filters(self, req):
-        if req.perm.has_permission('BUILD_VIEW'):
+        if 'BUILD_VIEW' in req.perm:
             yield ('build', 'Builds')
 
     def get_timeline_events(self, req, start, stop, filters):
@@ -465,9 +465,8 @@
                     errors += [(step.name, error) for error
                                in step.errors]
 
-            title = Markup('Build of <em>%s [%s]</em> on %s %s') % (
-                label, rev, platform, _status_label[status]
-            )
+            title = tag('Build of ', tag.em('%s [%s]' % (label, rev)),
+                        ' on %s %s' % (platform, _status_label[status]))
             message = ''
             if req.args.get('format') == 'rss':
                 href = req.abs_href.build(config, id)
@@ -536,12 +535,12 @@
         for report in Report.select(self.env, build=build.id, step=step.name):
             summarizer = summarizers.get(report.category)
             if summarizer:
-                summary = summarizer.render_summary(req, config, build, step,
-                                                    report.category)
+                tmpl, data = summarizer.render_summary(req, config, build,
+                                                        step, report.category)
             else:
-                summary = None
+                tmpl = data = None
             reports.append({'category': report.category,
-                            'summary': Markup(summary)})
+                            'template': tmpl, 'data': data})
         return reports
 
 
@@ -565,13 +564,13 @@
 
         for generator in self.generators:
             if category in generator.get_supported_categories():
-                template = generator.generate_chart_data(req, config,
-                                                         category)
+                tmpl, data = generator.generate_chart_data(req, config,
+                                                           category)
                 break
         else:
             raise TracError('Unknown report category "%s"' % category)
 
-        return template, 'text/xml'
+        return tmpl, data, 'text/xml'
 
 
 class SourceFileLinkFormatter(Component):
@@ -609,7 +608,7 @@
             link = href(config.path, filepath)
             if m.group('line'):
                 link += '#L' + m.group('line')[1:]
-            return Markup(html.A(m.group(0), href=link))
+            return Markup(tag.a(m.group(0), href=link))
 
         def _formatter(step, type, level, message):
             buf = []
Copyright (C) 2012-2017 Edgewall Software