diff --git a/runbot/__manifest__.py b/runbot/__manifest__.py index d7d25f9f..3f366e90 100644 --- a/runbot/__manifest__.py +++ b/runbot/__manifest__.py @@ -29,6 +29,7 @@ 'templates/build.xml', 'templates/build_stats.xml', 'templates/bundle.xml', + 'templates/bundle_list.xml', 'templates/commit.xml', 'templates/dashboard.xml', 'templates/frontend.xml', diff --git a/runbot/controllers/frontend.py b/runbot/controllers/frontend.py index a9fcf053..52638b2f 100644 --- a/runbot/controllers/frontend.py +++ b/runbot/controllers/frontend.py @@ -197,6 +197,56 @@ class Runbot(Controller): return request.render('runbot.bundle', context) + @route(['/runbot/bundle', '/runbot/bundle/page/'], website=True, auth='user', type='http', sitemap=False) + def bundle_list(self, project_id=None, is_base='0', pr_filter=None, team_id=None, tag_id=None, search=None, page=1, **kwargs): + if not project_id: + project_id = request.env.ref('runbot.main_project').id + + is_base = bool(int(is_base)) + + pr_filters = { + 'all': { 'text': 'All', 'domain': False}, + 'has_pr': { 'text': 'With Pull Request(s)', 'domain': ('pr_count', '>', 0)}, + 'has_no_pr': { 'text': 'Without Pull Request(s)', 'domain': ('pr_count', '=', 0)}, + 'has_open_pr': { 'text': 'With Open Pull Request(s)', 'domain': ('pr_open_count', '>', 0)}, + 'has_no_open_pr': { 'text': 'Without Open Pull Request(s)', 'domain': ('pr_open_count', '=', 0)}, + } + teams = request.env['runbot.team'].search([], order='name') + tags = request.env['runbot.bundle.tag'].search([], order='name') + domain = [('project_id', '=', project_id), ('is_base', '=', is_base)] + + if pr_filter: + domain.append(pr_filters.get(pr_filter, {}).get('domain', ())) + if team_id: + team = request.env['runbot.team'].browse(int(team_id)) + domain.append(('owner_id', 'in', team.user_ids.ids)) + if tag_id: + tag_name = request.env['runbot.bundle.tag'].browse(int(tag_id)).name + domain.append(('tags', 'like', f'%{tag_name}%')) + + if search: + search = search if len(search) < 60 else search[:60] + domain.append(('name', 'like', f'%{search}%')) + + bundle_count = request.env['runbot.bundle.pr'].search_count(domain) + pager = request.website.pager( + url='/runbot/bundle', + url_args={'is_base': '1' if is_base else 0, 'pr_filter': pr_filter, 'team_id': team_id}, + total=bundle_count, + page=page, + step=20, + ) + bundle_ids = request.env['runbot.bundle.pr'].search(domain, limit=20, offset=pager.get('offset', 0), order='id desc').ids + + context = { + 'pager': pager, + 'pr_filters': pr_filters, + 'teams': teams, + 'tags': tags, + 'bundles': request.env['runbot.bundle'].browse(bundle_ids), + } + return request.render('runbot.bundle_list', context) + @o_route([ '/runbot/bundle//force', '/runbot/bundle//force/', diff --git a/runbot/models/bundle.py b/runbot/models/bundle.py index 4578658c..c5c3b706 100644 --- a/runbot/models/bundle.py +++ b/runbot/models/bundle.py @@ -50,6 +50,10 @@ class Bundle(models.Model): commit_limit = fields.Integer("Commit limit") file_limit = fields.Integer("File limit") + # Administrative fields + owner_id = fields.Many2one('res.users', 'Bundle Owner') + tag_ids = fields.Many2many('runbot.bundle.tag', string='Tags') + @api.depends('name') def _compute_host_id(self): assigned_only = None @@ -241,3 +245,64 @@ class Bundle(models.Model): for branch in self.branch_ids.sorted(key=lambda b: (b.is_pr)): branch_groups[branch.remote_id.repo_id].append(branch) return branch_groups + + +class BundleTag(models.Model): + + _name = "runbot.bundle.tag" + _description = "Bundle tag" + + _sql_constraints = [ + ('unique_bundle_tag_nam', 'unique (name)', 'avoid duplicate tags'), + ] + + name = fields.Char('Tag', ) + bundle_ids = fields.Many2many('runbot.bundle', string='Bundles') + + +class BundlePr(models.Model): + + _name = "runbot.bundle.pr" + _description = "Bundle PR Link" + _auto = False + _order = 'id desc' + + id = fields.Many2one('runbot.bundle', string='Bundle', readonly=True) + name = fields.Char(string='Bundle Name', readonly=True) + project_id = fields.Many2one('runbot.project', readonly=True) + is_base = fields.Boolean('Is base', readonly=True) + owner_id = fields.Many2one('res.users', string='Bundle Owner', readonly=True) + tags = fields.Char('Comma Separated Tags', readonly=True) + branch_count = fields.Integer('Branch count', readonly=True) + pr_count = fields.Integer('PR count', readonly=True) + pr_open_count = fields.Integer('Open PR count', readonly=True) + pr_closed_count = fields.Integer('Closed PR count', readonly=True) + + def init(self): + """ Create an SQL view for bundle """ + tools.drop_view_if_exists(self._cr, 'runbot_bundle_pr') + self._cr.execute(""" + CREATE VIEW runbot_bundle_pr AS ( + SELECT + runbot_bundle.id AS id, + runbot_bundle.name AS name, + runbot_bundle.project_id AS project_id, + runbot_bundle.is_base AS is_base, + runbot_bundle.owner_id AS owner_id, + STRING_AGG(runbot_bundle_tag.name,',') AS tags, + count(runbot_branch.id) AS branch_count, + SUM(CASE WHEN runbot_branch.is_pr THEN 1 ELSE 0 END) AS pr_count, + SUM(CASE WHEN runbot_branch.is_pr AND runbot_branch.alive THEN 1 ELSE 0 END) AS pr_open_count, + SUM(CASE WHEN runbot_branch.is_pr AND NOT runbot_branch.alive THEN 1 ELSE 0 END) AS pr_closed_count + FROM + runbot_bundle + JOIN + runbot_branch ON runbot_branch.bundle_id = runbot_bundle.id + LEFT JOIN + runbot_bundle_runbot_bundle_tag_rel ON runbot_bundle_runbot_bundle_tag_rel.runbot_bundle_id = runbot_bundle.id + LEFT JOIN + runbot_bundle_tag ON runbot_bundle_tag.id = runbot_bundle_runbot_bundle_tag_rel.runbot_bundle_tag_id + GROUP BY + runbot_bundle.id + )""" + ) diff --git a/runbot/security/ir.model.access.csv b/runbot/security/ir.model.access.csv index c2227dc8..99bb3347 100644 --- a/runbot/security/ir.model.access.csv +++ b/runbot/security/ir.model.access.csv @@ -73,6 +73,10 @@ access_runbot_project_runbot_admin,access_runbot_project_runbot_admin,runbot.mod access_runbot_bundle_user,access_runbot_bundle_user,runbot.model_runbot_bundle,runbot.group_user,1,0,0,0 access_runbot_bundle_runbot_admin,access_runbot_bundle_runbot_admin,runbot.model_runbot_bundle,runbot.group_runbot_admin,1,1,1,1 +access_runbot_bundle_pr_user,access_runbot_bundle_pr_usr,runbot.model_runbot_bundle_pr,runbot.group_user,1,0,0,0 +access_runbot_bundle_tag_admin,access_runbot_bundle_tag_admin,runbot.model_runbot_bundle_tag,runbot.group_runbot_admin,1,1,1,1 +access_runbot_bundle_tag_user,access_runbot_bundle_tag_user,runbot.model_runbot_bundle_tag,runbot.group_user,1,0,0,0 + access_runbot_batch_user,access_runbot_batch_user,runbot.model_runbot_batch,runbot.group_user,1,0,0,0 access_runbot_batch_runbot_admin,access_runbot_batch_runbot_admin,runbot.model_runbot_batch,runbot.group_runbot_admin,1,1,1,1 diff --git a/runbot/templates/bundle_list.xml b/runbot/templates/bundle_list.xml new file mode 100644 index 00000000..d5a9d34b --- /dev/null +++ b/runbot/templates/bundle_list.xml @@ -0,0 +1,123 @@ + + + + + + + + diff --git a/runbot/templates/frontend.xml b/runbot/templates/frontend.xml index 863e750c..ead3ecbe 100644 --- a/runbot/templates/frontend.xml +++ b/runbot/templates/frontend.xml @@ -90,7 +90,7 @@ - View batch... + diff --git a/runbot/views/bundle_views.xml b/runbot/views/bundle_views.xml index 03f205a5..5dfe0cf9 100644 --- a/runbot/views/bundle_views.xml +++ b/runbot/views/bundle_views.xml @@ -37,6 +37,8 @@ + + @@ -122,6 +124,7 @@ +