Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 52 additions & 14 deletions runbot/models/bundle.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import datetime
import re

from collections import defaultdict
from odoo import models, fields, api, tools
from itertools import chain

from odoo import api, fields, models, tools


class Bundle(models.Model):
Expand Down Expand Up @@ -55,7 +56,11 @@ class Bundle(models.Model):
# extra_info
description = fields.Char('Description', compute='_compute_description', store=True, readonly=False)
tag_ids = fields.Many2many('runbot.bundle.tag', string='Tags')
team_id = fields.Many2one('runbot.team', compute='_compute_team_id', store=True, readonly=False)
author_ids = fields.Many2many('res.users', string='Involved Users', compute='_compute_author_ids', domain=[('share', '=', False)])
team_ids = fields.Many2many('runbot.team', string='Involved Teams', compute='_compute_team_ids')
team_id = fields.Many2one('runbot.team', string='Owning Team', compute='_compute_team_id', inverse='_inverse_team_id', store=True, tracking=True)
manual_team_id = fields.Many2one('runbot.team', 'Manually set team')
auto_team_id = fields.Many2one('runbot.team', 'Automatically set team', compute='_compute_auto_team_id', readonly=True)

priority_offset = fields.Integer("Priority offset", help="Offset in seconds to remove from the create date of a batch to define priority, positive value means higher priority, negative value means lower priority.")

Expand Down Expand Up @@ -201,19 +206,51 @@ def _compute_all_trigger_custom_ids(self):
parent_bundle = self.env['runbot.bundle'].search([('name', '=', targets.pop())])
bundle.all_trigger_custom_ids = parent_bundle.all_trigger_custom_ids

@api.depends('name')
def _compute_team_id(self):
ngram_re = re.compile(r'.+\((?P<ngram>[a-z]{2,4})\)$')
team_by_ngram_project = dict()
for team in self.env['runbot.team'].search([('module_ownership_ids', '!=', False)]):
for user in team.user_ids:
if m := ngram_re.match(user.name.lower()):
team_by_ngram_project[m.group('ngram'), team.project_id] = team
for bundle in self:
if bundle.is_base or not bundle.name:
@api.depends('name', 'branch_ids.pr_author', 'branch_ids.forwardport_of_id')
def _compute_author_ids(self):
self.author_ids = self.env['res.users'].browse()
bundles = self.filtered(lambda b: not b.is_base and not b.is_staging)

github_logins_by_bundle = {bundle: set(bundle.branch_ids.filtered('is_pr').mapped(lambda br: (br.forwardport_of_id and br.forwardport_of_id.pr_author) or br.pr_author)) for bundle in bundles}
github_logins = set(chain.from_iterable(github_logins_by_bundle.values()))
users = self.env['res.users'].search([('share', '=', False), ('github_login', 'in', github_logins)])
user_ids_by_github_login = {u.github_login: u.id for u in users}
for bundle, github_logins in github_logins_by_bundle.items():
if users_ids := list(filter(None, {user_ids_by_github_login.get(gl) for gl in github_logins})):
bundle.author_ids = users_ids
bundles -= bundle

valid_bundle_name_re = re.compile(r'^.{3,6}-.*-.{2,5}$')
bundles = bundles.filtered(lambda b: valid_bundle_name_re.match(b.name))
if not bundles:
return

ngram_re = re.compile(r'.+\(([a-z]{2,5})\)$')
user_ids_by_ngram = {u[1][0]: u[0] for u in self.env['res.users'].search([('share', '=', False)]).mapped(lambda rec: (rec.id, ngram_re.findall(rec.complete_name))) if u[1]}
for bundle in bundles:
if not bundle.name:
continue
bundle_ngram = bundle.name.split('-')[-1].lower()
bundle.team_id = team_by_ngram_project.get((bundle_ngram, bundle.project_id))
bundle.author_ids = list(filter(None, [user_ids_by_ngram.get(bundle_ngram)]))

@api.depends('author_ids')
def _compute_team_ids(self):
for bundle in self:
bundle.team_ids = bundle.author_ids.runbot_team_ids.filtered(lambda rec: rec.module_ownership_ids)

@api.depends('manual_team_id', 'auto_team_id')
def _compute_team_id(self):
for bundle in self:
bundle.team_id = bundle.manual_team_id or bundle.auto_team_id

@api.depends('name', 'team_ids', 'author_ids')
def _compute_auto_team_id(self):
for bundle in self:
bundle.auto_team_id = bundle.team_ids and bundle.team_ids[0]

def _inverse_team_id(self):
self.manual_team_id = self.team_id


@api.depends('branch_ids')
def _compute_description(self):
Expand Down Expand Up @@ -348,6 +385,7 @@ class BundleTag(models.Model):

_name = "runbot.bundle.tag"
_description = "Bundle tag"
_order = "id desc, name"

