diff --git a/runbot/__manifest__.py b/runbot/__manifest__.py index c6f3e01b..d3ae0c49 100644 --- a/runbot/__manifest__.py +++ b/runbot/__manifest__.py @@ -21,6 +21,7 @@ 'templates/assets.xml', 'templates/dashboard.xml', 'templates/nginx.xml', + 'templates/badge.xml', 'data/runbot_cron.xml' ], } diff --git a/runbot/controllers/__init__.py b/runbot/controllers/__init__.py index 9f86673f..96d149ab 100644 --- a/runbot/controllers/__init__.py +++ b/runbot/controllers/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- from . import frontend -from . import hook \ No newline at end of file +from . import hook +from . import badge diff --git a/runbot/controllers/badge.py b/runbot/controllers/badge.py new file mode 100644 index 00000000..43351ab4 --- /dev/null +++ b/runbot/controllers/badge.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +import hashlib + +import werkzeug +from matplotlib.font_manager import FontProperties +from matplotlib.textpath import TextToPath + +from odoo.http import request, route, Controller + + +class RunbotBadge(Controller): + + @route([ + '/runbot/badge//.svg', + '/runbot/badge///.svg', + ], type="http", auth="public", methods=['GET', 'HEAD']) + def badge(self, repo_id, branch, theme='default'): + + domain = [('repo_id', '=', repo_id), + ('branch_id.branch_name', '=', branch), + ('branch_id.sticky', '=', True), + ('state', 'in', ['testing', 'running', 'done']), + ('result', 'not in', ['skipped', 'manually_killed']), + ] + + last_update = '__last_update' + builds = request.env['runbot.build'].sudo().search_read( + domain, ['state', 'result', 'job_age', last_update], + order='id desc', limit=1) + + if not builds: + return request.not_found() + + build = builds[0] + etag = request.httprequest.headers.get('If-None-Match') + retag = hashlib.md5(build[last_update].encode()).hexdigest() + + if etag == retag: + return werkzeug.wrappers.Response(status=304) + + if build['state'] == 'testing': + state = 'testing' + cache_factor = 1 + else: + cache_factor = 2 + if build['result'] == 'ok': + state = 'success' + elif build['result'] == 'warn': + state = 'warning' + else: + state = 'failed' + + # from https://github.com/badges/shields/blob/master/colorscheme.json + color = { + 'testing': "#dfb317", + 'success': "#4c1", + 'failed': "#e05d44", + 'warning': "#fe7d37", + }[state] + + def text_width(s): + fp = FontProperties(family='DejaVu Sans', size=11) + w, h, d = TextToPath().get_text_width_height_descent(s, fp, False) + return int(w + 1) + + class Text(object): + __slot__ = ['text', 'color', 'width'] + + def __init__(self, text, color): + self.text = text + self.color = color + self.width = text_width(text) + 10 + + data = { + 'left': Text(branch, '#555'), + 'right': Text(state, color), + } + five_minutes = 5 * 60 + headers = [ + ('Content-Type', 'image/svg+xml'), + ('Cache-Control', 'max-age=%d' % (five_minutes * cache_factor,)), + ('ETag', retag), + ] + return request.render("runbot.badge_" + theme, data, headers=headers) diff --git a/runbot/templates/badge.xml b/runbot/templates/badge.xml new file mode 100644 index 00000000..98f3f289 --- /dev/null +++ b/runbot/templates/badge.xml @@ -0,0 +1,47 @@ + + + + + + +