[MERGE] runbot: migration to odoo 13.0

This commit is contained in:
Xavier-Do 2020-01-14 13:26:26 +01:00
commit 300f39003f
46 changed files with 401 additions and 368 deletions

View File

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

View File

@ -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():

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
<odoo>
<record id="website.homepage_page" model="website.page">
<field name="url">/home</field>
</record>
</odoo>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}

View File

@ -41,7 +41,7 @@
return false;
});
});
$(function() {
new Clipboard('.clipbtn');
});
//$(function() {
// new Clipboard('.clipbtn');
//});
})(jQuery);

View File

@ -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";
}

View File

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

View File

@ -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}}&amp;view_type=form&amp;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}}&amp;view_type=form&amp;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">&amp;nbsp;<i class="fa fa-chain-broken" title="Build result ignored for parent" />&amp;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>

View File

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

View File

@ -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=&quot;website.menu_id.child_id&quot;][@t-as=&quot;submenu&quot;]" 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) &gt; 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>&amp;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']">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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():

View File

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

View File

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