From 53cbb5cc5d6dba4c8cd4223661dd539b276ff676 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Fri, 21 Feb 2025 09:56:34 +0100 Subject: [PATCH] [IMP] runbot: introduce matrix for upgrade The initial way to configure upgrades was working fine and automatically but does not alway match the reality of what should be supported. Saying, upgrade from the last main version and last intermediate (for 18, from 17.0 and 17.4) we may also want to test from 17.2 because for some reason there are many people in 17.2 taht will need to upgrade in 18.0. But the transition 17.2->17.3 doesn't make sense anymore after a while since people will go in 18 imediatly at some point. This matrix can be generated automatically from the parameters and new versions should appear automatically, but it is possible to tweak the values on demand. --- runbot/__manifest__.py | 1 + runbot/models/bundle.py | 3 + runbot/models/upgrade.py | 118 ++++++++++++++++++++++++- runbot/security/ir.model.access.csv | 10 +++ runbot/security/runbot_security.xml | 7 +- runbot/static/src/js/fields/fields.js | 59 ++++++++++++- runbot/static/src/js/fields/fields.xml | 28 ++++++ runbot/views/menus.xml | 6 +- runbot/views/upgrade_matrix_views.xml | 73 +++++++++++++++ 9 files changed, 300 insertions(+), 5 deletions(-) create mode 100644 runbot/static/src/js/fields/fields.xml create mode 100644 runbot/views/upgrade_matrix_views.xml diff --git a/runbot/__manifest__.py b/runbot/__manifest__.py index eab898c66..6ab544a2a 100644 --- a/runbot/__manifest__.py +++ b/runbot/__manifest__.py @@ -52,6 +52,7 @@ 'views/res_config_settings_views.xml', 'views/stat_views.xml', 'views/upgrade.xml', + 'views/upgrade_matrix_views.xml', 'views/warning_views.xml', 'views/custom_trigger_wizard_views.xml', 'wizards/stat_regex_wizard_views.xml', diff --git a/runbot/models/bundle.py b/runbot/models/bundle.py index ae097a0ac..e865b7988 100644 --- a/runbot/models/bundle.py +++ b/runbot/models/bundle.py @@ -204,6 +204,9 @@ def create(self, values_list): if records.is_base: model = self.browse() model.env.registry.clear_cache() + matrix = self.env['runbot.upgrade.matrix'].search([('project_id', '=', record.project_id.id)], limit=1) + if matrix: + matrix._update_matrix_entries() elif record.project_id.tmp_prefix and record.name.startswith(record.project_id.tmp_prefix): record['no_build'] = True elif record.project_id.staging_prefix and record.name.startswith(record.project_id.staging_prefix): diff --git a/runbot/models/upgrade.py b/runbot/models/upgrade.py index cea4fa2f8..2e50a240a 100644 --- a/runbot/models/upgrade.py +++ b/runbot/models/upgrade.py @@ -1,5 +1,5 @@ import re -from odoo import models, fields +from odoo import models, fields, api from odoo.exceptions import UserError @@ -69,3 +69,119 @@ def _parse_upgrade_errors(self): return res else: raise UserError('Nothing found here') + + +class UpgradeMatrix(models.Model): + _name = 'runbot.upgrade.matrix' + _description = 'Upgrade matrix' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char('Name', required=True) + project_id = fields.Many2one('runbot.project', 'Project', required=True) + entry_ids = fields.One2many('runbot.upgrade.matrix.entry', 'matrix_id', 'Entries') + auto_update = fields.Boolean('Auto update', default=True, help="Automatically update the matrix entries enabled state when new versions are created") + + # fields defining default behaviour to generate the matix, + upgrade_to_major_versions = fields.Boolean() + upgrade_to_all_versions = fields.Boolean() + upgrade_from_previous_major_version = fields.Boolean() + upgrade_from_last_intermediate_version = fields.Boolean() + upgrade_from_all_intermediate_version = fields.Boolean() + + + def update_matrix_entries(self): + for metric in self: + metric._update_matrix_entries() + + def _update_matrix_entries(self): + self.ensure_one() + existing_entries = self.with_context(active_test=False).entry_ids + entries_per_versions = {(e.from_version_id.id, e.to_version_id.id): e for e in existing_entries} + + # get all versions + versions = self.env['runbot.bundle'].search([('project_id', '=', self.project_id.id), ('is_base', '=', True)]).mapped('version_id') + for target_version in versions: + compatible_versions = target_version.intermediate_version_ids | target_version.previous_major_version_id + for source_version in versions: + if (source_version.id, target_version.id) not in entries_per_versions: + if target_version == source_version: + continue + if source_version not in compatible_versions: + continue + self.env['runbot.upgrade.matrix.entry'].create({ + 'matrix_id': self.id, + 'from_version_id': source_version.id, + 'to_version_id': target_version.id + }) + if self.auto_update: + existing_entries._update_enabled() + + def reset_matrix_enabled(self): + for matrix in self: + matrix.entry_ids._update_enabled(force=True) + + +class UpgradeMatrixEntry(models.Model): + _name = 'runbot.upgrade.matrix.entry' + _description = 'Upgrade matrix entry' + _order = 'to_version_number desc, from_version_number desc, id desc' + + matrix_id = fields.Many2one('runbot.upgrade.matrix', 'Matrix', required=True, ondelete='cascade') + from_version_id = fields.Many2one('runbot.version', 'From version', required=True, ondelete='cascade') + to_version_id = fields.Many2one('runbot.version', 'To version', required=True, ondelete='cascade') + from_version_number = fields.Char(related='from_version_id.number', store=True) + to_version_number = fields.Char(related='to_version_id.number', store=True) + target_bundle_id = fields.Many2one('runbot.bundle', compute='_compute_target_bundle_id', store=True) + enabled = fields.Boolean('Enabled', default=True) + active = fields.Boolean('Active', compute='_compute_active', store=True) + manually_edited = fields.Boolean('Manually edited', default=False) + + _sql_constraints = [ + ('unique_matrix_entry', 'unique(matrix_id, from_version_id, to_version_id)', 'Matrix entry already exists') + ] + + @api.onchange('enabled') + def _onchange_enabled(self): + self.manually_edited = True + + def create(self, vals): + entry = super().create(vals) + entry._update_enabled() + + @api.depends('to_version_id', 'matrix_id.project_id') + def _compute_target_bundle_id(self): + for entry in self: + entry.target_bundle_id = entry.to_version_id.with_context(project_id=entry.matrix_id.project_id.id).base_bundle_id + + @api.depends('target_bundle_id.sticky') + def _compute_active(self): + for entry in self: + entry.active = entry.target_bundle_id.sticky + + def _update_enabled(self, force=False): + for entry in self: + if entry.manually_edited and not force: + continue + entry.manually_edited = False + + matrix = entry.matrix_id + to_enabled = False + from_enabled = False + + if matrix.upgrade_to_all_versions: + to_enabled = True + + elif matrix.upgrade_to_major_versions and entry.to_version_id.is_major: + to_enabled = True + + if not to_enabled: + entry.enabled = False + continue + + if matrix.upgrade_from_all_intermediate_version: + from_enabled = True + elif matrix.upgrade_from_last_intermediate_version and entry.to_version_id.intermediate_version_ids and entry.from_version_id == entry.to_version_id.intermediate_version_ids[-1]: + from_enabled = True + elif matrix.upgrade_from_previous_major_version and entry.from_version_id == entry.to_version_id.previous_major_version_id: + from_enabled = True + entry.enabled = to_enabled and from_enabled diff --git a/runbot/security/ir.model.access.csv b/runbot/security/ir.model.access.csv index cd54fa04e..994ad2e23 100644 --- a/runbot/security/ir.model.access.csv +++ b/runbot/security/ir.model.access.csv @@ -129,8 +129,18 @@ access_runbot_upgrade_regex_user,access_runbot_upgrade_regex_user,runbot.model_r access_runbot_upgrade_regex_admin,access_runbot_upgrade_regex_admin,runbot.model_runbot_upgrade_regex,runbot.group_runbot_admin,1,1,1,1 access_runbot_upgrade_exception_user,access_runbot_upgrade_exception_user,runbot.model_runbot_upgrade_exception,runbot.group_user,1,0,0,0 +access_runbot_upgrade_exception_manager,access_runbot_upgrade_exception_manager,runbot.model_runbot_upgrade_exception,runbot.group_runbot_upgrade_manager,1,1,1,1 access_runbot_upgrade_exception_admin,access_runbot_upgrade_exception_admin,runbot.model_runbot_upgrade_exception,runbot.group_runbot_admin,1,1,1,1 +access_runbot_upgrade_matrix_user,access_runbot_upgrade_matrix_user,runbot.model_runbot_upgrade_matrix,runbot.group_user,1,0,0,0 +access_runbot_upgrade_matrix_manager,access_runbot_upgrade_matrix_manager,runbot.model_runbot_upgrade_matrix,runbot.group_runbot_upgrade_manager,1,1,1,1 +access_runbot_upgrade_matrix_admin,access_runbot_upgrade_matrix_admin,runbot.model_runbot_upgrade_matrix,runbot.group_runbot_admin,1,1,1,1 + +access_runbot_upgrade_matrix_entry_user,access_runbot_upgrade_matrix_entry_user,runbot.model_runbot_upgrade_matrix_entry,runbot.group_user,1,0,0,0 +access_runbot_upgrade_matrix_entry_manager,access_runbot_upgrade_matrix_entry_manager,runbot.model_runbot_upgrade_matrix_entry,runbot.group_runbot_upgrade_manager,1,1,1,1 +access_runbot_upgrade_matrix_entry_admin,access_runbot_upgrade_matrix_entry_admin,runbot.model_runbot_upgrade_matrix_entry,runbot.group_runbot_admin,1,1,1,1 + + access_runbot_dockerfile_user,access_runbot_dockerfile_user,runbot.model_runbot_dockerfile,runbot.group_user,1,0,0,0 access_runbot_dockerfile_admin,access_runbot_dockerfile_admin,runbot.model_runbot_dockerfile,runbot.group_runbot_admin,1,1,1,1 diff --git a/runbot/security/runbot_security.xml b/runbot/security/runbot_security.xml index 1aa25c6eb..fafdd1988 100644 --- a/runbot/security/runbot_security.xml +++ b/runbot/security/runbot_security.xml @@ -62,11 +62,16 @@ + + Upgrade manager + + + Runbot administrator - + diff --git a/runbot/static/src/js/fields/fields.js b/runbot/static/src/js/fields/fields.js index 302f6a5ee..c2cf52b2c 100644 --- a/runbot/static/src/js/fields/fields.js +++ b/runbot/static/src/js/fields/fields.js @@ -10,12 +10,18 @@ import { useDynamicPlaceholder } from "@web/views/fields/dynamic_placeholder_hoo import { standardFieldProps } from "@web/views/fields/standard_field_props"; import { useInputField } from "@web/views/fields/input_field_hook"; -import { useRef, xml, Component } from "@odoo/owl"; +import { useRef, xml, Component, onWillRender } from "@odoo/owl"; import { useAutoresize } from "@web/core/utils/autoresize"; import { getFormattedValue } from "@web/views/utils"; import { UrlField } from "@web/views/fields/url/url_field"; +import { X2ManyField } from "@web/views/fields/x2many/x2many_field"; +import { formatX2many } from "@web/views/fields/formatters"; + +import { BooleanToggleField } from "@web/views/fields/boolean_toggle/boolean_toggle_field"; + + function stringify(obj) { return JSON.stringify(obj, null, '\t') } @@ -170,3 +176,54 @@ registry.category("fields").add("pull_request_url", { //} // //registry.category("fields").add("github_team", GithubTeamWidget); + + + + +export class Matrixx2ManyField extends X2ManyField { + static template = 'runbot.Matrixx2ManyField'; + static props = { ...standardFieldProps }; + + static components = { BooleanToggleField }; + + setup() { + + onWillRender(() => { + this.initValues(); + }) + } + + getEntry(from, to) { + const entry = this.entry_per_version[[from, to]]; + return entry + } + + initValues() { + this.data = this.props.record.data[this.props.name] + this.to_versions = []; + this.from_versions = []; + this.entry_per_version = {}; + this.data.records.forEach((record) => { + if (!this.to_versions.includes(record.data.to_version_number)) { + this.to_versions.push(record.data.to_version_number) + } + if (!this.from_versions.includes(record.data.from_version_number)) { + this.from_versions.push(record.data.from_version_number) + } + this.entry_per_version[[record.data.from_version_number, record.data.to_version_number]] = record; + }); + console.log(this.to_versions) + this.to_versions.sort() + this.to_versions.reverse() + this.from_versions.sort() + this.from_versions.reverse() + } +} + +export const matrixx2ManyField = { + component: Matrixx2ManyField, + useSubView: false, +}; + + +registry.category("fields").add("version_matrix", matrixx2ManyField); diff --git a/runbot/static/src/js/fields/fields.xml b/runbot/static/src/js/fields/fields.xml new file mode 100644 index 000000000..faec6eee4 --- /dev/null +++ b/runbot/static/src/js/fields/fields.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+
+ +
diff --git a/runbot/views/menus.xml b/runbot/views/menus.xml index 19063a9a1..b65b085a4 100644 --- a/runbot/views/menus.xml +++ b/runbot/views/menus.xml @@ -26,7 +26,10 @@ - + + + + @@ -56,7 +59,6 @@ - diff --git a/runbot/views/upgrade_matrix_views.xml b/runbot/views/upgrade_matrix_views.xml new file mode 100644 index 000000000..ce1b7909e --- /dev/null +++ b/runbot/views/upgrade_matrix_views.xml @@ -0,0 +1,73 @@ + + + + runbot.upgrade.matrix.form + runbot.upgrade.matrix + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + runbot.upgrade.matrix.tree + runbot.upgrade.matrix + + + + + + + + + + Upgrade matrix + runbot.upgrade.matrix + list,form + +
+