# HG changeset patch # User cmlenz # Date 1124360061 0 # Node ID 0e21778c04ef5bc50b24f6942458ab982a9b317c # Parent 75a6af157f05a63de9d92275314f9e76fd6de73f Refactoring: split up the components and templates that render the web interface. diff --git a/bitten/trac_ext/api.py b/bitten/trac_ext/api.py new file mode 100644 --- /dev/null +++ b/bitten/trac_ext/api.py @@ -0,0 +1,33 @@ +# -*- coding: iso8859-1 -*- +# +# Copyright (C) 2005 Christopher Lenz +# +# Bitten is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# Trac is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# Author: Christopher Lenz + +from trac.core import * + + +class ILogFormatter(Interface): + """Extension point interface for components that format build log + messages.""" + + def get_formatter(req, build, step, type): + """Return a function that gets called for every log message. + + The function must take two positional arguments, `level` and `message`, + and return the formatted message. + """ diff --git a/bitten/trac_ext/htdocs/bitten.css b/bitten/trac_ext/htdocs/bitten.css new file mode 100644 --- /dev/null +++ b/bitten/trac_ext/htdocs/bitten.css @@ -0,0 +1,31 @@ +/* Timeline styles */ +#content.timeline dt.successbuild, #content.timeline dt.successbuild a, +#content.timeline dt.failedbuild, #content.timeline dt.failedbuild a { + background-image: url(bitten_build.png) !important +} + +#content.build form.config td.active { vertical-align: bottom; } +#content.build form.platforms ul { list-style: none; padding-left: 1em; } + +#content.build #builds { margin-top: 2em; } +#content.build #builds th.rev { width: 6em; } +#content.build #builds td :link, #content.build #builds td :visited { + font-weight: bold; +} +#content.build #builds td.completed { background: #9d9; } +#content.build #builds td.failed { background: #d99; } +#content.build #builds td.in-progress { background: #ff9; } + +#content.build .reports { + background: #d7d7d7; + float: right; + font-size: 90%; + margin: .5em 0 .5em 1em; + padding: .5em; +} +#content.build .reports h3 { margin: 0; } +#content.build .reports ul { margin: 0; padding: 0; list-style: none; } + +#content.build .log { clear: right; overflow: auto; white-space: pre; } +#content.build .log .warning { color: #660; font-weight: bold; } +#content.build .log .error { color: #900; font-weight: bold; } diff --git a/bitten/trac_ext/htdocs/bitten_build.png b/bitten/trac_ext/htdocs/bitten_build.png new file mode 100755 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1e2f5669121b121d8119ca1ca05ff66663a41075 GIT binary patch literal 300 zc%17D@N?(olHy`uVBq!ia0vp^JRr=%3?!FCJ6-`&%*9TgAsieWw;%dH0CH6Vd_r9R z|NsB&*|Vd^PoG~B(Kl^QOH0e_8mF|hwBjhusHi9_D=SMa-YEH<-$7=T1o;Is{6_%J zBLQkaan1sd$YKTtZXpn6ymYtj4^U9C#5JNMI6tkVJh3R1!8b9vC_gtfB{NaMEwd=K zJijQrSiwZk;FX$sDNwN(NU?KKYGO%dex5=|W^O8jfw{hcp}v8s@ER``pb8657srr_ zImrnLAwek&Oo7(}7}?m4P0(;<6-c-A$O#MC$vB}aC1N7aBcp=tk_|6qZ|sy!=t^W} ZIP#C{{ok5>vOqH!JYD@<);T3K0RZWzW`O_z diff --git a/bitten/trac_ext/htdocs/build.css b/bitten/trac_ext/htdocs/build.css deleted file mode 100644 --- a/bitten/trac_ext/htdocs/build.css +++ /dev/null @@ -1,31 +0,0 @@ -/* Timeline styles */ -#content.timeline dt.successbuild, #content.timeline dt.successbuild a, -#content.timeline dt.failedbuild, #content.timeline dt.failedbuild a { - background-image: url(build.png) !important -} - -#content.build form.config td.active { vertical-align: bottom; } -#content.build form.platforms ul { list-style: none; padding-left: 1em; } - -#content.build #builds { margin-top: 2em; } -#content.build #builds th.rev { width: 6em; } -#content.build #builds td :link, #content.build #builds td :visited { - font-weight: bold; -} -#content.build #builds td.completed { background: #9d9; } -#content.build #builds td.failed { background: #d99; } -#content.build #builds td.in-progress { background: #ff9; } - -#content.build .reports { - background: #d7d7d7; - float: right; - font-size: 90%; - margin: .5em 0 .5em 1em; - padding: .5em; -} -#content.build .reports h3 { margin: 0; } -#content.build .reports ul { margin: 0; padding: 0; list-style: none; } - -#content.build .log { clear: right; overflow: auto; white-space: pre; } -#content.build .log .warning { color: #660; font-weight: bold; } -#content.build .log .error { color: #900; font-weight: bold; } diff --git a/bitten/trac_ext/htdocs/build.png b/bitten/trac_ext/htdocs/build.png deleted file mode 100755 index 1e2f5669121b121d8119ca1ca05ff66663a41075..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@ + +
+

