runbot/runbot/models/team.py
Xavier-Do 435ac449f5 [FIX] runbot: various fixes and ref
- clean thread username
- allow to write on params for debug (was mainly usefull to forbid it
at the beginning)
- imrpove some guidelines about method and actions naming/ ordering
- move some code for a cleaner organisation.
- remove some useless request.env.user (not useful anymore)
2023-09-25 10:52:16 +02:00

197 lines
9.3 KiB
Python

# -*- coding: utf-8 -*-
import ast
import hashlib
import logging
import re
from ..common import make_github_session
from collections import defaultdict
from dateutil.relativedelta import relativedelta
from fnmatch import fnmatch
from odoo import models, fields, api
from odoo.exceptions import ValidationError, UserError
_logger = logging.getLogger(__name__)
class RunbotTeam(models.Model):
_name = 'runbot.team'
_description = "Runbot Team"
_order = 'name, id'
_inherit = 'mail.thread'
name = fields.Char('Team', required=True)
project_id = fields.Many2one('runbot.project', 'Project', help='Project to monitor', required=True,
default=lambda self: self.env.ref('runbot.main_project'))
organisation = fields.Char('organisation', related="project_id.organisation")
user_ids = fields.Many2many('res.users', string='Team Members', domain=[('share', '=', False)])
dashboard_id = fields.Many2one('runbot.dashboard', string='Dashboard')
build_error_ids = fields.One2many('runbot.build.error', 'team_id', string='Team Errors', domain=[('parent_id', '=', False)])
path_glob = fields.Char(
'Module Wildcards',
help='Comma separated list of `fnmatch` wildcards used to assign errors automaticaly\n'
'Negative wildcards starting with a `-` can be used to discard some path\n'
'e.g.: `*website*,-*website_sale*`'
)
module_ownership_ids = fields.One2many('runbot.module.ownership', 'team_id')
codeowner_ids = fields.One2many('runbot.codeowner', 'team_id')
trigger_ids = fields.Many2many('runbot.trigger', string='Followed triggers')
upgrade_exception_ids = fields.One2many('runbot.upgrade.exception', 'team_id', string='Team Upgrade Exceptions')
github_team = fields.Char('Github team', tracking=True)
github_logins = fields.Char('Github logins', help='Additional github logins, prefer adding the login on the member of the team', tracking=True)
skip_team_pr = fields.Boolean('Skip team pr', help="Don't add codeowner if pr was created by a member of the team", tracking=True)
skip_fw_pr = fields.Boolean('Skip forward-port pr', help="Don't add codeowner if pr is a forwardport, even when forced pushed", tracking=True)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if 'dashboard_id' not in vals or vals['dashboard_id'] == False:
dashboard = self.env['runbot.dashboard'].search([('name', '=', vals['name'])])
if not dashboard:
dashboard = dashboard.create({'name': vals['name']})
vals['dashboard_id'] = dashboard.id
return super().create(vals_list)
@api.model
def _get_team(self, file_path, repos=None):
# path = file_path.removeprefix('/data/build/')
path = file_path
if path.startswith('/data/build/'):
path = path.split('/', 3)[3]
repo_name = path.split('/')[0]
module = None
if repos:
repos = repos.filtered(lambda repo: repo.name == repo_name)
else:
repos = self.env['runbot.repo'].search([('name', '=', repo_name)])
for repo in repos:
module = repo._get_module(path)
if module:
break
if module:
for ownership in self.module_ownership_ids.sorted(lambda t: t.is_fallback):
if module == ownership.module_id.name:
return ownership.team_id
for team in self:
if not team.path_glob:
continue
if any([fnmatch(file_path, pattern.strip().strip('-')) for pattern in team.path_glob.split(',') if pattern.strip().startswith('-')]):
continue
if any([fnmatch(file_path, pattern.strip()) for pattern in team.path_glob.split(',') if not pattern.strip().startswith('-')]):
return team
return False
def _get_members_logins(self):
self.ensure_one()
team_loggins = set()
if self.github_logins:
team_loggins = set(self.github_logins.split(','))
team_loggins |= set(self.user_ids.filtered(lambda user: user.github_login).mapped('github_login'))
return team_loggins
def _fetch_members(self):
self.check_access_rights('write')
self.check_access_rule('write')
for team in self:
if team.github_team:
url = f"https://api.github.com/orgs/{team.organisation}/teams/{team.github_team}"
session = make_github_session(team.project_id.sudo().token)
response = session.get(url)
if response.status_code != 200:
raise UserError(f'Cannot find team {team.github_team}')
team_infos = response.json()
members_url = team_infos['members_url'].replace('{/member}', '')
members = session.get(members_url).json()
team_members_logins = set(team.user_ids.mapped('github_login'))
members = [member['login'] for member in members if member['login'] not in team_members_logins]
team.github_logins = ','.join(sorted(members))
class Module(models.Model):
_name = 'runbot.module'
_description = 'Modules'
name = fields.Char('Name')
ownership_ids = fields.One2many('runbot.module.ownership', 'module_id')
team_ids = fields.Many2many('runbot.team', string="Teams", compute='_compute_team_ids')
@api.depends('ownership_ids')
def _compute_team_ids(self):
for record in self:
record.team_ids = record.ownership_ids.team_id
class ModuleOwnership(models.Model):
_name = 'runbot.module.ownership'
_description = "Module ownership"
module_id = fields.Many2one('runbot.module', string='Module', required=True, ondelete='cascade')
team_id = fields.Many2one('runbot.team', string='Team', required=True)
is_fallback = fields.Boolean('Fallback')
def name_get(self):
return [(record.id, f'{record.module_id.name} -> {record.team_id.name}{" (fallback)" if record.is_fallback else ""}' ) for record in self]
class RunbotDashboard(models.Model):
_name = 'runbot.dashboard'
_description = "Runbot Dashboard"
_order = 'name, id'
name = fields.Char('Team', required=True)
team_ids = fields.One2many('runbot.team', 'dashboard_id', string='Teams')
dashboard_tile_ids = fields.Many2many('runbot.dashboard.tile', string='Dashboards tiles')
class RunbotDashboardTile(models.Model):
_name = 'runbot.dashboard.tile'
_description = "Runbot Dashboard Tile"
_order = 'sequence, id'
sequence = fields.Integer('Sequence')
name = fields.Char('Name')
dashboard_ids = fields.Many2many('runbot.dashboard', string='Dashboards')
display_name = fields.Char(compute='_compute_display_name')
project_id = fields.Many2one('runbot.project', 'Project', help='Project to monitor', required=True,
default=lambda self: self.env.ref('runbot.main_project'))
category_id = fields.Many2one('runbot.category', 'Category', help='Trigger Category to monitor', required=True,
default=lambda self: self.env.ref('runbot.default_category'))
trigger_id = fields.Many2one('runbot.trigger', 'Trigger', help='Trigger to monitor in chosen category')
config_id = fields.Many2one('runbot.build.config', 'Config', help='Select a sub_build with this config')
domain_filter = fields.Char('Domain Filter', help='If present, will be applied on builds', default="[('global_result', '=', 'ko')]")
custom_template_id = fields.Many2one('ir.ui.view', help='Change for a custom Dashboard card template',
domain=[('type', '=', 'qweb')], default=lambda self: self.env.ref('runbot.default_dashboard_tile_view'))
sticky_bundle_ids = fields.Many2many('runbot.bundle', compute='_compute_sticky_bundle_ids', string='Sticky Bundles')
build_ids = fields.Many2many('runbot.build', compute='_compute_build_ids', string='Builds')
@api.depends('project_id', 'category_id', 'trigger_id', 'config_id')
def _compute_display_name(self):
for board in self:
names = [board.project_id.name, board.category_id.name, board.trigger_id.name, board.config_id.name, board.name]
board.display_name = ' / '.join([n for n in names if n])
@api.depends('project_id')
def _compute_sticky_bundle_ids(self):
sticky_bundles = self.env['runbot.bundle'].search([('sticky', '=', True)])
for dashboard in self:
dashboard.sticky_bundle_ids = sticky_bundles.filtered(lambda b: b.project_id == dashboard.project_id)
@api.depends('project_id', 'category_id', 'trigger_id', 'config_id', 'domain_filter')
def _compute_build_ids(self):
for dashboard in self:
last_done_batch_ids = dashboard.sticky_bundle_ids.with_context(category_id=dashboard.category_id.id).last_done_batch
if dashboard.trigger_id:
all_build_ids = last_done_batch_ids.slot_ids.filtered(lambda s: s.trigger_id == dashboard.trigger_id).all_build_ids
else:
all_build_ids = last_done_batch_ids.all_build_ids
domain = ast.literal_eval(dashboard.domain_filter) if dashboard.domain_filter else []
if dashboard.config_id:
domain.append(('config_id', '=', dashboard.config_id.id))
dashboard.build_ids = all_build_ids.filtered_domain(domain)