From e477bd2957abe29a4d25cbd4d5480855a35a37cf Mon Sep 17 00:00:00 2001
From: SilvioC2C
Date: Tue, 25 Jun 2024 15:52:03 +0200
Subject: [PATCH] [MIG] connector_jira: Migration to 17.0
---
connector_jira/__manifest__.py | 15 ++++---
connector_jira/components/binder.py | 6 +--
.../migrations/15.0.1.0.0/pre-migrate.py | 24 -----------
.../migrations/15.0.2.0.0/pre-migrate.py | 32 --------------
.../models/account_analytic_line/common.py | 20 ++++-----
.../models/account_analytic_line/importer.py | 22 ++++++----
connector_jira/models/jira_backend/common.py | 15 ++-----
connector_jira/models/jira_binding/common.py | 2 +-
.../models/jira_issue_type/common.py | 4 +-
.../models/project_project/common.py | 42 ++++++++-----------
.../project_project/project_link_jira.py | 1 +
connector_jira/models/project_task/common.py | 33 ++++++---------
.../models/project_task/importer.py | 18 ++++----
.../models/project_task/task_link_jira.py | 1 +
connector_jira/views/jira_backend_views.xml | 36 ++++++++++------
.../views/jira_issue_type_views.xml | 9 ++--
.../views/project_link_jira_views.xml | 24 ++++-------
.../views/project_project_views.xml | 16 +++----
connector_jira/views/project_task_views.xml | 27 ++++--------
connector_jira/views/task_link_jira_views.xml | 6 +--
.../views/timesheet_account_analytic_line.xml | 12 +++---
.../jira_account_analytic_line_import.py | 1 +
requirements.txt | 2 +-
23 files changed, 143 insertions(+), 225 deletions(-)
delete mode 100644 connector_jira/migrations/15.0.1.0.0/pre-migrate.py
delete mode 100644 connector_jira/migrations/15.0.2.0.0/pre-migrate.py
diff --git a/connector_jira/__manifest__.py b/connector_jira/__manifest__.py
index d5f279104..321194fc2 100644
--- a/connector_jira/__manifest__.py
+++ b/connector_jira/__manifest__.py
@@ -2,18 +2,23 @@
{
"name": "JIRA Connector",
- "version": "15.0.2.0.0",
+ "version": "17.0.1.0.0",
"author": "Camptocamp,Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Connector",
"depends": [
- "connector",
+ # Odoo community
"project",
"hr_timesheet",
- "queue_job",
"web",
- "web_widget_url_advanced",
+ # OCA/connector
+ "connector",
+ # OCA/queue
+ "queue_job",
+ # OCA/server-ux
"multi_step_wizard",
+ # OCA/web
+ "web_widget_url_advanced",
],
"external_dependencies": {
"python": [
@@ -24,7 +29,7 @@
"requests-toolbelt>=0.9.1",
"requests-jwt>=0.6.0",
"PyJWT>=1.7.1,<2.9.0",
- "cryptography<37",
+ "cryptography>=38,<39", # Compatibility w/ Odoo 17.0 requirements
"atlassian_jwt>=3.0.0",
],
},
diff --git a/connector_jira/components/binder.py b/connector_jira/components/binder.py
index 85e13518f..c5cd522f0 100644
--- a/connector_jira/components/binder.py
+++ b/connector_jira/components/binder.py
@@ -44,11 +44,7 @@ class JiraModelBinder(Component):
_name = "jira.model.binder"
_inherit = ["base.binder", "jira.base"]
-
- _apply_on = [
- "jira.issue.type",
- ]
-
+ _apply_on = ["jira.issue.type"]
_odoo_field = "id"
def to_internal(self, external_id, unwrap=False):
diff --git a/connector_jira/migrations/15.0.1.0.0/pre-migrate.py b/connector_jira/migrations/15.0.1.0.0/pre-migrate.py
deleted file mode 100644
index cd1d6b5a3..000000000
--- a/connector_jira/migrations/15.0.1.0.0/pre-migrate.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2022 Camptocamp SA
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
-from openupgradelib import openupgrade
-
-
-def migrate(cr, version):
- add_missing_xmlid_on_channel(cr)
-
-
-def add_missing_xmlid_on_channel(cr):
- query = """
- SELECT id FROM queue_job_channel
- WHERE complete_name='root.connector_jira.import';
- """
- cr.execute(query)
- channel = cr.fetchall()
- if channel:
- openupgrade.add_xmlid(
- cr,
- "connector_jira",
- "import_root",
- "queue.job.channel",
- channel[0][0],
- )
diff --git a/connector_jira/migrations/15.0.2.0.0/pre-migrate.py b/connector_jira/migrations/15.0.2.0.0/pre-migrate.py
deleted file mode 100644
index b64af6379..000000000
--- a/connector_jira/migrations/15.0.2.0.0/pre-migrate.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2024 Camptocamp SA
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
-
-
-def migrate(cr, version):
- remove_field_selection(cr)
-
-
-def remove_field_selection(cr):
- queries = [
- # delete xml ids of ir.model.fields.selections
- "DELETE FROM ir_model_data imd "
- "USING ir_model_fields_selection fs, ir_model_fields f, ir_model m "
- "WHERE imd.module='connector_jira' "
- "AND imd.model='ir.model.fields.selection' "
- "AND res_id=fs.id "
- "AND f.model_id = m.id "
- "AND m.name='jira.backend.auth' "
- "AND fs.field_id=f.id;",
- # delete ir_model_fields_selection
- "DELETE FROM ir_model_fields_selection "
- "USING ir_model_fields f, ir_model m "
- "WHERE f.model_id = m.id "
- "AND m.name='jira.backend.auth' "
- "AND field_id=f.id;",
- # delete ir.model
- "DELETE from ir_model WHERE model='jira.backend.auth';",
- "DROP TABLE jira_backend_auth",
- ]
-
- for query in queries:
- cr.execute(query)
diff --git a/connector_jira/models/account_analytic_line/common.py b/connector_jira/models/account_analytic_line/common.py
index b9819c5a8..e4d0b669c 100644
--- a/connector_jira/models/account_analytic_line/common.py
+++ b/connector_jira/models/account_analytic_line/common.py
@@ -58,23 +58,16 @@ class JiraAccountAnalyticLine(models.Model):
# for instance, we do not import "Tasks" but we import "Epics",
# the analytic line for a "Task" will be linked to an "Epic" on
# Odoo, but we still want to know the original task here
- jira_issue_key = fields.Char(
- string="Original Task Key",
- readonly=True,
- )
+ jira_issue_key = fields.Char(string="Original Task Key")
jira_issue_type_id = fields.Many2one(
comodel_name="jira.issue.type",
string="Original Issue Type",
- readonly=True,
)
jira_issue_url = fields.Char(
string="Original JIRA issue Link",
compute="_compute_jira_issue_url",
)
- jira_epic_issue_key = fields.Char(
- string="Original Epic Key",
- readonly=True,
- )
+ jira_epic_issue_key = fields.Char(string="Original Epic Key")
jira_epic_issue_url = fields.Char(
string="Original JIRA Epic Link",
compute="_compute_jira_issue_url",
@@ -241,10 +234,11 @@ def _connector_jira_unlink_validate(self):
_("Timesheet linked to JIRA Worklog can not be deleted!")
)
- @api.model
- def create(self, vals):
- self._connector_jira_create_validate(vals)
- return super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ self._connector_jira_create_validate(vals)
+ return super().create(vals_list)
def write(self, vals):
self._connector_jira_write_validate(vals)
diff --git a/connector_jira/models/account_analytic_line/importer.py b/connector_jira/models/account_analytic_line/importer.py
index a994351e3..910fbe84c 100644
--- a/connector_jira/models/account_analytic_line/importer.py
+++ b/connector_jira/models/account_analytic_line/importer.py
@@ -288,19 +288,25 @@ def _recurse_import_task(self):
def _create_data(self, map_record, **kwargs):
return super()._create_data(
map_record,
- task_binding=self.task_binding,
- project_binding=self.project_binding,
- fallback_project=self.fallback_project,
- linked_issue=self.external_issue,
+ **dict(
+ kwargs or [],
+ task_binding=self.task_binding,
+ project_binding=self.project_binding,
+ fallback_project=self.fallback_project,
+ linked_issue=self.external_issue,
+ ),
)
def _update_data(self, map_record, **kwargs):
return super()._update_data(
map_record,
- task_binding=self.task_binding,
- project_binding=self.project_binding,
- fallback_project=self.fallback_project,
- linked_issue=self.external_issue,
+ **dict(
+ kwargs or [],
+ task_binding=self.task_binding,
+ project_binding=self.project_binding,
+ fallback_project=self.fallback_project,
+ linked_issue=self.external_issue,
+ ),
)
def run(self, external_id, force=False, record=None, **kwargs):
diff --git a/connector_jira/models/jira_backend/common.py b/connector_jira/models/jira_backend/common.py
index 6bf2dd3d2..58324002e 100644
--- a/connector_jira/models/jira_backend/common.py
+++ b/connector_jira/models/jira_backend/common.py
@@ -67,7 +67,6 @@ class JiraBackend(models.Model):
uri = fields.Char(
string="Jira URI",
- readonly=True,
help="the value is provided when the app is installed on Jira Cloud.",
)
name = fields.Char(
@@ -79,9 +78,7 @@ class JiraBackend(models.Model):
help="URL to use when registering the backend as an app on the marketplace",
compute="_compute_app_descriptor_url",
)
- display_url = fields.Char(
- help="Url used for the Jira app in messages", readonly=True
- )
+ display_url = fields.Char(help="Url used for the Jira app in messages")
application_key = fields.Char(
compute="_compute_application_key",
store=True,
@@ -130,7 +127,6 @@ class JiraBackend(models.Model):
],
default="setup",
required=True,
- readonly=True,
help="State of the Backend.\n"
"Setup: in this state you can register the backend on "
"https://marketplace.atlassian.com/ as an app, using the app descriptor url.\n"
@@ -138,12 +134,11 @@ class JiraBackend(models.Model):
"(transition is automatic).",
)
private_key = fields.Char(
- readonly=True,
groups="connector.group_connector_manager",
help="The shared secret for JWT, provided at app installation",
)
public_key = fields.Text(
- readonly=True, help="The Client Key for JWT, provided at app installation"
+ help="The Client Key for JWT, provided at app installation"
)
verify_ssl = fields.Boolean(default=True, string="Verify SSL?")
@@ -182,7 +177,6 @@ class JiraBackend(models.Model):
comodel_name="jira.issue.type",
inverse_name="backend_id",
string="Issue Types",
- readonly=True,
)
epic_link_field_name = fields.Char(
@@ -201,11 +195,10 @@ class JiraBackend(models.Model):
)
# TODO: use something better to show this info
- # For instance, we could use web_notify to simply show a system msg.
- report_user_sync = fields.Html(readonly=True)
+ # For instance, we could use web_notify to simply show a system msg.
+ report_user_sync = fields.Html()
@api.model_create_multi
- @api.returns("self", lambda value: value.id)
def create(self, vals_list):
records = super().create(vals_list)
records._compute_application_key()
diff --git a/connector_jira/models/jira_binding/common.py b/connector_jira/models/jira_binding/common.py
index 31cc238b4..daac47235 100644
--- a/connector_jira/models/jira_binding/common.py
+++ b/connector_jira/models/jira_binding/common.py
@@ -26,7 +26,7 @@ class JiraBinding(models.AbstractModel):
ondelete="restrict",
)
jira_updated_at = MilliDatetime()
- external_id = fields.Char(string="ID on Jira", index=True)
+ external_id = fields.Char(string="ID on Jira", index="trigram")
_sql_constraints = [
(
diff --git a/connector_jira/models/jira_issue_type/common.py b/connector_jira/models/jira_issue_type/common.py
index 6690e9f5d..50dc2fb02 100644
--- a/connector_jira/models/jira_issue_type/common.py
+++ b/connector_jira/models/jira_issue_type/common.py
@@ -11,8 +11,8 @@ class JiraIssueType(models.Model):
_inherit = "jira.binding"
_description = "Jira Issue Type"
- name = fields.Char(required=True, readonly=True)
- description = fields.Char(readonly=True)
+ name = fields.Char(required=True)
+ description = fields.Char()
backend_id = fields.Many2one(ondelete="cascade")
def is_sync_for_project(self, project_binding):
diff --git a/connector_jira/models/project_project/common.py b/connector_jira/models/project_project/common.py
index f79c5a906..da317b1a5 100644
--- a/connector_jira/models/project_project/common.py
+++ b/connector_jira/models/project_project/common.py
@@ -34,8 +34,8 @@ class JiraProjectBaseFields(models.AbstractModel):
jira_key = fields.Char(
string="JIRA Key",
required=True,
- size=10,
- ) # limit on JIRA
+ size=10, # limit on JIRA
+ )
sync_issue_type_ids = fields.Many2many(
comodel_name="jira.issue.type",
string="Issue Levels to Synchronize",
@@ -99,13 +99,10 @@ def _selection_project_type(self):
def _add_sql_constraints(self):
# we replace the sql constraint by a python one
# to include the organizations
- for key, definition, _msg in self._sql_constraints:
+ for key, definition, __ in self._sql_constraints:
conname = f"{self._table}_{key}"
if key == "jira_binding_uniq":
- has_definition = tools.constraint_definition(
- self.env.cr, self._table, conname
- )
- if has_definition:
+ if tools.constraint_definition(self.env.cr, self._table, conname):
tools.drop_constraint(self.env.cr, self._table, conname)
else:
tools.add_constraint(self.env.cr, self._table, conname, definition)
@@ -201,11 +198,11 @@ def _is_linked(self):
return True
return False
- @api.model
- def create(self, values):
- record = super().create(values)
- record._ensure_jira_key()
- return record
+ @api.model_create_multi
+ def create(self, vals_list):
+ records = super().create(vals_list)
+ records._ensure_jira_key()
+ return records
def write(self, values):
if "project_template" in values:
@@ -251,14 +248,12 @@ def _compute_jira_key(self):
keys = project.mapped("jira_bind_ids.jira_key")
project.jira_key = ", ".join(keys)
- def name_get(self):
- names = []
- for project in self:
- project_id, name = super(ProjectProject, project).name_get()[0]
- if project.jira_key:
- name = f"[{project.jira_key}] {name}"
- names.append((project_id, name))
- return names
+ # pylint: disable=W8110
+ @api.depends("jira_key")
+ def _compute_display_name(self):
+ super()._compute_display_name()
+ for project in self.filtered("jira_key"):
+ project.display_name = f"[{project.jira_key}] {project.display_name}"
@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
@@ -272,14 +267,13 @@ def name_search(self, name="", args=None, operator="ilike", limit=100):
]
if operator in expression.NEGATIVE_TERM_OPERATORS:
domain = ["&", "!"] + domain[1:]
- return self.search(
- domain + (args or []),
- limit=limit,
- ).name_get()
+ projects = self.search(domain + (args or []), limit=limit)
+ return [(p.id, p.display_name) for p in projects.sudo()]
def create_and_link_jira(self):
action_link = self.env.ref("connector_jira.open_project_link_jira")
action = action_link.read()[0]
+ # TODO: remove dependency on ``active_id[s]/model``
action["context"] = dict(
self.env.context,
active_id=self.id,
diff --git a/connector_jira/models/project_project/project_link_jira.py b/connector_jira/models/project_project/project_link_jira.py
index b8686f907..c7da2ffb7 100644
--- a/connector_jira/models/project_project/project_link_jira.py
+++ b/connector_jira/models/project_project/project_link_jira.py
@@ -37,6 +37,7 @@ class ProjectLinkJira(models.TransientModel):
@api.model
def _default_project_id(self):
+ # TODO: remove dependency on ``active_id[s]/model``
return self.env.context.get("active_id")
@api.model
diff --git a/connector_jira/models/project_task/common.py b/connector_jira/models/project_task/common.py
index 83b8ce11a..d6fc79b5b 100644
--- a/connector_jira/models/project_task/common.py
+++ b/connector_jira/models/project_task/common.py
@@ -29,22 +29,18 @@ class JiraProjectTask(models.Model):
)
jira_key = fields.Char(
string="Key",
- readonly=True,
)
jira_issue_type_id = fields.Many2one(
comodel_name="jira.issue.type",
string="Issue Type",
- readonly=True,
)
jira_epic_link_id = fields.Many2one(
comodel_name="jira.project.task",
string="Epic",
- readonly=True,
)
jira_parent_id = fields.Many2one(
comodel_name="jira.project.task",
string="Parent Issue",
- readonly=True,
help="Parent issue when the issue is a subtask. "
"Empty if the type of parent is filtered out "
"of the synchronizations.",
@@ -153,14 +149,12 @@ def _compute_jira_issue_url(self):
main_binding = record.jira_bind_ids[0]
record.jira_issue_url = main_binding.jira_issue_url
- def name_get(self):
- names = []
- for task in self:
- task_id, name = super(ProjectTask, task).name_get()[0]
- if task.jira_compound_key:
- name = f"[{task.jira_compound_key}] {name}"
- names.append((task_id, name))
- return names
+ # pylint: disable=W8110
+ @api.depends("jira_compound_key")
+ def _compute_display_name(self):
+ super()._compute_display_name()
+ for task in self.filtered("jira_compound_key"):
+ task.display_name = f"[{task.jira_compound_key}] {task.display_name}"
@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
@@ -174,10 +168,8 @@ def name_search(self, name="", args=None, operator="ilike", limit=100):
]
if operator in expression.NEGATIVE_TERM_OPERATORS:
domain = ["&", "!"] + domain[1:]
- return self.search(
- domain + (args or []),
- limit=limit,
- ).name_get()
+ tasks = self.search(domain + (args or []), limit=limit)
+ return [(t.id, t.display_name) for t in tasks.sudo()]
@api.model
def _get_connector_jira_fields(self):
@@ -239,10 +231,11 @@ def _connector_jira_unlink_validate(self):
_("Task linked to JIRA Issue can not be deleted!")
)
- @api.model
- def create(self, vals):
- self._connector_jira_create_validate(vals)
- return super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ self._connector_jira_create_validate(vals)
+ return super().create(vals_list)
def write(self, vals):
self._connector_jira_write_validate(vals)
diff --git a/connector_jira/models/project_task/importer.py b/connector_jira/models/project_task/importer.py
index 0896aae64..50653ee0d 100644
--- a/connector_jira/models/project_task/importer.py
+++ b/connector_jira/models/project_task/importer.py
@@ -181,7 +181,7 @@ def __init__(self, work_context):
self.project_binding = None
def _get_external_data(self):
- """Return the raw Jira data for ``self.external_id``"""
+ # OVERRIDE: return the raw Jira data for ``self.external_id``
result = super()._get_external_data()
epic_field_name = self.backend_record.epic_link_field_name
if epic_field_name:
@@ -209,17 +209,21 @@ def _is_issue_type_sync(self):
def _create_data(self, map_record, **kwargs):
return super()._create_data(
map_record,
- jira_epic=self.jira_epic,
- project_binding=self.project_binding,
- **kwargs,
+ **dict(
+ kwargs or [],
+ jira_epic=self.jira_epic,
+ project_binding=self.project_binding,
+ ),
)
def _update_data(self, map_record, **kwargs):
return super()._update_data(
map_record,
- jira_epic=self.jira_epic,
- project_binding=self.project_binding,
- **kwargs,
+ **dict(
+ kwargs or [],
+ jira_epic=self.jira_epic,
+ project_binding=self.project_binding,
+ ),
)
def _import(self, binding, **kwargs):
diff --git a/connector_jira/models/project_task/task_link_jira.py b/connector_jira/models/project_task/task_link_jira.py
index dde32f028..903ccfb11 100644
--- a/connector_jira/models/project_task/task_link_jira.py
+++ b/connector_jira/models/project_task/task_link_jira.py
@@ -49,6 +49,7 @@ def _selection_state(self):
def default_get(self, fields):
values = super().default_get(fields)
context = self.env.context
+ # TODO: remove dependency on ``active_id[s]/model``
if context.get("active_model") == "project.task" and context.get("active_id"):
task = self.env["project.task"].browse(context["active_id"])
project_linked_backends = task.mapped("project_id.jira_bind_ids.backend_id")
diff --git a/connector_jira/views/jira_backend_views.xml b/connector_jira/views/jira_backend_views.xml
index 8d48f8cb6..1ee805da2 100644
--- a/connector_jira/views/jira_backend_views.xml
+++ b/connector_jira/views/jira_backend_views.xml
@@ -16,9 +16,9 @@
name="check_connection"
type="object"
string="Check Connection"
- states="setup,running"
+ invisible="state not in ('setup', 'running')"
/>
-
+
@@ -33,15 +33,16 @@
>
-
+
You can use the App Descriptor URL to register a new App on Atlassian Marketplace. Check the module README for the detailed process.
-
+
@@ -49,13 +50,17 @@
-
+
By clicking on the buttons,
you will initiate the synchronizations
@@ -91,9 +96,9 @@
-
+
@@ -216,7 +221,7 @@
type="object"
string="Configure Epic Link"
class="btn-sm btn-link oe_inline"
- attrs="{'invisible': [('state', '=', 'authenticate')]}"
+ invisible="state == 'authenticate'"
/>
@@ -236,7 +241,8 @@
/>
@@ -246,8 +252,12 @@
-
-
+
+
diff --git a/connector_jira/views/jira_issue_type_views.xml b/connector_jira/views/jira_issue_type_views.xml
index 83537b439..733efa369 100644
--- a/connector_jira/views/jira_issue_type_views.xml
+++ b/connector_jira/views/jira_issue_type_views.xml
@@ -6,12 +6,9 @@
diff --git a/connector_jira/views/project_link_jira_views.xml b/connector_jira/views/project_link_jira_views.xml
index bfb1179cd..aa2b6d75c 100644
--- a/connector_jira/views/project_link_jira_views.xml
+++ b/connector_jira/views/project_link_jira_views.xml
@@ -10,21 +10,18 @@
-
+
-
+
-
+
-
-
+
+
The project will be created on JIRA in background.
-
+
The project is now linked with JIRA.
diff --git a/connector_jira/views/project_project_views.xml b/connector_jira/views/project_project_views.xml
index cf5f84c61..87cfcf79c 100644
--- a/connector_jira/views/project_project_views.xml
+++ b/connector_jira/views/project_project_views.xml
@@ -29,19 +29,13 @@