diff --git a/runbot/common.py b/runbot/common.py index 034ab99c..f9acc67f 100644 --- a/runbot/common.py +++ b/runbot/common.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import contextlib -import fcntl import itertools import logging import os @@ -14,6 +13,7 @@ from collections import OrderedDict from datetime import timedelta from babel.dates import format_timedelta +from werkzeug import utils from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT @@ -122,3 +122,25 @@ def list_local_dbs(additionnal_conditions=None): %s """ % additionnal_condition_str) return [d[0] for d in local_cr.fetchall()] + + +def pseudo_markdown(text): + text = utils.escape(text) + patterns = { + r'\*\*(.+?)\*\*': '\g<1>', + r'~~(.+?)~~': '\g<1>', # it's not official markdown but who cares + r'__(.+?)__': '\g<1>', # same here, maybe we should change the method name + r'`(.+?)`': '\g<1>', + } + + for p, b in patterns.items(): + text = re.sub(p, b, text, flags=re.DOTALL) + + # icons + re_icon = re.compile(r'@icon-([a-z0-9-]+)') + text = re_icon.sub('', text) + + # links + re_links = re.compile(r'\[(.+?)\]\((.+?)\)') + text = re_links.sub('\g<1>', text) + return text diff --git a/runbot/models/build.py b/runbot/models/build.py index 26d73a23..62ee6f1a 100644 --- a/runbot/models/build.py +++ b/runbot/models/build.py @@ -8,7 +8,7 @@ import shutil import subprocess import time import datetime -from ..common import dt2time, fqdn, now, grep, local_pgadmin_cursor, s2human, Commit, dest_reg, os, list_local_dbs +from ..common import dt2time, fqdn, now, grep, local_pgadmin_cursor, s2human, Commit, dest_reg, os, list_local_dbs, pseudo_markdown from ..container import docker_build, docker_stop, docker_state, Command from ..fields import JsonDictField from odoo.addons.runbot.models.repo import RunbotException @@ -65,6 +65,7 @@ class runbot_build(models.Model): repo_id = fields.Many2one(related='branch_id.repo_id', readonly=True, store=True) name = fields.Char('Revno', required=True) description = fields.Char('Description', help='Informative description') + md_description = fields.Char(compute='_compute_md_description', String='MD Parsed Description', help='Informative description mardown parsed') host = fields.Char('Host') port = fields.Integer('Port') dest = fields.Char(compute='_compute_dest', type='char', string='Dest', readonly=1, store=True) @@ -178,6 +179,11 @@ class runbot_build(models.Model): max_days += int(build.gc_delay if build.gc_delay else 0) build.gc_date = ref_date + datetime.timedelta(days=(max_days)) + @api.depends('description') + def _compute_md_description(self): + for build in self: + build.md_description = pseudo_markdown(build.description) + def _get_top_parent(self): self.ensure_one() build = self @@ -656,10 +662,10 @@ class runbot_build(models.Model): if build.requested_action == 'wake_up': if docker_state(build._get_docker_name(), build._path()) == 'RUNNING': build.write({'requested_action': False, 'local_state': 'running'}) - build._log('wake_up', 'Waking up failed, docker is already running', level='SEPARATOR') + build._log('wake_up', 'Waking up failed, **docker is already running**', type='markdown', level='SEPARATOR') elif not os.path.exists(build._path()): build.write({'requested_action': False, 'local_state': 'done'}) - build._log('wake_up', 'Impossible to wake-up, build dir does not exists anymore', level='SEPARATOR') + build._log('wake_up', 'Impossible to wake-up, **build dir does not exists anymore**', type='markdown', level='SEPARATOR') else: try: log_path = build._path('logs', 'wake_up.txt') @@ -673,7 +679,7 @@ class runbot_build(models.Model): 'local_state': 'running', 'port': port, }) - build._log('wake_up', 'Waking up build', level='SEPARATOR') + build._log('wake_up', '**Waking up build**', type='markdown', level='SEPARATOR') self.env['runbot.build.config.step']._run_odoo_run(build, log_path) # reload_nginx will be triggered by _run_odoo_run except Exception: diff --git a/runbot/models/build_config.py b/runbot/models/build_config.py index 1b937365..40eb6f5b 100644 --- a/runbot/models/build_config.py +++ b/runbot/models/build_config.py @@ -205,7 +205,7 @@ class ConfigStep(models.Model): def _run(self, build): log_path = build._path('logs', '%s.txt' % self.name) build.write({'job_start': now(), 'job_end': False}) # state, ... - build._log('run', 'Starting step %s from config %s' % (self.name, build.config_id.name), level='SEPARATOR') + build._log('run', 'Starting step **%s** from config **%s**' % (self.name, build.config_id.name), type='markdown', level='SEPARATOR') return self._run_step(build, log_path) def _run_step(self, build, log_path): diff --git a/runbot/models/event.py b/runbot/models/event.py index e4f7fff9..95c32be2 100644 --- a/runbot/models/event.py +++ b/runbot/models/event.py @@ -2,11 +2,12 @@ import logging -from odoo import models, fields, api, tools +from ..common import pseudo_markdown +from odoo import models, fields, tools _logger = logging.getLogger(__name__) -TYPES = [(t, t.capitalize()) for t in 'client server runbot subbuild link'.split()] +TYPES = [(t, t.capitalize()) for t in 'client server runbot subbuild link markdown'.split()] class runbot_event(models.Model): @@ -71,6 +72,12 @@ FOR EACH ROW EXECUTE PROCEDURE runbot_set_logging_build(); """) + def _markdown(self): + """ Apply pseudo markdown parser for message. + """ + self.ensure_one() + return pseudo_markdown(self.message) + class RunbotErrorLog(models.Model): _name = "runbot.error.log" diff --git a/runbot/static/src/css/runbot.css b/runbot/static/src/css/runbot.css index 5d3caee8..3340f171 100644 --- a/runbot/static/src/css/runbot.css +++ b/runbot/static/src/css/runbot.css @@ -1,6 +1,5 @@ .separator { border-top: 2px solid #666; - font-weight: bold; } [data-toggle="collapse"] .fa:before { diff --git a/runbot/templates/build.xml b/runbot/templates/build.xml index bda3e3d8..13845140 100644 --- a/runbot/templates/build.xml +++ b/runbot/templates/build.xml @@ -117,7 +117,7 @@ - Description:
+ Description:
Subject:
Author:
@@ -158,7 +158,7 @@ Build - + with config @@ -199,13 +199,13 @@ - - - + + + - + Build # @@ -220,6 +220,7 @@ +
diff --git a/runbot/tests/test_build.py b/runbot/tests/test_build.py index c2a25ac3..53fa07f6 100644 --- a/runbot/tests/test_build.py +++ b/runbot/tests/test_build.py @@ -92,6 +92,18 @@ class Test_Build(RunbotCase): build.env.cr.execute("SELECT config_data, config_data->'test_write' AS written, config_data->'test_build' AS test_build FROM runbot_build WHERE id = %s", [build.id]) self.assertEqual([({'test_write': 'written', 'test_build': 'foo'}, 'written', 'foo')], self.env.cr.fetchall()) + def test_markdown_description(self): + build = self.create_build({ + 'branch_id': self.branch.id, + 'name': 'd0d0caca0000ffffffffffffffffffffffffffff', + 'port': '1234', + 'description': 'A nice **description**' + }) + self.assertEqual(build.md_description, 'A nice description') + + build.description = "" + self.assertEqual(build.md_description, "<script>console.log('foo')</script>") + def test_config_data_duplicate(self): build = self.create_build({ diff --git a/runbot/tests/test_event.py b/runbot/tests/test_event.py index c53aad2a..83bf44ea 100644 --- a/runbot/tests/test_event.py +++ b/runbot/tests/test_event.py @@ -68,3 +68,60 @@ class TestIrLogging(RunbotCase): build._log('runbot function', 'runbot message') log_lines = self.IrLogging.search([('type', '=', 'runbot'), ('name', '=', 'odoo.runbot'), ('func', '=', 'runbot function'), ('message', '=', 'runbot message'), ('level', '=', 'INFO')]) self.assertEqual(len(log_lines), 1, '_log should be able to add logs from the runbot') + + def test_markdown(self): + log = self.IrLogging.create({ + 'name': 'odoo.runbot', + 'type': 'runbot', + 'path': 'runbot', + 'level': 'INFO', + 'line': 0, + 'func': 'test_markdown', + 'message': 'some **bold text** and also some __underlined text__ and maybe a bit of ~~strikethrough text~~' + }) + + self.assertEqual( + log._markdown(), + 'some bold text and also some underlined text and maybe a bit of strikethrough text' + ) + + log.message = 'a bit of code `import foo\nfoo.bar`' + self.assertEqual( + log._markdown(), + 'a bit of code import foo\nfoo.bar' + ) + + # test icon + log.message = 'Hello @icon-file-text-o' + self.assertEqual( + log._markdown(), + 'Hello ' + ) + + # test links + log.message = 'This [link](https://wwww.somewhere.com) goes to somewhere and [this one](http://www.nowhere.com) to nowhere.' + self.assertEqual( + log._markdown(), + 'This link goes to somewhere and this one to nowhere.' + ) + + # test link with icon + log.message = '[@icon-download](https://wwww.somewhere.com) goes to somewhere.' + self.assertEqual( + log._markdown(), + ' goes to somewhere.' + ) + + # test links with icon and text + log.message = 'This [link@icon-download](https://wwww.somewhere.com) goes to somewhere.' + self.assertEqual( + log._markdown(), + 'This link goes to somewhere.' + ) + + # test sanitization + log.message = 'foo ' + self.assertEqual( + log._markdown(), + 'foo <script>console.log("hello world")</script>' + )