diff --git a/runbot/models/build_error.py b/runbot/models/build_error.py
index 8a5b9db7..c2eccda4 100644
--- a/runbot/models/build_error.py
+++ b/runbot/models/build_error.py
@@ -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'' for v in range(0, max_value, 2))
+ rects = ''.join(f'' for idx, value in enumerate(values))
+ return f'
'
+
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):
diff --git a/runbot/views/build_error_views.xml b/runbot/views/build_error_views.xml
index 9a607672..5b55f33b 100644
--- a/runbot/views/build_error_views.xml
+++ b/runbot/views/build_error_views.xml
@@ -48,6 +48,7 @@
+
@@ -188,6 +189,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -347,6 +360,11 @@
+
+
+
+
+