mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[MERGE] runbot: migration to odoo 13.0
This commit is contained in:
commit
300f39003f
@ -6,7 +6,7 @@
|
||||
'author': "Odoo SA",
|
||||
'website': "http://runbot.odoo.com",
|
||||
'category': 'Website',
|
||||
'version': '4.6',
|
||||
'version': '4.8',
|
||||
'depends': ['website', 'base'],
|
||||
'data': [
|
||||
'security/runbot_security.xml',
|
||||
@ -33,5 +33,6 @@
|
||||
'data/build_parse.xml',
|
||||
'data/runbot_error_regex_data.xml',
|
||||
'data/error_link.xml',
|
||||
'data/website_data.xml',
|
||||
],
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ def time2str(t):
|
||||
|
||||
def dt2time(datetime):
|
||||
"""Convert datetime to time"""
|
||||
return time.mktime(time.strptime(datetime, DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
return time.mktime(datetime.timetuple())
|
||||
|
||||
|
||||
def now():
|
||||
|
@ -35,7 +35,7 @@ class RunbotBadge(Controller):
|
||||
|
||||
build = builds[0]
|
||||
etag = request.httprequest.headers.get('If-None-Match')
|
||||
retag = hashlib.md5(build[last_update].encode()).hexdigest()
|
||||
retag = hashlib.md5(str(build[last_update]).encode()).hexdigest()
|
||||
|
||||
if etag == retag:
|
||||
return werkzeug.wrappers.Response(status=304)
|
||||
|
@ -39,6 +39,7 @@ class Runbot(Controller):
|
||||
'host_stats': [],
|
||||
'pending_total': pending[0],
|
||||
'pending_level': pending[1],
|
||||
'hosts_data': request.env['runbot.host'].search([]),
|
||||
'search': search,
|
||||
'refresh': refresh,
|
||||
}
|
||||
@ -101,15 +102,11 @@ class Runbot(Controller):
|
||||
def branch_info(branch):
|
||||
return {
|
||||
'branch': branch,
|
||||
'fqdn': fqdn(),
|
||||
'builds': [build_dict[build_id] for build_id in build_by_branch_ids.get(branch.id) or []]
|
||||
}
|
||||
|
||||
context.update({
|
||||
'branches': [branch_info(b) for b in branches],
|
||||
'testing': build_obj.search_count([('repo_id', '=', repo.id), ('local_state', '=', 'testing')]),
|
||||
'running': build_obj.search_count([('repo_id', '=', repo.id), ('local_state', '=', 'running')]),
|
||||
'pending': build_obj.search_count([('repo_id', '=', repo.id), ('local_state', '=', 'pending')]),
|
||||
'qu': QueryURL('/runbot/repo/' + slug(repo), search=search, refresh=refresh),
|
||||
'fqdn': fqdn(),
|
||||
})
|
||||
@ -117,14 +114,6 @@ class Runbot(Controller):
|
||||
# consider host gone if no build in last 100
|
||||
build_threshold = max(build_ids or [0]) - 100
|
||||
|
||||
for result in build_obj.read_group([('id', '>', build_threshold)], ['host'], ['host']):
|
||||
if result['host']:
|
||||
context['host_stats'].append({
|
||||
'host': result['host'],
|
||||
'testing': build_obj.search_count([('local_state', '=', 'testing'), ('host', '=', result['host'])]),
|
||||
'running': build_obj.search_count([('local_state', '=', 'running'), ('host', '=', result['host'])]),
|
||||
})
|
||||
|
||||
context.update({'message': request.env['ir.config_parameter'].sudo().get_param('runbot.runbot_message')})
|
||||
return request.render('runbot.repo', context)
|
||||
|
||||
@ -302,21 +291,27 @@ class Runbot(Controller):
|
||||
return request.render("runbot.glances", qctx)
|
||||
|
||||
@route('/runbot/monitoring', type='http', auth='user', website=True)
|
||||
def monitoring(self, refresh=None):
|
||||
@route('/runbot/monitoring/<int:config_id>', type='http', auth='user', website=True)
|
||||
@route('/runbot/monitoring/<int:config_id>/<int:view_id>', type='http', auth='user', website=True)
|
||||
def monitoring(self, config_id=None, view_id=None, refresh=None):
|
||||
glances_ctx = self._glances_ctx()
|
||||
pending = self._pending()
|
||||
hosts_data = request.env['runbot.host'].search([])
|
||||
|
||||
monitored_config_id = int(request.env['ir.config_parameter'].sudo().get_param('runbot.monitored_config_id', 1))
|
||||
request.env.cr.execute("""SELECT DISTINCT ON (branch_id) branch_id, id FROM runbot_build
|
||||
WHERE config_id = %s
|
||||
AND global_state in ('running', 'done')
|
||||
AND branch_id in (SELECT id FROM runbot_branch where sticky='t')
|
||||
AND local_state != 'duplicate'
|
||||
ORDER BY branch_id ASC, id DESC""", [int(monitored_config_id)])
|
||||
last_monitored = request.env['runbot.build'].browse([r[1] for r in request.env.cr.fetchall()])
|
||||
last_monitored = None
|
||||
if config_id or config_id is None:
|
||||
monitored_config_id = config_id or int(request.env['ir.config_parameter'].sudo().get_param('runbot.monitored_config_id', 1))
|
||||
request.env.cr.execute("""SELECT DISTINCT ON (branch_id) branch_id, id FROM runbot_build
|
||||
WHERE config_id = %s
|
||||
AND global_state in ('running', 'done')
|
||||
AND branch_id in (SELECT id FROM runbot_branch where sticky='t')
|
||||
AND local_state != 'duplicate'
|
||||
ORDER BY branch_id ASC, id DESC""", [int(monitored_config_id)])
|
||||
last_monitored = request.env['runbot.build'].browse([r[1] for r in request.env.cr.fetchall()])
|
||||
|
||||
config = request.env['runbot.build.config'].browse(monitored_config_id)
|
||||
qctx = {
|
||||
'config': config,
|
||||
'refresh': refresh,
|
||||
'pending_total': pending[0],
|
||||
'pending_level': pending[1],
|
||||
@ -326,7 +321,7 @@ class Runbot(Controller):
|
||||
'auto_tags': request.env['runbot.build.error'].disabling_tags(),
|
||||
'build_errors': request.env['runbot.build.error'].search([('random', '=', True)])
|
||||
}
|
||||
return request.render("runbot.monitoring", qctx)
|
||||
return request.render(request.env['ir.ui.view'].browse('view_id') if view_id else config.monitoring_view_id.id or "runbot.monitoring", qctx)
|
||||
|
||||
@route(['/runbot/branch/<int:branch_id>', '/runbot/branch/<int:branch_id>/page/<int:page>'], website=True, auth='public', type='http')
|
||||
def branch_builds(self, branch_id=None, search='', page=1, limit=50, refresh='', **kwargs):
|
||||
@ -337,9 +332,9 @@ class Runbot(Controller):
|
||||
url='/runbot/branch/%s' % branch_id,
|
||||
total=builds_count,
|
||||
page=page,
|
||||
step=50
|
||||
step=50,
|
||||
)
|
||||
builds = request.env['runbot.build'].search(domain, limit=limit, offset=pager.get('offset',0))
|
||||
|
||||
context = {'pager': pager, 'builds': builds}
|
||||
context = {'pager': pager, 'builds': builds, 'repo': request.env['runbot.branch'].browse(branch_id).repo_id}
|
||||
return request.render("runbot.branch", context)
|
||||
|
@ -31,7 +31,7 @@ class RunbotHook(http.Controller):
|
||||
|
||||
# force update of dependencies to in case a hook is lost
|
||||
if not payload or event == 'push' or (event == 'pull_request' and payload.get('action') in ('synchronize', 'opened', 'reopened')):
|
||||
(repo | repo.dependency_ids).write({'hook_time': time.time()})
|
||||
(repo | repo.dependency_ids).set_hook_time(time.time())
|
||||
else:
|
||||
_logger.debug('Ignoring unsupported hook %s %s', event, payload.get('action', ''))
|
||||
return ""
|
||||
|
5
runbot/data/website_data.xml
Normal file
5
runbot/data/website_data.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<odoo>
|
||||
<record id="website.homepage_page" model="website.page">
|
||||
<field name="url">/home</field>
|
||||
</record>
|
||||
</odoo>
|
@ -12,6 +12,7 @@ _re_patch = re.compile(r'.*patch-\d+$')
|
||||
class runbot_branch(models.Model):
|
||||
|
||||
_name = "runbot.branch"
|
||||
_description = "Branch"
|
||||
_order = 'name'
|
||||
_sql_constraints = [('branch_repo_uniq', 'unique (name,repo_id)', 'The branch must be unique per repository !')]
|
||||
|
||||
@ -35,7 +36,7 @@ class runbot_branch(models.Model):
|
||||
no_auto_build = fields.Boolean("Don't automatically build commit on this branch", default=False)
|
||||
rebuild_requested = fields.Boolean("Request a rebuild", help="Rebuild the latest commit even when no_auto_build is set.", default=False)
|
||||
|
||||
branch_config_id = fields.Many2one('runbot.build.config', 'Run Config')
|
||||
branch_config_id = fields.Many2one('runbot.build.config', 'Branch Config')
|
||||
config_id = fields.Many2one('runbot.build.config', 'Run Config', compute='_compute_config_id', inverse='_inverse_config_id')
|
||||
|
||||
@api.depends('sticky', 'defined_sticky', 'target_branch_name', 'name')
|
||||
@ -47,16 +48,24 @@ class runbot_branch(models.Model):
|
||||
elif branch.defined_sticky:
|
||||
branch.closest_sticky = branch.defined_sticky # be carefull with loop
|
||||
elif branch.target_branch_name:
|
||||
corresping_branch = self.search([('branch_name', '=', branch.target_branch_name), ('repo_id', '=', branch.repo_id.id)])
|
||||
branch.closest_sticky = corresping_branch.closest_sticky
|
||||
corresponding_branch = self.search([('branch_name', '=', branch.target_branch_name), ('repo_id', '=', branch.repo_id.id)])
|
||||
branch.closest_sticky = corresponding_branch.closest_sticky
|
||||
else:
|
||||
repo_ids = (branch.repo_id | branch.repo_id.duplicate_id).ids
|
||||
self.env.cr.execute("select id from runbot_branch where sticky = 't' and repo_id = any(%s) and %s like name||'%%'", (repo_ids, branch.name or ''))
|
||||
branch.closest_sticky = self.browse(self.env.cr.fetchone())
|
||||
|
||||
@api.depends('closest_sticky.previous_version')
|
||||
@api.depends('closest_sticky') #, 'closest_sticky.previous_version')
|
||||
def _compute_previous_version(self):
|
||||
for branch in self:
|
||||
for branch in self.sorted(key='sticky', reverse=True):
|
||||
# orm does not support non_searchable.non_stored dependency.
|
||||
# thus, the closest_sticky.previous_version dependency will log an error
|
||||
# when previous_version is written.
|
||||
# this dependency is usefull to make the compute recursive, avoiding to have
|
||||
# both record and record.closest_sticky in self, in that order, making the record.previous_version
|
||||
# empty in all cases.
|
||||
# Sorting self on sticky will mitigate the problem. but it is still posible to
|
||||
# have computation errors if defined_sticky is not sticky. (which is not a normal use case)
|
||||
if branch.closest_sticky == branch:
|
||||
repo_ids = (branch.repo_id | branch.repo_id.duplicate_id).ids
|
||||
domain = [('branch_name', 'like', '%.0'), ('sticky', '=', True), ('branch_name', '!=', 'master'), ('repo_id', 'in', repo_ids)]
|
||||
@ -66,11 +75,12 @@ class runbot_branch(models.Model):
|
||||
else:
|
||||
branch.previous_version = branch.closest_sticky.previous_version
|
||||
|
||||
@api.depends('previous_version', 'closest_sticky.intermediate_stickies')
|
||||
@api.depends('previous_version', 'closest_sticky')
|
||||
def _compute_intermediate_stickies(self):
|
||||
for branch in self:
|
||||
for branch in self.sorted(key='sticky', reverse=True):
|
||||
if branch.closest_sticky == branch:
|
||||
if not branch.previous_version:
|
||||
branch.intermediate_stickies = [(5, 0, 0)]
|
||||
continue
|
||||
repo_ids = (branch.repo_id | branch.repo_id.duplicate_id).ids
|
||||
domain = [('id', '>', branch.previous_version.id), ('sticky', '=', True), ('branch_name', '!=', 'master'), ('repo_id', 'in', repo_ids)]
|
||||
@ -133,7 +143,7 @@ class runbot_branch(models.Model):
|
||||
return False
|
||||
return True
|
||||
|
||||
@api.model
|
||||
@api.model_create_single
|
||||
def create(self, vals):
|
||||
if not vals.get('config_id') and ('use-coverage' in (vals.get('name') or '')):
|
||||
coverage_config = self.env.ref('runbot.runbot_build_config_test_coverage', raise_if_not_found=False)
|
||||
@ -287,6 +297,6 @@ class runbot_branch(models.Model):
|
||||
for branch in self:
|
||||
if not branch.rebuild_requested:
|
||||
branch.rebuild_requested = True
|
||||
branch.repo_id.write({'hook_time': time.time()})
|
||||
branch.repo_id.set_hook_time(time.time())
|
||||
else:
|
||||
branch.rebuild_requested = False
|
||||
|
@ -32,6 +32,8 @@ def make_selection(array):
|
||||
|
||||
class runbot_build(models.Model):
|
||||
_name = "runbot.build"
|
||||
_description = "Build"
|
||||
|
||||
_order = 'id desc'
|
||||
_rec_name = 'id'
|
||||
|
||||
@ -55,16 +57,16 @@ class runbot_build(models.Model):
|
||||
# state machine
|
||||
|
||||
global_state = fields.Selection(make_selection(state_order), string='Status', compute='_compute_global_state', store=True)
|
||||
local_state = fields.Selection(make_selection(state_order), string='Build Status', default='pending', required=True, oldname='state', index=True)
|
||||
local_state = fields.Selection(make_selection(state_order), string='Build Status', default='pending', required=True, index=True)
|
||||
global_result = fields.Selection(make_selection(result_order), string='Result', compute='_compute_global_result', store=True)
|
||||
local_result = fields.Selection(make_selection(result_order), string='Build Result', oldname='result')
|
||||
local_result = fields.Selection(make_selection(result_order), string='Build Result')
|
||||
triggered_result = fields.Selection(make_selection(result_order), string='Triggered Result') # triggered by db only
|
||||
|
||||
requested_action = fields.Selection([('wake_up', 'To wake up'), ('deathrow', 'To kill')], string='Action requested', index=True)
|
||||
|
||||
nb_pending = fields.Integer("Number of pending in queue", default=0)
|
||||
nb_testing = fields.Integer("Number of test slot use", default=0)
|
||||
nb_running = fields.Integer("Number of test slot use", default=0)
|
||||
nb_running = fields.Integer("Number of run slot use", default=0)
|
||||
|
||||
# should we add a stored field for children results?
|
||||
active_step = fields.Many2one('runbot.build.config.step', 'Active step')
|
||||
@ -74,7 +76,7 @@ class runbot_build(models.Model):
|
||||
build_start = fields.Datetime('Build start')
|
||||
build_end = fields.Datetime('Build end')
|
||||
job_time = fields.Integer(compute='_compute_job_time', string='Job time')
|
||||
build_time = fields.Integer(compute='_compute_build_time', string='Job time')
|
||||
build_time = fields.Integer(compute='_compute_build_time', string='Build time')
|
||||
build_age = fields.Integer(compute='_compute_build_age', string='Build age')
|
||||
duplicate_id = fields.Many2one('runbot.build', 'Corresponding Build', index=True)
|
||||
revdep_build_ids = fields.Many2many('runbot.build', 'runbot_rev_dep_builds',
|
||||
@ -116,18 +118,21 @@ class runbot_build(models.Model):
|
||||
for build in self:
|
||||
build.log_list = ','.join({step.name for step in build.config_id.step_ids() if step._has_log()})
|
||||
|
||||
@api.depends('nb_testing', 'nb_pending', 'local_state', 'duplicate_id.global_state')
|
||||
@api.depends('children_ids.global_state', 'local_state', 'duplicate_id.global_state')
|
||||
def _compute_global_state(self):
|
||||
# could we use nb_pending / nb_testing ? not in a compute, but in a update state method
|
||||
for record in self:
|
||||
if record.duplicate_id:
|
||||
record.global_state = record.duplicate_id.global_state
|
||||
else:
|
||||
waiting_score = record._get_state_score('waiting')
|
||||
if record._get_state_score(record.local_state) < waiting_score or record.nb_pending + record.nb_testing == 0:
|
||||
record.global_state = record.local_state
|
||||
if record._get_state_score(record.local_state) > waiting_score and record.children_ids: # if finish, check children
|
||||
children_state = record._get_youngest_state([child.global_state for child in record.children_ids])
|
||||
if record._get_state_score(children_state) > waiting_score:
|
||||
record.global_state = record.local_state
|
||||
else:
|
||||
record.global_state = 'waiting'
|
||||
else:
|
||||
record.global_state = 'waiting'
|
||||
record.global_state = record.local_state
|
||||
|
||||
def _get_youngest_state(self, states):
|
||||
index = min([self._get_state_score(state) for state in states])
|
||||
@ -166,24 +171,7 @@ class runbot_build(models.Model):
|
||||
def _get_result_score(self, result):
|
||||
return result_order.index(result)
|
||||
|
||||
def _update_nb_children(self, new_state, old_state=None):
|
||||
# could be interresting to update state in batches.
|
||||
tracked_count_list = ['pending', 'testing', 'running']
|
||||
if (new_state not in tracked_count_list and old_state not in tracked_count_list) or new_state == old_state:
|
||||
return
|
||||
|
||||
for record in self:
|
||||
values = {}
|
||||
if old_state in tracked_count_list:
|
||||
values['nb_%s' % old_state] = record['nb_%s' % old_state] - 1
|
||||
if new_state in tracked_count_list:
|
||||
values['nb_%s' % new_state] = record['nb_%s' % new_state] + 1
|
||||
|
||||
record.write(values)
|
||||
if record.parent_id:
|
||||
record.parent_id._update_nb_children(new_state, old_state)
|
||||
|
||||
@api.depends('real_build.active_step')
|
||||
@api.depends('active_step', 'duplicate_id.active_step')
|
||||
def _compute_job(self):
|
||||
for build in self:
|
||||
build.job = build.real_build.active_step.name
|
||||
@ -196,13 +184,13 @@ class runbot_build(models.Model):
|
||||
def copy(self, values=None):
|
||||
raise UserError("Cannot duplicate build!")
|
||||
|
||||
@api.model_create_single
|
||||
def create(self, vals):
|
||||
branch = self.env['runbot.branch'].search([('id', '=', vals.get('branch_id', False))]) # branche 10174?
|
||||
if branch.no_build:
|
||||
return self.env['runbot.build']
|
||||
vals['config_id'] = vals['config_id'] if 'config_id' in vals else branch.config_id.id
|
||||
build_id = super(runbot_build, self).create(vals)
|
||||
build_id._update_nb_children(build_id.local_state)
|
||||
extra_info = {'sequence': build_id.id if not build_id.sequence else build_id.sequence}
|
||||
context = self.env.context
|
||||
|
||||
@ -309,8 +297,6 @@ class runbot_build(models.Model):
|
||||
build_by_old_values = defaultdict(lambda: self.env['runbot.build'])
|
||||
for record in self:
|
||||
build_by_old_values[record.local_state] += record
|
||||
for local_state, builds in build_by_old_values.items():
|
||||
builds._update_nb_children(values.get('local_state'), local_state)
|
||||
assert 'state' not in values
|
||||
local_result = values.get('local_result')
|
||||
for build in self:
|
||||
@ -318,6 +304,8 @@ class runbot_build(models.Model):
|
||||
res = super(runbot_build, self).write(values)
|
||||
for build in self:
|
||||
assert bool(not build.duplicate_id) ^ (build.local_state == 'duplicate') # don't change duplicate state without removing duplicate id.
|
||||
if 'log_counter' in values: # not 100% usefull but more correct ( see test_ir_logging)
|
||||
self.flush()
|
||||
return res
|
||||
|
||||
def update_build_end(self):
|
||||
@ -360,6 +348,8 @@ class runbot_build(models.Model):
|
||||
build.job_time = int(dt2time(build.job_end) - dt2time(build.job_start))
|
||||
elif build.job_start:
|
||||
build.job_time = int(time.time() - dt2time(build.job_start))
|
||||
else:
|
||||
build.job_time = 0
|
||||
|
||||
@api.depends('build_start', 'build_end', 'duplicate_id.build_time')
|
||||
def _compute_build_time(self):
|
||||
@ -370,6 +360,8 @@ class runbot_build(models.Model):
|
||||
build.build_time = int(dt2time(build.build_end) - dt2time(build.build_start))
|
||||
elif build.build_start:
|
||||
build.build_time = int(time.time() - dt2time(build.build_start))
|
||||
else:
|
||||
build.build_time = 0
|
||||
|
||||
@api.depends('job_start', 'duplicate_id.build_age')
|
||||
def _compute_build_age(self):
|
||||
@ -379,6 +371,8 @@ class runbot_build(models.Model):
|
||||
build.build_age = build.duplicate_id.build_age
|
||||
elif build.job_start:
|
||||
build.build_age = int(time.time() - dt2time(build.build_start))
|
||||
else:
|
||||
build.build_age = 0
|
||||
|
||||
def _get_params(self):
|
||||
try:
|
||||
|
@ -21,6 +21,7 @@ PYTHON_DEFAULT = "# type python code here\n\n\n\n\n\n"
|
||||
|
||||
class Config(models.Model):
|
||||
_name = "runbot.build.config"
|
||||
_description = "Build config"
|
||||
_inherit = "mail.thread"
|
||||
|
||||
name = fields.Char('Config name', required=True, unique=True, track_visibility='onchange', help="Unique name for config please use trigram as postfix for custom configs")
|
||||
@ -29,9 +30,10 @@ class Config(models.Model):
|
||||
update_github_state = fields.Boolean('Notify build state to github', default=False, track_visibility='onchange')
|
||||
protected = fields.Boolean('Protected', default=False, track_visibility='onchange')
|
||||
group = fields.Many2one('runbot.build.config', 'Configuration group', help="Group of config's and config steps")
|
||||
group_name = fields.Char(related='group.name')
|
||||
group_name = fields.Char('Group name', related='group.name')
|
||||
monitoring_view_id = fields.Many2one('ir.ui.view', 'Monitoring view')
|
||||
|
||||
@api.model
|
||||
@api.model_create_single
|
||||
def create(self, values):
|
||||
res = super(Config, self).create(values)
|
||||
res._check_step_ids_order()
|
||||
@ -84,6 +86,7 @@ class Config(models.Model):
|
||||
|
||||
class ConfigStep(models.Model):
|
||||
_name = 'runbot.build.config.step'
|
||||
_description = "Config step"
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
# general info
|
||||
@ -161,7 +164,7 @@ class ConfigStep(models.Model):
|
||||
copy._write({'protected': False})
|
||||
return copy
|
||||
|
||||
@api.model
|
||||
@api.model_create_single
|
||||
def create(self, values):
|
||||
self._check(values)
|
||||
return super(ConfigStep, self).create(values)
|
||||
@ -560,6 +563,7 @@ class ConfigStep(models.Model):
|
||||
|
||||
class ConfigStepOrder(models.Model):
|
||||
_name = 'runbot.build.config.step.order'
|
||||
_description = "Config step order"
|
||||
_order = 'sequence, id'
|
||||
# a kind of many2many rel with sequence
|
||||
|
||||
@ -571,7 +575,7 @@ class ConfigStepOrder(models.Model):
|
||||
def _onchange_step_id(self):
|
||||
self.sequence = self.step_id.default_sequence
|
||||
|
||||
@api.model
|
||||
@api.model_create_single
|
||||
def create(self, values):
|
||||
if 'sequence' not in values and values.get('step_id'):
|
||||
values['sequence'] = self.env['runbot.build.config.step'].browse(values.get('step_id')).default_sequence
|
||||
|
@ -3,6 +3,7 @@ from odoo import models, fields
|
||||
|
||||
class RunbotBuildDependency(models.Model):
|
||||
_name = "runbot.build.dependency"
|
||||
_description = "Build dependency"
|
||||
|
||||
build_id = fields.Many2one('runbot.build', 'Build', required=True, ondelete='cascade', index=True)
|
||||
dependecy_repo_id = fields.Many2one('runbot.repo', 'Dependency repo', required=True, ondelete='cascade')
|
||||
|
@ -13,6 +13,8 @@ _logger = logging.getLogger(__name__)
|
||||
class RunbotBuildError(models.Model):
|
||||
|
||||
_name = "runbot.build.error"
|
||||
_description = "Build error"
|
||||
|
||||
_inherit = "mail.thread"
|
||||
_rec_name = "id"
|
||||
|
||||
@ -34,7 +36,7 @@ class RunbotBuildError(models.Model):
|
||||
parent_id = fields.Many2one('runbot.build.error', 'Linked to')
|
||||
child_ids = fields.One2many('runbot.build.error', 'parent_id', string='Child Errors', context={'active_test': False})
|
||||
children_build_ids = fields.Many2many('runbot.build', compute='_compute_children_build_ids', string='Children builds')
|
||||
error_history_ids = fields.One2many('runbot.build.error', compute='_compute_error_history_ids', string='Old errors')
|
||||
error_history_ids = fields.Many2many('runbot.build.error', compute='_compute_error_history_ids', string='Old errors', context={'active_test': False})
|
||||
first_seen_build_id = fields.Many2one('runbot.build', compute='_compute_first_seen_build_id', string='First Seen build')
|
||||
first_seen_date = fields.Datetime(string='First Seen Date', related='first_seen_build_id.create_date')
|
||||
last_seen_build_id = fields.Many2one('runbot.build', compute='_compute_last_seen_build_id', string='Last Seen build')
|
||||
@ -47,7 +49,7 @@ class RunbotBuildError(models.Model):
|
||||
if build_error.test_tags and '-' in build_error.test_tags:
|
||||
raise ValidationError('Build error test_tags should not be negated')
|
||||
|
||||
@api.model
|
||||
@api.model_create_single
|
||||
def create(self, vals):
|
||||
cleaners = self.env['runbot.error.regex'].search([('re_type', '=', 'cleaning')])
|
||||
content = vals.get('content')
|
||||
@ -57,7 +59,6 @@ class RunbotBuildError(models.Model):
|
||||
})
|
||||
return super().create(vals)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if 'active' in vals:
|
||||
for build_error in self:
|
||||
@ -100,7 +101,7 @@ class RunbotBuildError(models.Model):
|
||||
for build_error in self:
|
||||
build_error.first_seen_build_id = build_error.children_build_ids and build_error.children_build_ids[-1] or False
|
||||
|
||||
@api.depends('fingerprint')
|
||||
@api.depends('fingerprint', 'child_ids.fingerprint')
|
||||
def _compute_error_history_ids(self):
|
||||
for error in self:
|
||||
fingerprints = [error.fingerprint] + [rec.fingerprint for rec in error.child_ids]
|
||||
@ -160,9 +161,9 @@ class RunbotBuildError(models.Model):
|
||||
|
||||
@api.model
|
||||
def test_tags_list(self):
|
||||
active_errors = self.search([('test_tags', '!=', 'False'), ('random', '=', True)])
|
||||
active_errors = self.search([('test_tags', '!=', False), ('random', '=', True)])
|
||||
test_tag_list = active_errors.mapped('test_tags')
|
||||
return [test_tag for error_tags in test_tag_list for test_tag in error_tags.split(',')]
|
||||
return [test_tag for error_tags in test_tag_list for test_tag in (error_tags).split(',')]
|
||||
|
||||
@api.model
|
||||
def disabling_tags(self):
|
||||
@ -172,6 +173,7 @@ class RunbotBuildError(models.Model):
|
||||
class RunbotBuildErrorTag(models.Model):
|
||||
|
||||
_name = "runbot.build.error.tag"
|
||||
_description = "Build error tag"
|
||||
|
||||
name = fields.Char('Tag')
|
||||
error_ids = fields.Many2many('runbot.build.error', string='Errors')
|
||||
@ -180,6 +182,7 @@ class RunbotBuildErrorTag(models.Model):
|
||||
class RunbotErrorRegex(models.Model):
|
||||
|
||||
_name = "runbot.error.regex"
|
||||
_description = "Build error regex"
|
||||
_inherit = "mail.thread"
|
||||
_rec_name = 'id'
|
||||
_order = 'sequence, id'
|
||||
|
@ -16,9 +16,8 @@ class runbot_event(models.Model):
|
||||
|
||||
build_id = fields.Many2one('runbot.build', 'Build', index=True, ondelete='cascade')
|
||||
active_step_id = fields.Many2one('runbot.build.config.step', 'Active step', index=True)
|
||||
type = fields.Selection(TYPES, string='Type', required=True, index=True)
|
||||
type = fields.Selection(selection_add=TYPES, string='Type', required=True, index=True)
|
||||
|
||||
@api.model_cr
|
||||
def init(self):
|
||||
parent_class = super(runbot_event, self)
|
||||
if hasattr(parent_class, 'init'):
|
||||
@ -75,6 +74,7 @@ FOR EACH ROW EXECUTE PROCEDURE runbot_set_logging_build();
|
||||
|
||||
class RunbotErrorLog(models.Model):
|
||||
_name = "runbot.error.log"
|
||||
_description = "Error log"
|
||||
_auto = False
|
||||
_order = 'id desc'
|
||||
|
||||
@ -130,7 +130,6 @@ class RunbotErrorLog(models.Model):
|
||||
BuildError._parse_logs(self)
|
||||
|
||||
|
||||
@api.model_cr
|
||||
def init(self):
|
||||
""" Create an SQL view for ir.logging """
|
||||
tools.drop_view_if_exists(self._cr, 'runbot_error_log')
|
||||
|
@ -6,6 +6,7 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class RunboHost(models.Model):
|
||||
_name = "runbot.host"
|
||||
_description = "Host"
|
||||
_order = 'id'
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
@ -37,7 +38,7 @@ class RunboHost(models.Model):
|
||||
host.nb_testing = count_by_host_state[host.name].get('testing', 0)
|
||||
host.nb_running = count_by_host_state[host.name].get('running', 0)
|
||||
|
||||
@api.model
|
||||
@api.model_create_single
|
||||
def create(self, values):
|
||||
if not 'disp_name' in values:
|
||||
values['disp_name'] = values['name']
|
||||
@ -57,10 +58,15 @@ class RunboHost(models.Model):
|
||||
return int(icp.get_param('runbot.runbot_running_max', default=75))
|
||||
|
||||
def set_psql_conn_count(self):
|
||||
|
||||
_logger.debug('Updating psql connection count...')
|
||||
self.ensure_one()
|
||||
with local_pgadmin_cursor() as local_cr:
|
||||
local_cr.execute("SELECT sum(numbackends) FROM pg_stat_database;")
|
||||
res = local_cr.fetchone()
|
||||
self.psql_conn_count = res and res[0] or 0
|
||||
|
||||
def _total_testing(self):
|
||||
return sum(host.nb_testing for host in self)
|
||||
|
||||
def _total_workers(self):
|
||||
return sum(host.get_nb_worker() for host in self)
|
||||
|
@ -4,7 +4,7 @@ from dateutil.relativedelta import relativedelta
|
||||
from odoo import models, fields
|
||||
|
||||
odoo.service.server.SLEEP_INTERVAL = 5
|
||||
odoo.addons.base.ir.ir_cron._intervalTypes['seconds'] = lambda interval: relativedelta(seconds=interval)
|
||||
odoo.addons.base.models.ir_cron._intervalTypes['seconds'] = lambda interval: relativedelta(seconds=interval)
|
||||
|
||||
class ir_cron(models.Model):
|
||||
_inherit = "ir.cron"
|
||||
|
@ -30,10 +30,11 @@ class RunbotException(Exception):
|
||||
class runbot_repo(models.Model):
|
||||
|
||||
_name = "runbot.repo"
|
||||
_description = "Repo"
|
||||
_order = 'sequence, id'
|
||||
|
||||
name = fields.Char('Repository', required=True)
|
||||
short_name = fields.Char('Repository', compute='_compute_short_name', store=False, readonly=True)
|
||||
short_name = fields.Char('Short name', compute='_compute_short_name', store=False, readonly=True)
|
||||
sequence = fields.Integer('Sequence')
|
||||
path = fields.Char(compute='_get_path', string='Directory', readonly=True)
|
||||
base = fields.Char(compute='_get_base_url', string='Base URL', readonly=True) # Could be renamed to a more explicit name like base_url
|
||||
@ -60,7 +61,7 @@ class runbot_repo(models.Model):
|
||||
token = fields.Char("Github token", groups="runbot.group_runbot_admin")
|
||||
group_ids = fields.Many2many('res.groups', string='Limited to groups')
|
||||
|
||||
repo_config_id = fields.Many2one('runbot.build.config', 'Run Config')
|
||||
repo_config_id = fields.Many2one('runbot.build.config', 'Repo Config')
|
||||
config_id = fields.Many2one('runbot.build.config', 'Run Config', compute='_compute_config_id', inverse='_inverse_config_id')
|
||||
|
||||
server_files = fields.Char('Server files', help='Comma separated list of possible server files') # odoo-bin,openerp-server,openerp-server.py
|
||||
@ -104,20 +105,15 @@ class runbot_repo(models.Model):
|
||||
for repo in self:
|
||||
repo.hook_time = times.get(repo.id, 0)
|
||||
|
||||
def write(self, values):
|
||||
# hooktime and reftime table are here to avoid sql update on repo.
|
||||
# using inverse will still trigger write_date and write_uid update.
|
||||
# this hack allows to avoid that
|
||||
|
||||
hook_time = values.pop('hook_time', None)
|
||||
get_ref_time = values.pop('get_ref_time', None)
|
||||
def set_hook_time(self, value):
|
||||
for repo in self:
|
||||
if hook_time:
|
||||
self.env['runbot.repo.hooktime'].create({'time': hook_time, 'repo_id': repo.id})
|
||||
if get_ref_time:
|
||||
self.env['runbot.repo.reftime'].create({'time': get_ref_time, 'repo_id': repo.id})
|
||||
if values:
|
||||
super().write(values)
|
||||
self.env['runbot.repo.hooktime'].create({'time': value, 'repo_id': repo.id})
|
||||
self.invalidate_cache()
|
||||
|
||||
def set_ref_time(self, value):
|
||||
for repo in self:
|
||||
self.env['runbot.repo.reftime'].create({'time': value, 'repo_id': repo.id})
|
||||
self.invalidate_cache()
|
||||
|
||||
def _gc_times(self):
|
||||
self.env.cr.execute("""
|
||||
@ -283,7 +279,7 @@ class runbot_repo(models.Model):
|
||||
|
||||
get_ref_time = round(self._get_fetch_head_time(), 4)
|
||||
if not self.get_ref_time or get_ref_time > self.get_ref_time:
|
||||
self.get_ref_time = get_ref_time
|
||||
self.set_ref_time(get_ref_time)
|
||||
fields = ['refname', 'objectname', 'committerdate:iso8601', 'authorname', 'authoremail', 'subject', 'committername', 'committeremail']
|
||||
fmt = "%00".join(["%(" + field + ")" for field in fields])
|
||||
git_refs = self._git(['for-each-ref', '--format', fmt, '--sort=-committerdate', 'refs/heads', 'refs/pull'])
|
||||
@ -392,7 +388,7 @@ class runbot_repo(models.Model):
|
||||
indirect.build_type = 'indirect'
|
||||
new_build.revdep_build_ids += indirect
|
||||
|
||||
@api.multi
|
||||
|
||||
def _create_pending_builds(self):
|
||||
""" Find new commits in physical repos"""
|
||||
refs = {}
|
||||
@ -451,7 +447,6 @@ class runbot_repo(models.Model):
|
||||
repo = self
|
||||
repo._git(['fetch', '-p', 'origin', '+refs/heads/*:refs/heads/*', '+refs/pull/*/head:refs/pull/*'])
|
||||
|
||||
@api.multi
|
||||
def _update(self, force=True):
|
||||
""" Update the physical git reposotories on FS"""
|
||||
for repo in reversed(self):
|
||||
@ -462,10 +457,9 @@ class runbot_repo(models.Model):
|
||||
|
||||
def _commit(self):
|
||||
self.env.cr.commit()
|
||||
self.invalidate_cache()
|
||||
self.env.reset()
|
||||
self.env.cache.invalidate()
|
||||
self.env.clear()
|
||||
|
||||
@api.multi
|
||||
def _scheduler(self, host):
|
||||
nb_workers = host.get_nb_worker()
|
||||
|
||||
@ -693,7 +687,7 @@ class runbot_repo(models.Model):
|
||||
self._commit()
|
||||
except Exception as e:
|
||||
self.env.cr.rollback()
|
||||
self.env.reset()
|
||||
self.env.clear()
|
||||
_logger.exception(e)
|
||||
message = str(e)
|
||||
if host.last_exception == message:
|
||||
@ -765,6 +759,7 @@ class runbot_repo(models.Model):
|
||||
|
||||
class RefTime(models.Model):
|
||||
_name = "runbot.repo.reftime"
|
||||
_description = "Repo reftime"
|
||||
_log_access = False
|
||||
|
||||
time = fields.Float('Time', index=True, required=True)
|
||||
@ -773,7 +768,8 @@ class RefTime(models.Model):
|
||||
|
||||
class HookTime(models.Model):
|
||||
_name = "runbot.repo.hooktime"
|
||||
_description = "Repo hooktime"
|
||||
_log_access = False
|
||||
|
||||
time = fields.Float('Time')
|
||||
repo_id = fields.Many2one('runbot.repo', 'Repository', required=True, ondelete='cascade')
|
||||
repo_id = fields.Many2one('runbot.repo', 'Repository', required=True, ondelete='cascade')
|
||||
|
@ -33,7 +33,6 @@ class ResConfigSettings(models.TransientModel):
|
||||
)
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def set_values(self):
|
||||
super(ResConfigSettings, self).set_values()
|
||||
set_param = self.env['ir.config_parameter'].sudo().set_param
|
||||
|
@ -17,6 +17,10 @@
|
||||
<field name="implied_ids" eval="[(4, ref('runbot.group_user'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="base.group_portal" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('runbot.group_user'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="group_runbot_admin" model="res.groups">
|
||||
<field name="name">Manager</field>
|
||||
<field name="category_id" ref="module_category"/>
|
||||
|
80
runbot/static/src/css/runbot.css
Normal file
80
runbot/static/src/css/runbot.css
Normal file
@ -0,0 +1,80 @@
|
||||
.separator {
|
||||
border-top: 2px solid #666;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
[data-toggle="collapse"] .fa:before {
|
||||
content: "\f139";
|
||||
}
|
||||
|
||||
[data-toggle="collapse"].collapsed .fa:before {
|
||||
content: "\f13a";
|
||||
}
|
||||
|
||||
body, .table{
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color:#444;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background-color: #fff;
|
||||
color: #444;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.btn-default:hover {
|
||||
background-color: #ccc;
|
||||
color: #444;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.btn-sm, .btn-group-sm > .btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.89rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
.btn-ssm, .btn-group-ssm > .btn {
|
||||
padding: 0.22rem 0.4rem;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.killed, .bg-killed, .bg-killed-light {
|
||||
background-color: #aaa;
|
||||
}
|
||||
|
||||
.dropdown-toggle:after { content: none }
|
||||
|
||||
.branch_name {
|
||||
max-width: 250px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.branch_time {
|
||||
float:right;
|
||||
margin-left:10px;
|
||||
}
|
||||
|
||||
.bg-success-light {
|
||||
background-color: #dff0d8;
|
||||
}
|
||||
.bg-danger-light {
|
||||
background-color: #f2dede;
|
||||
}
|
||||
.bg-info-light {
|
||||
background-color: #d9edf7;
|
||||
}
|
||||
|
||||
.text-info{
|
||||
color: #096b72 !important;
|
||||
}
|
||||
.build_subject_buttons {
|
||||
display: flex;
|
||||
}
|
||||
.build_buttons {
|
||||
margin-left: auto
|
||||
}
|
@ -41,7 +41,7 @@
|
||||
return false;
|
||||
});
|
||||
});
|
||||
$(function() {
|
||||
new Clipboard('.clipbtn');
|
||||
});
|
||||
//$(function() {
|
||||
// new Clipboard('.clipbtn');
|
||||
//});
|
||||
})(jQuery);
|
||||
|
@ -1,12 +0,0 @@
|
||||
.separator {
|
||||
border-top: 2px solid #666;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
[data-toggle="collapse"] .fa:before {
|
||||
content: "\f139";
|
||||
}
|
||||
|
||||
[data-toggle="collapse"].collapsed .fa:before {
|
||||
content: "\f13a";
|
||||
}
|
@ -33,7 +33,7 @@
|
||||
<t t-if="build.global_result == 'skipped'"><t t-set="rowclass">default</t></t>
|
||||
<t t-if="build.global_result in ['killed', 'manually_killed']"><t t-set="rowclass">killed</t></t>
|
||||
</t>
|
||||
<tr t-attf-class="{{rowclass}}">
|
||||
<tr t-attf-class="bg-{{rowclass}}-light">
|
||||
<td><t t-esc="build.create_date" /></td>
|
||||
<td><a t-attf-href="/runbot/build/{{build['id']}}" title="Build details" aria-label="Build details"><t t-esc="build.dest" /></a></td>
|
||||
<td>
|
||||
|
@ -40,13 +40,6 @@
|
||||
</t>
|
||||
</small>
|
||||
</t>
|
||||
<t t-set="nb_sum" t-value="bu.nb_pending+bu.nb_testing+bu.nb_running"/>
|
||||
<t t-if="nb_sum > 1"><!-- maybe only display this info if > 3 -->
|
||||
<span t-attf-title="{{bu.nb_pending}} pending, {{bu.nb_testing}} testing, {{bu.nb_running}} running">
|
||||
<t t-esc="nb_sum"/>
|
||||
<i class="fa fa-cogs"/>
|
||||
</span>
|
||||
</t>
|
||||
</template>
|
||||
<template id="runbot.build_button">
|
||||
<div t-attf-class="pull-right">
|
||||
@ -54,9 +47,9 @@
|
||||
<a t-if="bu.real_build.local_state=='running'" t-attf-href="http://{{bu['domain']}}/?db={{bu.real_build.dest}}-all" class="btn btn-primary" title="Sign in on this build" aria-label="Sign in on this build"><i class="fa fa-sign-in"/></a>
|
||||
<a t-if="bu.real_build.local_state=='done' and bu.real_build.requested_action != 'wake_up'" href="#" t-att-data-runbot-build="bu.real_build.id" class="btn btn-default runbot-wakeup" title="Wake up this build" aria-label="Wake up this build"><i class="fa fa-coffee"/></a>
|
||||
<a t-attf-href="/runbot/build/{{bu['id']}}" class="btn btn-default" title="Build details" aria-label="Build details"><i class="fa fa-file-text-o"/></a>
|
||||
<a t-attf-href="https://#{repo.base}/commit/#{bu['name']}" class="btn btn-default" title="Open commit on GitHub" aria-label="Open commit on GitHub"><i class="fa fa-github"/></a>
|
||||
<a t-if="show_commit_button" t-attf-href="https://#{repo.base}/commit/#{bu['name']}" class="btn btn-default" title="Open commit on GitHub" aria-label="Open commit on GitHub"><i class="fa fa-github"/></a>
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="Build options" aria-label="Build options" aria-expanded="false"><i class="fa fa-cog"/><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li t-if="bu.global_result=='skipped'" groups="runbot.group_runbot_admin">
|
||||
<a href="#" class="runbot-rebuild" t-att-data-runbot-build="bu['id']">Force Build <i class="fa fa-level-up"></i></a>
|
||||
</li>
|
||||
@ -89,12 +82,12 @@
|
||||
<li t-if="bu.global_state not in ('testing', 'waiting', 'pending')" class="divider"></li>
|
||||
<li><a t-attf-href="/runbot/build/{{bu['id']}}">Logs <i class="fa fa-file-text-o"/></a></li>
|
||||
<t t-set="log_url" t-value="'http://%s' % bu.real_build.host if bu.real_build.host != fqdn else ''"/>
|
||||
<t t-if="bu.real_build.host" t-foreach="bu.log_list.split(',')" t-as="log_name" >
|
||||
<t t-if="bu.real_build.host" t-foreach="(bu.log_list or '').split(',')" t-as="log_name" >
|
||||
<li><a t-attf-href="{{log_url}}/runbot/static/build/#{bu['real_build'].dest}/logs/#{log_name}.txt">Full <t t-esc="log_name"/> logs <i class="fa fa-file-text-o"/></a></li>
|
||||
</t>
|
||||
<li t-if="bu.coverage and bu.real_build.host"><a t-attf-href="http://{{bu.real_build.host}}/runbot/static/build/#{bu['real_build'].dest}/coverage/index.html">Coverage <i class="fa fa-file-text-o"/></a></li>
|
||||
<li t-if="bu.global_state!='pending'" class="divider"></li>
|
||||
<li><a t-attf-href="{{br['branch'].branch_url}}">Branch or pull <i class="fa fa-github"/></a></li>
|
||||
<li><a t-attf-href="{{br['branch'].branch_url}}"><t t-esc="'Branch ' if not br['branch'].pull_head_name else 'Pull '"/><i class="fa fa-github"/></a></li>
|
||||
<li><a t-attf-href="https://{{repo.base}}/commit/{{bu['name']}}">Commit <i class="fa fa-github"/></a></li>
|
||||
<li><a t-attf-href="https://{{repo.base}}/compare/{{br['branch'].branch_name}}">Compare <i class="fa fa-github"/></a></li>
|
||||
<!-- TODO branch.pull from -->
|
||||
@ -118,54 +111,32 @@
|
||||
</template>
|
||||
<template id="runbot.build">
|
||||
<t t-call='website.layout'>
|
||||
<div class="container" style="width: 100%;">
|
||||
|
||||
<t t-set="nav_form">
|
||||
<form class="form-inline">
|
||||
<div class="btn-group">
|
||||
<t t-call="runbot.build_button">
|
||||
<t t-set="bu" t-value="build"/>
|
||||
<t t-set="klass" t-value="''"/>
|
||||
<t t-set="show_commit_button" t-value="True"/>
|
||||
</t>
|
||||
</div>
|
||||
</form>
|
||||
<form class="form-inline" t-attf-action="/runbot/build/#{build['id']}/force" method='POST' t-if="request.params.get('ask_rebuild')" groups="runbot.group_user">
|
||||
<a href='#' class="btn btn-danger runbot-rebuild" t-attf-data-runbot-build="#{build['id']}" > <i class='fa fa-refresh'/> Force Rebuild</a>
|
||||
</form>
|
||||
</t>
|
||||
<div class="row" >
|
||||
<div class='col-md-12'>
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" t-attf-href="/runbot/repo/#{ slug(repo) }"><b><t t-esc="repo.base"/></b></a>
|
||||
<a class="navbar-brand" t-attf-href="/runbot/build/{{build['id']}}">
|
||||
<t t-esc="build['dest']"/>
|
||||
<t t-call="runbot.build_name">
|
||||
<t t-set="bu" t-value="build"/>
|
||||
</t>
|
||||
</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<form class="navbar-form navbar-left form-inline">
|
||||
<div class="btn-group">
|
||||
<t t-call="runbot.build_button">
|
||||
<t t-set="bu" t-value="build"/>
|
||||
<t t-set="klass" t-value="''"/>
|
||||
</t>
|
||||
</div>
|
||||
</form>
|
||||
<p class="navbar-text">
|
||||
</p>
|
||||
|
||||
<form class="navbar-form navbar-left form-inline" t-attf-action="/runbot/build/#{build['id']}/force" method='POST' t-if="request.params.get('ask_rebuild')" groups="runbot.group_user">
|
||||
<a href='#' class="btn btn-danger runbot-rebuild" t-attf-data-runbot-build="#{build['id']}" > <i class='fa fa-refresh'/> Force Rebuild</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<table class="table table-condensed tabel-bordered">
|
||||
<tr>
|
||||
<t t-set="rowclass"><t t-call="runbot.build_class"><t t-set="build" t-value="build"/></t></t>
|
||||
<td t-attf-class="{{rowclass}}">
|
||||
<td t-attf-class="bg-{{rowclass.strip()}}-light">
|
||||
Subject: <t t-esc="build['subject']"/><br/>
|
||||
Author: <t t-esc="build['author']"/><br/>
|
||||
Committer: <t t-esc="build['committer']"/><br/>
|
||||
Commit: <a t-attf-href="https://{{build.repo_id.base}}/commit/{{build.name}}"><t t-esc="build.name"/></a><br/>
|
||||
<t t-foreach="build.dependency_ids" t-as="dep">
|
||||
<t t-foreach="build.sudo().dependency_ids" t-as="dep">
|
||||
Dep: <t t-esc="dep.dependecy_repo_id.short_name"/>:<a t-attf-href="https://{{dep.dependecy_repo_id.base}}/commit/{{dep.dependency_hash}}"><t t-esc="dep.dependency_hash"/></a>
|
||||
<t t-if='dep.closest_branch_id'> from branch <t t-esc="dep.closest_branch_id.name"/></t>
|
||||
<br/>
|
||||
@ -173,26 +144,27 @@
|
||||
Branch: <span id="branchclp"><t t-esc="build.branch_id.branch_name"/></span>
|
||||
<a href="#" class="clipbtn octicon octicon-clippy" data-clipboard-target="#branchclp" title="Copy branch name to clipboard"/><br/>
|
||||
Build host: <t t-esc="build.real_build.host"/><br/>
|
||||
Build dest: <t t-esc="build['dest']"/><br/>
|
||||
</td>
|
||||
<td t-if="build.real_build.children_ids">
|
||||
Children:
|
||||
<t t-if="build.real_build.nb_pending > 0"><t t-esc="build.real_build.nb_pending"/> pending </t>
|
||||
<t t-if="build.real_build.nb_testing > 0"><t t-esc="build.real_build.nb_testing"/> testing </t>
|
||||
<t t-if="build.real_build.nb_running > 0"><t t-esc="build.real_build.nb_running"/> running </t>
|
||||
<table class="table table-condensed">
|
||||
<t t-foreach="build.real_build.children_ids.sorted('id')" t-as="child">
|
||||
<t t-set="rowclass"><t t-call="runbot.build_class"><t t-set="build" t-value="child"/></t></t>
|
||||
<tr><td t-attf-class="{{rowclass}}">
|
||||
<tr><td t-attf-class="bg-{{rowclass.strip()}}-light">
|
||||
<a t-attf-href="/runbot/build/{{child.id}}" >Build <t t-esc="child.id"/></a>
|
||||
with config <a t-attf-href="/web#id={{child.config_id.id}}&view_type=form&model=runbot.build.config"><t t-esc="child.config_id.name"/></a>
|
||||
with config <t t-esc="child.config_id.name"/>
|
||||
<a groups="runbot.group_build_config_user" t-attf-href="/web#id={{child.config_id.id}}&view_type=form&model=runbot.build.config">...</a>
|
||||
<t t-if="child.orphan_result"><i class="fa fa-chain-broken" title="Build result ignored for parent" /></t>
|
||||
<t t-if="child.job"> Running step: <t t-esc="child.job"/></t>
|
||||
<t t-if="child.global_state in ['testing', 'waiting']">
|
||||
<i class="fa fa-spinner fa-spin"/>
|
||||
<t t-esc="child.global_state"/>
|
||||
</t>
|
||||
<a t-if="child.real_build.local_state=='running'" t-attf-href="http://{{child.domain}}/?db={{child.real_build.dest}}-all" title="Sign in on this build" aria-label="Sign in on this build"><i class="fa fa-sign-in"/></a>
|
||||
<a t-if="child.real_build.local_state=='done' and child.real_build.requested_action != 'wake_up'" href="#" t-att-data-runbot-build="child.real_build.id" class="runbot-wakeup" title="Wake up this build" aria-label="Wake up this build"><i class="fa fa-coffee"/></a>
|
||||
<t t-call="runbot.build_button">
|
||||
<t t-set="bu" t-value="child"/>
|
||||
<t t-set="klass" t-value="'btn-group-ssm'"/>
|
||||
</t>
|
||||
|
||||
</td></tr>
|
||||
</t>
|
||||
@ -203,7 +175,7 @@
|
||||
<p t-if="build.parent_id">Child of <a t-attf-href="/runbot/build/#{build.parent_id.id}"><t t-esc="build.parent_id.dest"/></a>
|
||||
<t t-if="build.orphan_result">&nbsp;<i class="fa fa-chain-broken" title="Build result ignored for parent" />&nbsp;Orphaned build, the result does not affect parent build result</t></p>
|
||||
<p t-if="build.duplicate_id">Duplicate of <a t-attf-href="/runbot/build/#{build.duplicate_id.id}"><t t-esc="build.duplicate_id.dest"/></a></p>
|
||||
<table class="table table-condensed table-striped">
|
||||
<table class="table table-condensed">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Level</th>
|
||||
@ -213,13 +185,13 @@
|
||||
<t t-foreach="build.real_build.sudo().log_ids" t-as="l">
|
||||
<t t-set="subbuild" t-value="(([child for child in build.real_build.children_ids if child.id == int(l.path)] if l.type == 'subbuild' else False) or [build.browse()])[0]"/>
|
||||
<t t-set="logclass" t-value="dict(CRITICAL='danger', ERROR='danger', WARNING='warning', OK='success', SEPARATOR='separator').get(l.level)"/>
|
||||
<tr t-att-class="logclass">
|
||||
<tr t-attf-class="bg-{{logclass}}-light">
|
||||
<td style="white-space: nowrap; width:1%;"><t t-esc="l.create_date"/></td>
|
||||
<td style="white-space: nowrap; width:1%;"><b t-if="l.level != 'SEPARATOR' and l.type != 'link'" t-esc="l.level"/></td>
|
||||
<td style="white-space: nowrap; width:1%;"><t t-if="l.level != 'SEPARATOR' and l.type != 'link'" t-esc="l.type"/></td>
|
||||
<t t-set="message_class" t-value="''"/>
|
||||
<t t-if="subbuild" t-set="message_class"><t t-call="runbot.build_class"><t t-set="build" t-value="subbuild"/></t></t>
|
||||
<td t-att-class="message_class">
|
||||
<td t-attf-class="bg-{{message_class.strip() or logclass}}-light">
|
||||
<t t-if="l.type not in ('runbot', 'link')">
|
||||
<t t-if="l.type == 'subbuild'">
|
||||
<a t-attf-href="/runbot/build/{{l.path}}">Build #<t t-esc="l.path"/></a>
|
||||
@ -241,7 +213,7 @@
|
||||
<t t-if="l.type == 'subbuild' and subbuild.sudo().error_log_ids">
|
||||
<a data-toggle="collapse" t-attf-data-target="#subbuild-{{subbuild.id}}"><i class="fa"></i></a>
|
||||
<div t-attf-id="subbuild-{{subbuild.id}}" class="collapse in">
|
||||
<table class="table table-condensed table-striped" style="margin-bottom:0;">
|
||||
<table class="table table-condensed" style="margin-bottom:0;">
|
||||
<t t-foreach="subbuild.sudo().error_log_ids" t-as="sl">
|
||||
<tr>
|
||||
<td t-att-class="dict(CRITICAL='danger', ERROR='danger', WARNING='warning', OK='success', SEPARATOR='separator').get(sl.level)">
|
||||
@ -262,7 +234,6 @@
|
||||
</t>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
@ -141,21 +141,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div>
|
||||
<span t-attf-class="label label-{{pending_level}}">Pending: <t t-esc="pending_total"/></span>
|
||||
<t t-set="testing">0</t>
|
||||
<t t-set="workers">0</t>
|
||||
|
||||
<t t-foreach="hosts_data.sorted(key=lambda h:h.name)" t-as="host">
|
||||
<t t-set="testing" t-value="int(testing) + host.nb_testing"/>
|
||||
<t t-set="workers" t-value="int(workers) + host.sudo().get_nb_worker()"/>
|
||||
</t>
|
||||
<t t-set="klass">success</t>
|
||||
|
||||
<t t-if="int(testing)/workers > 0"><t t-set="klass">info</t></t>
|
||||
<t t-if="int(testing)/workers > 0.75"><t t-set="klass">warning</t></t>
|
||||
<t t-if="int(testing)/workers >= 1"><t t-set="klass">danger</t></t>
|
||||
|
||||
<span t-attf-class="label label-{{klass}}">Testing: <t t-esc="testing"/>/<t t-esc="workers"/></span>
|
||||
<t t-call="slots_infos"/>
|
||||
</div>
|
||||
<t t-foreach="glances_data.keys()" t-as="repo">
|
||||
<div>
|
||||
@ -178,9 +164,9 @@
|
||||
<t t-if="host.nb_testing > host.sudo().get_nb_worker()"><t t-set="klass">danger</t></t>
|
||||
<span t-attf-class="label label-{{klass}}"><span t-esc="host.nb_testing"/>/<span t-esc="host.sudo().get_nb_worker()"/></span>
|
||||
<t t-esc="host.nb_running"/>
|
||||
<t t-set="succes_time" t-value="int(datetime.datetime.now().timestamp() - datetime.datetime.strptime(host.last_success, '%Y-%m-%d %H:%M:%S').timestamp())"/>
|
||||
<t t-set="start_time" t-value="int(datetime.datetime.now().timestamp() - datetime.datetime.strptime(host.last_start_loop, '%Y-%m-%d %H:%M:%S').timestamp())"/>
|
||||
<t t-set="end_time" t-value="int(datetime.datetime.now().timestamp() - datetime.datetime.strptime(host.last_end_loop, '%Y-%m-%d %H:%M:%S').timestamp())"/>
|
||||
<t t-set="succes_time" t-value="int(datetime.datetime.now().timestamp() - host.last_success.timestamp())"/>
|
||||
<t t-set="start_time" t-value="int(datetime.datetime.now().timestamp() - host.last_start_loop.timestamp())"/>
|
||||
<t t-set="end_time" t-value="int(datetime.datetime.now().timestamp() - host.last_end_loop.timestamp())"/>
|
||||
|
||||
<t t-set="klass">success</t>
|
||||
<t t-if="succes_time > 30"><t t-set="klass">info</t></t>
|
||||
|
@ -2,13 +2,48 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Replace default menu ( Home / Contactus and co...) with 5 first repos) -->
|
||||
<template id="inherits_branch_in_menu" inherit_id="website.layout" name="Inherits Show top 5 branches in menu">
|
||||
<xpath expr="//t[@t-foreach="website.menu_id.child_id"][@t-as="submenu"]" position="replace">
|
||||
<t t-if="repos" >
|
||||
<t t-foreach="repos[:5]" t-as="re">
|
||||
<li><a t-attf-href="/runbot/repo/{{slug(re)}}?search={{request.params.get('search', '')}}"><i class='fa fa-github' /> <t t-esc="re.short_name"/></a></li>
|
||||
</t>
|
||||
</t>
|
||||
<template id="inherits_branch_in_menu" inherit_id="website.layout" name="Inherits Show top 6 repo in menu and dropdown">
|
||||
<xpath expr="//footer" position="replace">
|
||||
</xpath>
|
||||
<xpath expr="//nav" position="replace">
|
||||
<nav class="navbar navbar-expand-md navbar-light bg-light">
|
||||
<a t-if="repo" t-attf-href="/runbot/repo/{{slug(repo)}}?search={{request.params.get('search', '')}}">
|
||||
<b style="color:#777;"><t t-esc="repo.short_name"/></b>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#top_menu_collapse">
|
||||
<span class="navbar-toggler-icon"/>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="top_menu_collapse">
|
||||
<ul class="nav navbar-nav ml-auto text-right" id="top_menu">
|
||||
<t t-if="repos" >
|
||||
<t t-foreach="repos[:6]" t-as="re">
|
||||
<li ><a t-attf-href="/runbot/repo/{{slug(re)}}?search={{request.params.get('search', '')}}"><i class='fa fa-github' /> <t t-esc="re.short_name"/></a></li>
|
||||
</t>
|
||||
<li t-if="len(repos)>6" class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-plus"/></a>
|
||||
<ul class="dropdown-menu">
|
||||
<t t-foreach='repos[6:]' t-as='re'>
|
||||
<li><a t-attf-href="/runbot/repo/{{slug(re)}}"><t t-esc="re.short_name"/></a></li>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
</t>
|
||||
<li class="nav-item divider" t-ignore="true" t-if="not user_id._is_public()"/>
|
||||
<li class="nav-item dropdown" t-ignore="true" t-if="not user_id._is_public()">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown">
|
||||
<b>
|
||||
<span t-esc="user_id.name[:23] + '...' if user_id.name and len(user_id.name) > 25 else user_id.name"/>
|
||||
</b>
|
||||
</a>
|
||||
<div class="dropdown-menu js_usermenu" role="menu">
|
||||
<a id="o_logout" class="dropdown-item" t-attf-href="/web/session/logout?redirect=/" role="menuitem">Logout</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<t t-raw="nav_form or ''">
|
||||
</t>
|
||||
</div>
|
||||
</nav>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@ -21,6 +56,18 @@
|
||||
<attribute name="t-value">'o_connected_user' if env['ir.ui.view'].user_has_groups('base.group_website_publisher') else None</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="runbot.slots_infos" name="Hosts slot nb pending/testing/slots">
|
||||
<span t-attf-class="label label-{{pending_level}}">Pending: <t t-esc="pending_total"/></span>
|
||||
<t t-set="testing" t-value="hosts_data._total_testing()"/>
|
||||
<t t-set="workers" t-value="hosts_data._total_workers()"/>
|
||||
<t t-set="klass">success</t>
|
||||
<t t-if="int(testing)/workers > 0"><t t-set="klass">info</t></t>
|
||||
<t t-if="int(testing)/workers > 0.75"><t t-set="klass">warning</t></t>
|
||||
<t t-if="int(testing)/workers >= 1"><t t-set="klass">danger</t></t>
|
||||
<span t-attf-class="label label-{{klass}}">Testing: <t t-esc="testing"/>/<t t-esc="workers"/></span>
|
||||
</template>
|
||||
|
||||
<!-- Frontend repository block -->
|
||||
<template id="runbot.repo">
|
||||
<t t-call='website.layout'>
|
||||
@ -28,58 +75,20 @@
|
||||
<t t-if="refresh">
|
||||
<meta http-equiv="refresh" t-att-content="refresh"/>
|
||||
</t>
|
||||
<style>
|
||||
.killed {
|
||||
background-color: #aaa;
|
||||
}
|
||||
</style>
|
||||
</t>
|
||||
<t t-set="nav_form">
|
||||
<form class="form-inline my-2 my-lg-0" role="search" t-att-action="qu(search='')" method="get">
|
||||
<div class="input-group md-form form-sm form-2 pl-0">
|
||||
<input class="form-control my-0 py-1 red-border" type="text" placeholder="Search" aria-label="Search" name="search" t-att-value="search"/>
|
||||
<div class="input-group-append">
|
||||
<button type='submit' class="input-group-text red lighten-3" id="basic-text1"><i class="fa fa-search text-grey"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</t>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class='col-md-12'>
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<t t-if="repo">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><b style="font-size: 18px;"><t t-esc="repo.base"/></b><b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<t t-foreach='repos' t-as='re'>
|
||||
<li><a t-attf-href="/runbot/repo/{{slug(re)}}"><t t-esc="re.base"/></a></li>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</t>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<t t-if="repo">
|
||||
<form class="navbar-form navbar-right" role="search" t-att-action="qu(search='')" method="get">
|
||||
<div class="form-group">
|
||||
<input type="search" name="search" class="form-control" placeholder="Search" t-att-value="search"/>
|
||||
<button type="submit" class="btn btn-default">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
</t>
|
||||
</div>
|
||||
<p class="text-center">
|
||||
<t t-foreach="host_stats" t-as="hs">
|
||||
<span class="label label-default">
|
||||
<t t-esc="hs['host']"/>: <t t-esc="hs['testing']"/> testing
|
||||
</span>&nbsp;
|
||||
</t>
|
||||
<span t-attf-class="label label-{{pending_level}}">Pending: <t t-esc="pending_total"/></span>
|
||||
</p>
|
||||
</div>
|
||||
</nav>
|
||||
<div t-if="message" class="alert alert-warning" role="alert">
|
||||
<t t-esc="message" />
|
||||
</div>
|
||||
@ -90,21 +99,16 @@
|
||||
<table t-if="repo" class="table table-condensed table-bordered" style="table-layout: initial;">
|
||||
<tr>
|
||||
<th>Branch</th>
|
||||
<td colspan="4" class="text-right">
|
||||
<t t-esc="repo.base"/>:
|
||||
<t t-esc="testing"/> testing,
|
||||
<t t-esc="running"/> running,
|
||||
<t t-esc="pending"/> pending.
|
||||
<td colspan="4">
|
||||
<span class="pull-right" t-call="runbot.slots_infos"/>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr t-foreach="branches" t-as="br">
|
||||
<td>
|
||||
<i t-if="br['branch'].sticky" class="fa fa-star" style="color: #f0ad4e" />
|
||||
<a t-attf-href="/runbot/branch/#{br['branch'].id}"><b t-esc="br['branch'].branch_name"/></a>
|
||||
<small><t t-esc="br['builds'] and br['builds'][0].get_formated_build_time()"/></small><br/>
|
||||
<div class="btn-group btn-group-xs">
|
||||
<a t-attf-href="{{br['branch'].branch_url}}" class="btn btn-default btn-xs">Branch or pull <i class="fa fa-github"/></a>
|
||||
<td style="width:12%">
|
||||
<small class="branch_time" ><t t-esc="br['builds'] and br['builds'][0].get_formated_build_time()"/></small>
|
||||
<div class="branch_name"><i t-if="br['branch'].sticky" class="fa fa-star" style="color: #f0ad4e" /><a t-attf-href="/runbot/branch/#{br['branch'].id}"><b t-esc="br['branch'].branch_name"/></a></div>
|
||||
<div class="btn-group btn-group-xs">
|
||||
<a t-attf-href="{{br['branch'].branch_url}}" class="btn btn-default btn-xs"><t t-esc="'Branch ' if not br['branch'].pull_head_name else 'Pull '"/><i class="fa fa-github"/></a>
|
||||
<a t-attf-href="/runbot/quick_connect/#{br['branch'].id}" class="btn btn-default btn-xs" aria-label="Quick Connect"><i class="fa fa-fast-forward" title="Quick Connect"/></a>
|
||||
</div>
|
||||
<t t-if="br['branch'].sticky">
|
||||
@ -134,20 +138,21 @@
|
||||
<t t-if="bu.global_result == 'skipped'"><t t-set="klass">default</t></t>
|
||||
<t t-if="bu.global_result in ['killed', 'manually_killed']"><t t-set="klass">killed</t></t>
|
||||
</t>
|
||||
<td t-attf-class="{{klass}}">
|
||||
<td t-attf-class="bg-{{klass}}-light" style="width:22%">
|
||||
<t t-call="runbot.build_button">
|
||||
<t t-set="klass">btn-group-sm</t>
|
||||
<t t-set="show_rebuild_button" t-value="bu==br['builds'][0]"></t>
|
||||
<t t-set="show_commit_button" t-value="True"/>
|
||||
</t>
|
||||
<t t-if="bu['build_type']=='scheduled'"><i class="fa fa-moon-o" t-att-title="bu.build_type_label()" t-att-aria-label="bu.build_type_label()"/></t>
|
||||
<t t-if="bu['build_type'] in ('rebuild', 'indirect')"><i class="fa fa-recycle" t-att-title="bu.build_type_label()" t-att-aria-label="bu.build_type_label()"/></t>
|
||||
<t t-if="bu['subject']">
|
||||
<span t-if="bu['subject']" class="build_subject">
|
||||
<t t-if="bu.config_id != bu.branch_id.config_id">
|
||||
<b t-esc="bu.config_id.name"/>
|
||||
</t>
|
||||
<span t-esc="bu['subject'][:32] + ('...' if bu['subject'][32:] else '') " t-att-title="bu['subject']"/>
|
||||
<br/>
|
||||
</t>
|
||||
</span>
|
||||
<t t-id="bu['author']">
|
||||
<t t-esc="bu['author']"/>
|
||||
<t t-if="bu['committer'] and bu['author'] != bu['committer']" t-id="bu['committer']">
|
||||
|
@ -39,6 +39,8 @@ class RunbotCase(TransactionCase):
|
||||
self.start_patcher('docker_build', 'odoo.addons.runbot.models.build.docker_build')
|
||||
self.start_patcher('docker_ps', 'odoo.addons.runbot.models.repo.docker_ps', [])
|
||||
self.start_patcher('docker_stop', 'odoo.addons.runbot.models.repo.docker_stop')
|
||||
self.start_patcher('cr_commit', 'odoo.sql_db.Cursor.commit', None)
|
||||
self.start_patcher('repo_commit', 'odoo.addons.runbot.models.repo.runbot_repo._commit', None)
|
||||
|
||||
def start_patcher(self, patcher_name, patcher_path, return_value=Dummy, side_effect=Dummy):
|
||||
patcher = patch(patcher_path)
|
||||
|
@ -120,7 +120,7 @@ class TestBranchRelations(RunbotCase):
|
||||
self.assertEqual(b.previous_version.branch_name, '13.0')
|
||||
self.assertEqual(sorted(b.intermediate_stickies.mapped('branch_name')), ['saas-13.1', 'saas-13.2'])
|
||||
|
||||
b.closest_sticky = self.last
|
||||
b.defined_sticky = self.last
|
||||
|
||||
self.assertEqual(b.closest_sticky.branch_name, 'saas-13.2')
|
||||
self.assertEqual(b.previous_version.branch_name, '13.0')
|
||||
|
@ -327,68 +327,66 @@ class Test_Build(RunbotCase):
|
||||
'extra_params': '5',
|
||||
})
|
||||
|
||||
def assert_state(nb_pending, nb_testing, nb_running, global_state, build):
|
||||
self.assertEqual(build.nb_pending, nb_pending)
|
||||
self.assertEqual(build.nb_testing, nb_testing)
|
||||
self.assertEqual(build.nb_running, nb_running)
|
||||
def assert_state(global_state, build):
|
||||
self.assertEqual(build.global_state, global_state)
|
||||
|
||||
assert_state(5, 0, 0, 'pending', build1)
|
||||
assert_state(3, 0, 0, 'pending', build1_1)
|
||||
assert_state(1, 0, 0, 'pending', build1_2)
|
||||
assert_state(1, 0, 0, 'pending', build1_1_1)
|
||||
assert_state(1, 0, 0, 'pending', build1_1_2)
|
||||
assert_state('pending', build1)
|
||||
assert_state('pending', build1_1)
|
||||
assert_state('pending', build1_2)
|
||||
assert_state('pending', build1_1_1)
|
||||
assert_state('pending', build1_1_2)
|
||||
|
||||
build1.local_state = 'testing'
|
||||
build1_1.local_state = 'testing'
|
||||
build1.local_state = 'done'
|
||||
build1_1.local_state = 'done'
|
||||
|
||||
assert_state(3, 0, 0, 'waiting', build1)
|
||||
assert_state(2, 0, 0, 'waiting', build1_1)
|
||||
assert_state(1, 0, 0, 'pending', build1_2)
|
||||
assert_state(1, 0, 0, 'pending', build1_1_1)
|
||||
assert_state(1, 0, 0, 'pending', build1_1_2)
|
||||
assert_state('waiting', build1)
|
||||
assert_state('waiting', build1_1)
|
||||
assert_state('pending', build1_2)
|
||||
assert_state('pending', build1_1_1)
|
||||
assert_state('pending', build1_1_2)
|
||||
|
||||
build1_1_1.local_state = 'testing'
|
||||
|
||||
assert_state(2, 1, 0, 'waiting', build1)
|
||||
assert_state(1, 1, 0, 'waiting', build1_1)
|
||||
assert_state(1, 0, 0, 'pending', build1_2)
|
||||
assert_state(0, 1, 0, 'testing', build1_1_1)
|
||||
assert_state(1, 0, 0, 'pending', build1_1_2)
|
||||
assert_state('waiting', build1)
|
||||
assert_state('waiting', build1_1)
|
||||
assert_state('pending', build1_2)
|
||||
assert_state('testing', build1_1_1)
|
||||
assert_state('pending', build1_1_2)
|
||||
|
||||
build1_2.local_state = 'testing'
|
||||
|
||||
assert_state(1, 2, 0, 'waiting', build1)
|
||||
assert_state(1, 1, 0, 'waiting', build1_1)
|
||||
assert_state(0, 1, 0, 'testing', build1_2)
|
||||
assert_state(0, 1, 0, 'testing', build1_1_1)
|
||||
assert_state(1, 0, 0, 'pending', build1_1_2)
|
||||
assert_state('waiting', build1)
|
||||
assert_state('waiting', build1_1)
|
||||
assert_state('testing', build1_2)
|
||||
assert_state('testing', build1_1_1)
|
||||
assert_state('pending', build1_1_2)
|
||||
|
||||
build1_2.local_state = 'testing' # writing same state a second time
|
||||
|
||||
assert_state(1, 2, 0, 'waiting', build1)
|
||||
assert_state(1, 1, 0, 'waiting', build1_1)
|
||||
assert_state(0, 1, 0, 'testing', build1_2)
|
||||
assert_state(0, 1, 0, 'testing', build1_1_1)
|
||||
assert_state(1, 0, 0, 'pending', build1_1_2)
|
||||
assert_state('waiting', build1)
|
||||
assert_state('waiting', build1_1)
|
||||
assert_state('testing', build1_2)
|
||||
assert_state('testing', build1_1_1)
|
||||
assert_state('pending', build1_1_2)
|
||||
|
||||
build1_1_2.local_state = 'done'
|
||||
build1_1_1.local_state = 'done'
|
||||
build1_2.local_state = 'done'
|
||||
|
||||
assert_state(0, 0, 0, 'done', build1)
|
||||
assert_state(0, 0, 0, 'done', build1_1)
|
||||
assert_state(0, 0, 0, 'done', build1_2)
|
||||
assert_state(0, 0, 0, 'done', build1_1_1)
|
||||
assert_state(0, 0, 0, 'done', build1_1_2)
|
||||
assert_state('done', build1)
|
||||
assert_state('done', build1_1)
|
||||
assert_state('done', build1_2)
|
||||
assert_state('done', build1_1_1)
|
||||
assert_state('done', build1_1_2)
|
||||
|
||||
def test_duplicate_childrens(self):
|
||||
build_old = self.create_build({
|
||||
'branch_id': self.branch_10.id,
|
||||
'name': 'd0d0caca0000ffffffffffffffffffffffffffff',
|
||||
'extra_params': '0',
|
||||
'local_state': 'done'
|
||||
})
|
||||
build_parent = self.create_build({
|
||||
'branch_id': self.branch_10.id,
|
||||
@ -404,8 +402,7 @@ class Test_Build(RunbotCase):
|
||||
build_parent.local_state = 'done'
|
||||
self.assertEqual(build_child.local_state, 'duplicate')
|
||||
self.assertEqual(build_child.duplicate_id, build_old)
|
||||
self.assertEqual(build_parent.nb_pending, 0)
|
||||
self.assertEqual(build_parent.nb_testing, 0)
|
||||
self.assertEqual(build_child.global_state, 'done')
|
||||
self.assertEqual(build_parent.global_state, 'done')
|
||||
|
||||
|
||||
@ -734,6 +731,10 @@ class TestClosestBranch(RunbotCase):
|
||||
'repo_id': self.community_repo.id,
|
||||
'name': 'refs/pull/123456'
|
||||
})
|
||||
|
||||
# trigger compute and ensure that mock_github is used. (using correct side effect would work too)
|
||||
self.assertEqual(server_pr.pull_head_name, 'foo-dev:bar_branch')
|
||||
|
||||
mock_github.return_value = {
|
||||
'head': {'label': 'foo-dev:foobar_branch'},
|
||||
'base': {'ref': '10.0'},
|
||||
@ -743,6 +744,8 @@ class TestClosestBranch(RunbotCase):
|
||||
'repo_id': self.enterprise_repo.id,
|
||||
'name': 'refs/pull/789101'
|
||||
})
|
||||
self.assertEqual(addons_pr.pull_head_name, 'foo-dev:foobar_branch')
|
||||
closest = addons_pr._get_closest_branch(self.community_repo.id)
|
||||
self.assertEqual((self.branch_odoo_10, 'pr_target'), addons_pr._get_closest_branch(self.community_repo.id))
|
||||
|
||||
def test_closest_branch_05_master(self):
|
||||
|
@ -92,6 +92,7 @@ class TestBuildError(RunbotCase):
|
||||
self.assertIn(ko_build_new, new_build_error.build_ids, 'The parsed build with a re-apearing error should generate a new runbot.build.error')
|
||||
self.assertIn(build_error, new_build_error.error_history_ids, 'The old error should appear in history')
|
||||
|
||||
|
||||
def test_build_error_links(self):
|
||||
build_a = self.create_test_build({'local_result': 'ko'})
|
||||
build_b = self.create_test_build({'local_result': 'ko'})
|
||||
@ -111,8 +112,7 @@ class TestBuildError(RunbotCase):
|
||||
# test that the random bug is parent when linking errors
|
||||
all_errors = error_a | error_b
|
||||
all_errors.link_errors()
|
||||
|
||||
self.assertIn(error_b.child_ids, error_a, 'Random error should be the parent')
|
||||
self.assertEqual(error_b.child_ids, error_a, 'Random error should be the parent')
|
||||
|
||||
# Test that changing bug resolution is propagated to children
|
||||
error_b.active = True
|
||||
|
@ -53,9 +53,6 @@ class Test_Frontend(RunbotCase):
|
||||
self.assertEqual(self.sticky_branch, context['branches'][0]['branch'], "The sticky branch should be in first place")
|
||||
self.assertEqual(self.branch, context['branches'][1]['branch'], "The non sticky branch should be in second place")
|
||||
self.assertEqual(len(context['branches'][0]['builds']), 4, "Only the 4 last builds should appear in the context")
|
||||
self.assertEqual(context['pending'], 2, "There should be 2 pending builds")
|
||||
self.assertEqual(context['running'], 2, "There should be 2 running builds")
|
||||
self.assertEqual(context['testing'], 2, "There should be 2 testing builds")
|
||||
self.assertEqual(context['pending_total'], 2, "There should be 2 pending builds")
|
||||
self.assertEqual(context['pending_level'], 'info', "The pending level should be info")
|
||||
return Response()
|
||||
|
@ -176,20 +176,22 @@ class Test_Repo(RunbotCase):
|
||||
|
||||
_logger.info('Create pending builds took: %ssec', (time.time() - inserted_time))
|
||||
|
||||
|
||||
@common.warmup
|
||||
def test_times(self):
|
||||
def _test_times(model, field_name):
|
||||
def _test_times(model, setter, field_name):
|
||||
repo1 = self.Repo.create({'name': 'bla@example.com:foo/bar'})
|
||||
repo2 = self.Repo.create({'name': 'bla@example.com:foo2/bar2'})
|
||||
count = self.cr.sql_log_count
|
||||
repo1[field_name] = 1.1
|
||||
self.assertEqual(self.cr.sql_log_count - count, 1, "Only one insert should have been triggered")
|
||||
repo2[field_name] = 1.2
|
||||
with self.assertQueryCount(1):
|
||||
getattr(repo1, setter)(1.1)
|
||||
getattr(repo2, setter)(1.2)
|
||||
self.assertEqual(len(self.env[model].search([])), 2)
|
||||
self.assertEqual(repo1[field_name], 1.1)
|
||||
self.assertEqual(repo2[field_name], 1.2)
|
||||
|
||||
repo1[field_name] = 1.3
|
||||
repo2[field_name] = 1.4
|
||||
getattr(repo1, setter)(1.3)
|
||||
getattr(repo2, setter)(1.4)
|
||||
|
||||
self.assertEqual(len(self.env[model].search([])), 4)
|
||||
self.assertEqual(repo1[field_name], 1.3)
|
||||
@ -205,8 +207,8 @@ class Test_Repo(RunbotCase):
|
||||
self.assertEqual(repo1[field_name], 1.3)
|
||||
self.assertEqual(repo2[field_name], 1.4)
|
||||
|
||||
_test_times('runbot.repo.hooktime', 'hook_time')
|
||||
_test_times('runbot.repo.reftime', 'get_ref_time')
|
||||
_test_times('runbot.repo.hooktime', 'set_hook_time', 'hook_time')
|
||||
_test_times('runbot.repo.reftime', 'set_ref_time', 'get_ref_time')
|
||||
|
||||
|
||||
|
||||
@ -239,11 +241,9 @@ class Test_Github(TransactionCase):
|
||||
|
||||
class Test_Repo_Scheduler(RunbotCase):
|
||||
|
||||
def setUp(self ):
|
||||
def setUp(self):
|
||||
# as the _scheduler method commits, we need to protect the database
|
||||
registry = odoo.registry()
|
||||
registry.enter_test_mode()
|
||||
self.addCleanup(registry.leave_test_mode)
|
||||
super(Test_Repo_Scheduler, self).setUp()
|
||||
|
||||
self.fqdn_patcher = patch('odoo.addons.runbot.models.host.fqdn')
|
||||
@ -261,6 +261,7 @@ class Test_Repo_Scheduler(RunbotCase):
|
||||
@patch('odoo.addons.runbot.models.build.runbot_build._schedule')
|
||||
@patch('odoo.addons.runbot.models.build.runbot_build._init_pendings')
|
||||
def test_repo_scheduler(self, mock_init_pendings, mock_schedule, mock_kill):
|
||||
|
||||
self.env['ir.config_parameter'].set_param('runbot.runbot_workers', 6)
|
||||
builds = []
|
||||
# create 6 builds that are testing on the host to verify that
|
||||
@ -305,7 +306,3 @@ class Test_Repo_Scheduler(RunbotCase):
|
||||
self.Build.search([('name', '=', 'a')]).write({'local_state': 'done'})
|
||||
|
||||
self.foo_repo._scheduler(host)
|
||||
build.invalidate_cache()
|
||||
scheduled_build.invalidate_cache()
|
||||
self.assertEqual(build.host, 'host.runbot.com')
|
||||
self.assertFalse(scheduled_build.host)
|
||||
|
@ -11,8 +11,6 @@ class TestSchedule(RunbotCase):
|
||||
def setUp(self):
|
||||
# entering test mode to avoid that the _schedule method commits records
|
||||
registry = odoo.registry()
|
||||
registry.enter_test_mode()
|
||||
self.addCleanup(registry.leave_test_mode)
|
||||
super(TestSchedule, self).setUp()
|
||||
|
||||
self.repo = self.Repo.create({'name': 'bla@example.com:foo/bar'})
|
||||
|
@ -3,7 +3,7 @@
|
||||
<data>
|
||||
<template id="assets_front_end" inherit_id="web.assets_frontend" name="runbot assets">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/runbot/static/src/less/runbot.less"/>
|
||||
<link rel="stylesheet" href="/runbot/static/src/css/runbot.css"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
|
@ -121,7 +121,7 @@
|
||||
<filter string="Fixed" name="fixed_errors" domain="[('active', '=', False)]"/>
|
||||
<filter string="Not Fixed" name="not_fixed_errors" domain="[('active', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter string="Not Asigned" name="not_assigned_errors" domain="[('responsible', '=', False)]"/>
|
||||
<filter string="Not Assigned" name="not_assigned_errors" domain="[('responsible', '=', False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -82,21 +82,19 @@
|
||||
<field name="name"/>
|
||||
<field name="global_state"/>
|
||||
<field name="dest"/>
|
||||
<separator/>
|
||||
<filter string="Pending" domain="[('global_state','=', 'pending')]"/>
|
||||
<filter string="Testing" domain="[('global_state','in', ('testing', 'waiting'))]"/>
|
||||
<filter string="Running" domain="[('global_state','=', 'running')]"/>
|
||||
<filter string="Done" domain="[('global_state','=','done')]"/>
|
||||
<filter string="Duplicate" domain="[('local_state','=', 'duplicate')]"/>
|
||||
<separator />
|
||||
<filter string="Pending" name='pending' domain="[('global_state','=', 'pending')]"/>
|
||||
<filter string="Testing" name='testing' domain="[('global_state','in', ('testing', 'waiting'))]"/>
|
||||
<filter string="Running" name='running' domain="[('global_state','=', 'running')]"/>
|
||||
<filter string="Done" name='done' domain="[('global_state','=','done')]"/>
|
||||
<filter string="Duplicate" name='duplicate' domain="[('local_state','=', 'duplicate')]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Repo" domain="[]" context="{'group_by':'repo_id'}"/>
|
||||
<filter string="Branch" domain="[]" context="{'group_by':'branch_id'}"/>
|
||||
<filter string="Status" domain="[]" context="{'group_by':'global_state'}"/>
|
||||
<filter string="Result" domain="[]" context="{'group_by':'global_result'}"/>
|
||||
<filter string="Start" domain="[]" context="{'group_by':'job_start'}"/>
|
||||
<filter string="Host" domain="[]" context="{'group_by':'host'}"/>
|
||||
<filter string="Create Date" domain="[]" context="{'group_by':'create_date'}"/>
|
||||
<filter string="Repo" name='repo' domain="[]" context="{'group_by':'repo_id'}"/>
|
||||
<filter string="Branch" name='branch' domain="[]" context="{'group_by':'branch_id'}"/>
|
||||
<filter string="Status" name='status' domain="[]" context="{'group_by':'global_state'}"/>
|
||||
<filter string="Result" name='result' domain="[]" context="{'group_by':'global_result'}"/>
|
||||
<filter string="Start" name='start' domain="[]" context="{'group_by':'job_start'}"/>
|
||||
<filter string="Host" name='host' domain="[]" context="{'group_by':'host'}"/>
|
||||
<filter string="Create Date" name='create_date' domain="[]" context="{'group_by':'create_date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@ -105,7 +103,6 @@
|
||||
<field name="name">Builds</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">runbot.build</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph,pivot</field>
|
||||
</record>
|
||||
<menuitem id="menu_build" action="action_build" parent="runbot_menu_root"/>
|
||||
|
@ -23,6 +23,7 @@
|
||||
<field name="update_github_state" groups="base.group_no_one"/>
|
||||
<field name="protected" groups="base.group_no_one"/>
|
||||
<field name="group" groups="base.group_no_one"/>
|
||||
<field name="monitoring_view_id" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
@ -113,7 +114,7 @@
|
||||
<search string="Search config">
|
||||
<field name="name"/>
|
||||
<field name="group_name"/>
|
||||
<filter string="Is in a group" domain="[(['group', '!=', False])]"/>
|
||||
<filter string="Is in a group" name='is_in_group' domain="[(['group', '!=', False])]"/>
|
||||
<filter string="No step's defined" name="no_step" domain="[(['step_order_ids', '=', False])]"/>
|
||||
</search>
|
||||
</field>
|
||||
@ -126,12 +127,12 @@
|
||||
<search string="Search config step">
|
||||
<field name="name"/>
|
||||
<field name="group_name"/>
|
||||
<filter string="Install job" domain="[(['job_type', '=', 'install_odoo'])]"/>
|
||||
<filter string="Run job" domain="[(['job_type', '=', 'run_odoo'])]"/>
|
||||
<filter string="Python job" domain="[(['job_type', '=', 'python'])]"/>
|
||||
<filter string="Create job" domain="[(['job_type', '=', 'create_build'])]"/>
|
||||
<filter string="Install job" name='install_job' domain="[(['job_type', '=', 'install_odoo'])]"/>
|
||||
<filter string="Run job" name='run_job' domain="[(['job_type', '=', 'run_odoo'])]"/>
|
||||
<filter string="Python job" name='python_job' domain="[(['job_type', '=', 'python'])]"/>
|
||||
<filter string="Create job" name='create_job' domain="[(['job_type', '=', 'create_build'])]"/>
|
||||
<separator/>
|
||||
<filter string="Is in a group" domain="[(['group', '!=', False])]"/>
|
||||
<filter string="Is in a group" name='is_in_group' domain="[(['group', '!=', False])]"/>
|
||||
<separator/>
|
||||
<filter string="No config defined" name="no_step" domain="[(['step_order_ids', '=', False])]"/>
|
||||
</search>
|
||||
|
@ -16,8 +16,6 @@
|
||||
<field name="last_success" readonly='1'/>
|
||||
<field name="assigned_only"/>
|
||||
<field name="nb_worker"/>
|
||||
<field name="nb_testing"/>
|
||||
<field name="nb_running"/>
|
||||
<field name="last_exception" readonly='1'/>
|
||||
<field name="exception_count" readonly='1'/>
|
||||
</group>
|
||||
|
@ -6,6 +6,7 @@ from odoo import fields, models, api
|
||||
class MultiBuildWizard(models.TransientModel):
|
||||
|
||||
_name = 'runbot.build.config.multi.wizard'
|
||||
_description = "Multi wizard"
|
||||
|
||||
base_name = fields.Char('Generic name', required=True)
|
||||
prefix = fields.Char('Prefix', help="Leave blank to use login.")
|
||||
|
@ -30,7 +30,6 @@
|
||||
<field name="name">Generate Multi Build Config</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">runbot.build.config.multi.wizard</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="runbot_multi_build_wizard_form"/>
|
||||
<field name="target">new</field>
|
||||
|
@ -43,12 +43,12 @@ class RunbotClient():
|
||||
sleep_time = self.env['runbot.repo']._scheduler_loop_turn(host)
|
||||
host.last_end_loop = fields.Datetime.now()
|
||||
self.env.cr.commit()
|
||||
self.env.reset()
|
||||
self.env.clear()
|
||||
self.sleep(sleep_time)
|
||||
except Exception as e:
|
||||
_logger.exception('Builder main loop failed with: %s', e)
|
||||
self.env.cr.rollback()
|
||||
self.env.reset()
|
||||
self.env.clear()
|
||||
self.sleep(10)
|
||||
|
||||
if self.ask_interrupt.is_set():
|
||||
|
@ -2,7 +2,7 @@
|
||||
'name': 'Runbot CLA',
|
||||
'category': 'Website',
|
||||
'summary': 'Runbot CLA',
|
||||
'version': '2.0',
|
||||
'version': '2.1',
|
||||
'description': "Runbot CLA",
|
||||
'author': 'Odoo SA',
|
||||
'depends': ['runbot'],
|
||||
|
@ -5,11 +5,4 @@
|
||||
<field name="job_type">cla_check</field>
|
||||
<field name="protected" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="runbot.runbot_build_config_default" model="runbot.build.config">
|
||||
<field name="step_order_ids" eval="[(0, 0, {'step_id': ref('runbot_build_config_step_check_cla')})]"/>
|
||||
</record>
|
||||
<record id="runbot.runbot_build_config_default_no_run" model="runbot.build.config">
|
||||
<field name="step_order_ids" eval="[(0, 0, {'step_id': ref('runbot_build_config_step_check_cla')})]"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
Loading…
Reference in New Issue
Block a user