+

Triggered by: Changeset [] of

+

Built by: ( on )

+

Completed: ( ago)
Took:

+

()

+

Generated Reports

  • +
+

+

+
+ diff --git a/bitten/trac_ext/templates/bitten_config.cs b/bitten/trac_ext/templates/bitten_config.cs new file mode 100644 --- /dev/null +++ b/bitten/trac_ext/templates/bitten_config.cs @@ -0,0 +1,155 @@ + + +
+

+

+
+ + +
+
+ + + + + + + + +
+ +

+ +
+
+ + + +
+
+
+

Target Platforms

    +
  • +
+
+ + +
+
+
    +
  • Active: yesno
  • +
  • Path:
+
+ + +
+ +
Changeset
[] ( )
started agotook
+
+
+

Rules

+ + + + + + + +
Property nameMatch pattern
+
+
+
+ + + + +
+
+ + + diff --git a/bitten/trac_ext/templates/build.cs b/bitten/trac_ext/templates/build.cs deleted file mode 100644 --- a/bitten/trac_ext/templates/build.cs +++ /dev/null @@ -1,184 +0,0 @@ - - -
-

-

-
- - -
-
- - - - - - - - -
- -

- -
-
- - - -
-
-
-

Target Platforms

    -
  • -
-
- - -
-
-
    -
  • Active: yesno
  • -
  • Path:
-
- - -
- -
Changeset
[] ( )
started agotook
-
-
-

Rules

- - - - - - - -
Property nameMatch pattern
-
-
-
- - - - -
-
- -

Triggered by: Changeset [] of

-

Built by: ( on )

-

Completed: ( ago)
Took:

-

()

- -

Generated Reports

    - -
  • - -
- -

-

