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