WIP : runbot error min max version

This commit is contained in:
Christophe Monniez 2025-02-27 18:34:55 +01:00
parent 1ed9278d6e
commit 2b1ddce781
2 changed files with 85 additions and 6 deletions

View File

@ -110,6 +110,9 @@ class BuildError(models.Model):
analogous_ids = fields.One2many('runbot.build.error', compute='_compute_analogous_ids', string="Analogous Errors", help="Analogous Errors based on unique qualifiers") analogous_ids = fields.One2many('runbot.build.error', compute='_compute_analogous_ids', string="Analogous Errors", help="Analogous Errors based on unique qualifiers")
analogous_content_ids= fields.One2many('runbot.build.error.content', compute='_compute_analogous_content_ids', string="Analogous Error Contents", help="Analogous Error contents based on unique qualifiers") analogous_content_ids= fields.One2many('runbot.build.error.content', compute='_compute_analogous_content_ids', string="Analogous Error Contents", help="Analogous Error contents based on unique qualifiers")
min_version_id = fields.Many2one('runbot.version', string='Min Version', compute='_compute_min_max_version', search='_search_min_version')
max_version_id = fields.Many2one('runbot.version', string='Max Version', compute='_compute_min_max_version', search='_search_max_version')
# Build error related data # Build error related data
build_error_link_ids = fields.Many2many('runbot.build.error.link', compute=_compute_related_error_content_ids('build_error_link_ids'), search=_search_related_error_content_ids('build_error_link_ids')) build_error_link_ids = fields.Many2many('runbot.build.error.link', compute=_compute_related_error_content_ids('build_error_link_ids'), search=_search_related_error_content_ids('build_error_link_ids'))
unique_build_error_link_ids = fields.Many2many('runbot.build.error.link', compute='_compute_unique_build_error_link_ids') unique_build_error_link_ids = fields.Many2many('runbot.build.error.link', compute='_compute_unique_build_error_link_ids')
@ -234,6 +237,75 @@ class BuildError(models.Model):
else: else:
record.analogous_content_ids = False record.analogous_content_ids = False
@api.depends('error_content_ids')
def _compute_min_max_version(self):
query = SQL(
r"""
SELECT runbot_build_error.id, MIN(runbot_version.number), MAX(runbot_version.number)
FROM runbot_build_error_link
JOIN runbot_build_error_content
ON runbot_build_error_link.error_content_id = runbot_build_error_content.id
JOIN runbot_build_error
ON runbot_build_error.id = runbot_build_error_content.error_id
JOIN runbot_build
ON runbot_build_error_link.build_id = runbot_build.id
JOIN runbot_version
ON runbot_build.version_id = runbot_version.id
WHERE runbot_build.version_id is not null
AND runbot_build_error.id IN %s
GROUP BY runbot_build_error.id;
""",
tuple(self.ids)
)
self.env.cr.execute(query)
min_max_by_error_id = {error_id: (vmin, vmax) for error_id,vmin,vmax in self.env.cr.fetchall()}
all_versions_by_number = {rec.number:rec.id for rec in self.env['runbot.version'].search([])}
for record in self:
min_max = min_max_by_error_id.get(record.id, False)
record.min_version_id = all_versions_by_number[min_max[0]] if min_max else False
record.max_version_id = all_versions_by_number[min_max[1]] if min_max else False
def _search_min_max(self, version, aggregator):
comp = '>=' if aggregator == 'MIN' else '<='
query = SQL(
rf"""
SELECT runbot_build_error.id
FROM runbot_build_error_link
JOIN runbot_build_error_content
ON runbot_build_error_link.error_content_id = runbot_build_error_content.id
JOIN runbot_build_error
ON runbot_build_error.id = runbot_build_error_content.error_id
JOIN runbot_build
ON runbot_build_error_link.build_id = runbot_build.id
JOIN runbot_version
ON runbot_build.version_id = runbot_version.id
WHERE runbot_build.version_id is not null
GROUP BY runbot_build_error.id
HAVING {aggregator}(runbot_version.number) {comp} %s
;
""",
version.number
)
self.env.cr.execute(query)
return [rec[0] for rec in self.env.cr.fetchall()]
def _search_min_version(self, operator, value):
if operator not in ('=', 'ilike'):
raise NotImplementedError()
if isinstance(value, str):
min_version = self.env['runbot.version'].search([('name', operator, value)], limit=1)
else:
min_version = self.env['runbot.version'].browse(value)
return [('id', 'in', self._search_min_max(min_version, 'MIN'))]
def _search_max_version(self, operator, value):
if operator not in ('=', 'ilike'):
raise NotImplementedError()
if isinstance(value, str):
min_version = self.env['runbot.version'].search([('name', operator, value)], limit=1)
else:
min_version = self.env['runbot.version'].browse(value)
return [('id', 'in', self._search_min_max(min_version, 'MAX'))]
@api.constrains('test_tags') @api.constrains('test_tags')
def _check_test_tags(self): def _check_test_tags(self):
@ -485,6 +557,8 @@ class BuildErrorContent(models.Model):
build_ids = fields.Many2many('runbot.build', compute='_compute_build_ids') build_ids = fields.Many2many('runbot.build', compute='_compute_build_ids')
bundle_ids = fields.One2many('runbot.bundle', compute='_compute_bundle_ids') bundle_ids = fields.One2many('runbot.bundle', compute='_compute_bundle_ids')
version_ids = fields.One2many('runbot.version', compute='_compute_version_ids', string='Versions', search='_search_version') version_ids = fields.One2many('runbot.version', compute='_compute_version_ids', string='Versions', search='_search_version')
min_version_id = fields.Many2one('runbot.version', compute='_compute_version_ids', string='Min Version', search='_search_min_version')
max_version_id = fields.Many2one('runbot.version', compute='_compute_version_ids', string='Max Version')
trigger_ids = fields.Many2many('runbot.trigger', compute='_compute_trigger_ids', string='Triggers', search='_search_trigger_ids') 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') tag_ids = fields.Many2many('runbot.build.error.tag', string='Tags')
qualifiers = JsonDictField('Qualifiers', index=True) qualifiers = JsonDictField('Qualifiers', index=True)
@ -561,7 +635,10 @@ class BuildErrorContent(models.Model):
@api.depends('build_ids') @api.depends('build_ids')
def _compute_version_ids(self): def _compute_version_ids(self):
for build_error in self: for build_error in self:
build_error.version_ids = build_error.build_ids.version_id version_ids = build_error.build_ids.mapped('version_id').sorted(lambda rec: rec.number)
build_error.version_ids = version_ids
build_error.min_version_id = version_ids[0] if version_ids else False
build_error.max_version_id = version_ids[-1] if version_ids else False
@api.depends('build_ids') @api.depends('build_ids')
def _compute_trigger_ids(self): def _compute_trigger_ids(self):
@ -602,11 +679,9 @@ class BuildErrorContent(models.Model):
return hashlib.sha256(s.encode()).hexdigest() return hashlib.sha256(s.encode()).hexdigest()
def _search_version(self, operator, value): def _search_version(self, operator, value):
exclude_domain = [] _logger.info('operator: %s', operator)
if operator == '=': _logger.info('value: %s', value)
exclude_ids = self.env['runbot.build.error'].search([('version_ids', '!=', value)]) return [('build_error_link_ids.version_id', operator, value)]
exclude_domain = [('id', 'not in', exclude_ids.ids)]
return [('build_error_link_ids.version_id', operator, value)] + exclude_domain
def _search_trigger_ids(self, operator, value): def _search_trigger_ids(self, operator, value):
return [('build_error_link_ids.trigger_id', operator, value)] return [('build_error_link_ids.trigger_id', operator, value)]