- - - diff --git a/bitten/trac_ext/tests/web_ui.py b/bitten/trac_ext/tests/web_ui.py --- a/bitten/trac_ext/tests/web_ui.py +++ b/bitten/trac_ext/tests/web_ui.py @@ -7,10 +7,10 @@ from trac.web.main import Request, RequestDone from bitten.model import BuildConfig, TargetPlatform, Build, schema from bitten.trac_ext.main import BuildSystem -from bitten.trac_ext.web_ui import BuildModule +from bitten.trac_ext.web_ui import BuildConfigController -class BuildModuleTestCase(unittest.TestCase): +class BuildConfigControllerTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() @@ -36,7 +36,7 @@ perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) @@ -49,7 +49,7 @@ perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) @@ -65,7 +65,7 @@ perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) @@ -82,7 +82,7 @@ perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) @@ -94,7 +94,7 @@ hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) @@ -114,7 +114,7 @@ 'description': 'Bla bla'}) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build/test', redirected_to[0]) @@ -138,7 +138,7 @@ args={'action': 'new', 'cancel': '1', 'name': 'test'}) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build', redirected_to[0]) @@ -155,7 +155,7 @@ hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) @@ -179,7 +179,7 @@ 'description': 'Bla bla'}) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build/foo', redirected_to[0]) @@ -209,7 +209,7 @@ args={'action': 'edit', 'cancel': '1'}) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build/test', redirected_to[0]) @@ -225,7 +225,7 @@ hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) @@ -246,7 +246,7 @@ hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build/test?action=edit', redirected_to[0]) @@ -266,7 +266,7 @@ hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build/test?action=edit', redirected_to[0]) @@ -287,7 +287,7 @@ hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) @@ -315,7 +315,7 @@ perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build/test?action=edit', redirected_to[0]) @@ -342,14 +342,14 @@ perm=PermissionCache(self.env, 'joe')) req.hdf['htdocs_location'] = '/htdocs' - module = BuildModule(self.env) + module = BuildConfigController(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual('/trac.cgi/build/test?action=edit', redirected_to[0]) def suite(): - return unittest.makeSuite(BuildModuleTestCase, 'test') + return unittest.makeSuite(BuildConfigControllerTestCase, 'test') if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/bitten/trac_ext/web_ui.py b/bitten/trac_ext/web_ui.py --- a/bitten/trac_ext/web_ui.py +++ b/bitten/trac_ext/web_ui.py @@ -31,35 +31,54 @@ from trac.wiki import wiki_to_html from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, BuildLog from bitten.store import ReportStore - - -class ILogFormatter(Interface): - """Extension point interface for components that format build log - messages.""" +from bitten.trac_ext.api import ILogFormatter - def get_formatter(req, build, step, type): - """Return a function that gets called for every log message. - - The function must take two positional arguments, `level` and `message`, - and return the formatted message. - """ +_status_label = {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': env.href.build(build.config, build.id), + 'chgset_href': env.href.changeset(build.rev)} + if build.started: + hdf['started'] = strftime('%x %X', localtime(build.started)) + hdf['started_delta'] = pretty_timedelta(build.started) + if build.stopped: + hdf['stopped'] = strftime('%x %X', localtime(build.stopped)) + hdf['stopped_delta'] = pretty_timedelta(build.stopped) + hdf['duration'] = pretty_timedelta(build.stopped, build.started) + hdf['slave'] = { + 'name': build.slave, + 'ip_address': build.slave_info.get(Build.IP_ADDRESS), + 'os': 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 + +class BittenChrome(Component): + """Provides the Bitten templates and static resources.""" + + implements(ITemplateProvider) + + # ITemplatesProvider methods + + def get_htdocs_dir(self): + return pkg_resources.resource_filename(__name__, 'htdocs') + + def get_templates_dir(self): + return pkg_resources.resource_filename(__name__, 'templates') -class BuildModule(Component): - """Implements the Bitten web interface.""" - - implements(INavigationContributor, IRequestHandler, ITimelineEventProvider, - ITemplateProvider) +class BuildConfigController(Component): + """Implements the web interface for build configurations.""" - log_formatters = ExtensionPoint(ILogFormatter) - - _status_label = {Build.IN_PROGRESS: 'in progress', - Build.SUCCESS: 'completed', - Build.FAILURE: 'failed'} - _level_label = {BuildLog.DEBUG: 'debug', - BuildLog.INFO: 'info', - BuildLog.WARNING: 'warning', - BuildLog.ERROR: 'error'} + implements(INavigationContributor, IRequestHandler) # INavigationContributor methods @@ -76,12 +95,10 @@ # IRequestHandler methods def match_request(self, req): - match = re.match(r'/build(?:/([\w.-]+))?(?:/([\d]+))?$', req.path_info) + match = re.match(r'/build(?:/([\w.-]+))?$', req.path_info) if match: if match.group(1): req.args['config'] = match.group(1) - if match.group(2): - req.args['id'] = match.group(2) return True def process_request(self, req): @@ -89,7 +106,6 @@ action = req.args.get('action') config = req.args.get('config') - id = req.args.get('id') if req.method == 'POST': if config: @@ -112,9 +128,7 @@ if action == 'new': self._do_create_config(req) else: - if id: - self._render_build(req, id) - elif config: + if config: if action == 'edit': platform_id = req.args.get('platform') if platform_id: @@ -134,46 +148,8 @@ else: self._render_overview(req) - add_stylesheet(req, 'build.css') - return 'build.cs', None - - # ITemplatesProvider methods - - def get_htdocs_dir(self): - return pkg_resources.resource_filename(__name__, 'htdocs') - - def get_templates_dir(self): - return pkg_resources.resource_filename(__name__, 'templates') - - # ITimelineEventProvider methods - - def get_timeline_filters(self, req): - if req.perm.has_permission('BUILD_VIEW'): - yield ('build', 'Builds') - - def get_timeline_events(self, req, start, stop, filters): - if 'build' in filters: - add_stylesheet(req, 'build.css') - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT b.id,b.config,c.label,b.rev,p.name,b.slave," - "b.stopped,b.status FROM bitten_build AS b" - " INNER JOIN bitten_config AS c ON (c.name=b.config)" - " INNER JOIN bitten_platform AS p ON (p.id=b.platform) " - "WHERE b.stopped>=%s AND b.stopped<=%s " - "AND b.status IN (%s, %s) ORDER BY b.stopped", - (start, stop, Build.SUCCESS, Build.FAILURE)) - event_kinds = {Build.SUCCESS: 'successbuild', - Build.FAILURE: 'failedbuild'} - for id, config, label, rev, platform, slave, stopped, status in cursor: - title = 'Build of %s [%s] by %s (%s) %s' \ - % (escape(label), escape(rev), escape(slave), - escape(platform), self._status_label[status]) - if req.args.get('format') == 'rss': - href = self.env.abs_href.build(config, id) - else: - href = self.env.href.build(config, id) - yield event_kinds[status], href, title, stopped, None, '' + add_stylesheet(req, 'bitten.css') + return 'bitten_config.cs', None # Internal methods @@ -343,7 +319,7 @@ for build in Build.select(self.env, config=config.name, rev=rev): if build.status == Build.PENDING: continue - req.hdf['%s.%s' % (prefix, build.platform)] = self._build_to_hdf(req, build) + req.hdf['%s.%s' % (prefix, build.platform)] = _build_to_hdf(self.env, req, build) if idx > 4: break except TracError, e: @@ -387,47 +363,59 @@ } req.hdf['page.mode'] = 'edit_platform' - def _render_build(self, req, build_id): - build = Build.fetch(self.env, build_id) + +class BuildController(Component): + """Renders the build page.""" + implements(INavigationContributor, IRequestHandler, ITimelineEventProvider) + + log_formatters = ExtensionPoint(ILogFormatter) + + _level_label = {BuildLog.DEBUG: 'debug', + BuildLog.INFO: 'info', + BuildLog.WARNING: 'warning', + BuildLog.ERROR: 'error'} + + # INavigationContributor methods + + def get_active_navigation_item(self, req): + return 'build' + + def get_navigation_items(self, req): + return [] + + # IRequestHandler methods + + def match_request(self, req): + match = re.match(r'/build/([\w.-]+)/([\d]+)', req.path_info) + if match: + if match.group(1): + req.args['config'] = match.group(1) + if match.group(2): + req.args['id'] = match.group(2) + return True + + def process_request(self, req): + req.perm.assert_permission('BUILD_VIEW') + + db = self.env.get_db_cnx() + build_id = int(req.args.get('id')) + build = Build.fetch(self.env, build_id, db=db) assert build, 'Build %s does not exist' % build_id + add_link(req, 'up', self.env.href.build(build.config), '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['build'] = self._build_to_hdf(req, build, include_output=True) req.hdf['page.mode'] = 'view_build' - - config = BuildConfig.fetch(self.env, build.config) + config = BuildConfig.fetch(self.env, build.config, db=db) req.hdf['build.config'] = { 'name': config.label, 'href': self.env.href.build(config.name) } - def _build_to_hdf(self, req, build, include_output=False): - hdf = {'id': build.id, 'name': build.slave, 'rev': build.rev, - 'status': self._status_label[build.status], - 'cls': self._status_label[build.status].replace(' ', '-'), - 'href': self.env.href.build(build.config, build.id), - 'chgset_href': self.env.href.changeset(build.rev)} - if build.started: - hdf['started'] = strftime('%x %X', localtime(build.started)) - hdf['started_delta'] = pretty_timedelta(build.started) - if build.stopped: - hdf['stopped'] = strftime('%x %X', localtime(build.stopped)) - hdf['stopped_delta'] = pretty_timedelta(build.stopped) - hdf['duration'] = pretty_timedelta(build.stopped, build.started) - hdf['slave'] = { - 'name': build.slave, - 'ip_address': build.slave_info.get(Build.IP_ADDRESS), - 'os': 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) - } - db = self.env.get_db_cnx() + req.hdf['build'] = _build_to_hdf(self.env, req, build) steps = [] for step in BuildStep.select(self.env, build=build.id, db=db): steps.append({ @@ -435,33 +423,62 @@ 'duration': pretty_timedelta(step.started, step.stopped), 'failed': step.status == BuildStep.FAILURE }) - if include_output: - for log in BuildLog.select(self.env, build=build.id, - step=step.name, db=db): - formatters = [] - items = [] - for formatter in self.log_formatters: - formatters.append(formatter.get_formatter(req, build, - step, - log.type)) - for level, message in log.messages: - for format in formatters: - message = format(level, message) - items.append({'level': level, 'message': message}) - steps[-1]['log'] = items + for log in BuildLog.select(self.env, build=build.id, + step=step.name, db=db): + formatters = [] + items = [] + for formatter in self.log_formatters: + formatters.append(formatter.get_formatter(req, build, + step, + log.type)) + for level, message in log.messages: + for format in formatters: + message = format(level, message) + items.append({'level': level, 'message': message}) + steps[-1]['log'] = items - store = ReportStore(self.env) - reports = [] - for report in store.retrieve_reports(build, step): - report_type = report.attr['type'] - report_href = self.env.href.buildreport(build.id, step.name, - report_type) - reports.append({'type': report_type, 'href': report_href}) - steps[-1]['reports'] = reports + store = ReportStore(self.env) + reports = [] + for report in store.retrieve_reports(build, step): + report_type = report.attr['type'] + report_href = self.env.href.buildreport(build.id, step.name, + report_type) + reports.append({'type': report_type, 'href': report_href}) + steps[-1]['reports'] = reports + req.hdf['build.steps'] = steps - hdf['steps'] = steps + add_stylesheet(req, 'bitten.css') + return 'bitten_build.cs', None - return hdf + # ITimelineEventProvider methods + + def get_timeline_filters(self, req): + if req.perm.has_permission('BUILD_VIEW'): + yield ('build', 'Builds') + + def get_timeline_events(self, req, start, stop, filters): + if 'build' in filters: + add_stylesheet(req, 'bitten.css') + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute("SELECT b.id,b.config,c.label,b.rev,p.name,b.slave," + "b.stopped,b.status FROM bitten_build AS b" + " INNER JOIN bitten_config AS c ON (c.name=b.config)" + " INNER JOIN bitten_platform AS p ON (p.id=b.platform) " + "WHERE b.stopped>=%s AND b.stopped<=%s " + "AND b.status IN (%s, %s) ORDER BY b.stopped", + (start, stop, Build.SUCCESS, Build.FAILURE)) + event_kinds = {Build.SUCCESS: 'successbuild', + Build.FAILURE: 'failedbuild'} + for id, config, label, rev, platform, slave, stopped, status in cursor: + title = 'Build of %s [%s] by %s (%s) %s' \ + % (escape(label), escape(rev), escape(slave), + escape(platform), _status_label[status]) + if req.args.get('format') == 'rss': + href = self.env.abs_href.build(config, id) + else: + href = self.env.href.build(config, id) + yield event_kinds[status], href, title, stopped, None, '' class SourceFileLinkFormatter(Component): @@ -495,7 +512,7 @@ return _formatter -class BuildReportView(Component): +class BuildReportController(Component): """Temporary web interface that simply displays the XML source of a report using the Trac `Mimeview` component."""