mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[IMP] runbot: allow pseudo markdown in log messages
With this commit, a kind of markdown is allowed in log messages and build.description. Following patterns are supported: **strong** ~~striketrough~~ __underline__ `code` [link](target) @icon-font-awesome-class
This commit is contained in:
parent
3a428d4877
commit
3b00d2576c
@ -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'\*\*(.+?)\*\*': '<strong>\g<1></strong>',
|
||||
r'~~(.+?)~~': '<del>\g<1></del>', # it's not official markdown but who cares
|
||||
r'__(.+?)__': '<ins>\g<1></ins>', # same here, maybe we should change the method name
|
||||
r'`(.+?)`': '<code>\g<1></code>',
|
||||
}
|
||||
|
||||
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('<i class="fa fa-\g<1>"></i>', text)
|
||||
|
||||
# links
|
||||
re_links = re.compile(r'\[(.+?)\]\((.+?)\)')
|
||||
text = re_links.sub('<a href="\g<2>">\g<1></a>', text)
|
||||
return text
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
|
@ -1,6 +1,5 @@
|
||||
.separator {
|
||||
border-top: 2px solid #666;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
[data-toggle="collapse"] .fa:before {
|
||||
|
@ -117,7 +117,7 @@
|
||||
<t t-set="rowclass"><t t-call="runbot.build_class"><t t-set="build" t-value="build"/></t></t>
|
||||
<td t-attf-class="bg-{{rowclass.strip()}}-light">
|
||||
<t t-if="build.description">
|
||||
<b>Description:</b> <t t-esc="build.description"/><br/>
|
||||
<b>Description:</b> <t t-raw="build.md_description"/><br/>
|
||||
</t>
|
||||
<b>Subject:</b> <t t-esc="build.subject"/><br/>
|
||||
<b>Author:</b> <t t-esc="build.author"/><br/>
|
||||
@ -158,7 +158,7 @@
|
||||
<tr t-attf-class="bg-{{rowclass.strip()}}-light"><td>
|
||||
<a t-attf-href="/runbot/build/{{child.id}}" >Build <t t-esc="child.id"/></a>
|
||||
<t t-if="child.description">
|
||||
<t t-esc="child.description" />
|
||||
<t t-raw="child.md_description" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
with config <t t-esc="child.config_id.name"/>
|
||||
@ -199,13 +199,13 @@
|
||||
<t t-set="subbuild" t-value="(([child for child in build.real_build.children_ids if child.id == int(l.path)] if l.type == 'subbuild' else False) or [build.browse()])[0]"/>
|
||||
<t t-set="logclass" t-value="dict(CRITICAL='danger', ERROR='danger', WARNING='warning', OK='success', SEPARATOR='separator').get(l.level)"/>
|
||||
<tr t-attf-class="'bg-%s-light' % {{logclass}} if {{logclass}} != 'separator' else {{logclass}}">
|
||||
<td style="white-space: nowrap; width:1%;"><t t-esc="l.create_date"/></td>
|
||||
<td style="white-space: nowrap; width:1%;"><b t-if="l.level != 'SEPARATOR' and l.type != 'link'" t-esc="l.level"/></td>
|
||||
<td style="white-space: nowrap; width:1%;"><t t-if="l.level != 'SEPARATOR' and l.type != 'link'" t-esc="l.type"/></td>
|
||||
<td style="white-space: nowrap; width:1%;"><t t-esc="l.create_date.strftime('%Y-%m-%d %H:%M:%S')"/></td>
|
||||
<td style="white-space: nowrap; width:1%;"><b t-if="l.level != 'SEPARATOR' and l.type not in ['link', 'markdown']" t-esc="l.level"/></td>
|
||||
<td style="white-space: nowrap; width:1%;"><t t-if="l.level != 'SEPARATOR' and l.type not in ['link', 'markdown']" t-esc="l.type"/></td>
|
||||
<t t-set="message_class" t-value="''"/>
|
||||
<t t-if="subbuild" t-set="message_class"><t t-call="runbot.build_class"><t t-set="build" t-value="subbuild"/></t></t>
|
||||
<td t-attf-class="bg-{{message_class.strip() or logclass}}-light">
|
||||
<t t-if="l.type not in ('runbot', 'link')">
|
||||
<t t-if="l.type not in ('runbot', 'link', 'markdown')">
|
||||
<t t-if="l.type == 'subbuild'">
|
||||
<a t-attf-href="/runbot/build/{{l.path}}">Build #<t t-esc="l.path"/></a>
|
||||
</t>
|
||||
@ -220,6 +220,7 @@
|
||||
<t t-esc="message[0]"/><a t-attf-href="{{l.path}}"><t t-esc="message[1]"/></a><t t-esc="message[2]"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="l.type == 'markdown'" t-raw="l._markdown()"/>
|
||||
<t t-else="">
|
||||
<t t-if="'\n' not in l.message" t-esc="l.message"/>
|
||||
<pre t-if="'\n' in l.message" style="margin:0;padding:0; border: none;"><t t-esc="l.message"/></pre>
|
||||
|
@ -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 <strong>description</strong>')
|
||||
|
||||
build.description = "<script>console.log('foo')</script>"
|
||||
self.assertEqual(build.md_description, "<script>console.log('foo')</script>")
|
||||
|
||||
def test_config_data_duplicate(self):
|
||||
|
||||
build = self.create_build({
|
||||
|
@ -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 <strong>bold text</strong> and also some <ins>underlined text</ins> and maybe a bit of <del>strikethrough text</del>'
|
||||
)
|
||||
|
||||
log.message = 'a bit of code `import foo\nfoo.bar`'
|
||||
self.assertEqual(
|
||||
log._markdown(),
|
||||
'a bit of code <code>import foo\nfoo.bar</code>'
|
||||
)
|
||||
|
||||
# test icon
|
||||
log.message = 'Hello @icon-file-text-o'
|
||||
self.assertEqual(
|
||||
log._markdown(),
|
||||
'Hello <i class="fa fa-file-text-o"></i>'
|
||||
)
|
||||
|
||||
# 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 <a href="https://wwww.somewhere.com">link</a> goes to somewhere and <a href="http://www.nowhere.com">this one</a> to nowhere.'
|
||||
)
|
||||
|
||||
# test link with icon
|
||||
log.message = '[@icon-download](https://wwww.somewhere.com) goes to somewhere.'
|
||||
self.assertEqual(
|
||||
log._markdown(),
|
||||
'<a href="https://wwww.somewhere.com"><i class="fa fa-download"></i></a> 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 <a href="https://wwww.somewhere.com">link<i class="fa fa-download"></i></a> goes to somewhere.'
|
||||
)
|
||||
|
||||
# test sanitization
|
||||
log.message = 'foo <script>console.log("hello world")</script>'
|
||||
self.assertEqual(
|
||||
log._markdown(),
|
||||
'foo <script>console.log("hello world")</script>'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user