View File

@ -345,6 +345,8 @@
<field name="first_seen_date" string="First Seen" optional="hide" readonly="1"/> <field name="first_seen_date" string="First Seen" optional="hide" readonly="1"/>
<field name="last_seen_date" string="Last Seen" readonly="1" options="{'link_field': 'last_seen_build_id'}"/> <field name="last_seen_date" string="Last Seen" readonly="1" options="{'link_field': 'last_seen_build_id'}"/>
<field name="last_seen_build_id" column_invisible="True"/> <field name="last_seen_build_id" column_invisible="True"/>
<field name="min_version_id" optional="hide"/>
<field name="max_version_id" optional="hide"/>
<field name="error_count" readonly="1"/> <field name="error_count" readonly="1"/>
<field name="build_count" readonly="1"/> <field name="build_count" readonly="1"/>
<field name="team_id"/> <field name="team_id"/>
@ -401,6 +403,8 @@
<field name="content"/> <field name="content"/>
<field name="description"/> <field name="description"/>
<field name="version_ids"/> <field name="version_ids"/>
<field name="min_version_id"/>
<field name="max_version_id"/>
<field name="responsible"/> <field name="responsible"/>
<field name="team_id"/> <field name="team_id"/>
<filter string="Assigned to me" name="my_errors" domain="[('responsible', '=', uid)]"/> <filter string="Assigned to me" name="my_errors" domain="[('responsible', '=', uid)]"/>