mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[IMP] runbot: permit to qualify build errors
Identifying build errors by fingerprint is not enough. Most of the times we want to link build errors based on some criteria. With this commit, a build error can be qualified by using regular expressions that will extract informations. Those informations will be stored in a jsondict field. That way, we can find simmilar build errors when they share some qualifiers. To extract information, the regular expression must use named group patterns. e.g: `^Tour (?P<tour_name>\w+) failed at step (?P<tour_step>.+)` The above regular expression should extract the tour name and tour step from a build error and store them like: `{ "tour_name": "article_portal_tour", "tour_step": "clik on 'Help'" }` The field can be queried to find similar errors. Also, a button is added on the Build Error Form to search for errors that share the same qualifiers.
This commit is contained in:
parent
509c152156
commit
4bdd2e20b8
@ -9,6 +9,9 @@ from markupsafe import Markup
|
||||
from werkzeug.urls import url_join
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
from odoo.tools import SQL
|
||||
|
||||
from ..fields import JsonDictField
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@ -349,6 +352,7 @@ class BuildErrorContent(models.Model):
|
||||
version_ids = fields.One2many('runbot.version', compute='_compute_version_ids', string='Versions', search='_search_version')
|
||||
trigger_ids = fields.Many2many('runbot.trigger', compute='_compute_trigger_ids', string='Triggers', search='_search_trigger_ids')
|
||||
tag_ids = fields.Many2many('runbot.build.error.tag', string='Tags')
|
||||
qualifiers = JsonDictField('Qualifiers', index=True)
|
||||
|
||||
responsible = fields.Many2one(related='error_id.responsible')
|
||||
customer = fields.Many2one(related='error_id.customer')
|
||||
@ -491,6 +495,17 @@ class BuildErrorContent(models.Model):
|
||||
domain = [('id', 'in', self.ids)] if self else []
|
||||
return [r['id_arr'] for r in self.env['runbot.build.error.content'].read_group(domain, ['id_count:count(id)', 'id_arr:array_agg(id)', 'fingerprint'], ['fingerprint']) if r['id_count'] >1]
|
||||
|
||||
def _qualify(self):
|
||||
qualify_regexes = self.env['runbot.error.qualify.regex'].search([])
|
||||
for record in self:
|
||||
all_qualifiers = {}
|
||||
for qualify_regex in qualify_regexes:
|
||||
res = qualify_regex._qualify(record.content) # TODO, MAYBE choose the source field
|
||||
if res:
|
||||
# res.update({'qualifier_id': qualify_regex.id}) Probably not a good idea
|
||||
all_qualifiers.update(res)
|
||||
record.qualifiers = all_qualifiers
|
||||
|
||||
####################
|
||||
# Actions
|
||||
####################
|
||||
@ -546,6 +561,9 @@ class BuildErrorContent(models.Model):
|
||||
'view_mode': 'tree,form'
|
||||
}
|
||||
|
||||
def action_qualify(self):
|
||||
self._qualify()
|
||||
|
||||
|
||||
class BuildErrorTag(models.Model):
|
||||
|
||||
@ -618,3 +636,76 @@ class ErrorBulkWizard(models.TransientModel):
|
||||
if self.chatter_comment:
|
||||
for build_error in error_ids:
|
||||
build_error.message_post(body=Markup('%s') % self.chatter_comment, subject="Bullk Wizard Comment")
|
||||
|
||||
|
||||
class ErrorQualifyRegex(models.Model):
|
||||
|
||||
_name = "runbot.error.qualify.regex"
|
||||
_description = "Build error qualifying regex"
|
||||
_inherit = "mail.thread"
|
||||
_rec_name = 'id'
|
||||
_order = 'sequence, id'
|
||||
|
||||
sequence = fields.Integer('Sequence', default=100)
|
||||
active = fields.Boolean('Active', default=True, tracking=True)
|
||||
regex = fields.Char('Regular expression', required=True)
|
||||
source_field = fields.Selection(
|
||||
[
|
||||
("content", "Content"),
|
||||
("module", "Module Name"),
|
||||
("function", "Function Name"),
|
||||
("file_path", "File Path"),
|
||||
],
|
||||
default="content",
|
||||
string="Source Field",
|
||||
help="Build error field on which the regex will be applied to extract a qualifier",
|
||||
)
|
||||
|
||||
test_ids = fields.One2many('runbot.error.qualify.test', 'qualify_regex_id', string="Test Sample", help="Error samples to test qualifying regex")
|
||||
|
||||
@api.constrains('regex')
|
||||
def _validate(self):
|
||||
for rec in self:
|
||||
try:
|
||||
r = re.compile(rec.regex)
|
||||
except re.error as e:
|
||||
raise ValidationError("Unable to compile regular expression: %s" % e)
|
||||
# verify that a named group exist in the pattern
|
||||
if not re.search(r'\(\?P<\w+>.+\)', r.pattern):
|
||||
raise ValidationError(
|
||||
"The regular expresion should contain at least one named group pattern e.g: '(?P<module>.+)'"
|
||||
)
|
||||
|
||||
def _qualify(self, content):
|
||||
self.ensure_one()
|
||||
result = False
|
||||
if content and self.regex:
|
||||
result = re.search(self.regex, content, flags=re.MULTILINE)
|
||||
return result.groupdict() if result else {}
|
||||
|
||||
@api.depends('regex', 'test_string')
|
||||
def _compute_qualifiers(self):
|
||||
for record in self:
|
||||
if record.regex and record.test_string:
|
||||
record.qualifiers = record._qualify(record.test_string)
|
||||
else:
|
||||
record.qualifiers = {}
|
||||
|
||||
|
||||
class QualifyErrorTest(models.Model):
|
||||
_name = 'runbot.error.qualify.test'
|
||||
_description = 'Extended Relation between a qualify regex and a build error taken as sample'
|
||||
|
||||
qualify_regex_id = fields.Many2one('runbot.error.qualify.regex', required=True)
|
||||
error_content_id = fields.Many2one('runbot.build.error.content', string='Build Error', required=True)
|
||||
build_error_summary = fields.Char(related='error_content_id.summary')
|
||||
build_error_content = fields.Text(related='error_content_id.content')
|
||||
expected_result = JsonDictField('Expected Qualifiers')
|
||||
result = JsonDictField('Result', compute='_compute_result')
|
||||
is_matching = fields.Boolean(compute='_compute_result', default=False)
|
||||
|
||||
@api.depends('qualify_regex_id', 'error_content_id')
|
||||
def _compute_result(self):
|
||||
for record in self:
|
||||
record.result = record.qualify_regex_id._qualify(record.build_error_content)
|
||||
record.is_matching = record.result == record.expected_result and record.result != {}
|
||||
|
@ -36,6 +36,9 @@ access_runbot_team_user,runbot_team_user,runbot.model_runbot_team,group_user,1,0
|
||||
access_runbot_error_bulk_wizard_admin,access_runbot_error_bulk_wizard_admin,runbot.model_runbot_error_bulk_wizard,runbot.group_runbot_admin,1,1,1,1
|
||||
access_runbot_error_bulk_wizard_manager,access_runbot_error_bulk_wizard_manager,runbot.model_runbot_error_bulk_wizard,runbot.group_runbot_error_manager,1,1,1,1
|
||||
|
||||
runbot.access_runbot_error_qualify_regex_admin,access_runbot_error_qualify_regex_admin,runbot.model_runbot_error_qualify_regex,runbot.group_runbot_admin,1,1,1,1
|
||||
runbot.access_runbot_error_qualify_test_admin, access_runbot_error_qualify_test_admin,runbot.model_runbot_error_qualify_test,runbot.group_runbot_admin,1,1,1,1
|
||||
|
||||
access_runbot_module_admin,runbot_module_admin,runbot.model_runbot_module,runbot.group_runbot_admin,1,1,1,1
|
||||
access_runbot_module_team_manager,runbot_module_team_manager,runbot.model_runbot_module,runbot.group_runbot_team_manager,1,1,1,1
|
||||
access_runbot_module_user,runbot_module_user,runbot.model_runbot_module,group_user,1,0,0,0
|
||||
|
|
@ -98,6 +98,9 @@
|
||||
<field name="model">runbot.build.error.content</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<header>
|
||||
<button type="object" name="action_qualify" string="Qualify"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="error_id"/>
|
||||
@ -107,6 +110,7 @@
|
||||
<field name="module_name" readonly="1"/>
|
||||
<field name="function" readonly="1"/>
|
||||
<field name="file_path" readonly="1"/>
|
||||
<field name="qualifiers" readonly="1"/>
|
||||
</group>
|
||||
<group name="infos" string="" col="2">
|
||||
<group>
|
||||
@ -243,6 +247,7 @@
|
||||
>
|
||||
<header>
|
||||
<button name="action_find_duplicates" type="object" string="Find duplicates" display="always"/>
|
||||
<button name="action_qualify" string="Qualify" type="object" groups="runbot.group_runbot_admin,runbot.group_runbot_error_manager"/>
|
||||
</header>
|
||||
<field name="error_display_id" optional="show"/>
|
||||
<field name="module_name" optional="show" readonly="1"/>
|
||||
@ -316,6 +321,9 @@
|
||||
<filter string="Undeterministic" name="random_error" domain="[('random', '=', True)]"/>
|
||||
<filter string="Deterministic" name="random_error" domain="[('random', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Qualified" name="qualified_errors" domain="[('qualifiers', '!=', False)]"/>
|
||||
<filter string="Not Qualified" name="not_qualified_errors" domain="[('qualifiers', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Fixed" name="fixed_errors" domain="[('error_id.active', '=', False)]"/>
|
||||
<filter string="Not Fixed" name="not_fixed_errors" domain="[('error_id.active', '=', True)]"/>
|
||||
<separator/>
|
||||
@ -405,5 +413,75 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="build_error_qualify_regex_tree" model="ir.ui.view">
|
||||
<field name="name">runbot.error.qualify.regex.tree</field>
|
||||
<field name="model">runbot.error.qualify.regex</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Qualifying Regexes">
|
||||
<header>
|
||||
</header>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="regex" readonly="1"/>
|
||||
<field name="source_field" readonly="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="build_error_qualify_regex_form" model="ir.ui.view">
|
||||
<field name="name">runbot.error.qualify.regex.form</field>
|
||||
<field name="model">runbot.error.qualify.regex</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<div class="alert alert-info" role="alert">
|
||||
The regular expresion must have at least one named group pattern e.g: <code>'(?P<module>\w+)'</code>
|
||||
</div>
|
||||
<sheet>
|
||||
<group name="Regex And Source">
|
||||
<field name="regex"/>
|
||||
<field name="source_field"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="test_ids">
|
||||
<tree string="Test Samples" decoration-success="is_matching" decoration-danger="not is_matching">
|
||||
<field name="error_content_id"/>
|
||||
<field name="build_error_summary"/>
|
||||
<field name="expected_result" widget="runbotjsonb"/>
|
||||
<field name="result" widget="runbotjsonb" readonly="1"/>
|
||||
<field name="is_matching" column_invisible="True"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="runbot_error_qualify_test_form" model="ir.ui.view">
|
||||
<field name="name">runbot.error.qualify.test.form</field>
|
||||
<field name="model">runbot.error.qualify.test</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group name="Error Sample">
|
||||
<field name="error_content_id"/>
|
||||
<field name="expected_result" widget="runbotjsonb"/>
|
||||
</group>
|
||||
<group name="Result">
|
||||
<field name="result" widget="runbotjsonb" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="build_error_content"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_view_build_error_qualify_regex_tree" model="ir.actions.act_window">
|
||||
<field name="name">Build Errors Qualifying Regexes</field>
|
||||
<field name="res_model">runbot.error.qualify.regex</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
@ -37,6 +37,7 @@
|
||||
<menuitem name="Manage errors" id="runbot_menu_manage_errors" parent="runbot_menu_root" sequence="900"/>
|
||||
<menuitem name="Errors" id="runbot_menu_build_error_tree" parent="runbot_menu_manage_errors" sequence="5" action="open_view_build_error_tree"/>
|
||||
<menuitem name="Errors contents" id="runbot_menu_build_error_content_tree" parent="runbot_menu_manage_errors" sequence="10" action="open_view_build_error_content_tree"/>
|
||||
<menuitem name="Errors Qualifying" id="runbot_menu_build_error_qualify_regex_tree" parent="runbot_menu_manage_errors" sequence="10" action="open_view_build_error_qualify_regex_tree"/>
|
||||
|
||||
<menuitem name="Teams" id="runbot_menu_teams" parent="runbot_menu_root" sequence="1000"/>
|
||||
<menuitem name="Teams" id="runbot_menu_team_tree" parent="runbot_menu_teams" sequence="30" action="open_view_runbot_team"/>
|
||||
|
Loading…
Reference in New Issue
Block a user