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