[IMP] runbot: add kanban view and stages to build error page

Introduce a kanban view to the runbot build error records.
Kanban cards show the most valuable field with relevant icons.

The concept of "state" was also introduced for the build error
model.
Currently there is 4 states:
 1. New: default state for any new error build
 2. Solved: when the issue should be solved
 3. Ignored: build error currently ignored (test-tags)
 4. Done: When the issue stop happening
Certain states change automatically based as handled in a cron
Generally, any not-ignored on-going task end up in done after some
period of time if it is not seen for some time
( see full details in function `_update_stage`).
The other state are expected to be used by the user. For instance
after fixing an underteministic error the user can change the
state from new -> solved so that it is moved to done if the build
error is not seen for 7 days (instead of 15 if it was in new).
This commit is contained in:
lse-odoo 2025-02-28 18:12:02 +01:00 committed by Loan (LSE)
parent 3cf9dd6fa2
commit a364997043
5 changed files with 122 additions and 2 deletions

View File

@ -6,7 +6,7 @@
'author': "Odoo SA",
'website': "http://runbot.odoo.com",
'category': 'Website',
'version': '5.9',
'version': '5.10',
'application': True,
'depends': ['base', 'base_automation', 'website'],
'data': [
@ -18,6 +18,7 @@
'data/runbot_data.xml',
'data/runbot_error_regex_data.xml',
'data/website_data.xml',
'data/ir_cron_data.xml',
'security/runbot_security.xml',
'security/ir.model.access.csv',

View File

@ -0,0 +1,10 @@
<odoo>
<record id="runbot_update_build_errors_stage" model="ir.cron">
<field name="name">Runbot: Update Build Errors Stage</field>
<field name="model_id" ref="model_runbot_build_error"/>
<field name="state">code</field>
<field name="code">model._update_stage()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
</record>
</odoo>

View File

@ -0,0 +1,11 @@
from odoo import api, SUPERUSER_ID
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
# Archived build errors are set are considered Done
env['runbot.build.error'].search([['active', "=", False]]).write({'state': 'done'})
# Build errors with test_tags are set to Ignored
env['runbot.build.error'].search([['test_tags', '!=', False]]).write({'state': 'ignored'})

View File

@ -105,6 +105,17 @@ class BuildError(models.Model):
error_count = fields.Integer("Error count", store=True, compute='_compute_count')
previous_error_id = fields.Many2one('runbot.build.error', string="Already seen error")
state = fields.Selection([
('new', 'New/Unsolved'),
('solved', 'Solved'),
('ignored', 'Ignored'),
('done', 'Done'),
], default='new', tracking=True, group_expand=True,
help="New: Error is new and not yet solved. Build error not happening for a long time in this state will automatically move to Done\n"
"Solved: Error should be solved. Build error not happening for a small time in this state will automatically move to Done\n"
"Ignored: Error is ignored. No change of state will apply\n"
"Done: Error is done. No change of state will apply except if issue re-appear, then it will go back to New"
)
responsible = fields.Many2one('res.users', 'Assigned fixer', tracking=True)
customer = fields.Many2one('res.users', 'Customer', tracking=True)
team_id = fields.Many2one('runbot.team', 'Assigned team', tracking=True)
@ -586,6 +597,37 @@ class BuildError(models.Model):
base_error = self_sorted[0]
base_error._merge(self_sorted - base_error)
def _update_state(self, nbr_day_solved_to_done=7, nbr_day_new_to_done=15):
"""Called automatically by scheduled action to update the state of the error if necessary"""
now = fields.Datetime.now()
# Done errors that did happen again recently are moved back to new
self.search([
('state', '=', 'done'),
('last_seen_date', '>=', now - relativedelta(days=nbr_day_new_to_done)),
]).write({'state': 'new'})
# New error that did not appear after a long time are marked as done
# Solved error that did not appear after a short time are marked as done
self.search([
'|',
'&',
('state', '=', 'new'),
('last_seen_date', '<', now - relativedelta(days=nbr_day_new_to_done)),
'&',
('state', '=', 'solved'),
('last_seen_date', '<', now - relativedelta(days=nbr_day_solved_to_done)),
]).write({'state': 'done'})
@api.model
def _read_group_fill_results(self, domain, groupby, annoted_aggregates, read_group_result, read_group_order=None):
# Override to fold ignored and done state
read_groups = super()._read_group_fill_results(domain, groupby, annoted_aggregates, read_group_result, read_group_order)
for read_group in read_groups:
if read_group.get('state', False) in ('ignored', 'done'):
read_group['__fold'] = True
return read_groups
class BuildErrorContent(models.Model):

View File

@ -5,6 +5,9 @@
<field name="model">runbot.build.error</field>
<field name="arch" type="xml">
<form>
<header>
<field name="state" widget="statusbar" options="{'clickable': '1'}"/>
</header>
<sheet>
<div name="button_box">
<button class="oe_stat_button" type="object" icon="fa-exclamation-circle" name="action_get_build_link_record">
@ -329,6 +332,59 @@
<field name="binding_view_types">list</field>
</record>
<record id="build_error_view_kanban" model="ir.ui.view">
<field name="name">runbot.build.error.kanban</field>
<field name="model">runbot.build.error</field>
<field name="arch" type="xml">
<kanban default_group_by="state" quick_create="false" default_order="last_seen_date desc">
<templates>
<t t-name="card">
<widget name="web_ribbon" title="Test-tags" bg_color="bg-danger" invisible="not test_tags"/>
<field name="name" class="fw-bold fs-5"/>
<group>
<div style="display: flex; align-items: center;">
<i class="fa fa-clock-o me-2" title="Date interval from first seen to last seen"/>
<field name="first_seen_date" widget="remaining_days"/>
<i class="fa fa-long-arrow-right mx-2 oe_read_only" title="to"/>
<field name="last_seen_date" widget="remaining_days"/>
</div>
<div class="d-flex align-items-center gap-1">
<i class="fa fa-repeat" title="Number of occurence"/>
<field name="error_count"/>
</div>
<div class="d-flex align-items-center gap-1">
<i class="fa fa-code-fork" title="Concerned Odoo versions"/>
<field name="version_ids" widget="many2many_tags"/>
</div>
<div class="d-flex align-items-center gap-1">
<i class="fa fa-bullseye" title="Triggers"/>
<field name="trigger_ids" widget="many2many_tags"/>
</div>
</group>
<footer>
<div class="d-flex align-items-center gap-1">
<field name="activity_ids" widget="kanban_activity"/>
</div>
<div class="d-flex align-items-center gap-1 ms-auto">
<i class="fa fa-random text-danger" title="inconsistant" invisible="not random"/>
<i class="fa fa-users" title="Responsible team"/>
<field name="team_id"/> <i t-if="!record.team_id.raw_value">no team</i>
<i class="fa fa-address-card" title="Investigator"/>
<field name="customer" widget="many2one_avatar_user"/>
<i class="fa fa-wrench" title="Solver"/>
<field name="responsible" widget="many2one_avatar_user"/>
<field name="fixing_pr_url" widget="url" text="PR" invisible="not fixing_pr_url"/>
</div>
</footer>
</t>
</templates>
</kanban>
</field>
</record>
<record id="build_error_view_tree" model="ir.ui.view">
<field name="name">runbot.build.error.list</field>
<field name="model">runbot.build.error</field>
@ -478,7 +534,7 @@
<field name="name">Errors</field>
<field name="res_model">runbot.build.error</field>
<field name="path">error</field>
<field name="view_mode">list,form</field>
<field name="view_mode">kanban,list,form</field>
<field name="context">{'search_default_not_fixed_errors': True, 'active_test': False}</field>
</record>