name = fields.Char(string='Bundle Tag')
bundle_ids = fields.Many2many('runbot.bundle', string='Bundles')
42 changes: 33 additions & 9 deletions runbot/tests/test_branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ def test_relations_no_match(self):
self.assertEqual(b.bundle_id.base_id.name, 'master')

def test_relations_pr(self):
self.Branch.create({
dev_branch = self.Branch.create({
'remote_id': self.remote_odoo_dev.id,
'name': 'master-test-tri',
'name': 'master-test-tri-imp',
'is_pr': False,
})

Expand All @@ -167,17 +167,19 @@ def test_relations_pr(self):
'login': 'Pr author'
},
}
b = self.Branch.create({
pr_branch = self.Branch.create({
'remote_id': self.remote_odoo_dev.id,
'name': '100',
'is_pr': True,
})

self.assertEqual(b.bundle_id.name, 'master-test-tri-imp')
self.assertEqual(b.bundle_id.base_id.name, 'master')
self.assertEqual(b.bundle_id.previous_major_version_base_id.name, '13.0')
self.assertEqual(sorted(b.bundle_id.intermediate_version_base_ids.mapped('name')), ['saas-13.1', 'saas-13.2'])
bundle = pr_branch.bundle_id

self.assertEqual(bundle.name, 'master-test-tri-imp')
self.assertEqual(bundle.base_id.name, 'master')
self.assertEqual(bundle.previous_major_version_base_id.name, '13.0')
self.assertEqual(sorted(bundle.intermediate_version_base_ids.mapped('name')), ['saas-13.1', 'saas-13.2'])
self.assertIn(dev_branch, bundle.branch_ids)

class TestBranchForbidden(RunbotCase):
"""Test that a branch matching the repo forbidden regex, goes to dummy bundle"""
Expand Down Expand Up @@ -309,14 +311,17 @@ def test_bundle_team_attribution(self):
self.stop_patcher('isfile')
self.stop_patcher('isdir') # needed to create the user avatar
create_context = {'no_reset_password': True, 'mail_create_nolog': True, 'mail_create_nosubscribe': True, 'mail_notrack': True}
test_user = new_test_user(self.env, login='testrunbot', name='testrunbot (tru)', context=create_context)
committer_user = new_test_user(self.env, login='testrunbot', name='testrunbot (tru)', email='trut@somewhere.com', context=create_context)
author_user = new_test_user(self.env, login='testrunbot_author', name='test author (aut)', email='aut@somewhere.com', context=create_context)
github_user = new_test_user(self.env, login='github_author', name='github author (gaut)', email='gaut@somewhere.com', context=create_context)
github_user.github_login = 'gaut_github'

team = self.env['runbot.team'].create({
'name': 'Test Team',
'project_id': self.project.id,
})

team.user_ids += test_user
team.user_ids += committer_user

branch = self.Branch.create({
'remote_id': self.remote_odoo_dev.id,
Expand All @@ -332,6 +337,7 @@ def test_bundle_team_attribution(self):

bundle = self.env['runbot.bundle'].search([('name', '=', branch.name)])
self.assertEqual(bundle.team_id, team)
self.assertEqual(bundle.author_ids, committer_user, 'The only involved author should be the one based on bundle ngram')

# now test that a team can be manually set on a bundle
other_team = self.env['runbot.team'].create({
Expand All @@ -341,3 +347,21 @@ def test_bundle_team_attribution(self):

bundle.team_id = other_team
self.assertEqual(bundle.team_id, other_team)

self.patchers['github_patcher'].return_value = {
'base': {'ref': 'saas-19.1'},
'head': {'label': 'dev:saas-19.1-test-tru', 'repo': {'full_name': 'dev/odoo'}},
'title': '[IMP] Title',
'body': 'Body',
'user': {
'login': github_user.github_login,
},
}
pr_branch = self.Branch.create({
'remote_id': self.remote_odoo_dev.id,
'name': '100',
'is_pr': True,
})

self.assertIn(pr_branch, bundle.branch_ids)
self.assertIn(github_user, bundle.author_ids)
6 changes: 4 additions & 2 deletions runbot/views/bundle_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@
<field name="name" widget="char_frontend_url"/>
<field name="description"/>
<field name="tag_ids" widget="many2many_tags" options="{'not_delete': True, 'no_create': True}"/>
<field name="project_id"/>
<field name="team_id" string="Owning Team"/>
<field name="team_ids" widget="many2many_tags"/>
<field name="author_ids" widget="many2many_tags"/>
</group>
<group>
<group string="Base options">
<field name="project_id"/>
<field name="priority_offset"/>
<field name="team_id"/>
<field name="sticky" readonly="0"/>
<field name="is_base" readonly="1"/>
<field name="is_staging" readonly="1"/>
Expand Down