From c57a6735710dcf125fc53398f952ecbfc1d32b58 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Mon, 23 Feb 2026 15:52:43 +0100 Subject: [PATCH 01/64] [IMP] runbot: allow a trigger to use an extra slot --- runbot/models/batch.py | 2 +- runbot/models/repo.py | 1 + runbot/views/repo_views.xml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/runbot/models/batch.py b/runbot/models/batch.py index e044662f8..f1cd44fc9 100644 --- a/runbot/models/batch.py +++ b/runbot/models/batch.py @@ -163,7 +163,7 @@ def _create_build(self, params, slot): build_type = 'normal' if self.category_id != self.env.ref('runbot.default_category'): build_type = 'scheduled' - elif self.bundle_id.priority: + elif self.bundle_id.priority or params.trigger_id.use_extra_slot: build_type = 'priority' build = self.env['runbot.build'].create({ diff --git a/runbot/models/repo.py b/runbot/models/repo.py index b3ac2872d..ef0c3a10a 100644 --- a/runbot/models/repo.py +++ b/runbot/models/repo.py @@ -55,6 +55,7 @@ class Trigger(models.Model): project_id = fields.Many2one('runbot.project', string="Project id", required=True) repo_ids = fields.Many2many('runbot.repo', relation='runbot_trigger_triggers', string="Triggers", domain="[('project_id', '=', project_id)]") dependency_ids = fields.Many2many('runbot.repo', relation='runbot_trigger_dependencies', string="Dependencies") + use_extra_slot = fields.Boolean('Use extra slot', help="If checked, builds from this trigger can use an extra slot on the builders (for light and fast triggers)") starts_before_ids = fields.Many2many( 'runbot.trigger', diff --git a/runbot/views/repo_views.xml b/runbot/views/repo_views.xml index 02d7e44e6..fa88dab46 100644 --- a/runbot/views/repo_views.xml +++ b/runbot/views/repo_views.xml @@ -21,6 +21,7 @@ + From 1fdf95a8a72f88d87f2e90854c587c84a7e828f9 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Mon, 16 Feb 2026 08:52:17 +0100 Subject: [PATCH 02/64] [FIX] runbot: don't link rebase on builds When using automatic rebase, in some cases the created build could be linked to an existing one. This is becase since threehash we don't use the commit.id, and thus ignore the rebase_on in the fingerprint This fixes the issue by adding the rebase_on treehash in the fingerprint In an ideal world we would be able to predict the threehash if the rebase was real. --- runbot/models/build.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/runbot/models/build.py b/runbot/models/build.py index 854e38bd6..d6ebf0776 100644 --- a/runbot/models/build.py +++ b/runbot/models/build.py @@ -105,8 +105,18 @@ class BuildParameters(models.Model): # @api.depends('version_id', 'project_id', 'extra_params', 'config_id', 'config_data', 'modules', 'commit_link_ids', 'builds_reference_ids') def _compute_fingerprint(self): + def get_commit_links_ident(commit_link): + commit_idents = [] + for c in commit_link.commit_id: + commit_ident = c.tree_hash or c.name + if c.rebase_on_id: + commit_ident += (c.rebase_on_id.tree_hash or c.rebase_on_id.name) + # in a ideal world, we would be able to determine what the real threehash would be + commit_idents.append(commit_ident) + return sorted(commit_idents) + for param in self: - commit_ident = sorted([c.tree_hash or '' for c in param.commit_link_ids.commit_id]) + commit_ident = get_commit_links_ident(param.commit_link_ids) if param.trigger_id.batch_dependent: commit_ident = sorted(param.commit_link_ids.commit_id.ids) cleaned_vals = { @@ -125,7 +135,7 @@ def _compute_fingerprint(self): } if param.upgrade_to_build_id: cleaned_vals['upgrade_to_build_dockerfile_id'] = param.upgrade_to_build_id.params_id.dockerfile_id.id - cleaned_vals['upgrade_to_build_commits'] = sorted([c.tree_hash or c.id for c in param.upgrade_to_build_id.params_id.commit_link_ids.commit_id]) + cleaned_vals['upgrade_to_build_commits'] = get_commit_links_ident(param.upgrade_to_build_id.params_id.commit_link_ids) if param.upgrade_from_build_id: cleaned_vals['upgrade_from_build_id'] = param.upgrade_from_build_id.id if param.trigger_id.batch_dependent: From b41e813b360afe8ad15606953efd2fe56378c238 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Thu, 26 Feb 2026 14:32:15 +0100 Subject: [PATCH 03/64] [IMP] runbot: add priority level to build_views Can be set on staging bundle to increase priority First builds of forwarports will have a lower priority. --- runbot/__manifest__.py | 2 +- runbot/controllers/frontend.py | 6 ++++++ runbot/migrations/19.0.5.16/pre-migration.py | 8 ++++++++ runbot/models/batch.py | 6 ++++++ runbot/models/build.py | 2 ++ runbot/models/bundle.py | 2 ++ runbot/models/runbot.py | 2 +- runbot/templates/batch.xml | 17 +++++++++++++++++ runbot/views/build_views.xml | 2 ++ runbot/views/bundle_views.xml | 1 + 10 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 runbot/migrations/19.0.5.16/pre-migration.py diff --git a/runbot/__manifest__.py b/runbot/__manifest__.py index 9a8491ab8..38db70949 100644 --- a/runbot/__manifest__.py +++ b/runbot/__manifest__.py @@ -6,7 +6,7 @@ 'author': "Odoo SA", 'website': "http://runbot.odoo.com", 'category': 'Website', - 'version': '5.15', + 'version': '5.16', 'application': True, 'depends': ['base', 'base_automation', 'website', 'auth_oauth'], 'data': [ diff --git a/runbot/controllers/frontend.py b/runbot/controllers/frontend.py index 1512a7a3c..1598fda9d 100644 --- a/runbot/controllers/frontend.py +++ b/runbot/controllers/frontend.py @@ -220,6 +220,12 @@ def batch(self, batch_id=None, **kwargs): } return request.render('runbot.batch', context) + @route(['/runbot/batch//prioritize'], website=True, auth='user', type='http', sitemap=False) + def batch_priority(self, batch_id=None, **kwargs): + batch = request.env['runbot.batch'].browse(batch_id) + batch.sudo().priority_level = int(batch.create_date.timestamp() - 3600) + return werkzeug.utils.redirect('/runbot/batch/%s' % batch_id) + @route(['/runbot/batch/slot//build'], auth='user', type='http') def slot_create_build(self, slot=None, **kwargs): build = slot.sudo()._create_missing_build() diff --git a/runbot/migrations/19.0.5.16/pre-migration.py b/runbot/migrations/19.0.5.16/pre-migration.py new file mode 100644 index 000000000..bffcd9582 --- /dev/null +++ b/runbot/migrations/19.0.5.16/pre-migration.py @@ -0,0 +1,8 @@ +import logging + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + cr.execute("""ALTER TABLE runbot_batch ADD COLUMN priority_level integer""") + cr.execute("""ALTER TABLE runbot_build ADD COLUMN priority_level integer""") diff --git a/runbot/models/batch.py b/runbot/models/batch.py index f1cd44fc9..84e2bb707 100644 --- a/runbot/models/batch.py +++ b/runbot/models/batch.py @@ -20,6 +20,7 @@ class Batch(models.Model): slot_ids = fields.One2many('runbot.batch.slot', 'batch_id') all_build_ids = fields.Many2many('runbot.build', compute='_compute_all_build_ids', help="Recursive builds") state = fields.Selection([('preparing', 'Preparing'), ('ready', 'Ready'), ('done', 'Done'), ('skipped', 'Skipped')]) + priority_level = fields.Integer("Priority level", help="Priority level of the batch, determined from the create date and the bundle priority offset. The lower, the higher priority.") hidden = fields.Boolean('Hidden', default=False) age = fields.Integer(compute='_compute_age', string='Build age') category_id = fields.Many2one('runbot.category', index=True, default=lambda self: self.env.ref('runbot.default_category', raise_if_not_found=False)) @@ -182,6 +183,11 @@ def _create_build(self, params, slot): def _prepare(self, auto_rebase=False, use_base_commits=False): _logger.info('Preparing batch %s', self.id) + + priority_offset = self.bundle_id.priority_offset + if not priority_offset and self.bundle_id.branch_ids.forwardport_of_id and self.bundle_id.last_batchs == self: # this is the only batch of a forwardported pr. + priority_offset = - 3600 * 5 + self.priority_level = int(self.create_date.timestamp() - priority_offset) if use_base_commits: self._warning('This batch will use base commits instead of bundle commits') if not self.bundle_id.base_id: diff --git a/runbot/models/build.py b/runbot/models/build.py index d6ebf0776..65c884ab8 100644 --- a/runbot/models/build.py +++ b/runbot/models/build.py @@ -276,6 +276,7 @@ class BuildResult(models.Model): create_batch_id = fields.Many2one('runbot.batch', related='params_id.create_batch_id', store=True, index=True) create_bundle_id = fields.Many2one('runbot.bundle', related='params_id.create_batch_id.bundle_id', index=True) dynamic_config = JsonDictField('Dynamic Config', related='params_id.dynamic_config') + priority_level = fields.Integer('Priority', related='create_batch_id.priority_level', store=True, index=True) # state machine global_state = fields.Selection(make_selection(state_order), string='Status', compute='_compute_global_state', store=True, recursive=True) @@ -522,6 +523,7 @@ def _add_child(self, param_values, orphan=False, description=False, additionnal_ 'params_id': self.params_id.copy(param_values).id, 'parent_id': self.id, 'build_type': self.build_type, + 'priority_level': self.priority_level, 'description': description, 'orphan_result': orphan, 'keep_host': self.keep_host, diff --git a/runbot/models/bundle.py b/runbot/models/bundle.py index 1c54d9d53..747440072 100644 --- a/runbot/models/bundle.py +++ b/runbot/models/bundle.py @@ -57,6 +57,8 @@ class Bundle(models.Model): tag_ids = fields.Many2many('runbot.bundle.tag', string='Tags') team_id = fields.Many2one('runbot.team', compute='_compute_team_id', store=True, readonly=False) + priority_offset = fields.Integer("Priority offset", help="Offset in seconds to remove from the create date of a batch to define priority, positive value means higher priority, negative value means lower priority.") + def _compute_frontend_url(self): for bundle in self: bundle.frontend_url = f'/runbot/bundle/{bundle.id}' diff --git a/runbot/models/runbot.py b/runbot/models/runbot.py index f750ef8fc..047e3090b 100644 --- a/runbot/models/runbot.py +++ b/runbot/models/runbot.py @@ -138,7 +138,7 @@ def _allocate_builds(self, host, nb_slots, domain=None): if domain: non_allocated_domain = Domain.AND([non_allocated_domain, domain]) query = self.env['runbot.build']._search(non_allocated_domain) - query.order = 'runbot_build.create_batch_id' + query.order = 'runbot_build.priority_level' self.env.execute_query(SQL("""UPDATE runbot_build SET diff --git a/runbot/templates/batch.xml b/runbot/templates/batch.xml index 54a25393e..8ba5ba19c 100644 --- a/runbot/templates/batch.xml +++ b/runbot/templates/batch.xml @@ -18,6 +18,23 @@ + + Priority + + + High + + + + Low + + + Normal + + Set to high + + + Category diff --git a/runbot/views/build_views.xml b/runbot/views/build_views.xml index 722201d09..1994de3fa 100644 --- a/runbot/views/build_views.xml +++ b/runbot/views/build_views.xml @@ -83,6 +83,8 @@ + + diff --git a/runbot/views/bundle_views.xml b/runbot/views/bundle_views.xml index d3e004185..d2804c8f3 100644 --- a/runbot/views/bundle_views.xml +++ b/runbot/views/bundle_views.xml @@ -56,6 +56,7 @@ + From 14bf876b47de90f07b8dffc1ea6ef47284235689 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Fri, 27 Feb 2026 15:29:10 +0100 Subject: [PATCH 04/64] [IMP] runbot: add cache to some layers --- runbot/data/dockerfile_data.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runbot/data/dockerfile_data.xml b/runbot/data/dockerfile_data.xml index 9d2601f97..806d9d8fc 100644 --- a/runbot/data/dockerfile_data.xml +++ b/runbot/data/dockerfile_data.xml @@ -120,6 +120,7 @@ Install branch debian/control with latest postgresql-client # This layer updates the repository list to get the latest postgresql-client, mainly needed if the host postgresql version is higher than the default version of the docker os +# CACHE 60 ADD https://raw.githubusercontent.com/odoo/odoo/{odoo_branch}/debian/control /tmp/control.txt RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc -o /etc/apt/trusted.gpg.d/psql_client.asc \ && echo "deb http://apt.postgresql.org/pub/repos/apt/ {os_release_name}-pgdg main" > /etc/apt/sources.list.d/pgclient.list \ @@ -195,7 +196,8 @@ ENV PIP_BREAK_SYSTEM_PACKAGES=1 template Install branch requirements - ADD --chown={USERNAME} https://raw.githubusercontent.com/odoo/odoo/{odoo_branch}/requirements.txt /tmp/requirements.txt + # CACHE 60 +ADD --chown={USERNAME} https://raw.githubusercontent.com/odoo/odoo/{odoo_branch}/requirements.txt /tmp/requirements.txt RUN python3 -m pip install --no-cache-dir -r /tmp/requirements.txt From a5f08f9b12f89cef7aae4e154820b6d2f19fe3b5 Mon Sep 17 00:00:00 2001 From: Pierre Paridans Date: Thu, 12 Feb 2026 12:31:50 +0100 Subject: [PATCH 05/64] [IMP] runbot: simplify backend urls --- runbot/templates/batch.xml | 2 +- runbot/templates/branch.xml | 2 +- runbot/templates/build.xml | 4 ++-- runbot/templates/build_error.xml | 4 ++-- runbot/templates/bundle.xml | 4 ++-- runbot/templates/commit.xml | 2 +- runbot/templates/utils.xml | 5 ++--- runbot/views/branch_views.xml | 1 + runbot/views/build_error_views.xml | 2 ++ runbot/views/build_views.xml | 4 +++- runbot/views/bundle_views.xml | 6 ++++++ runbot/views/codeowner_views.xml | 1 + runbot/views/commit_views.xml | 2 ++ runbot/views/config_views.xml | 2 ++ runbot/views/dashboard_views.xml | 5 +++++ runbot/views/dockerfile_views.xml | 2 ++ runbot/views/host_views.xml | 1 + runbot/views/repo_views.xml | 5 +++++ runbot/views/semgrep_rules.xml | 2 ++ runbot/views/stat_views.xml | 1 + runbot/views/upgrade.xml | 2 ++ runbot/views/upgrade_matrix_views.xml | 1 + runbot/views/warning_views.xml | 1 + 23 files changed, 48 insertions(+), 13 deletions(-) diff --git a/runbot/templates/batch.xml b/runbot/templates/batch.xml index 8ba5ba19c..0896e8a1b 100644 --- a/runbot/templates/batch.xml +++ b/runbot/templates/batch.xml @@ -13,7 +13,7 @@   + t-attf-href="/odoo/batch/{{batch.id}}" class="btn btn-default btn-sm" target="_blank" title="View Batch in Backend"> diff --git a/runbot/templates/branch.xml b/runbot/templates/branch.xml index 78e0948e0..611f06e77 100644 --- a/runbot/templates/branch.xml +++ b/runbot/templates/branch.xml @@ -12,7 +12,7 @@ diff --git a/runbot/templates/build.xml b/runbot/templates/build.xml index d9428a0d3..b751ae50b 100644 --- a/runbot/templates/build.xml +++ b/runbot/templates/build.xml @@ -234,7 +234,7 @@ with config - ... + ... @@ -355,7 +355,7 @@ - + () diff --git a/runbot/templates/build_error.xml b/runbot/templates/build_error.xml index e64fc4706..87ad07804 100644 --- a/runbot/templates/build_error.xml +++ b/runbot/templates/build_error.xml @@ -36,7 +36,7 @@
+ t-attf-href="/odoo/error/{{build_error.id}}" target="_blank" title="View in Backend"> @@ -113,7 +113,7 @@

Team + t-attf-href="/odoo/team/{{team.id}}" target="_blank" title="View in Backend">

diff --git a/runbot/templates/bundle.xml b/runbot/templates/bundle.xml index feea6ec3f..393c1628e 100644 --- a/runbot/templates/bundle.xml +++ b/runbot/templates/bundle.xml @@ -12,7 +12,7 @@
+ t-attf-href="/odoo/bundle/{{bundle.id}}" class="btn btn-default" title="View in Backend"> @@ -74,7 +74,7 @@
+ t-attf-href="/odoo/branch/{{branch.id}}" target="_blank" title="View Branch in Backend"/> diff --git a/runbot/templates/commit.xml b/runbot/templates/commit.xml index f45aeac72..de5d1eb02 100644 --- a/runbot/templates/commit.xml +++ b/runbot/templates/commit.xml @@ -33,7 +33,7 @@ diff --git a/runbot/templates/utils.xml b/runbot/templates/utils.xml index 3a7d06da1..1687e18e7 100644 --- a/runbot/templates/utils.xml +++ b/runbot/templates/utils.xml @@ -105,7 +105,7 @@ `; + setup() { this.canvasRef = useRef("canvas"); useEffect(() => this.renderErrorGraph()); } renderErrorGraph(activeCell) { - const data = this.props.record.data[this.props.name] || {}; const errorId = data.error_id; const projectId = data.project_id; const categoryId = data.category_id; const breaking_pr_close_dates = data.breaking_pr_close_dates; const fixing_pr_close_dates = data.fixing_pr_close_dates; - - const canvas = this.canvasRef.el + const canvas = this.canvasRef.el; const ctx = canvas.getContext("2d"); const maxValue = data.max_count; const canvasBorder = 1; @@ -35,26 +34,25 @@ export class HistoryGraph extends Component { canvas.width = canvasWidth; canvas.height = canvasHeight; - function getColor(value, opacity) { if (value >= 10) { return `rgba(255, 0, 0, ${opacity})`; // red } else if (value >= 5) { return `rgba(255, 165, 0, ${opacity})`; // orange } - return `rgba(0, 170, 0, ${opacity})` // green + return `rgba(0, 170, 0, ${opacity})`; // green } ctx.clearRect(0, 0, canvasWidth, canvasHeight); ctx.fillStyle = "#EEE"; ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.strokeStyle = "#333"; - ctx.lineWidth = canvasBorder * 2; // * 2 to account for each side, not only inner width - ctx.strokeRect(0, 0, canvasWidth, canvasHeight,); + ctx.lineWidth = canvasBorder * 2; // * 2 to account for each side, not only inner width + ctx.strokeRect(0, 0, canvasWidth, canvasHeight); data.date_labels.forEach((dateLabel, idx) => { data.version_labels.forEach((versionLabel, idy) => { - let version_id = data.versions_ids[idy] + let version_id = data.versions_ids[idy]; let value = data.daily_version_freq[idx][idy] || 0; let cellColor = "white"; let cellOpacity = 0; @@ -68,13 +66,13 @@ export class HistoryGraph extends Component { ctx.fillStyle = cellColor; ctx.fillRect(posX, posY, cellWidth, cellHeight); + if (activeCell && activeCell.col === idx && activeCell.row === idy) { ctx.strokeStyle = "black"; ctx.lineWidth = 2; ctx.strokeRect(posX, posY, cellWidth, cellHeight); } - if (fixing_pr_close_dates[version_id] == dateLabel) { ctx.fillStyle = "black"; ctx.font = "12px Arial"; @@ -85,8 +83,6 @@ export class HistoryGraph extends Component { ctx.font = "12px Arial"; ctx.fillText("✗", posX + cellWidth / 2 - 4, posY + cellHeight / 2 + 4); } - - }); }); if (mouseActions) { @@ -126,7 +122,7 @@ export class HistoryGraph extends Component { const tooltip = canvas.parentElement.querySelector(".history-graph-tooltip"); if (tooltip) { tooltip.remove(); - this.renderErrorGraph() + this.renderErrorGraph(); } }; @@ -136,10 +132,10 @@ export class HistoryGraph extends Component { const url = `/runbot/batches/${projectId}/${categoryId}/${dateLabel}/${errorId}`; window.open(url, "_blank"); } - } + }; } - } + getCellFromEvent(event) { const data = this.props.record.data[this.props.name] || {}; const rect = this.canvasRef.el.getBoundingClientRect(); @@ -147,7 +143,7 @@ export class HistoryGraph extends Component { const y = event.clientY - rect.top - 1; // Adjust for canvas border const col = Math.floor(x / this.props.cellSize); const row = Math.floor(y / this.props.cellSize); - if ( col >= 0 && col < data.date_labels.length && row >= 0 && row < data.version_labels.length) { + if ( col >= 0 && col < data.date_labels.length && row >= 0 && row < data.version_labels.length) { const value = data.daily_version_freq[col][row] || 0; const dateLabel = data.date_labels[col]; const versionLabel = data.version_labels[row]; diff --git a/runbot/static/src/js/fields/tracking_value.js b/runbot/static/src/js/fields/tracking_value.js index 6d90b0e7a..3dbd57d0e 100644 --- a/runbot/static/src/js/fields/tracking_value.js +++ b/runbot/static/src/js/fields/tracking_value.js @@ -6,22 +6,27 @@ patch(Message.prototype, { super.setup(...arguments); this.kept = false; }, + isMultiline(trackingValue) { const oldValue = trackingValue.oldValue; const newValue = trackingValue.newValue; - return ((oldValue && typeof oldValue=== "string" && oldValue.includes("\n")) && (newValue && typeof oldValue=== "string" && newValue.includes("\n"))) + return ((oldValue && typeof oldValue=== "string" && oldValue.includes("\n")) && (newValue && typeof oldValue=== "string" && newValue.includes("\n"))); }, + formatTracking(trackingFieldInfo, trackingValue) { - return super.formatTracking(trackingFieldInfo, trackingValue) + return super.formatTracking(trackingFieldInfo, trackingValue); }, + toggleKept() { this.kept = !this.kept; }, + copyToClipboard(trackingValue) { return function () { navigator.clipboard.writeText(trackingValue); }; }, + lines(trackingValue) { const oldValue = trackingValue.oldValue; const newValue = trackingValue.newValue; @@ -29,6 +34,7 @@ patch(Message.prototype, { const lines = this.prepareForRendering(diff); return lines; }, + makeDiff(text1, text2) { var dmp = new diff_match_patch(); var a = dmp.diff_linesToChars_(text1, text2); @@ -40,10 +46,11 @@ patch(Message.prototype, { dmp.diff_cleanupSemantic(diffs); return diffs; }, + prepareForRendering(diffs) { var lines = []; - var pre_line_counter = 0 - var post_line_counter = 0 + var pre_line_counter = 0; + var post_line_counter = 0; for (var x = 0; x < diffs.length; x++) { var diff_type = diffs[x][0]; var data = diffs[x][1]; @@ -56,18 +63,18 @@ patch(Message.prototype, { //text = text.replace(/\n/g, '
'); //text = text.replace(/ /g, '  '); if (diff_type == -1) { - lines.push({type:"removed", pre_line_counter: pre_line_counter, post_line_counter: "-", line: line}) - pre_line_counter += 1 + lines.push({ type: "removed", pre_line_counter: pre_line_counter, post_line_counter: "-", line: line }); + pre_line_counter += 1; } else if (diff_type == 0) { - lines.push({type:"kept", pre_line_counter: "", post_line_counter: post_line_counter, line: line}) - pre_line_counter += 1 - post_line_counter +=1 + lines.push({ type: "kept", pre_line_counter: "", post_line_counter: post_line_counter, line: line }); + pre_line_counter += 1; + post_line_counter += 1; } else if (diff_type == 1) { - lines.push({type:"added", pre_line_counter: "+", post_line_counter: post_line_counter, line: line}) - post_line_counter +=1 + lines.push({ type: "added", pre_line_counter: "+", post_line_counter: post_line_counter, line: line }); + post_line_counter += 1; } } } return lines; - }, + }, }); diff --git a/runbot/static/src/js/views/form_controller.js b/runbot/static/src/js/views/form_controller.js index 544aaf244..4fc4d83a2 100644 --- a/runbot/static/src/js/views/form_controller.js +++ b/runbot/static/src/js/views/form_controller.js @@ -5,6 +5,7 @@ import { patch } from "@web/core/utils/patch"; patch(FormController.prototype, { // Prevent saving on tab switching beforeVisibilityChange: () => {}, + // Prevent closing page with dirty fields async beforeUnload(ev) { if (await this.model.root.isDirty()) { @@ -13,5 +14,5 @@ patch(FormController.prototype, { } else { super.beforeUnload(ev); } - } -}) + }, +}); From 88273ae6d19179868cb9f32a54289b8aa6a76c67 Mon Sep 17 00:00:00 2001 From: Pierre Paridans Date: Thu, 12 Feb 2026 14:32:27 +0100 Subject: [PATCH 10/64] [IMP] runbot: remove unused variable in js --- runbot/static/src/js/fields/history_graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runbot/static/src/js/fields/history_graph.js b/runbot/static/src/js/fields/history_graph.js index 05819c74e..84db495af 100644 --- a/runbot/static/src/js/fields/history_graph.js +++ b/runbot/static/src/js/fields/history_graph.js @@ -127,7 +127,7 @@ export class HistoryGraph extends Component { }; canvas.onclick = (event) => { - const { col, row, value, dateLabel, versionLabel } = this.getCellFromEvent(event); + const { col, row, dateLabel } = this.getCellFromEvent(event); if (col >= 0 && row >= 0) { const url = `/runbot/batches/${projectId}/${categoryId}/${dateLabel}/${errorId}`; window.open(url, "_blank"); From 82ab595ab9d7448892a2185a1e5b60574af96308 Mon Sep 17 00:00:00 2001 From: Pierre Paridans Date: Thu, 12 Feb 2026 14:33:58 +0100 Subject: [PATCH 11/64] [REF] runbot: linting: Unexpected var, use let or const instead. --- runbot/static/src/js/fields/fields.js | 2 +- runbot/static/src/js/fields/history_graph.js | 2 +- runbot/static/src/js/fields/tracking_value.js | 30 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/runbot/static/src/js/fields/fields.js b/runbot/static/src/js/fields/fields.js index f0d9e296f..400744fa8 100644 --- a/runbot/static/src/js/fields/fields.js +++ b/runbot/static/src/js/fields/fields.js @@ -17,7 +17,7 @@ import { BooleanToggleField } from "@web/views/fields/boolean_toggle/boolean_tog function colorizeJson(json) { json = json.replace(/&/g, "&").replace(//g, ">"); return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { - var cls = ""; + let cls = ""; if (/^"/.test(match)) { if (/:$/.test(match)) { cls = "o_runbot_json_key"; diff --git a/runbot/static/src/js/fields/history_graph.js b/runbot/static/src/js/fields/history_graph.js index 84db495af..6f6a3370c 100644 --- a/runbot/static/src/js/fields/history_graph.js +++ b/runbot/static/src/js/fields/history_graph.js @@ -52,7 +52,7 @@ export class HistoryGraph extends Component { data.date_labels.forEach((dateLabel, idx) => { data.version_labels.forEach((versionLabel, idy) => { - let version_id = data.versions_ids[idy]; + const version_id = data.versions_ids[idy]; let value = data.daily_version_freq[idx][idy] || 0; let cellColor = "white"; let cellOpacity = 0; diff --git a/runbot/static/src/js/fields/tracking_value.js b/runbot/static/src/js/fields/tracking_value.js index 3dbd57d0e..b08647d1b 100644 --- a/runbot/static/src/js/fields/tracking_value.js +++ b/runbot/static/src/js/fields/tracking_value.js @@ -36,27 +36,27 @@ patch(Message.prototype, { }, makeDiff(text1, text2) { - var dmp = new diff_match_patch(); - var a = dmp.diff_linesToChars_(text1, text2); - var lineText1 = a.chars1; - var lineText2 = a.chars2; - var lineArray = a.lineArray; - var diffs = dmp.diff_main(lineText1, lineText2, false); + const dmp = new diff_match_patch(); + const a = dmp.diff_linesToChars_(text1, text2); + const lineText1 = a.chars1; + const lineText2 = a.chars2; + const lineArray = a.lineArray; + const diffs = dmp.diff_main(lineText1, lineText2, false); dmp.diff_charsToLines_(diffs, lineArray); dmp.diff_cleanupSemantic(diffs); return diffs; }, prepareForRendering(diffs) { - var lines = []; - var pre_line_counter = 0; - var post_line_counter = 0; - for (var x = 0; x < diffs.length; x++) { - var diff_type = diffs[x][0]; - var data = diffs[x][1]; - var data_lines = data.split("\n"); - for (var line_index in data_lines) { - var line = data_lines[line_index]; + const lines = []; + let pre_line_counter = 0; + let post_line_counter = 0; + for (let x = 0; x < diffs.length; x++) { + const diff_type = diffs[x][0]; + const data = diffs[x][1]; + const data_lines = data.split("\n"); + for (const line_index in data_lines) { + let line = data_lines[line_index]; line = line.replace(/&/g, "&"); line = line.replace(//g, ">"); From e4ab69d1ea78385de2807af2c7f5f303b982a504 Mon Sep 17 00:00:00 2001 From: Pierre Paridans Date: Thu, 12 Feb 2026 14:37:18 +0100 Subject: [PATCH 12/64] [REF] runbot: linting: useless escaping in js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Escaping non-special characters in strings, template literals, and regular expressions doesn’t have any effect. --- runbot/static/src/js/fields/fields.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runbot/static/src/js/fields/fields.js b/runbot/static/src/js/fields/fields.js index 400744fa8..9d36fb7ff 100644 --- a/runbot/static/src/js/fields/fields.js +++ b/runbot/static/src/js/fields/fields.js @@ -16,7 +16,7 @@ import { BooleanToggleField } from "@web/views/fields/boolean_toggle/boolean_tog // https://stackoverflow.com/questions/4810841/pretty-print-json-using-javascript function colorizeJson(json) { json = json.replace(/&/g, "&").replace(//g, ">"); - return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, function (match) { let cls = ""; if (/^"/.test(match)) { if (/:$/.test(match)) { From 4c8f257ff6e0be8507180d4d743ae8943e9203fa Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Fri, 20 Mar 2026 08:47:26 +0100 Subject: [PATCH 13/64] [IMP] runbot: easiets search on pr pull head name --- runbot/controllers/frontend.py | 10 ++++------ runbot/templates/frontend.xml | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/runbot/controllers/frontend.py b/runbot/controllers/frontend.py index 1598fda9d..30e8608e1 100644 --- a/runbot/controllers/frontend.py +++ b/runbot/controllers/frontend.py @@ -80,7 +80,7 @@ def _pending(self): '/runbot/', '/runbot//search/'], website=True, auth='public', type='http') def bundles(self, project=None, search='', refresh=False, limit=40, has_pr=None, **kwargs): - search = search if len(search) < 60 else search[:60] + search = search if len(search) < 60 else search[:200] env = request.env categories = env['runbot.category'].search([]) projects = self.env['runbot.project'].search([('hidden', '=', False)]) @@ -119,13 +119,11 @@ def bundles(self, project=None, search='', refresh=False, limit=40, has_pr=None, pr_numbers = [] for search_elem in search.split("|"): if search_elem.isnumeric(): - pr_numbers.append(int(search_elem)) + search_domains.append([('branch_ids', 'any', [('name', '=', search_elem)])]) + if ':' in search_elem: + search_domains.append([('branch_ids', 'any', [('pull_head_name', '=', search_elem)])]) operator = '=ilike' if '%' in search_elem else 'ilike' search_domains.append([('name', operator, search_elem)]) - if pr_numbers: - res = request.env['runbot.branch'].search([('name', 'in', pr_numbers)]) - if res: - search_domains.append([('id', 'in', res.mapped('bundle_id').ids)]) search_domain = Domain.OR(search_domains) domain = Domain.AND([domain, search_domain]) diff --git a/runbot/templates/frontend.xml b/runbot/templates/frontend.xml index 0298dca6d..8a267be32 100644 --- a/runbot/templates/frontend.xml +++ b/runbot/templates/frontend.xml @@ -6,7 +6,7 @@