mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 15:35:46 +07:00
[IMP] runbot: build error frequency graphs
Adds frequency graphs for build errors as small html widgets. All data is gathered from the last 30 days. - Daily frequency (how many times per day) - Hourly frequency (how many times per individual hour, 0-23) - Day Of Week frequency (how many times per day of week, 0-6) - Day of month frequency (how many times per day of month, 0-30) Co-Authored-By: Xavier-Do <xdo@odoo.com>
This commit is contained in:
parent
637cdb81e7
commit
baacd1e148
@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
@ -6,6 +7,7 @@ import re
|
||||
|
||||
from collections import defaultdict
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateutil import rrule
|
||||
from markupsafe import Markup
|
||||
from werkzeug.urls import url_join
|
||||
from odoo import models, fields, api
|
||||
@ -17,6 +19,18 @@ from ..fields import JsonDictField
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_color(value: int):
|
||||
if value >= 10:
|
||||
return 'red'
|
||||
elif value >= 5:
|
||||
return 'orange'
|
||||
return 'green'
|
||||
|
||||
def draw_svg(values: list[int], max_value: int = 10, height: int = 30):
|
||||
lines = ''.join(f'<line x1="0" x2="{len(values) * 10}" y1="{v * 10}" y2="{v * 10}" stroke="gray" stroke_width="1"/>' for v in range(0, max_value, 2))
|
||||
rects = ''.join(f'<rect fill="{get_color(value)}" width="9" height="{min(value, max_value) * 10}" x="{idx * 10 + 0.5}" y="{(max_value - min(value, max_value)) * 10}"/>' for idx, value in enumerate(values))
|
||||
return f'<div style="height: {height}px"><svg xmlns="https://www.w3.org/2000/svg" viewbox="0 0 {len(values) * 10} {max_value * 10}" style="border: 1px solid black; height: 100%; width: 100%;" preserveAspectRatio="none" shape-rendering="cripsEdges">{lines}{rects}</svg></div>'
|
||||
|
||||
class BuildErrorLink(models.Model):
|
||||
_name = 'runbot.build.error.link'
|
||||
_description = 'Build Build Error Extended Relation'
|
||||
@ -123,6 +137,12 @@ class BuildError(models.Model):
|
||||
|
||||
random = fields.Boolean('Random', compute="_compute_random", store=True)
|
||||
|
||||
graph_history = fields.Html('30 days history', compute='_compute_graph', sanitize=False)
|
||||
graph_hourly_recurence = fields.Html('Hourly recurence', compute='_compute_graph', sanitize=False)
|
||||
graph_day_of_week_recurence = fields.Html('Weekly recurence', compute='_compute_graph', sanitize=False)
|
||||
graph_day_of_month_recurence = fields.Html('Monthly recurence', compute='_compute_graph', sanitize=False)
|
||||
|
||||
|
||||
@api.constrains('tags_min_version_id', 'tags_max_version_id')
|
||||
def _check_min_max_version(self):
|
||||
for build_error in self:
|
||||
@ -263,6 +283,67 @@ class BuildError(models.Model):
|
||||
else:
|
||||
record.analogous_content_ids = False
|
||||
|
||||
def _get_log_dates(self, start_date: datetime.datetime, end_date: datetime.datetime):
|
||||
"""
|
||||
Returns an count of build_error per hour for the last 30 days.
|
||||
-> Dict[Self, Dict[datetime, int]]
|
||||
"""
|
||||
assert self, 'Method does not work if called with empty recordset.'
|
||||
result = defaultdict(dict)
|
||||
if not self._origin.ids:
|
||||
return result
|
||||
self.env.cr.execute("""
|
||||
SELECT error.id as error_id, date_trunc('hour', link.log_date) as time, count(*) as count
|
||||
FROM runbot_build_error AS error
|
||||
JOIN runbot_build_error_content AS content ON content.error_id = error.id
|
||||
JOIN runbot_build_error_link AS link ON link.error_content_id = content.id
|
||||
WHERE error.id IN %s AND link.log_date BETWEEN %s AND %s
|
||||
GROUP BY error.id, date_trunc('hour', link.log_date)
|
||||
""", (tuple(self.ids), start_date, end_date))
|
||||
data = self.env.cr.dictfetchall()
|
||||
for d in data:
|
||||
result[self.browse(d['error_id'])][d['time']] = d['count']
|
||||
return result
|
||||
|
||||
@api.depends('build_error_link_ids')
|
||||
def _compute_graph(self):
|
||||
end_date = fields.Date.today() + relativedelta(days=1)
|
||||
start_date = end_date - relativedelta(days=30)
|
||||
log_date_per_error = self._get_log_dates(start_date, end_date)
|
||||
for error in self:
|
||||
dates = log_date_per_error[error]
|
||||
daily_freq = [
|
||||
sum(
|
||||
count
|
||||
for hour, count in dates.items() if hour.date() == date.date()
|
||||
)
|
||||
for date in rrule.rrule(rrule.DAILY, dtstart=start_date, until=end_date)
|
||||
]
|
||||
error.graph_history = draw_svg(daily_freq, max_value=max(daily_freq))
|
||||
hourly_freq = [
|
||||
sum(
|
||||
count
|
||||
for hour, count in dates.items() if hour.hour == h
|
||||
)
|
||||
for h in range(24)
|
||||
]
|
||||
error.graph_hourly_recurence = draw_svg(hourly_freq)
|
||||
day_of_week_freq = [
|
||||
sum(
|
||||
count
|
||||
for hour, count in dates.items() if hour.isoweekday() -1 == day
|
||||
)
|
||||
for day in range(7)
|
||||
]
|
||||
error.graph_day_of_week_recurence = draw_svg(day_of_week_freq)
|
||||
day_of_month_recurrence = [
|
||||
sum(
|
||||
count
|
||||
for hour, count in dates.items() if hour.day - 1 == day
|
||||
)
|
||||
for day in range(31)
|
||||
]
|
||||
error.graph_day_of_month_recurence = draw_svg(day_of_month_recurrence)
|
||||
|
||||
@api.constrains('test_tags')
|
||||
def _check_test_tags(self):
|
||||
|
@ -48,6 +48,7 @@
|
||||
<field name="random"/>
|
||||
<field name="first_seen_date" widget="frontend_url" options="{'link_field': 'first_seen_build_id'}"/>
|
||||
<field name="last_seen_date" widget="frontend_url" options="{'link_field': 'last_seen_build_id'}"/>
|
||||
<field name="graph_history"/>
|
||||
<field name="first_seen_build_id" invisible="True"/>
|
||||
<field name="last_seen_build_id" invisible="True"/>
|
||||
</group>
|
||||
@ -188,6 +189,18 @@
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Frequency graphs">
|
||||
<group>
|
||||
<group>
|
||||
<field name="graph_history"/>
|
||||
<field name="graph_hourly_recurence"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="graph_day_of_week_recurence"/>
|
||||
<field name="graph_day_of_month_recurence"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
@ -347,6 +360,11 @@
|
||||
<field name="fixing_pr_id" optional="hide"/>
|
||||
<field name="fixing_pr_alive" optional="hide"/>
|
||||
<field name="fixing_pr_url" widget="pull_request_url" text="view PR" readonly="1" invisible="not fixing_pr_url"/>
|
||||
<field name="graph_history" optional="show"/>
|
||||
<field name="graph_hourly_recurence" optional="hide"/>
|
||||
<field name="graph_day_of_week_recurence" optional="hide"/>
|
||||
<field name="graph_day_of_month_recurence" optional="hide"/>
|
||||
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
Loading…
Reference in New Issue
Block a user