[IMP] runbot: add a simpler bundle page

* add the possibility to tag bundles
* add a owner to the bundle

A new bundle page is added in order to filter and search bundles easily.
The bundles can be filtered by Pull Requests being open or closed.
They can be filtered by teams and tags and searched by names.
This commit is contained in:
Christophe Monniez 2022-09-08 23:12:40 +02:00
parent d72b17862e
commit 0ea9c4aefe
7 changed files with 247 additions and 1 deletions

View File

@ -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',

View File

@ -197,6 +197,56 @@ class Runbot(Controller):
return request.render('runbot.bundle', context)
@route(['/runbot/bundle', '/runbot/bundle/page/<int: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/<model("runbot.bundle"):bundle>/force',
'/runbot/bundle/<model("runbot.bundle"):bundle>/force/<int:auto_rebase>',

View File

@ -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
)"""
)

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
73 access_runbot_upgrade_regex_user access_runbot_warning_admin access_runbot_upgrade_regex_user access_runbot_warning_admin runbot.model_runbot_upgrade_regex runbot.model_runbot_warning runbot.group_user runbot.group_runbot_admin 1 0 1 0 1 0 1
74 access_runbot_upgrade_regex_admin access_runbot_database_user access_runbot_upgrade_regex_admin access_runbot_database_user runbot.model_runbot_upgrade_regex runbot.model_runbot_database runbot.group_runbot_admin runbot.group_user 1 1 0 1 0 1 0
75 access_runbot_upgrade_exception_user access_runbot_database_admin access_runbot_upgrade_exception_user access_runbot_database_admin runbot.model_runbot_upgrade_exception runbot.model_runbot_database runbot.group_user runbot.group_runbot_admin 1 0 1 0 1 0 1
76 access_runbot_upgrade_regex_user access_runbot_upgrade_regex_user runbot.model_runbot_upgrade_regex runbot.group_user 1 0 0 0
77 access_runbot_upgrade_regex_admin access_runbot_upgrade_regex_admin runbot.model_runbot_upgrade_regex runbot.group_runbot_admin 1 1 1 1
78 access_runbot_upgrade_exception_user access_runbot_upgrade_exception_user runbot.model_runbot_upgrade_exception runbot.group_user 1 0 0 0
79 access_runbot_upgrade_exception_admin access_runbot_upgrade_exception_admin runbot.model_runbot_upgrade_exception runbot.group_runbot_admin 1 1 1 1
80 access_runbot_upgrade_exception_admin access_runbot_dockerfile_user access_runbot_upgrade_exception_admin access_runbot_dockerfile_user runbot.model_runbot_upgrade_exception runbot.model_runbot_dockerfile runbot.group_runbot_admin runbot.group_user 1 1 0 1 0 1 0
81 access_runbot_dockerfile_user access_runbot_dockerfile_admin access_runbot_dockerfile_user access_runbot_dockerfile_admin runbot.model_runbot_dockerfile runbot.group_user runbot.group_runbot_admin 1 0 1 0 1 0 1
82 access_runbot_dockerfile_admin access_runbot_codeowner_admin access_runbot_dockerfile_admin runbot_codeowner_admin runbot.model_runbot_dockerfile runbot.model_runbot_codeowner runbot.group_runbot_admin 1 1 1 1

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="runbot.bundle_list">
<t t-call="runbot.layout">
<t t-call="runbot.bundle_list_top_bar" />
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="row bundle_row" t-foreach="bundles" t-as="bundle">
<div class="col-xl-2 align-self-center">
<div class="one_line">
<a t-esc="bundle.name" t-attf-href="/runbot/bundle/{{bundle.id}}"/>
</div>
<t t-foreach="bundle.tag_ids" t-as="bundle_tag">
<span class="badge badge-info"><t t-esc="bundle_tag.name"/></span>
</t>
</div>
<div class="col-xl-10">
<div class="row no-gutters">
<div class="col-xl-7 align-self-center">
<t t-foreach="bundle.branch_groups().items()" t-as="group">
<t t-foreach="group[1]" t-as="branch">
<small>
<div class="btn-toolbar mb-1" role="toolbar">
<div class="btn-group btn-group-ssm" role="group">
<a t-att-href="branch.branch_url" class="btn btn-default text-left" title="View Branch on Github"><i class="fa fa-github"/></a>
<a groups="runbot.group_runbot_admin" class="btn btn-default fa fa-list text-left" t-attf-href="/web/#id={{branch.id}}&amp;view_type=form&amp;model=runbot.branch" target="new" title="View Branch in Backend"/>
<a href="#" t-esc="branch.remote_id.short_name" class="btn btn-default disabled text-left"/>
<a t-attf-href="/runbot/branch/{{branch.id}}" class="btn btn-default text-left" title="View Branch Details"><span t-att-class="'' if branch.alive else 'line-through'" t-esc="branch.name"/> <i t-if="not branch.alive" title="deleted/closed" class="fa fa-ban text-danger"/></a>
</div>
</div>
</small>
</t>
</t>
</div>
<div class="col-xl-5 align-self-center">
<t t-set="batch" t-value="bundle.last_batch"/>
<t t-call="runbot.batch_tile"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="navbar navbar-default">
<span class="pull-right">
<t t-call="website.pager" />
</span>
</div>
</div>
</t>
</template>
<template id="bundle_list_top_bar" name="Topbar">
<nav class="navbar navbar-light border-top shadow-sm d-print-none">
<div class="container-fluid">
<div class="d-flex flex-column flex-sm-row justify-content-between w-100">
<t t-call="website.pager" />
<span class="navbar-brand h4 my-0 mr-auto">Filters</span>
<ul class="nav">
<li class="nav-item dropdown mr-2 my-1">
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-filter"/>PR
</a>
<div class="dropdown-menu">
<t t-foreach="pr_filters" t-as="pr_filter">
<a class="dropdown-item d-flex align-items-center justify-content-between"
t-attf-href="/runbot/bundle?{{ keep_query('*', pr_filter=pr_filter if pr_filter != 'all' else None) }}">
<span t-esc="pr_filters[pr_filter].get('text')"/>
</a>
</t>
</div>
</li>
<li class="nav-item dropdown mr-2 my-1">
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-users"/>Team
</a>
<div class="dropdown-menu">
<a class="dropdown-item d-flex align-items-center justify-content-between"
t-attf-href="/runbot/bundle?{{ keep_query('*', team_id=None) }}">
<span>Any</span>
</a>
<t t-foreach="teams" t-as="team">
<a class="dropdown-item d-flex align-items-center justify-content-between"
t-attf-href="/runbot/bundle?{{ keep_query('*', team_id=team.id) }}">
<span t-esc="team.name"/>
</a>
</t>
</div>
</li>
<li class="nav-item dropdown mr-2 my-1">
<a href="#" role="button" class="btn dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-tags"/>Tags
</a>
<div class="dropdown-menu">
<a class="dropdown-item d-flex align-items-center justify-content-between"
t-attf-href="/runbot/bundle?{{ keep_query('*', tag_id=None) }}">
<span>Any</span>
</a>
<t t-foreach="tags" t-as="tag">
<a class="dropdown-item d-flex align-items-center justify-content-between"
t-attf-href="/runbot/bundle?{{ keep_query('*', tag_id=tag.id) }}">
<span t-esc="tag.name"/>
</a>
</t>
</div>
</li>
</ul>
<div class="col-md-4 d-none d-md-flex flex-row align-items-center justify-content-end">
<t t-call="website.website_search_box_input">
<t t-set="_form_classes" t-valuef="mt8 float-right"/>
<t t-set="action" t-valuef="/runbot/bundle"/>
<t t-set="placeholder">Search Bundle Names</t>
<t t-set="search" t-value="search"/>
</t>
</div>
</div>
</div>
</nav>
</template>
</data>
</odoo>

View File

@ -90,7 +90,7 @@
<t t-esc="batch.get_formated_age()"/>
<i class="fa fa-exclamation-triangle" t-if="batch.has_warning"/>
</span>
<span class="float-right header_hover">View batch...</span>
<span class="float-right header_hover" t-field="batch.create_date" t-options="{'format': 'yyyy-MM-dd HH:mmZ'}"/>
</div>
</a>
<t t-if="batch.state=='preparing'">

View File

@ -37,6 +37,8 @@
</div>
<group>
<field name="name"/>
<field name="owner_id"/>
<field name="tag_ids" widget="many2many_tags"/>
<field name="project_id"/>
<field name="sticky" readonly="0"/>
<field name="to_upgrade" readonly="0"/>
@ -122,6 +124,7 @@
<field name="no_build"/>
<field name="branch_ids"/>
<field name="version_id"/>
<field name="owner_id"/>
</tree>
</field>
</record>