From fb7b8480b85422503bd120b41eb7b9e390a034df 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/build_config.py | 3 + runbot/models/bundle.py | 3 + runbot/models/upgrade.py | 137 ++++++++++++++++++++++++- 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 +++++++++++++ 10 files changed, 322 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 eab898c6..6ab544a2 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/build_config.py b/runbot/models/build_config.py index 0a4aa155..85ebcf1d 100644 --- a/runbot/models/build_config.py +++ b/runbot/models/build_config.py @@ -189,6 +189,9 @@ class ConfigStep(models.Model): upgrade_from_all_intermediate_version = fields.Boolean() # 13.2 # 13.1 upgrade_from_version_ids = fields.Many2many('runbot.version', relation='runbot_upgrade_from_version_ids', string='Forced version to use as source (cartesian with target)') + # wip replace previous field by matrix + upgrade_matrix_id = fields.Many2one('runbot.upgrade.matrix', 'Upgrade matrix', tracking=True) + upgrade_flat = fields.Boolean("Flat", help="Take all decisions in on build") upgrade_config_id = fields.Many2one('runbot.build.config',string='Upgrade Config', tracking=True, index=True) diff --git a/runbot/models/bundle.py b/runbot/models/bundle.py index ae097a0a..e865b798 100644 --- a/runbot/models/bundle.py +++ b/runbot/models/bundle.py @@ -204,6 +204,9 @@ class Bundle(models.Model): 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 cea4fa2f..d1594909 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,138 @@ class BuildResult(models.Model): 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() + + matrix_summary = fields.Text('Matrix summary', compute='_compute_matrix_summary', store=True, tracking=True) + + @api.depends('entry_ids.from_version_id', 'entry_ids.to_version_id', 'entry_ids.enabled') + def _compute_matrix_summary(self): + for matrix in self: + matrix.matrix_summary = '' + versions = {} + lines = [] + for entry in self.entry_ids.sorted(lambda e: (e.to_version_id.number, e.from_version_id.number)): + if entry.enabled: + versions.setdefault(entry.to_version_id, []).append(entry.from_version_id.number) + else: + versions.setdefault(entry.to_version_id, []).append('-') + for to_version, from_versions in versions.items(): + from_versions_string = ', '.join(sorted(from_versions)) + lines.append(f'{to_version.number} - ({from_versions_string})') + matrix.matrix_summary = '\n'.join(lines) + print('recompute') + print(matrix.matrix_summary) + + 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', string="To version number", store=True) + to_version_number = fields.Char(related='to_version_id.number', string="From version 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 cd54fa04..994ad2e2 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 1aa25c6e..fafdd198 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 302f6a5e..d0ee33b7 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 00000000..faec6eee --- /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 19063a9a..b65b085a 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 00000000..ce1b7909 --- /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 + +
+